#include /**************************************************************************** ** Copyright (c) 2006 - 2011, the LibQxt project. ** See the Qxt AUTHORS file for a list of authors and copyright holders. ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** * Neither the name of the LibQxt project nor the ** names of its contributors may be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ** DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ** ** *****************************************************************************/ #include "qxtglobalshortcut_p.h" #include #include #include #include using Identifier = QPair; static QMap keyRefs; static QHash keyIDs; static quint32 hotKeySerial = 0; static bool qxt_mac_handler_installed = false; OSStatus qxt_mac_handle_hot_key(EventHandlerCallRef nextHandler, EventRef event, void* data) { Q_UNUSED(nextHandler); Q_UNUSED(data); if (GetEventClass(event) == kEventClassKeyboard && GetEventKind(event) == kEventHotKeyPressed) { EventHotKeyID keyID; GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, nullptr, sizeof(keyID), nullptr, &keyID); Identifier id = keyIDs.key(keyID.id); QxtGlobalShortcutPrivate::activateShortcut(id.second, id.first); } return noErr; } quint32 QxtGlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) { quint32 native = 0; if (modifiers & Qt::ShiftModifier) native |= shiftKey; if (modifiers & Qt::ControlModifier) native |= cmdKey; if (modifiers & Qt::AltModifier) native |= optionKey; if (modifiers & Qt::MetaModifier) native |= controlKey; if (modifiers & Qt::KeypadModifier) native |= kEventKeyModifierNumLockMask; return native; } #if QT_VERSION >= QT_VERSION_CHECK(5,0,0) // This is only here to get it to compile on OSX bool QxtGlobalShortcutPrivate::nativeEventFilter(const QByteArray & eventType, void * message, long * result) { Q_UNUSED(result); Q_UNUSED(message); Q_UNUSED(eventType); return false; } #endif quint32 QxtGlobalShortcutPrivate::nativeKeycode(Qt::Key key) { UTF16Char ch; // Constants found in NSEvent.h from AppKit.framework switch (key) { case Qt::Key_Return: return kVK_Return; case Qt::Key_Enter: return kVK_ANSI_KeypadEnter; case Qt::Key_Tab: return kVK_Tab; case Qt::Key_Space: return kVK_Space; case Qt::Key_Backspace: return kVK_Delete; case Qt::Key_Control: return kVK_Command; case Qt::Key_Shift: return kVK_Shift; case Qt::Key_CapsLock: return kVK_CapsLock; case Qt::Key_Option: return kVK_Option; case Qt::Key_Meta: return kVK_Control; case Qt::Key_F17: return kVK_F17; case Qt::Key_VolumeUp: return kVK_VolumeUp; case Qt::Key_VolumeDown: return kVK_VolumeDown; case Qt::Key_F18: return kVK_F18; case Qt::Key_F19: return kVK_F19; case Qt::Key_F20: return kVK_F20; case Qt::Key_F5: return kVK_F5; case Qt::Key_F6: return kVK_F6; case Qt::Key_F7: return kVK_F7; case Qt::Key_F3: return kVK_F3; case Qt::Key_F8: return kVK_F8; case Qt::Key_F9: return kVK_F9; case Qt::Key_F11: return kVK_F11; case Qt::Key_F13: return kVK_F13; case Qt::Key_F16: return kVK_F16; case Qt::Key_F14: return kVK_F14; case Qt::Key_F10: return kVK_F10; case Qt::Key_F12: return kVK_F12; case Qt::Key_F15: return kVK_F15; case Qt::Key_Help: return kVK_Help; case Qt::Key_Home: return kVK_Home; case Qt::Key_PageUp: return kVK_PageUp; case Qt::Key_Delete: return kVK_ForwardDelete; case Qt::Key_F4: return kVK_F4; case Qt::Key_End: return kVK_End; case Qt::Key_F2: return kVK_F2; case Qt::Key_PageDown: return kVK_PageDown; case Qt::Key_F1: return kVK_F1; case Qt::Key_Left: return kVK_LeftArrow; case Qt::Key_Right: return kVK_RightArrow; case Qt::Key_Down: return kVK_DownArrow; case Qt::Key_Up: return kVK_UpArrow; default: ; } if (key == Qt::Key_Escape) ch = 27; else if (key == Qt::Key_Return) ch = 13; else if (key == Qt::Key_Enter) ch = 3; else if (key == Qt::Key_Tab) ch = 9; else ch = key; CFDataRef currentLayoutData; TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource(); if (currentKeyboard == nullptr) return 0; currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); CFRelease(currentKeyboard); if (currentLayoutData == nullptr) return 0; UCKeyboardLayout* header = (UCKeyboardLayout*)CFDataGetBytePtr(currentLayoutData); UCKeyboardTypeHeader* table = header->keyboardTypeList; uint8_t *data = (uint8_t*)header; // God, would a little documentation for this shit kill you... for (quint32 i=0; i < header->keyboardTypeCount; i++) { UCKeyStateRecordsIndex* stateRec = 0; if (table[i].keyStateRecordsIndexOffset != 0) { stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; } UCKeyToCharTableIndex* charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) continue; for (quint32 j=0; j < charTable->keyToCharTableCount; j++) { UCKeyOutput* keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); for (quint32 k=0; k < charTable->keyToCharTableSize; k++) { if (keyToChar[k] & kUCKeyOutputTestForIndexMask) { long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; if (stateRec && idx < stateRec->keyStateRecordCount) { UCKeyStateRecord* rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); if (rec->stateZeroCharData == ch) return k; } } else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { if (keyToChar[k] == ch) return k; } } // for k } // for j } // for i // The code above fails to translate keys like semicolon with Qt 5.7.1. // Last resort is to try mapping the rest of the keys directly. switch (key) { case Qt::Key_A: return kVK_ANSI_A; case Qt::Key_S: return kVK_ANSI_S; case Qt::Key_D: return kVK_ANSI_D; case Qt::Key_F: return kVK_ANSI_F; case Qt::Key_H: return kVK_ANSI_H; case Qt::Key_G: return kVK_ANSI_G; case Qt::Key_Z: return kVK_ANSI_Z; case Qt::Key_X: return kVK_ANSI_X; case Qt::Key_C: return kVK_ANSI_C; case Qt::Key_V: return kVK_ANSI_V; case Qt::Key_B: return kVK_ANSI_B; case Qt::Key_Q: return kVK_ANSI_Q; case Qt::Key_W: return kVK_ANSI_W; case Qt::Key_E: return kVK_ANSI_E; case Qt::Key_R: return kVK_ANSI_R; case Qt::Key_Y: return kVK_ANSI_Y; case Qt::Key_T: return kVK_ANSI_T; case Qt::Key_1: return kVK_ANSI_1; case Qt::Key_2: return kVK_ANSI_2; case Qt::Key_3: return kVK_ANSI_3; case Qt::Key_4: return kVK_ANSI_4; case Qt::Key_6: return kVK_ANSI_6; case Qt::Key_5: return kVK_ANSI_5; case Qt::Key_Equal: return kVK_ANSI_Equal; case Qt::Key_9: return kVK_ANSI_9; case Qt::Key_7: return kVK_ANSI_7; case Qt::Key_Minus: return kVK_ANSI_Minus; case Qt::Key_8: return kVK_ANSI_8; case Qt::Key_0: return kVK_ANSI_0; case Qt::Key_BracketRight: return kVK_ANSI_RightBracket; case Qt::Key_O: return kVK_ANSI_O; case Qt::Key_U: return kVK_ANSI_U; case Qt::Key_BracketLeft: return kVK_ANSI_LeftBracket; case Qt::Key_I: return kVK_ANSI_I; case Qt::Key_P: return kVK_ANSI_P; case Qt::Key_L: return kVK_ANSI_L; case Qt::Key_J: return kVK_ANSI_J; case Qt::Key_QuoteDbl: return kVK_ANSI_Quote; case Qt::Key_K: return kVK_ANSI_K; case Qt::Key_Semicolon: return kVK_ANSI_Semicolon; case Qt::Key_Backslash: return kVK_ANSI_Backslash; case Qt::Key_Comma: return kVK_ANSI_Comma; case Qt::Key_Slash: return kVK_ANSI_Slash; case Qt::Key_N: return kVK_ANSI_N; case Qt::Key_M: return kVK_ANSI_M; case Qt::Key_Period: return kVK_ANSI_Period; case Qt::Key_Dead_Grave: return kVK_ANSI_Grave; case Qt::Key_Asterisk: return kVK_ANSI_KeypadMultiply; case Qt::Key_Plus: return kVK_ANSI_KeypadPlus; case Qt::Key_Clear: return kVK_ANSI_KeypadClear; case Qt::Key_Escape: return kVK_Escape; default: ; } return 0; } bool QxtGlobalShortcutPrivate::registerShortcut(quint32 nativeKey, quint32 nativeMods) { if (!qxt_mac_handler_installed) { EventTypeSpec t; t.eventClass = kEventClassKeyboard; t.eventKind = kEventHotKeyPressed; InstallApplicationEventHandler(&qxt_mac_handle_hot_key, 1, &t, nullptr, nullptr); } EventHotKeyID keyID; keyID.signature = 'cute'; keyID.id = ++hotKeySerial; EventHotKeyRef ref = 0; bool rv = !RegisterEventHotKey(nativeKey, nativeMods, keyID, GetApplicationEventTarget(), 0, &ref); if (rv) { keyIDs.insert(Identifier(nativeMods, nativeKey), keyID.id); keyRefs.insert(keyID.id, ref); } return rv; } bool QxtGlobalShortcutPrivate::unregisterShortcut(quint32 nativeKey, quint32 nativeMods) { Identifier id(nativeMods, nativeKey); if (!keyIDs.contains(id)) return false; EventHotKeyRef ref = keyRefs.take(keyIDs[id]); keyIDs.remove(id); return !UnregisterEventHotKey(ref); }