/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qcocoakeymapper.h" #include #include QT_BEGIN_NAMESPACE // QCocoaKeyMapper debug facilities //#define DEBUG_KEY_BINDINGS //#define DEBUG_KEY_BINDINGS_MODIFIERS //#define DEBUG_KEY_MAPS // Possible modifier states. // NOTE: The order of these states match the order in updatePossibleKeyCodes()! static const Qt::KeyboardModifiers ModsTbl[] = { Qt::NoModifier, // 0 Qt::ShiftModifier, // 1 Qt::ControlModifier, // 2 Qt::ControlModifier | Qt::ShiftModifier, // 3 Qt::AltModifier, // 4 Qt::AltModifier | Qt::ShiftModifier, // 5 Qt::AltModifier | Qt::ControlModifier, // 6 Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 7 Qt::MetaModifier, // 8 Qt::MetaModifier | Qt::ShiftModifier, // 9 Qt::MetaModifier | Qt::ControlModifier, // 10 Qt::MetaModifier | Qt::ControlModifier | Qt::ShiftModifier,// 11 Qt::MetaModifier | Qt::AltModifier, // 12 Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier, // 13 Qt::MetaModifier | Qt::AltModifier | Qt::ControlModifier, // 14 Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 15 }; bool qt_mac_eat_unicode_key = false; /* key maps */ struct qt_mac_enum_mapper { int mac_code; int qt_code; #if defined(DEBUG_KEY_BINDINGS) # define QT_MAC_MAP_ENUM(x) x, #x const char *desc; #else # define QT_MAC_MAP_ENUM(x) x #endif }; //modifiers static qt_mac_enum_mapper qt_mac_modifier_symbols[] = { { shiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) }, { rightShiftKey, QT_MAC_MAP_ENUM(Qt::ShiftModifier) }, { controlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) }, { rightControlKey, QT_MAC_MAP_ENUM(Qt::MetaModifier) }, { cmdKey, QT_MAC_MAP_ENUM(Qt::ControlModifier) }, { optionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) }, { rightOptionKey, QT_MAC_MAP_ENUM(Qt::AltModifier) }, { kEventKeyModifierNumLockMask, QT_MAC_MAP_ENUM(Qt::KeypadModifier) }, { 0, QT_MAC_MAP_ENUM(0) } }; Qt::KeyboardModifiers qt_mac_get_modifiers(int keys) { #ifdef DEBUG_KEY_BINDINGS_MODIFIERS qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", keys, keys); #endif Qt::KeyboardModifiers ret = Qt::NoModifier; for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) { if (keys & qt_mac_modifier_symbols[i].mac_code) { #ifdef DEBUG_KEY_BINDINGS_MODIFIERS qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc); #endif ret |= Qt::KeyboardModifier(qt_mac_modifier_symbols[i].qt_code); } } if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { Qt::KeyboardModifiers oldModifiers = ret; ret &= ~(Qt::MetaModifier | Qt::ControlModifier); if (oldModifiers & Qt::ControlModifier) ret |= Qt::MetaModifier; if (oldModifiers & Qt::MetaModifier) ret |= Qt::ControlModifier; } return ret; } static int qt_mac_get_mac_modifiers(Qt::KeyboardModifiers keys) { #ifdef DEBUG_KEY_BINDINGS_MODIFIERS qDebug("Qt: internal: **Mapping modifiers: %d (0x%04x)", (int)keys, (int)keys); #endif int ret = 0; for (int i = 0; qt_mac_modifier_symbols[i].qt_code; i++) { if (keys & qt_mac_modifier_symbols[i].qt_code) { #ifdef DEBUG_KEY_BINDINGS_MODIFIERS qDebug("Qt: internal: got modifier: %s", qt_mac_modifier_symbols[i].desc); #endif ret |= qt_mac_modifier_symbols[i].mac_code; } } if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) { int oldModifiers = ret; ret &= ~(controlKeyBit | cmdKeyBit); if (oldModifiers & controlKeyBit) ret |= cmdKeyBit; if (oldModifiers & cmdKeyBit) ret |= controlKeyBit; } return ret; } //keyboard keys (non-modifiers) static qt_mac_enum_mapper qt_mac_keyboard_symbols[] = { { kHomeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Home) }, { kEnterCharCode, QT_MAC_MAP_ENUM(Qt::Key_Enter) }, { kEndCharCode, QT_MAC_MAP_ENUM(Qt::Key_End) }, { kBackspaceCharCode, QT_MAC_MAP_ENUM(Qt::Key_Backspace) }, { kTabCharCode, QT_MAC_MAP_ENUM(Qt::Key_Tab) }, { kPageUpCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, { kPageDownCharCode, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, { kReturnCharCode, QT_MAC_MAP_ENUM(Qt::Key_Return) }, { kEscapeCharCode, QT_MAC_MAP_ENUM(Qt::Key_Escape) }, { kLeftArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Left) }, { kRightArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Right) }, { kUpArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Up) }, { kDownArrowCharCode, QT_MAC_MAP_ENUM(Qt::Key_Down) }, { kHelpCharCode, QT_MAC_MAP_ENUM(Qt::Key_Help) }, { kDeleteCharCode, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //ascii maps, for debug { ':', QT_MAC_MAP_ENUM(Qt::Key_Colon) }, { ';', QT_MAC_MAP_ENUM(Qt::Key_Semicolon) }, { '<', QT_MAC_MAP_ENUM(Qt::Key_Less) }, { '=', QT_MAC_MAP_ENUM(Qt::Key_Equal) }, { '>', QT_MAC_MAP_ENUM(Qt::Key_Greater) }, { '?', QT_MAC_MAP_ENUM(Qt::Key_Question) }, { '@', QT_MAC_MAP_ENUM(Qt::Key_At) }, { ' ', QT_MAC_MAP_ENUM(Qt::Key_Space) }, { '!', QT_MAC_MAP_ENUM(Qt::Key_Exclam) }, { '"', QT_MAC_MAP_ENUM(Qt::Key_QuoteDbl) }, { '#', QT_MAC_MAP_ENUM(Qt::Key_NumberSign) }, { '$', QT_MAC_MAP_ENUM(Qt::Key_Dollar) }, { '%', QT_MAC_MAP_ENUM(Qt::Key_Percent) }, { '&', QT_MAC_MAP_ENUM(Qt::Key_Ampersand) }, { '\'', QT_MAC_MAP_ENUM(Qt::Key_Apostrophe) }, { '(', QT_MAC_MAP_ENUM(Qt::Key_ParenLeft) }, { ')', QT_MAC_MAP_ENUM(Qt::Key_ParenRight) }, { '*', QT_MAC_MAP_ENUM(Qt::Key_Asterisk) }, { '+', QT_MAC_MAP_ENUM(Qt::Key_Plus) }, { ',', QT_MAC_MAP_ENUM(Qt::Key_Comma) }, { '-', QT_MAC_MAP_ENUM(Qt::Key_Minus) }, { '.', QT_MAC_MAP_ENUM(Qt::Key_Period) }, { '/', QT_MAC_MAP_ENUM(Qt::Key_Slash) }, { '[', QT_MAC_MAP_ENUM(Qt::Key_BracketLeft) }, { ']', QT_MAC_MAP_ENUM(Qt::Key_BracketRight) }, { '\\', QT_MAC_MAP_ENUM(Qt::Key_Backslash) }, { '_', QT_MAC_MAP_ENUM(Qt::Key_Underscore) }, { '`', QT_MAC_MAP_ENUM(Qt::Key_QuoteLeft) }, { '{', QT_MAC_MAP_ENUM(Qt::Key_BraceLeft) }, { '}', QT_MAC_MAP_ENUM(Qt::Key_BraceRight) }, { '|', QT_MAC_MAP_ENUM(Qt::Key_Bar) }, { '~', QT_MAC_MAP_ENUM(Qt::Key_AsciiTilde) }, { '^', QT_MAC_MAP_ENUM(Qt::Key_AsciiCircum) }, { 0, QT_MAC_MAP_ENUM(0) } }; static qt_mac_enum_mapper qt_mac_keyvkey_symbols[] = { //real scan codes { kVK_F1, QT_MAC_MAP_ENUM(Qt::Key_F1) }, { kVK_F2, QT_MAC_MAP_ENUM(Qt::Key_F2) }, { kVK_F3, QT_MAC_MAP_ENUM(Qt::Key_F3) }, { kVK_F4, QT_MAC_MAP_ENUM(Qt::Key_F4) }, { kVK_F5, QT_MAC_MAP_ENUM(Qt::Key_F5) }, { kVK_F6, QT_MAC_MAP_ENUM(Qt::Key_F6) }, { kVK_F7, QT_MAC_MAP_ENUM(Qt::Key_F7) }, { kVK_F8, QT_MAC_MAP_ENUM(Qt::Key_F8) }, { kVK_F9, QT_MAC_MAP_ENUM(Qt::Key_F9) }, { kVK_F10, QT_MAC_MAP_ENUM(Qt::Key_F10) }, { kVK_F11, QT_MAC_MAP_ENUM(Qt::Key_F11) }, { kVK_F12, QT_MAC_MAP_ENUM(Qt::Key_F12) }, { kVK_F13, QT_MAC_MAP_ENUM(Qt::Key_F13) }, { kVK_F14, QT_MAC_MAP_ENUM(Qt::Key_F14) }, { kVK_F15, QT_MAC_MAP_ENUM(Qt::Key_F15) }, { kVK_F16, QT_MAC_MAP_ENUM(Qt::Key_F16) }, { kVK_Return, QT_MAC_MAP_ENUM(Qt::Key_Return) }, { kVK_Tab, QT_MAC_MAP_ENUM(Qt::Key_Tab) }, { kVK_Escape, QT_MAC_MAP_ENUM(Qt::Key_Escape) }, { kVK_Help, QT_MAC_MAP_ENUM(Qt::Key_Help) }, { kVK_UpArrow, QT_MAC_MAP_ENUM(Qt::Key_Up) }, { kVK_DownArrow, QT_MAC_MAP_ENUM(Qt::Key_Down) }, { kVK_LeftArrow, QT_MAC_MAP_ENUM(Qt::Key_Left) }, { kVK_RightArrow, QT_MAC_MAP_ENUM(Qt::Key_Right) }, { kVK_PageUp, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, { kVK_PageDown, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, { 0, QT_MAC_MAP_ENUM(0) } }; static qt_mac_enum_mapper qt_mac_private_unicode[] = { { 0xF700, QT_MAC_MAP_ENUM(Qt::Key_Up) }, //NSUpArrowFunctionKey { 0xF701, QT_MAC_MAP_ENUM(Qt::Key_Down) }, //NSDownArrowFunctionKey { 0xF702, QT_MAC_MAP_ENUM(Qt::Key_Left) }, //NSLeftArrowFunctionKey { 0xF703, QT_MAC_MAP_ENUM(Qt::Key_Right) }, //NSRightArrowFunctionKey { 0xF727, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertFunctionKey { 0xF728, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteFunctionKey { 0xF729, QT_MAC_MAP_ENUM(Qt::Key_Home) }, //NSHomeFunctionKey { 0xF72B, QT_MAC_MAP_ENUM(Qt::Key_End) }, //NSEndFunctionKey { 0xF72C, QT_MAC_MAP_ENUM(Qt::Key_PageUp) }, //NSPageUpFunctionKey { 0xF72D, QT_MAC_MAP_ENUM(Qt::Key_PageDown) }, //NSPageDownFunctionKey { 0xF72E, QT_MAC_MAP_ENUM(Qt::Key_Print) }, //NSPrintScreenFunctionKey { 0xF72F, QT_MAC_MAP_ENUM(Qt::Key_ScrollLock) }, //NSScrollLockFunctionKey { 0xF730, QT_MAC_MAP_ENUM(Qt::Key_Pause) }, //NSPauseFunctionKey { 0xF731, QT_MAC_MAP_ENUM(Qt::Key_SysReq) }, //NSSysReqFunctionKey { 0xF735, QT_MAC_MAP_ENUM(Qt::Key_Menu) }, //NSMenuFunctionKey { 0xF738, QT_MAC_MAP_ENUM(Qt::Key_Printer) }, //NSPrintFunctionKey { 0xF73A, QT_MAC_MAP_ENUM(Qt::Key_Clear) }, //NSClearDisplayFunctionKey { 0xF73D, QT_MAC_MAP_ENUM(Qt::Key_Insert) }, //NSInsertCharFunctionKey { 0xF73E, QT_MAC_MAP_ENUM(Qt::Key_Delete) }, //NSDeleteCharFunctionKey { 0xF741, QT_MAC_MAP_ENUM(Qt::Key_Select) }, //NSSelectFunctionKey { 0xF742, QT_MAC_MAP_ENUM(Qt::Key_Execute) }, //NSExecuteFunctionKey { 0xF743, QT_MAC_MAP_ENUM(Qt::Key_Undo) }, //NSUndoFunctionKey { 0xF744, QT_MAC_MAP_ENUM(Qt::Key_Redo) }, //NSRedoFunctionKey { 0xF745, QT_MAC_MAP_ENUM(Qt::Key_Find) }, //NSFindFunctionKey { 0xF746, QT_MAC_MAP_ENUM(Qt::Key_Help) }, //NSHelpFunctionKey { 0xF747, QT_MAC_MAP_ENUM(Qt::Key_Mode_switch) }, //NSModeSwitchFunctionKey { 0, QT_MAC_MAP_ENUM(0) } }; static int qt_mac_get_key(int modif, const QChar &key, int virtualKey) { #ifdef DEBUG_KEY_BINDINGS qDebug("**Mapping key: %d (0x%04x) - %d (0x%04x)", key.unicode(), key.unicode(), virtualKey, virtualKey); #endif if (key == kClearCharCode && virtualKey == 0x47) return Qt::Key_Clear; if (key.isDigit()) { #ifdef DEBUG_KEY_BINDINGS qDebug("%d: got key: %d", __LINE__, key.digitValue()); #endif return key.digitValue() + Qt::Key_0; } if (key.isLetter()) { #ifdef DEBUG_KEY_BINDINGS qDebug("%d: got key: %d", __LINE__, (key.toUpper().unicode() - 'A')); #endif return (key.toUpper().unicode() - 'A') + Qt::Key_A; } if (key.isSymbol()) { #ifdef DEBUG_KEY_BINDINGS qDebug("%d: got key: %d", __LINE__, (key.unicode())); #endif return key.unicode(); } for (int i = 0; qt_mac_keyboard_symbols[i].qt_code; i++) { if (qt_mac_keyboard_symbols[i].mac_code == key) { /* To work like Qt for X11 we issue Backtab when Shift + Tab are pressed */ if (qt_mac_keyboard_symbols[i].qt_code == Qt::Key_Tab && (modif & Qt::ShiftModifier)) { #ifdef DEBUG_KEY_BINDINGS qDebug("%d: got key: Qt::Key_Backtab", __LINE__); #endif return Qt::Key_Backtab; } #ifdef DEBUG_KEY_BINDINGS qDebug("%d: got key: %s", __LINE__, qt_mac_keyboard_symbols[i].desc); #endif return qt_mac_keyboard_symbols[i].qt_code; } } //last ditch try to match the scan code for (int i = 0; qt_mac_keyvkey_symbols[i].qt_code; i++) { if (qt_mac_keyvkey_symbols[i].mac_code == virtualKey) { #ifdef DEBUG_KEY_BINDINGS qDebug("%d: got key: %s", __LINE__, qt_mac_keyvkey_symbols[i].desc); #endif return qt_mac_keyvkey_symbols[i].qt_code; } } // check if they belong to key codes in private unicode range if (key >= 0xf700 && key <= 0xf747) { if (key >= 0xf704 && key <= 0xf726) { return Qt::Key_F1 + (key.unicode() - 0xf704) ; } for (int i = 0; qt_mac_private_unicode[i].qt_code; i++) { if (qt_mac_private_unicode[i].mac_code == key) { return qt_mac_private_unicode[i].qt_code; } } } //oh well #ifdef DEBUG_KEY_BINDINGS qDebug("Unknown case.. %s:%d %d[%d] %d", __FILE__, __LINE__, key.unicode(), key.toLatin1(), virtualKey); #endif return Qt::Key_unknown; } QCocoaKeyMapper::QCocoaKeyMapper() { memset(keyLayout, 0, sizeof(keyLayout)); } QCocoaKeyMapper::~QCocoaKeyMapper() { deleteLayouts(); } Qt::KeyboardModifiers QCocoaKeyMapper::queryKeyboardModifiers() { return qt_mac_get_modifiers(GetCurrentKeyModifiers()); } bool QCocoaKeyMapper::updateKeyboard() { const UCKeyboardLayout *uchrData = nullptr; QCFType source = TISCopyInputMethodKeyboardLayoutOverride(); if (!source) source = TISCopyCurrentKeyboardInputSource(); if (keyboard_mode != NullMode && source == currentInputSource) { return false; } Q_ASSERT(source); CFDataRef data = static_cast(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData)); uchrData = data ? reinterpret_cast(CFDataGetBytePtr(data)) : nullptr; keyboard_kind = LMGetKbdType(); if (uchrData) { keyboard_layout_format = uchrData; keyboard_mode = UnicodeMode; } else { keyboard_layout_format = nullptr; keyboard_mode = NullMode; } currentInputSource = source; keyboard_dead = 0; const auto newMode = keyboard_mode; deleteLayouts(); keyboard_mode = newMode; return true; } void QCocoaKeyMapper::deleteLayouts() { keyboard_mode = NullMode; for (int i = 0; i < 255; ++i) { if (keyLayout[i]) { delete keyLayout[i]; keyLayout[i] = nullptr; } } } void QCocoaKeyMapper::clearMappings() { deleteLayouts(); updateKeyboard(); } void QCocoaKeyMapper::updateKeyMap(unsigned short macVirtualKey, QChar unicodeKey) { updateKeyboard(); if (keyLayout[macVirtualKey]) return; UniCharCount buffer_size = 10; UniChar buffer[buffer_size]; keyLayout[macVirtualKey] = new KeyboardLayoutItem; for (int i = 0; i < 16; ++i) { UniCharCount out_buffer_size = 0; keyLayout[macVirtualKey]->qtKey[i] = 0; const UInt32 keyModifier = ((qt_mac_get_mac_modifiers(ModsTbl[i]) >> 8) & 0xFF); OSStatus err = UCKeyTranslate(keyboard_layout_format, macVirtualKey, kUCKeyActionDown, keyModifier, keyboard_kind, 0, &keyboard_dead, buffer_size, &out_buffer_size, buffer); if (err == noErr && out_buffer_size) { const QChar unicode(buffer[0]); int qtkey = qt_mac_get_key(keyModifier, unicode, macVirtualKey); if (qtkey == Qt::Key_unknown) qtkey = unicode.unicode(); keyLayout[macVirtualKey]->qtKey[i] = qtkey; } else { int qtkey = qt_mac_get_key(keyModifier, unicodeKey, macVirtualKey); if (qtkey == Qt::Key_unknown) qtkey = unicodeKey.unicode(); keyLayout[macVirtualKey]->qtKey[i] = qtkey; } } #ifdef DEBUG_KEY_MAPS qDebug("updateKeyMap for virtual key = 0x%02x!", (uint)macVirtualKey); for (int i = 0; i < 16; ++i) { qDebug(" [%d] (%d,0x%02x,'%c')", i, keyLayout[macVirtualKey]->qtKey[i], keyLayout[macVirtualKey]->qtKey[i], keyLayout[macVirtualKey]->qtKey[i]); } #endif } QList QCocoaKeyMapper::possibleKeys(const QKeyEvent *event) const { QList ret; const_cast(this)->updateKeyMap(event->nativeVirtualKey(), QChar(event->key())); KeyboardLayoutItem *kbItem = keyLayout[event->nativeVirtualKey()]; if (!kbItem) // Key is not in any keyboard layout (e.g. eisu-key on Japanese keyboard) return ret; int baseKey = kbItem->qtKey[0]; Qt::KeyboardModifiers keyMods = event->modifiers(); ret << int(baseKey + keyMods); // The base key is _always_ valid, of course for (int i = 1; i < 8; ++i) { Qt::KeyboardModifiers neededMods = ModsTbl[i]; int key = kbItem->qtKey[i]; if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) { ret << int(key + (keyMods & ~neededMods)); } } return ret; } QT_END_NAMESPACE