From 15576c961047231a49afda9b9ee0159ba132c0ae Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Fri, 4 Dec 2020 15:04:49 +0100 Subject: iOS: Handle keyboard events when using an external keyboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This enables the two possible approaches for handling external keyboard events. While support still exists for before 13.4 then both approaches are needed. This ensures that all external keyboard events are handled as key events and passed on accordingly. Additionally, this accounts for possible shortcuts too, therefore a new function is added to QShortcutMap to aid that. As a result, code has now moved from QCocoaKeyMapper to be part of the gui/platforms/darwin part to make it easier to reuse this code elsewhere. Fixes: QTBUG-85727 Change-Id: I349af43468b03fd8dcb16adba02669974affe154 Reviewed-by: Qt CI Bot Reviewed-by: Tor Arne Vestbø --- src/gui/CMakeLists.txt | 2 + src/gui/kernel/qshortcutmap.cpp | 43 +- src/gui/kernel/qshortcutmap_p.h | 1 + src/gui/platform/darwin/qapplekeymapper.mm | 653 +++++++++++++++++++++ src/gui/platform/darwin/qapplekeymapper_p.h | 103 ++++ src/plugins/platforms/cocoa/CMakeLists.txt | 1 - .../platforms/cocoa/qcocoaapplicationdelegate.mm | 2 +- src/plugins/platforms/cocoa/qcocoaintegration.h | 4 +- src/plugins/platforms/cocoa/qcocoaintegration.mm | 4 +- src/plugins/platforms/cocoa/qcocoakeymapper.h | 93 --- src/plugins/platforms/cocoa/qcocoakeymapper.mm | 547 ----------------- src/plugins/platforms/cocoa/qcocoamenuitem.mm | 6 +- src/plugins/platforms/cocoa/qcocoansmenu.mm | 6 +- src/plugins/platforms/cocoa/qnsview_dragging.mm | 6 +- src/plugins/platforms/cocoa/qnsview_keys.mm | 8 +- src/plugins/platforms/cocoa/qnsview_mouse.mm | 4 +- src/plugins/platforms/cocoa/qnsview_tablet.mm | 2 +- src/plugins/platforms/ios/qiosviewcontroller.h | 2 + src/plugins/platforms/ios/qiosviewcontroller.mm | 33 ++ src/plugins/platforms/ios/quiview.h | 1 + src/plugins/platforms/ios/quiview.mm | 47 +- 21 files changed, 897 insertions(+), 671 deletions(-) create mode 100644 src/gui/platform/darwin/qapplekeymapper.mm create mode 100644 src/gui/platform/darwin/qapplekeymapper_p.h delete mode 100644 src/plugins/platforms/cocoa/qcocoakeymapper.h delete mode 100644 src/plugins/platforms/cocoa/qcocoakeymapper.mm (limited to 'src') diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 979de8aadf..40c373c6d9 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -359,6 +359,7 @@ qt_internal_extend_target(Gui CONDITION MACOS platform/macos/qcocoanativeinterface.mm LIBRARIES ${FWAppKit} + ${FWCarbon} PUBLIC_LIBRARIES ${FWAppKit} ) @@ -369,6 +370,7 @@ qt_internal_extend_target(Gui CONDITION APPLE painting/qcoregraphics.mm painting/qcoregraphics_p.h painting/qrasterbackingstore.cpp painting/qrasterbackingstore_p.h platform/darwin/qmacmime.mm platform/darwin/qmacmime_p.h + platform/darwin/qapplekeymapper.mm platform/darwin/qapplekeymapper_p.h text/coretext/qcoretextfontdatabase.mm text/coretext/qcoretextfontdatabase_p.h text/coretext/qfontengine_coretext.mm text/coretext/qfontengine_coretext_p.h LIBRARIES diff --git a/src/gui/kernel/qshortcutmap.cpp b/src/gui/kernel/qshortcutmap.cpp index 2945115402..d83a00bc48 100644 --- a/src/gui/kernel/qshortcutmap.cpp +++ b/src/gui/kernel/qshortcutmap.cpp @@ -43,7 +43,8 @@ #include "qdebug.h" #include "qevent.h" #include "qlist.h" -#include "qcoreapplication.h" +#include "qguiapplication.h" +#include "qwindow.h" #include #include @@ -665,6 +666,46 @@ void QShortcutMap::dispatchEvent(QKeyEvent *e) QCoreApplication::sendEvent(const_cast(next->owner), &se); } +QList QShortcutMap::keySequences(bool getAll) const +{ + Q_D(const QShortcutMap); + QList keys; + for (auto sequence : d->sequences) { + bool addSequence = false; + if (sequence.enabled) { + if (getAll || sequence.context == Qt::ApplicationShortcut || + sequence.owner == QGuiApplication::focusObject()) { + addSequence = true; + } else { + QObject *possibleWindow = sequence.owner; + while (possibleWindow) { + if (qobject_cast(possibleWindow)) + break; + possibleWindow = possibleWindow->parent(); + } + if (possibleWindow == QGuiApplication::focusWindow()) { + if (sequence.context == Qt::WindowShortcut) { + addSequence = true; + } else if (sequence.context == Qt::WidgetWithChildrenShortcut) { + QObject *possibleWidget = QGuiApplication::focusObject(); + while (possibleWidget->parent()) { + possibleWidget = possibleWidget->parent(); + if (possibleWidget == sequence.owner) { + addSequence = true; + break; + } + } + } + } + } + if (addSequence) + keys << sequence.keyseq; + } + } + return keys; + +} + /* \internal QShortcutMap dump function, only available when DEBUG_QSHORTCUTMAP is defined. diff --git a/src/gui/kernel/qshortcutmap_p.h b/src/gui/kernel/qshortcutmap_p.h index ff92a38e56..287549ea3a 100644 --- a/src/gui/kernel/qshortcutmap_p.h +++ b/src/gui/kernel/qshortcutmap_p.h @@ -86,6 +86,7 @@ public: bool tryShortcut(QKeyEvent *e); bool hasShortcutForKeySequence(const QKeySequence &seq) const; + QList keySequences(bool getAll = false) const; #ifdef Dump_QShortcutMap void dumpMap() const; diff --git a/src/gui/platform/darwin/qapplekeymapper.mm b/src/gui/platform/darwin/qapplekeymapper.mm new file mode 100644 index 0000000000..c66fe784ed --- /dev/null +++ b/src/gui/platform/darwin/qapplekeymapper.mm @@ -0,0 +1,653 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 + +#ifdef Q_OS_MACOS +#include +#endif + +#if defined(QT_PLATFORM_UIKIT) +#include +#endif + +#include "qapplekeymapper_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQpaKeyMapper, "qt.qpa.keymapper"); +Q_LOGGING_CATEGORY(lcQpaKeyMapperKeys, "qt.qpa.keymapper.keys"); + +static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers) +{ + if (QCoreApplication::testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) + return modifiers; + + Qt::KeyboardModifiers swappedModifiers = modifiers; + swappedModifiers &= ~(Qt::MetaModifier | Qt::ControlModifier); + + if (modifiers & Qt::ControlModifier) + swappedModifiers |= Qt::MetaModifier; + if (modifiers & Qt::MetaModifier) + swappedModifiers |= Qt::ControlModifier; + + return swappedModifiers; +} + +Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters, + NSString *charactersIgnoringModifiers) +{ + if ([characters isEqualToString:@"\t"]) { + if (qtModifiers & Qt::ShiftModifier) + return Qt::Key_Backtab; + return Qt::Key_Tab; + } else if ([characters isEqualToString:@"\r"]) { + if (qtModifiers & Qt::KeypadModifier) + return Qt::Key_Enter; + return Qt::Key_Return; + } + if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { + QChar ch; + if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) && + ([charactersIgnoringModifiers length] != 0)) { + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + } else if ([characters length] != 0) { + ch = QChar([characters characterAtIndex:0]); + } + if (!ch.isNull()) + return Qt::Key(ch.toUpper().unicode()); + } + return Qt::Key_unknown; +} + +#ifdef Q_OS_MACOS +static constexpr std::tuple cocoaModifierMap[] = { + { NSEventModifierFlagShift, Qt::ShiftModifier }, + { NSEventModifierFlagControl, Qt::ControlModifier }, + { NSEventModifierFlagCommand, Qt::MetaModifier }, + { NSEventModifierFlagOption, Qt::AltModifier }, + { NSEventModifierFlagNumericPad, Qt::KeypadModifier } +}; + +Qt::KeyboardModifiers QAppleKeyMapper::fromCocoaModifiers(NSEventModifierFlags cocoaModifiers) +{ + Qt::KeyboardModifiers qtModifiers = Qt::NoModifier; + for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) { + if (cocoaModifiers & cocoaModifier) + qtModifiers |= qtModifier; + } + + return swapModifiersIfNeeded(qtModifiers); +} + +NSEventModifierFlags QAppleKeyMapper::toCocoaModifiers(Qt::KeyboardModifiers qtModifiers) +{ + qtModifiers = swapModifiersIfNeeded(qtModifiers); + + NSEventModifierFlags cocoaModifiers = 0; + for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) { + if (qtModifiers & qtModifier) + cocoaModifiers |= cocoaModifier; + } + + return cocoaModifiers; +} + +using CarbonModifiers = UInt32; // As opposed to EventModifiers which is UInt16 + +static CarbonModifiers toCarbonModifiers(Qt::KeyboardModifiers qtModifiers) +{ + qtModifiers = swapModifiersIfNeeded(qtModifiers); + + static constexpr std::tuple carbonModifierMap[] = { + { shiftKey, Qt::ShiftModifier }, + { controlKey, Qt::ControlModifier }, + { cmdKey, Qt::MetaModifier }, + { optionKey, Qt::AltModifier }, + { kEventKeyModifierNumLockMask, Qt::KeypadModifier } + }; + + CarbonModifiers carbonModifiers = 0; + for (const auto &[carbonModifier, qtModifier] : carbonModifierMap) { + if (qtModifiers & qtModifier) + carbonModifiers |= carbonModifier; + } + + return carbonModifiers; +} + +// Keyboard keys (non-modifiers) +static QHash standardKeys = { + { kHomeCharCode, Qt::Key_Home }, + { kEnterCharCode, Qt::Key_Enter }, + { kEndCharCode, Qt::Key_End }, + { kBackspaceCharCode, Qt::Key_Backspace }, + { kTabCharCode, Qt::Key_Tab }, + { kPageUpCharCode, Qt::Key_PageUp }, + { kPageDownCharCode, Qt::Key_PageDown }, + { kReturnCharCode, Qt::Key_Return }, + { kEscapeCharCode, Qt::Key_Escape }, + { kLeftArrowCharCode, Qt::Key_Left }, + { kRightArrowCharCode, Qt::Key_Right }, + { kUpArrowCharCode, Qt::Key_Up }, + { kDownArrowCharCode, Qt::Key_Down }, + { kHelpCharCode, Qt::Key_Help }, + { kDeleteCharCode, Qt::Key_Delete }, + // ASCII maps, for debugging + { ':', Qt::Key_Colon }, + { ';', Qt::Key_Semicolon }, + { '<', Qt::Key_Less }, + { '=', Qt::Key_Equal }, + { '>', Qt::Key_Greater }, + { '?', Qt::Key_Question }, + { '@', Qt::Key_At }, + { ' ', Qt::Key_Space }, + { '!', Qt::Key_Exclam }, + { '"', Qt::Key_QuoteDbl }, + { '#', Qt::Key_NumberSign }, + { '$', Qt::Key_Dollar }, + { '%', Qt::Key_Percent }, + { '&', Qt::Key_Ampersand }, + { '\'', Qt::Key_Apostrophe }, + { '(', Qt::Key_ParenLeft }, + { ')', Qt::Key_ParenRight }, + { '*', Qt::Key_Asterisk }, + { '+', Qt::Key_Plus }, + { ',', Qt::Key_Comma }, + { '-', Qt::Key_Minus }, + { '.', Qt::Key_Period }, + { '/', Qt::Key_Slash }, + { '[', Qt::Key_BracketLeft }, + { ']', Qt::Key_BracketRight }, + { '\\', Qt::Key_Backslash }, + { '_', Qt::Key_Underscore }, + { '`', Qt::Key_QuoteLeft }, + { '{', Qt::Key_BraceLeft }, + { '}', Qt::Key_BraceRight }, + { '|', Qt::Key_Bar }, + { '~', Qt::Key_AsciiTilde }, + { '^', Qt::Key_AsciiCircum } +}; + +static QHash virtualKeys = { + { kVK_F1, Qt::Key_F1 }, + { kVK_F2, Qt::Key_F2 }, + { kVK_F3, Qt::Key_F3 }, + { kVK_F4, Qt::Key_F4 }, + { kVK_F5, Qt::Key_F5 }, + { kVK_F6, Qt::Key_F6 }, + { kVK_F7, Qt::Key_F7 }, + { kVK_F8, Qt::Key_F8 }, + { kVK_F9, Qt::Key_F9 }, + { kVK_F10, Qt::Key_F10 }, + { kVK_F11, Qt::Key_F11 }, + { kVK_F12, Qt::Key_F12 }, + { kVK_F13, Qt::Key_F13 }, + { kVK_F14, Qt::Key_F14 }, + { kVK_F15, Qt::Key_F15 }, + { kVK_F16, Qt::Key_F16 }, + { kVK_Return, Qt::Key_Return }, + { kVK_Tab, Qt::Key_Tab }, + { kVK_Escape, Qt::Key_Escape }, + { kVK_Help, Qt::Key_Help }, + { kVK_UpArrow, Qt::Key_Up }, + { kVK_DownArrow, Qt::Key_Down }, + { kVK_LeftArrow, Qt::Key_Left }, + { kVK_RightArrow, Qt::Key_Right }, + { kVK_PageUp, Qt::Key_PageUp }, + { kVK_PageDown, Qt::Key_PageDown } +}; + +static QHash functionKeys = { + { NSUpArrowFunctionKey, Qt::Key_Up }, + { NSDownArrowFunctionKey, Qt::Key_Down }, + { NSLeftArrowFunctionKey, Qt::Key_Left }, + { NSRightArrowFunctionKey, Qt::Key_Right }, + // F1-35 function keys handled manually below + { NSInsertFunctionKey, Qt::Key_Insert }, + { NSDeleteFunctionKey, Qt::Key_Delete }, + { NSHomeFunctionKey, Qt::Key_Home }, + { NSEndFunctionKey, Qt::Key_End }, + { NSPageUpFunctionKey, Qt::Key_PageUp }, + { NSPageDownFunctionKey, Qt::Key_PageDown }, + { NSPrintScreenFunctionKey, Qt::Key_Print }, + { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, + { NSPauseFunctionKey, Qt::Key_Pause }, + { NSSysReqFunctionKey, Qt::Key_SysReq }, + { NSMenuFunctionKey, Qt::Key_Menu }, + { NSPrintFunctionKey, Qt::Key_Printer }, + { NSClearDisplayFunctionKey, Qt::Key_Clear }, + { NSInsertCharFunctionKey, Qt::Key_Insert }, + { NSDeleteCharFunctionKey, Qt::Key_Delete }, + { NSSelectFunctionKey, Qt::Key_Select }, + { NSExecuteFunctionKey, Qt::Key_Execute }, + { NSUndoFunctionKey, Qt::Key_Undo }, + { NSRedoFunctionKey, Qt::Key_Redo }, + { NSFindFunctionKey, Qt::Key_Find }, + { NSHelpFunctionKey, Qt::Key_Help }, + { NSModeSwitchFunctionKey, Qt::Key_Mode_switch } +}; + +static int toKeyCode(const QChar &key, int virtualKey, int modifiers) +{ + qCDebug(lcQpaKeyMapperKeys, "Mapping key: %d (0x%04x) / vk %d (0x%04x)", + key.unicode(), key.unicode(), virtualKey, virtualKey); + + if (key == QChar(kClearCharCode) && virtualKey == 0x47) + return Qt::Key_Clear; + + if (key.isDigit()) { + qCDebug(lcQpaKeyMapperKeys, "Got digit key: %d", key.digitValue()); + return key.digitValue() + Qt::Key_0; + } + + if (key.isLetter()) { + qCDebug(lcQpaKeyMapperKeys, "Got letter key: %d", (key.toUpper().unicode() - 'A')); + return (key.toUpper().unicode() - 'A') + Qt::Key_A; + } + if (key.isSymbol()) { + qCDebug(lcQpaKeyMapperKeys, "Got symbol key: %d", (key.unicode())); + return key.unicode(); + } + + if (auto qtKey = standardKeys.value(key.unicode())) { + // To work like Qt for X11 we issue Backtab when Shift + Tab are pressed + if (qtKey == Qt::Key_Tab && (modifiers & Qt::ShiftModifier)) { + qCDebug(lcQpaKeyMapperKeys, "Got key: Qt::Key_Backtab"); + return Qt::Key_Backtab; + } + + qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey; + return qtKey; + } + + // Last ditch try to match the scan code + if (auto qtKey = virtualKeys.value(virtualKey)) { + qCDebug(lcQpaKeyMapperKeys) << "Got scancode" << qtKey; + return qtKey; + } + + // Check if they belong to key codes in private unicode range + if (key >= QChar(NSUpArrowFunctionKey) && key <= QChar(NSModeSwitchFunctionKey)) { + if (auto qtKey = functionKeys.value(key.unicode())) { + qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey; + return qtKey; + } else if (key >= QChar(NSF1FunctionKey) && key <= QChar(NSF35FunctionKey)) { + auto functionKey = Qt::Key_F1 + (key.unicode() - NSF1FunctionKey) ; + qCDebug(lcQpaKeyMapperKeys) << "Got" << functionKey; + return functionKey; + } + } + + qCDebug(lcQpaKeyMapperKeys, "Unknown case.. %d[%d] %d", key.unicode(), key.toLatin1(), virtualKey); + return Qt::Key_unknown; +} + +// --------- Cocoa key mapping moved from Qt Core --------- + +static const int NSEscapeCharacter = 27; // not defined by Cocoa headers + +static const QHash cocoaKeys = { + { NSEnterCharacter, Qt::Key_Enter }, + { NSBackspaceCharacter, Qt::Key_Backspace }, + { NSTabCharacter, Qt::Key_Tab }, + { NSNewlineCharacter, Qt::Key_Return }, + { NSCarriageReturnCharacter, Qt::Key_Return }, + { NSBackTabCharacter, Qt::Key_Backtab }, + { NSEscapeCharacter, Qt::Key_Escape }, + { NSDeleteCharacter, Qt::Key_Backspace }, + { NSUpArrowFunctionKey, Qt::Key_Up }, + { NSDownArrowFunctionKey, Qt::Key_Down }, + { NSLeftArrowFunctionKey, Qt::Key_Left }, + { NSRightArrowFunctionKey, Qt::Key_Right }, + { NSF1FunctionKey, Qt::Key_F1 }, + { NSF2FunctionKey, Qt::Key_F2 }, + { NSF3FunctionKey, Qt::Key_F3 }, + { NSF4FunctionKey, Qt::Key_F4 }, + { NSF5FunctionKey, Qt::Key_F5 }, + { NSF6FunctionKey, Qt::Key_F6 }, + { NSF7FunctionKey, Qt::Key_F7 }, + { NSF8FunctionKey, Qt::Key_F8 }, + { NSF9FunctionKey, Qt::Key_F9 }, + { NSF10FunctionKey, Qt::Key_F10 }, + { NSF11FunctionKey, Qt::Key_F11 }, + { NSF12FunctionKey, Qt::Key_F12 }, + { NSF13FunctionKey, Qt::Key_F13 }, + { NSF14FunctionKey, Qt::Key_F14 }, + { NSF15FunctionKey, Qt::Key_F15 }, + { NSF16FunctionKey, Qt::Key_F16 }, + { NSF17FunctionKey, Qt::Key_F17 }, + { NSF18FunctionKey, Qt::Key_F18 }, + { NSF19FunctionKey, Qt::Key_F19 }, + { NSF20FunctionKey, Qt::Key_F20 }, + { NSF21FunctionKey, Qt::Key_F21 }, + { NSF22FunctionKey, Qt::Key_F22 }, + { NSF23FunctionKey, Qt::Key_F23 }, + { NSF24FunctionKey, Qt::Key_F24 }, + { NSF25FunctionKey, Qt::Key_F25 }, + { NSF26FunctionKey, Qt::Key_F26 }, + { NSF27FunctionKey, Qt::Key_F27 }, + { NSF28FunctionKey, Qt::Key_F28 }, + { NSF29FunctionKey, Qt::Key_F29 }, + { NSF30FunctionKey, Qt::Key_F30 }, + { NSF31FunctionKey, Qt::Key_F31 }, + { NSF32FunctionKey, Qt::Key_F32 }, + { NSF33FunctionKey, Qt::Key_F33 }, + { NSF34FunctionKey, Qt::Key_F34 }, + { NSF35FunctionKey, Qt::Key_F35 }, + { NSInsertFunctionKey, Qt::Key_Insert }, + { NSDeleteFunctionKey, Qt::Key_Delete }, + { NSHomeFunctionKey, Qt::Key_Home }, + { NSEndFunctionKey, Qt::Key_End }, + { NSPageUpFunctionKey, Qt::Key_PageUp }, + { NSPageDownFunctionKey, Qt::Key_PageDown }, + { NSPrintScreenFunctionKey, Qt::Key_Print }, + { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, + { NSPauseFunctionKey, Qt::Key_Pause }, + { NSSysReqFunctionKey, Qt::Key_SysReq }, + { NSMenuFunctionKey, Qt::Key_Menu }, + { NSHelpFunctionKey, Qt::Key_Help }, +}; + +QChar QAppleKeyMapper::toCocoaKey(Qt::Key key) +{ + // Prioritize overloaded keys + if (key == Qt::Key_Return) + return QChar(NSNewlineCharacter); + if (key == Qt::Key_Backspace) + return QChar(NSBackspaceCharacter); + + static QHash reverseCocoaKeys; + if (reverseCocoaKeys.isEmpty()) { + reverseCocoaKeys.reserve(cocoaKeys.size()); + for (auto it = cocoaKeys.begin(); it != cocoaKeys.end(); ++it) + reverseCocoaKeys.insert(it.value(), it.key()); + } + + return reverseCocoaKeys.value(key); +} + +Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode) +{ + if (auto key = cocoaKeys.value(keyCode.unicode())) + return key; + + return Qt::Key(keyCode.toUpper().unicode()); +} + +// ------------------------------------------------ + +Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() +{ + return fromCocoaModifiers(NSEvent.modifierFlags); +} + +bool QAppleKeyMapper::updateKeyboard() +{ + QCFType source = TISCopyInputMethodKeyboardLayoutOverride(); + if (!source) + source = TISCopyCurrentKeyboardInputSource(); + + if (m_keyboardMode != NullMode && source == m_currentInputSource) + return false; + + Q_ASSERT(source); + m_currentInputSource = source; + m_keyboardKind = LMGetKbdType(); + m_deadKeyState = 0; + + m_keyMap.clear(); + + if (auto data = CFDataRef(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData))) { + const UCKeyboardLayout *uchrData = reinterpret_cast(CFDataGetBytePtr(data)); + Q_ASSERT(uchrData); + m_keyboardLayoutFormat = uchrData; + m_keyboardMode = UnicodeMode; + } else { + m_keyboardLayoutFormat = nullptr; + m_keyboardMode = NullMode; + } + + qCDebug(lcQpaKeyMapper) << "Updated keyboard to" + << QString::fromCFString(CFStringRef(TISGetInputSourceProperty( + m_currentInputSource, kTISPropertyLocalizedName))); + + return true; +} + +static constexpr Qt::KeyboardModifiers modifierCombinations[] = { + 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 +}; + +/* + Returns a key map for the given \virtualKey based on all + possible modifier combinations. +*/ +const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virtualKey, QChar unicodeKey) const +{ + static_assert(sizeof(modifierCombinations) / sizeof(Qt::KeyboardModifiers) == kNumModifierCombinations); + + const_cast(this)->updateKeyboard(); + + auto &keyMap = m_keyMap[virtualKey]; + if (keyMap[Qt::NoModifier] != Qt::Key_unknown) + return keyMap; // Already filled + + qCDebug(lcQpaKeyMapper, "Updating key map for virtual key = 0x%02x!", (uint)virtualKey); + + // Key mapping via [NSEvent charactersByApplyingModifiers:] only works for key down + // events, but we might (wrongly) get into this code path for other key events such + // as NSEventTypeFlagsChanged. + const bool canMapCocoaEvent = NSApp.currentEvent.type == NSEventTypeKeyDown; + + if (!canMapCocoaEvent) + qCWarning(lcQpaKeyMapper) << "Could not map key to character for event" << NSApp.currentEvent; + + for (int i = 0; i < kNumModifierCombinations; ++i) { + Q_ASSERT(!i || keyMap[i] == 0); + + auto qtModifiers = modifierCombinations[i]; + auto carbonModifiers = toCarbonModifiers(qtModifiers); + const UInt32 modifierKeyState = (carbonModifiers >> 8) & 0xFF; + + static const UniCharCount maxStringLength = 10; + static UniChar unicodeString[maxStringLength]; + UniCharCount actualStringLength = 0; + OSStatus err = UCKeyTranslate(m_keyboardLayoutFormat, virtualKey, + kUCKeyActionDown, modifierKeyState, m_keyboardKind, OptionBits(0), + &m_deadKeyState, maxStringLength, &actualStringLength, unicodeString); + + // Use translated unicode key if valid + if (err == noErr && actualStringLength) + unicodeKey = QChar(unicodeString[0]); + + if (@available(macOS 10.15, *)) { + if (canMapCocoaEvent) { + // Until we've verified that the Cocoa API works as expected + // we first run the event through the Carbon APIs and then + // compare the results to Cocoa. + auto cocoaModifiers = toCocoaModifiers(qtModifiers); + auto *charactersWithModifiers = [NSApp.currentEvent charactersByApplyingModifiers:cocoaModifiers]; + Q_ASSERT(charactersWithModifiers && charactersWithModifiers.length > 0); + auto cocoaUnicodeKey = QChar([charactersWithModifiers characterAtIndex:0]); + if (cocoaUnicodeKey != unicodeKey) { + qCWarning(lcQpaKeyMapper) << "Mismatch between Cocoa" << cocoaUnicodeKey + << "and Carbon" << unicodeKey << "for virtual key" << virtualKey + << "with" << qtModifiers; + } + } + } + + int qtkey = toKeyCode(unicodeKey, virtualKey, qtModifiers); + if (qtkey == Qt::Key_unknown) + qtkey = unicodeKey.unicode(); + + keyMap[i] = qtkey; + + qCDebug(lcQpaKeyMapper, " [%d] (%d,0x%02x,'%c')", i, qtkey, qtkey, qtkey); + } + + return keyMap; +} + +QList QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const +{ + QList ret; + + const auto nativeVirtualKey = event->nativeVirtualKey(); + if (!nativeVirtualKey) + return ret; + + auto keyMap = keyMapForKey(nativeVirtualKey, QChar(event->key())); + + auto unmodifiedKey = keyMap[Qt::NoModifier]; + Q_ASSERT(unmodifiedKey != Qt::Key_unknown); + + auto eventModifiers = event->modifiers(); + + // The base key, with the complete set of modifiers, + // is always valid, and the first priority. + ret << int(unmodifiedKey) + int(eventModifiers); + + // FIXME: We only compute the first 8 combinations. Why? + for (int i = 1; i < 8; ++i) { + auto keyAfterApplyingModifiers = keyMap[i]; + if (keyAfterApplyingModifiers == unmodifiedKey) + continue; + + // Include key if event modifiers includes, or matches + // perfectly, the current candidate modifiers. + auto candidateModifiers = modifierCombinations[i]; + if ((eventModifiers & candidateModifiers) == candidateModifiers) + ret << int(keyAfterApplyingModifiers) + int(eventModifiers & ~candidateModifiers); + } + + return ret; +} + + + +#else +// Keyboard keys (non-modifiers) +API_AVAILABLE(ios(13.4)) static QHash uiKitKeys = { +#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) + { UIKeyInputF1, Qt::Key_F1 }, + { UIKeyInputF2, Qt::Key_F2 }, + { UIKeyInputF3, Qt::Key_F3 }, + { UIKeyInputF4, Qt::Key_F4 }, + { UIKeyInputF5, Qt::Key_F5 }, + { UIKeyInputF6, Qt::Key_F6 }, + { UIKeyInputF7, Qt::Key_F7 }, + { UIKeyInputF8, Qt::Key_F8 }, + { UIKeyInputF9, Qt::Key_F9 }, + { UIKeyInputF10, Qt::Key_F10 }, + { UIKeyInputF11, Qt::Key_F11 }, + { UIKeyInputF12, Qt::Key_F12 }, + { UIKeyInputHome, Qt::Key_Home }, + { UIKeyInputEnd, Qt::Key_End }, + { UIKeyInputPageUp, Qt::Key_PageUp }, + { UIKeyInputPageDown, Qt::Key_PageDown }, +#endif + { UIKeyInputEscape, Qt::Key_Escape }, + { UIKeyInputUpArrow, Qt::Key_Up }, + { UIKeyInputDownArrow, Qt::Key_Down }, + { UIKeyInputLeftArrow, Qt::Key_Left }, + { UIKeyInputRightArrow, Qt::Key_Right } +}; + +API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode) +{ + if (auto key = uiKitKeys.value(keyCode)) + return key; + + return Qt::Key_unknown; +} + +static constexpr std::tuple uiKitModifierMap[] = { + { UIKeyModifierShift, Qt::ShiftModifier }, + { UIKeyModifierControl, Qt::ControlModifier }, + { UIKeyModifierCommand, Qt::MetaModifier }, + { UIKeyModifierAlternate, Qt::AltModifier }, + { UIKeyModifierNumericPad, Qt::KeypadModifier } +}; + +ulong QAppleKeyMapper::toUIKitModifiers(Qt::KeyboardModifiers qtModifiers) +{ + qtModifiers = swapModifiersIfNeeded(qtModifiers); + + ulong nativeModifiers = 0; + for (const auto &[nativeModifier, qtModifier] : uiKitModifierMap) { + if (qtModifiers & qtModifier) + nativeModifiers |= nativeModifier; + } + + return nativeModifiers; +} + +Qt::KeyboardModifiers QAppleKeyMapper::fromUIKitModifiers(ulong nativeModifiers) +{ + Qt::KeyboardModifiers qtModifiers = Qt::NoModifier; + for (const auto &[nativeModifier, qtModifier] : uiKitModifierMap) { + if (nativeModifiers & nativeModifier) + qtModifiers |= qtModifier; + } + + return swapModifiersIfNeeded(qtModifiers); +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/platform/darwin/qapplekeymapper_p.h b/src/gui/platform/darwin/qapplekeymapper_p.h new file mode 100644 index 0000000000..8664ab378b --- /dev/null +++ b/src/gui/platform/darwin/qapplekeymapper_p.h @@ -0,0 +1,103 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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$ +** +****************************************************************************/ + +#ifndef QAPPLEKEYMAPPER_H +#define QAPPLEKEYMAPPER_H + +#ifdef Q_OS_MACOS +#include +#endif + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QAppleKeyMapper +{ +public: + static Qt::KeyboardModifiers queryKeyboardModifiers(); + QList possibleKeys(const QKeyEvent *event) const; + static Qt::Key fromNSString(Qt::KeyboardModifiers qtMods, NSString *characters, + NSString *charactersIgnoringModifiers); +#ifdef Q_OS_MACOS + static Qt::KeyboardModifiers fromCocoaModifiers(NSEventModifierFlags cocoaModifiers); + static NSEventModifierFlags toCocoaModifiers(Qt::KeyboardModifiers); + + static QChar toCocoaKey(Qt::Key key); + static Qt::Key fromCocoaKey(QChar keyCode); +#else + static Qt::Key fromUIKitKey(NSString *keyCode); + static Qt::KeyboardModifiers fromUIKitModifiers(ulong uikitModifiers); + static ulong toUIKitModifiers(Qt::KeyboardModifiers); +#endif +private: +#ifdef Q_OS_MACOS + static constexpr int kNumModifierCombinations = 16; + struct KeyMap : std::array + { + // Initialize first element to a sentinel that allows us + // to distinguish an uninitialized map from an initialized. + // Using 0 would not allow us to map U+0000 (NUL), however + // unlikely that is. + KeyMap() : std::array{Qt::Key_unknown} {} + }; + + bool updateKeyboard(); + + using VirtualKeyCode = unsigned short; + const KeyMap &keyMapForKey(VirtualKeyCode virtualKey, QChar unicodeKey) const; + + QCFType m_currentInputSource = nullptr; + + enum { NullMode, UnicodeMode, OtherMode } m_keyboardMode = NullMode; + const UCKeyboardLayout *m_keyboardLayoutFormat = nullptr; + KeyboardLayoutKind m_keyboardKind = kKLKCHRuchrKind; + mutable UInt32 m_deadKeyState = 0; // Maintains dead key state beween calls to UCKeyTranslate + + mutable QHash m_keyMap; +#endif +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/plugins/platforms/cocoa/CMakeLists.txt b/src/plugins/platforms/cocoa/CMakeLists.txt index d9832c2c6c..8dbf534c9b 100644 --- a/src/plugins/platforms/cocoa/CMakeLists.txt +++ b/src/plugins/platforms/cocoa/CMakeLists.txt @@ -26,7 +26,6 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin qcocoainputcontext.h qcocoainputcontext.mm qcocoaintegration.h qcocoaintegration.mm qcocoaintrospection.h qcocoaintrospection.mm - qcocoakeymapper.h qcocoakeymapper.mm qcocoamenu.h qcocoamenu.mm qcocoamenubar.h qcocoamenubar.mm qcocoamenuitem.h qcocoamenuitem.mm diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index e6e46cda81..40d8f639fc 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -371,7 +371,7 @@ QT_USE_NAMESPACE return; QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed()); - QGuiApplicationPrivate::modifier_buttons = QCocoaKeyMapper::fromCocoaModifiers([NSEvent modifierFlags]); + QGuiApplicationPrivate::modifier_buttons = QAppleKeyMapper::fromCocoaModifiers([NSEvent modifierFlags]); static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); activatedSignal.invoke(platformItem, Qt::QueuedConnection); diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index 4f1969dd98..caf47e38d3 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -48,7 +48,6 @@ #include "qcocoaclipboard.h" #include "qcocoadrag.h" #include "qcocoaservices.h" -#include "qcocoakeymapper.h" #if QT_CONFIG(vulkan) #include "qcocoavulkaninstance.h" #endif @@ -57,6 +56,7 @@ #include #include #include +#include Q_FORWARD_DECLARE_OBJC_CLASS(NSToolbar); @@ -156,7 +156,7 @@ private: QScopedPointer mCocoaDrag; QScopedPointer mNativeInterface; QScopedPointer mServices; - QScopedPointer mKeyboardMapper; + QScopedPointer mKeyboardMapper; #if QT_CONFIG(vulkan) mutable QCocoaVulkanInstance *mCocoaVulkanInstance = nullptr; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 872d9bb7f1..1ab30df7e8 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -136,7 +136,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList ¶mList) , mCocoaDrag(new QCocoaDrag) , mNativeInterface(new QCocoaNativeInterface) , mServices(new QCocoaServices) - , mKeyboardMapper(new QCocoaKeyMapper) + , mKeyboardMapper(new QAppleKeyMapper) { logVersionInformation(); @@ -420,7 +420,7 @@ QVariant QCocoaIntegration::styleHint(StyleHint hint) const Qt::KeyboardModifiers QCocoaIntegration::queryKeyboardModifiers() const { - return QCocoaKeyMapper::queryKeyboardModifiers(); + return QAppleKeyMapper::queryKeyboardModifiers(); } QList QCocoaIntegration::possibleKeys(const QKeyEvent *event) const diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.h b/src/plugins/platforms/cocoa/qcocoakeymapper.h deleted file mode 100644 index dbf164c18e..0000000000 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.h +++ /dev/null @@ -1,93 +0,0 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ - -#ifndef QCOCOAKEYMAPPER_H -#define QCOCOAKEYMAPPER_H - -#include - -#include -#include - -#include - -QT_BEGIN_NAMESPACE - -class QCocoaKeyMapper -{ -public: - static Qt::KeyboardModifiers queryKeyboardModifiers(); - QList possibleKeys(const QKeyEvent *event) const; - - static Qt::KeyboardModifiers fromCocoaModifiers(NSEventModifierFlags cocoaModifiers); - static NSEventModifierFlags toCocoaModifiers(Qt::KeyboardModifiers); - - static QChar toCocoaKey(Qt::Key key); - static Qt::Key fromCocoaKey(QChar keyCode); - -private: - static constexpr int kNumModifierCombinations = 16; - struct KeyMap : std::array - { - // Initialize first element to a sentinel that allows us - // to distinguish an uninitialized map from an initialized. - // Using 0 would not allow us to map U+0000 (NUL), however - // unlikely that is. - KeyMap() : std::array{Qt::Key_unknown} {} - }; - - bool updateKeyboard(); - - using VirtualKeyCode = unsigned short; - const KeyMap &keyMapForKey(VirtualKeyCode virtualKey, QChar unicodeKey) const; - - QCFType m_currentInputSource = nullptr; - - enum { NullMode, UnicodeMode, OtherMode } m_keyboardMode = NullMode; - const UCKeyboardLayout *m_keyboardLayoutFormat = nullptr; - KeyboardLayoutKind m_keyboardKind = kKLKCHRuchrKind; - mutable UInt32 m_deadKeyState = 0; // Maintains dead key state beween calls to UCKeyTranslate - - mutable QHash m_keyMap; -}; - -QT_END_NAMESPACE - -#endif - diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm deleted file mode 100644 index caa68ae694..0000000000 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm +++ /dev/null @@ -1,547 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2020 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 - -#include "qcocoakeymapper.h" - -#include -#include - -QT_BEGIN_NAMESPACE - -Q_LOGGING_CATEGORY(lcQpaKeyMapper, "qt.qpa.keymapper"); -Q_LOGGING_CATEGORY(lcQpaKeyMapperKeys, "qt.qpa.keymapper.keys"); - -static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers) -{ - if (QCoreApplication::testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) - return modifiers; - - Qt::KeyboardModifiers swappedModifiers = modifiers; - swappedModifiers &= ~(Qt::MetaModifier | Qt::ControlModifier); - - if (modifiers & Qt::ControlModifier) - swappedModifiers |= Qt::MetaModifier; - if (modifiers & Qt::MetaModifier) - swappedModifiers |= Qt::ControlModifier; - - return swappedModifiers; -} - -static constexpr std::tuple cocoaModifierMap[] = { - { NSEventModifierFlagShift, Qt::ShiftModifier }, - { NSEventModifierFlagControl, Qt::ControlModifier }, - { NSEventModifierFlagCommand, Qt::MetaModifier }, - { NSEventModifierFlagOption, Qt::AltModifier }, - { NSEventModifierFlagNumericPad, Qt::KeypadModifier } -}; - -Qt::KeyboardModifiers QCocoaKeyMapper::fromCocoaModifiers(NSEventModifierFlags cocoaModifiers) -{ - Qt::KeyboardModifiers qtModifiers = Qt::NoModifier; - for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) { - if (cocoaModifiers & cocoaModifier) - qtModifiers |= qtModifier; - } - - return swapModifiersIfNeeded(qtModifiers); -} - -NSEventModifierFlags QCocoaKeyMapper::toCocoaModifiers(Qt::KeyboardModifiers qtModifiers) -{ - qtModifiers = swapModifiersIfNeeded(qtModifiers); - - NSEventModifierFlags cocoaModifiers = 0; - for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) { - if (qtModifiers & qtModifier) - cocoaModifiers |= cocoaModifier; - } - - return cocoaModifiers; -} - -using CarbonModifiers = UInt32; // As opposed to EventModifiers which is UInt16 - -static CarbonModifiers toCarbonModifiers(Qt::KeyboardModifiers qtModifiers) -{ - qtModifiers = swapModifiersIfNeeded(qtModifiers); - - static constexpr std::tuple carbonModifierMap[] = { - { shiftKey, Qt::ShiftModifier }, - { controlKey, Qt::ControlModifier }, - { cmdKey, Qt::MetaModifier }, - { optionKey, Qt::AltModifier }, - { kEventKeyModifierNumLockMask, Qt::KeypadModifier } - }; - - CarbonModifiers carbonModifiers = 0; - for (const auto &[carbonModifier, qtModifier] : carbonModifierMap) { - if (qtModifiers & qtModifier) - carbonModifiers |= carbonModifier; - } - - return carbonModifiers; -} - -// Keyboard keys (non-modifiers) -static QHash standardKeys = { - { kHomeCharCode, Qt::Key_Home }, - { kEnterCharCode, Qt::Key_Enter }, - { kEndCharCode, Qt::Key_End }, - { kBackspaceCharCode, Qt::Key_Backspace }, - { kTabCharCode, Qt::Key_Tab }, - { kPageUpCharCode, Qt::Key_PageUp }, - { kPageDownCharCode, Qt::Key_PageDown }, - { kReturnCharCode, Qt::Key_Return }, - { kEscapeCharCode, Qt::Key_Escape }, - { kLeftArrowCharCode, Qt::Key_Left }, - { kRightArrowCharCode, Qt::Key_Right }, - { kUpArrowCharCode, Qt::Key_Up }, - { kDownArrowCharCode, Qt::Key_Down }, - { kHelpCharCode, Qt::Key_Help }, - { kDeleteCharCode, Qt::Key_Delete }, - // ASCII maps, for debugging - { ':', Qt::Key_Colon }, - { ';', Qt::Key_Semicolon }, - { '<', Qt::Key_Less }, - { '=', Qt::Key_Equal }, - { '>', Qt::Key_Greater }, - { '?', Qt::Key_Question }, - { '@', Qt::Key_At }, - { ' ', Qt::Key_Space }, - { '!', Qt::Key_Exclam }, - { '"', Qt::Key_QuoteDbl }, - { '#', Qt::Key_NumberSign }, - { '$', Qt::Key_Dollar }, - { '%', Qt::Key_Percent }, - { '&', Qt::Key_Ampersand }, - { '\'', Qt::Key_Apostrophe }, - { '(', Qt::Key_ParenLeft }, - { ')', Qt::Key_ParenRight }, - { '*', Qt::Key_Asterisk }, - { '+', Qt::Key_Plus }, - { ',', Qt::Key_Comma }, - { '-', Qt::Key_Minus }, - { '.', Qt::Key_Period }, - { '/', Qt::Key_Slash }, - { '[', Qt::Key_BracketLeft }, - { ']', Qt::Key_BracketRight }, - { '\\', Qt::Key_Backslash }, - { '_', Qt::Key_Underscore }, - { '`', Qt::Key_QuoteLeft }, - { '{', Qt::Key_BraceLeft }, - { '}', Qt::Key_BraceRight }, - { '|', Qt::Key_Bar }, - { '~', Qt::Key_AsciiTilde }, - { '^', Qt::Key_AsciiCircum } -}; - -static QHash virtualKeys = { - { kVK_F1, Qt::Key_F1 }, - { kVK_F2, Qt::Key_F2 }, - { kVK_F3, Qt::Key_F3 }, - { kVK_F4, Qt::Key_F4 }, - { kVK_F5, Qt::Key_F5 }, - { kVK_F6, Qt::Key_F6 }, - { kVK_F7, Qt::Key_F7 }, - { kVK_F8, Qt::Key_F8 }, - { kVK_F9, Qt::Key_F9 }, - { kVK_F10, Qt::Key_F10 }, - { kVK_F11, Qt::Key_F11 }, - { kVK_F12, Qt::Key_F12 }, - { kVK_F13, Qt::Key_F13 }, - { kVK_F14, Qt::Key_F14 }, - { kVK_F15, Qt::Key_F15 }, - { kVK_F16, Qt::Key_F16 }, - { kVK_Return, Qt::Key_Return }, - { kVK_Tab, Qt::Key_Tab }, - { kVK_Escape, Qt::Key_Escape }, - { kVK_Help, Qt::Key_Help }, - { kVK_UpArrow, Qt::Key_Up }, - { kVK_DownArrow, Qt::Key_Down }, - { kVK_LeftArrow, Qt::Key_Left }, - { kVK_RightArrow, Qt::Key_Right }, - { kVK_PageUp, Qt::Key_PageUp }, - { kVK_PageDown, Qt::Key_PageDown } -}; - -static QHash functionKeys = { - { NSUpArrowFunctionKey, Qt::Key_Up }, - { NSDownArrowFunctionKey, Qt::Key_Down }, - { NSLeftArrowFunctionKey, Qt::Key_Left }, - { NSRightArrowFunctionKey, Qt::Key_Right }, - // F1-35 function keys handled manually below - { NSInsertFunctionKey, Qt::Key_Insert }, - { NSDeleteFunctionKey, Qt::Key_Delete }, - { NSHomeFunctionKey, Qt::Key_Home }, - { NSEndFunctionKey, Qt::Key_End }, - { NSPageUpFunctionKey, Qt::Key_PageUp }, - { NSPageDownFunctionKey, Qt::Key_PageDown }, - { NSPrintScreenFunctionKey, Qt::Key_Print }, - { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, - { NSPauseFunctionKey, Qt::Key_Pause }, - { NSSysReqFunctionKey, Qt::Key_SysReq }, - { NSMenuFunctionKey, Qt::Key_Menu }, - { NSPrintFunctionKey, Qt::Key_Printer }, - { NSClearDisplayFunctionKey, Qt::Key_Clear }, - { NSInsertCharFunctionKey, Qt::Key_Insert }, - { NSDeleteCharFunctionKey, Qt::Key_Delete }, - { NSSelectFunctionKey, Qt::Key_Select }, - { NSExecuteFunctionKey, Qt::Key_Execute }, - { NSUndoFunctionKey, Qt::Key_Undo }, - { NSRedoFunctionKey, Qt::Key_Redo }, - { NSFindFunctionKey, Qt::Key_Find }, - { NSHelpFunctionKey, Qt::Key_Help }, - { NSModeSwitchFunctionKey, Qt::Key_Mode_switch } -}; - -static int toKeyCode(const QChar &key, int virtualKey, int modifiers) -{ - qCDebug(lcQpaKeyMapperKeys, "Mapping key: %d (0x%04x) / vk %d (0x%04x)", - key.unicode(), key.unicode(), virtualKey, virtualKey); - - if (key == QChar(kClearCharCode) && virtualKey == 0x47) - return Qt::Key_Clear; - - if (key.isDigit()) { - qCDebug(lcQpaKeyMapperKeys, "Got digit key: %d", key.digitValue()); - return key.digitValue() + Qt::Key_0; - } - - if (key.isLetter()) { - qCDebug(lcQpaKeyMapperKeys, "Got letter key: %d", (key.toUpper().unicode() - 'A')); - return (key.toUpper().unicode() - 'A') + Qt::Key_A; - } - if (key.isSymbol()) { - qCDebug(lcQpaKeyMapperKeys, "Got symbol key: %d", (key.unicode())); - return key.unicode(); - } - - if (auto qtKey = standardKeys.value(key.unicode())) { - // To work like Qt for X11 we issue Backtab when Shift + Tab are pressed - if (qtKey == Qt::Key_Tab && (modifiers & Qt::ShiftModifier)) { - qCDebug(lcQpaKeyMapperKeys, "Got key: Qt::Key_Backtab"); - return Qt::Key_Backtab; - } - - qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey; - return qtKey; - } - - // Last ditch try to match the scan code - if (auto qtKey = virtualKeys.value(virtualKey)) { - qCDebug(lcQpaKeyMapperKeys) << "Got scancode" << qtKey; - return qtKey; - } - - // Check if they belong to key codes in private unicode range - if (key >= QChar(NSUpArrowFunctionKey) && key <= QChar(NSModeSwitchFunctionKey)) { - if (auto qtKey = functionKeys.value(key.unicode())) { - qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey; - return qtKey; - } else if (key >= QChar(NSF1FunctionKey) && key <= QChar(NSF35FunctionKey)) { - auto functionKey = Qt::Key_F1 + (key.unicode() - NSF1FunctionKey) ; - qCDebug(lcQpaKeyMapperKeys) << "Got" << functionKey; - return functionKey; - } - } - - qCDebug(lcQpaKeyMapperKeys, "Unknown case.. %d[%d] %d", key.unicode(), key.toLatin1(), virtualKey); - return Qt::Key_unknown; -} - -// --------- Cocoa key mapping moved from Qt Core --------- - -static const int NSEscapeCharacter = 27; // not defined by Cocoa headers - -static const QHash cocoaKeys = { - { NSEnterCharacter, Qt::Key_Enter }, - { NSBackspaceCharacter, Qt::Key_Backspace }, - { NSTabCharacter, Qt::Key_Tab }, - { NSNewlineCharacter, Qt::Key_Return }, - { NSCarriageReturnCharacter, Qt::Key_Return }, - { NSBackTabCharacter, Qt::Key_Backtab }, - { NSEscapeCharacter, Qt::Key_Escape }, - { NSDeleteCharacter, Qt::Key_Backspace }, - { NSUpArrowFunctionKey, Qt::Key_Up }, - { NSDownArrowFunctionKey, Qt::Key_Down }, - { NSLeftArrowFunctionKey, Qt::Key_Left }, - { NSRightArrowFunctionKey, Qt::Key_Right }, - { NSF1FunctionKey, Qt::Key_F1 }, - { NSF2FunctionKey, Qt::Key_F2 }, - { NSF3FunctionKey, Qt::Key_F3 }, - { NSF4FunctionKey, Qt::Key_F4 }, - { NSF5FunctionKey, Qt::Key_F5 }, - { NSF6FunctionKey, Qt::Key_F6 }, - { NSF7FunctionKey, Qt::Key_F7 }, - { NSF8FunctionKey, Qt::Key_F8 }, - { NSF9FunctionKey, Qt::Key_F9 }, - { NSF10FunctionKey, Qt::Key_F10 }, - { NSF11FunctionKey, Qt::Key_F11 }, - { NSF12FunctionKey, Qt::Key_F12 }, - { NSF13FunctionKey, Qt::Key_F13 }, - { NSF14FunctionKey, Qt::Key_F14 }, - { NSF15FunctionKey, Qt::Key_F15 }, - { NSF16FunctionKey, Qt::Key_F16 }, - { NSF17FunctionKey, Qt::Key_F17 }, - { NSF18FunctionKey, Qt::Key_F18 }, - { NSF19FunctionKey, Qt::Key_F19 }, - { NSF20FunctionKey, Qt::Key_F20 }, - { NSF21FunctionKey, Qt::Key_F21 }, - { NSF22FunctionKey, Qt::Key_F22 }, - { NSF23FunctionKey, Qt::Key_F23 }, - { NSF24FunctionKey, Qt::Key_F24 }, - { NSF25FunctionKey, Qt::Key_F25 }, - { NSF26FunctionKey, Qt::Key_F26 }, - { NSF27FunctionKey, Qt::Key_F27 }, - { NSF28FunctionKey, Qt::Key_F28 }, - { NSF29FunctionKey, Qt::Key_F29 }, - { NSF30FunctionKey, Qt::Key_F30 }, - { NSF31FunctionKey, Qt::Key_F31 }, - { NSF32FunctionKey, Qt::Key_F32 }, - { NSF33FunctionKey, Qt::Key_F33 }, - { NSF34FunctionKey, Qt::Key_F34 }, - { NSF35FunctionKey, Qt::Key_F35 }, - { NSInsertFunctionKey, Qt::Key_Insert }, - { NSDeleteFunctionKey, Qt::Key_Delete }, - { NSHomeFunctionKey, Qt::Key_Home }, - { NSEndFunctionKey, Qt::Key_End }, - { NSPageUpFunctionKey, Qt::Key_PageUp }, - { NSPageDownFunctionKey, Qt::Key_PageDown }, - { NSPrintScreenFunctionKey, Qt::Key_Print }, - { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, - { NSPauseFunctionKey, Qt::Key_Pause }, - { NSSysReqFunctionKey, Qt::Key_SysReq }, - { NSMenuFunctionKey, Qt::Key_Menu }, - { NSHelpFunctionKey, Qt::Key_Help }, -}; - -QChar QCocoaKeyMapper::toCocoaKey(Qt::Key key) -{ - // Prioritize overloaded keys - if (key == Qt::Key_Return) - return QChar(NSNewlineCharacter); - if (key == Qt::Key_Backspace) - return QChar(NSBackspaceCharacter); - - static QHash reverseCocoaKeys; - if (reverseCocoaKeys.isEmpty()) { - reverseCocoaKeys.reserve(cocoaKeys.size()); - for (auto it = cocoaKeys.begin(); it != cocoaKeys.end(); ++it) - reverseCocoaKeys.insert(it.value(), it.key()); - } - - return reverseCocoaKeys.value(key); -} - -Qt::Key QCocoaKeyMapper::fromCocoaKey(QChar keyCode) -{ - if (auto key = cocoaKeys.value(keyCode.unicode())) - return key; - - return Qt::Key(keyCode.toUpper().unicode()); -} - -// ------------------------------------------------ - -Qt::KeyboardModifiers QCocoaKeyMapper::queryKeyboardModifiers() -{ - return fromCocoaModifiers(NSEvent.modifierFlags); -} - -bool QCocoaKeyMapper::updateKeyboard() -{ - QCFType source = TISCopyInputMethodKeyboardLayoutOverride(); - if (!source) - source = TISCopyCurrentKeyboardInputSource(); - - if (m_keyboardMode != NullMode && source == m_currentInputSource) - return false; - - Q_ASSERT(source); - m_currentInputSource = source; - m_keyboardKind = LMGetKbdType(); - m_deadKeyState = 0; - - m_keyMap.clear(); - - if (auto data = CFDataRef(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData))) { - const UCKeyboardLayout *uchrData = reinterpret_cast(CFDataGetBytePtr(data)); - Q_ASSERT(uchrData); - m_keyboardLayoutFormat = uchrData; - m_keyboardMode = UnicodeMode; - } else { - m_keyboardLayoutFormat = nullptr; - m_keyboardMode = NullMode; - } - - qCDebug(lcQpaKeyMapper) << "Updated keyboard to" - << QString::fromCFString(CFStringRef(TISGetInputSourceProperty( - m_currentInputSource, kTISPropertyLocalizedName))); - - return true; -} - -static constexpr Qt::KeyboardModifiers modifierCombinations[] = { - 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 -}; - -/* - Returns a key map for the given \virtualKey based on all - possible modifier combinations. -*/ -const QCocoaKeyMapper::KeyMap &QCocoaKeyMapper::keyMapForKey(VirtualKeyCode virtualKey, QChar unicodeKey) const -{ - static_assert(sizeof(modifierCombinations) / sizeof(Qt::KeyboardModifiers) == kNumModifierCombinations); - - const_cast(this)->updateKeyboard(); - - auto &keyMap = m_keyMap[virtualKey]; - if (keyMap[Qt::NoModifier] != Qt::Key_unknown) - return keyMap; // Already filled - - qCDebug(lcQpaKeyMapper, "Updating key map for virtual key = 0x%02x!", (uint)virtualKey); - - // Key mapping via [NSEvent charactersByApplyingModifiers:] only works for key down - // events, but we might (wrongly) get into this code path for other key events such - // as NSEventTypeFlagsChanged. - const bool canMapCocoaEvent = NSApp.currentEvent.type == NSEventTypeKeyDown; - - if (!canMapCocoaEvent) - qCWarning(lcQpaKeyMapper) << "Could not map key to character for event" << NSApp.currentEvent; - - for (int i = 0; i < kNumModifierCombinations; ++i) { - Q_ASSERT(!i || keyMap[i] == 0); - - auto qtModifiers = modifierCombinations[i]; - auto carbonModifiers = toCarbonModifiers(qtModifiers); - const UInt32 modifierKeyState = (carbonModifiers >> 8) & 0xFF; - - static const UniCharCount maxStringLength = 10; - static UniChar unicodeString[maxStringLength]; - UniCharCount actualStringLength = 0; - OSStatus err = UCKeyTranslate(m_keyboardLayoutFormat, virtualKey, - kUCKeyActionDown, modifierKeyState, m_keyboardKind, OptionBits(0), - &m_deadKeyState, maxStringLength, &actualStringLength, unicodeString); - - // Use translated unicode key if valid - if (err == noErr && actualStringLength) - unicodeKey = QChar(unicodeString[0]); - - if (@available(macOS 10.15, *)) { - if (canMapCocoaEvent) { - // Until we've verified that the Cocoa API works as expected - // we first run the event through the Carbon APIs and then - // compare the results to Cocoa. - auto cocoaModifiers = toCocoaModifiers(qtModifiers); - auto *charactersWithModifiers = [NSApp.currentEvent charactersByApplyingModifiers:cocoaModifiers]; - Q_ASSERT(charactersWithModifiers && charactersWithModifiers.length > 0); - auto cocoaUnicodeKey = QChar([charactersWithModifiers characterAtIndex:0]); - if (cocoaUnicodeKey != unicodeKey) { - qCWarning(lcQpaKeyMapper) << "Mismatch between Cocoa" << cocoaUnicodeKey - << "and Carbon" << unicodeKey << "for virtual key" << virtualKey - << "with" << qtModifiers; - } - } - } - - int qtkey = toKeyCode(unicodeKey, virtualKey, qtModifiers); - if (qtkey == Qt::Key_unknown) - qtkey = unicodeKey.unicode(); - - keyMap[i] = qtkey; - - qCDebug(lcQpaKeyMapper, " [%d] (%d,0x%02x,'%c')", i, qtkey, qtkey, qtkey); - } - - return keyMap; -} - -QList QCocoaKeyMapper::possibleKeys(const QKeyEvent *event) const -{ - QList ret; - - const auto nativeVirtualKey = event->nativeVirtualKey(); - if (!nativeVirtualKey) - return ret; - - auto keyMap = keyMapForKey(nativeVirtualKey, QChar(event->key())); - - auto unmodifiedKey = keyMap[Qt::NoModifier]; - Q_ASSERT(unmodifiedKey != Qt::Key_unknown); - - auto eventModifiers = event->modifiers(); - - // The base key, with the complete set of modifiers, - // is always valid, and the first priority. - ret << int(unmodifiedKey) + int(eventModifiers); - - // FIXME: We only compute the first 8 combinations. Why? - for (int i = 1; i < 8; ++i) { - auto keyAfterApplyingModifiers = keyMap[i]; - if (keyAfterApplyingModifiers == unmodifiedKey) - continue; - - // Include key if event modifiers includes, or matches - // perfectly, the current candidate modifiers. - auto candidateModifiers = modifierCombinations[i]; - if ((eventModifiers & candidateModifiers) == candidateModifiers) - ret << int(keyAfterApplyingModifiers) + int(eventModifiers & ~candidateModifiers); - } - - return ret; -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm index 4806b244c3..f11d7bd996 100644 --- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm +++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm @@ -50,9 +50,9 @@ #include "qcocoahelpers.h" #include "qcocoaapplication.h" // for custom application category #include "qcocoamenuloader.h" -#include "qcocoakeymapper.h" #include #include +#include #include @@ -356,7 +356,7 @@ NSMenuItem *QCocoaMenuItem::sync() auto key = accel[0].key(); auto modifiers = accel[0].keyboardModifiers(); - QChar cocoaKey = QCocoaKeyMapper::toCocoaKey(key); + QChar cocoaKey = QAppleKeyMapper::toCocoaKey(key); if (cocoaKey.isNull()) cocoaKey = QChar(key).toLower().unicode(); // Similar to qt_mac_removePrivateUnicode change the delete key, @@ -365,7 +365,7 @@ NSMenuItem *QCocoaMenuItem::sync() cocoaKey = QChar(NSDeleteCharacter); m_native.keyEquivalent = QStringView(&cocoaKey, 1).toNSString(); - m_native.keyEquivalentModifierMask = QCocoaKeyMapper::toCocoaModifiers(modifiers); + m_native.keyEquivalentModifierMask = QAppleKeyMapper::toCocoaModifiers(modifiers); } else #endif { diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm index b94d31251e..6760ae59ff 100644 --- a/src/plugins/platforms/cocoa/qcocoansmenu.mm +++ b/src/plugins/platforms/cocoa/qcocoansmenu.mm @@ -46,10 +46,10 @@ #include "qcocoawindow.h" #include "qnsview.h" #include "qcocoahelpers.h" -#include "qcocoakeymapper.h" #include #include +#include static NSString *qt_mac_removePrivateUnicode(NSString *string) { @@ -254,7 +254,7 @@ static NSString *qt_mac_removePrivateUnicode(NSString *string) QChar ch; int keyCode; ulong nativeModifiers = event.modifierFlags; - Qt::KeyboardModifiers modifiers = QCocoaKeyMapper::fromCocoaModifiers(nativeModifiers); + Qt::KeyboardModifiers modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers); NSString *charactersIgnoringModifiers = event.charactersIgnoringModifiers; NSString *characters = event.characters; @@ -264,7 +264,7 @@ static NSString *qt_mac_removePrivateUnicode(NSString *string) } else { ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); } - keyCode = QCocoaKeyMapper::fromCocoaKey(ch); + keyCode = QAppleKeyMapper::fromCocoaKey(ch); } else { // might be a dead key ch = QChar::ReplacementCharacter; diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm index d4ab5f4a24..945217e928 100644 --- a/src/plugins/platforms/cocoa/qnsview_dragging.mm +++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm @@ -197,7 +197,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin if (!target) return NSDragOperationNone; - const auto modifiers = QCocoaKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags); + const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags); const auto buttons = currentlyPressedMouseButtons(); const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint); @@ -261,7 +261,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin QPlatformDropQtResponse response(false, Qt::IgnoreAction); QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); - const auto modifiers = QCocoaKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags); + const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags); const auto buttons = currentlyPressedMouseButtons(); const auto point = mapWindowCoordinates(m_platformWindow->window(), target, windowPoint); @@ -302,7 +302,7 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin // this case won't send the matching release event, so we have to // synthesize it here. m_buttons = currentlyPressedMouseButtons(); - const auto modifiers = QCocoaKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags); + const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(NSApp.currentEvent.modifierFlags); NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin; NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index 09d78485f4..23b8254d63 100644 --- a/src/plugins/platforms/cocoa/qnsview_keys.mm +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -45,7 +45,7 @@ { ulong timestamp = [nsevent timestamp] * 1000; ulong nativeModifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers modifiers = QCocoaKeyMapper::fromCocoaModifiers(nativeModifiers); + Qt::KeyboardModifiers modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers); NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; NSString *characters = [nsevent characters]; if (m_inputSource != characters) { @@ -74,7 +74,7 @@ ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); else if ([characters length] != 0) ch = QChar([characters characterAtIndex:0]); - keyCode = QCocoaKeyMapper::fromCocoaKey(ch); + keyCode = QAppleKeyMapper::fromCocoaKey(ch); } // we will send a key event unless the input method sets m_sendKeyEvent to false @@ -194,7 +194,7 @@ { ulong timestamp = [nsevent timestamp] * 1000; ulong nativeModifiers = [nsevent modifierFlags]; - Qt::KeyboardModifiers modifiers = QCocoaKeyMapper::fromCocoaModifiers(nativeModifiers); + Qt::KeyboardModifiers modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers); // Scan codes are hardware dependent codes for each key. There is no way to get these // from Carbon or Cocoa, so leave it 0, as documented in QKeyEvent::nativeScanCode(). @@ -238,7 +238,7 @@ (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, qtCode, - modifiers ^ QCocoaKeyMapper::fromCocoaModifiers(mac_mask), + modifiers ^ QAppleKeyMapper::fromCocoaModifiers(mac_mask), nativeScanCode, nativeVirtualKey, nativeModifiers ^ mac_mask); } diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm index 336f1085b9..d00cfb7886 100644 --- a/src/plugins/platforms/cocoa/qnsview_mouse.mm +++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm @@ -279,7 +279,7 @@ QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); nativeDrag->setLastMouseEvent(theEvent, self); - const auto modifiers = QCocoaKeyMapper::fromCocoaModifiers(theEvent.modifierFlags); + const auto modifiers = QAppleKeyMapper::fromCocoaModifiers(theEvent.modifierFlags); auto button = cocoaButton2QtButton(theEvent); if (button == Qt::LeftButton && m_sendUpAsRightButton) button = Qt::RightButton; @@ -678,7 +678,7 @@ // after scrolling in Qt Creator: not taking the phase into account causes // the end of the event stream to be interpreted as font size changes. if (theEvent.momentumPhase == NSEventPhaseNone) - m_currentWheelModifiers = QCocoaKeyMapper::fromCocoaModifiers(theEvent.modifierFlags); + m_currentWheelModifiers = QAppleKeyMapper::fromCocoaModifiers(theEvent.modifierFlags); // "isInverted": natural OS X scrolling, inverted from the Qt/other platform/Jens perspective. bool isInverted = [theEvent isDirectionInvertedFromDevice]; diff --git a/src/plugins/platforms/cocoa/qnsview_tablet.mm b/src/plugins/platforms/cocoa/qnsview_tablet.mm index 804c502775..2b269d038a 100644 --- a/src/plugins/platforms/cocoa/qnsview_tablet.mm +++ b/src/plugins/platforms/cocoa/qnsview_tablet.mm @@ -109,7 +109,7 @@ Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) if (rotation > 180.0) rotation -= 360.0; - Qt::KeyboardModifiers keyboardModifiers = QCocoaKeyMapper::fromCocoaModifiers(theEvent.modifierFlags); + Qt::KeyboardModifiers keyboardModifiers = QAppleKeyMapper::fromCocoaModifiers(theEvent.modifierFlags); Qt::MouseButtons buttons = ignoreButtonMapping ? static_cast(static_cast([theEvent buttonMask])) : m_buttons; qCDebug(lcQpaTablet, "event on tablet %d with tool %d type %d unique ID %lld pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", diff --git a/src/plugins/platforms/ios/qiosviewcontroller.h b/src/plugins/platforms/ios/qiosviewcontroller.h index 7af4c83b48..79fe5ae3c5 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.h +++ b/src/plugins/platforms/ios/qiosviewcontroller.h @@ -50,6 +50,8 @@ QT_END_NAMESPACE - (instancetype)initWithQIOSScreen:(QT_PREPEND_NAMESPACE(QIOSScreen) *)screen; - (void)updateProperties; +- (NSArray*)keyCommands; +- (void)handleShortcut:(UIKeyCommand*)keyCommand; #ifndef Q_OS_TVOS @property (nonatomic, assign) UIInterfaceOrientation lockedOrientation; diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm index cd4af46ef7..7d994f4394 100644 --- a/src/plugins/platforms/ios/qiosviewcontroller.mm +++ b/src/plugins/platforms/ios/qiosviewcontroller.mm @@ -42,12 +42,14 @@ #include #include +#include #include #include #include #include +#include #include "qiosintegration.h" #include "qiosscreen.h" @@ -523,5 +525,36 @@ #endif } +- (NSArray*)keyCommands +{ + // FIXME: If we are on iOS 13.4 or later we can use UIKey instead of doing this + // So it should be safe to remove this entire function and handleShortcut() as + // a result + NSMutableArray *keyCommands = nil; + QShortcutMap &shortcutMap = QGuiApplicationPrivate::instance()->shortcutMap; + keyCommands = [[NSMutableArray alloc] init]; + const QList keys = shortcutMap.keySequences(); + for (const QKeySequence &seq : keys) { + const QString keyString = seq.toString(); + [keyCommands addObject:[UIKeyCommand + keyCommandWithInput:QString(keyString[keyString.length() - 1]).toNSString() + modifierFlags:QAppleKeyMapper::toUIKitModifiers(seq[0].keyboardModifiers()) + action:@selector(handleShortcut:)]]; + } + return keyCommands; +} + +- (void)handleShortcut:(UIKeyCommand *)keyCommand +{ + const QString str = QString::fromNSString([keyCommand input]); + Qt::KeyboardModifiers qtMods = QAppleKeyMapper::fromUIKitModifiers(keyCommand.modifierFlags); + QChar ch = str.isEmpty() ? QChar() : str.at(0); + QShortcutMap &shortcutMap = QGuiApplicationPrivate::instance()->shortcutMap; + QKeyEvent keyEvent(QEvent::ShortcutOverride, Qt::Key(ch.toUpper().unicode()), qtMods, str); + shortcutMap.tryShortcut(&keyEvent); +} + + + @end diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h index 1ab9481dd6..6a8e5a7707 100644 --- a/src/plugins/platforms/ios/quiview.h +++ b/src/plugins/platforms/ios/quiview.h @@ -56,6 +56,7 @@ QT_END_NAMESPACE - (instancetype)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window; - (void)sendUpdatedExposeEvent; - (BOOL)isActiveWindow; +- (bool)handlePresses:(NSSet *)presses eventType:(QEvent::Type)type; @property (nonatomic, assign) QT_PREPEND_NAMESPACE(QIOSWindow) *platformWindow; @end diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm index 4c56e03f42..5ca4d5ccc8 100644 --- a/src/plugins/platforms/ios/quiview.mm +++ b/src/plugins/platforms/ios/quiview.mm @@ -52,6 +52,7 @@ #include #include #include +#include #include Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") @@ -571,7 +572,7 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") QWindowSystemInterface::handleTouchCancelEvent(self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice()); } -- (int)mapPressTypeToKey:(UIPress*)press +- (int)mapPressTypeToKey:(UIPress*)press withModifiers:(Qt::KeyboardModifiers)qtModifiers { switch (press.type) { case UIPressTypeUpArrow: return Qt::Key_Up; @@ -582,6 +583,16 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") case UIPressTypeMenu: return Qt::Key_Menu; case UIPressTypePlayPause: return Qt::Key_MediaTogglePlayPause; } +#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) + if (@available(ios 13.4, *)) { + NSString *charactersIgnoringModifiers = press.key.charactersIgnoringModifiers; + Qt::Key key = QAppleKeyMapper::fromUIKitKey(charactersIgnoringModifiers); + if (key != Qt::Key_unknown) + return key; + return QAppleKeyMapper::fromNSString(qtModifiers, press.key.characters, + charactersIgnoringModifiers); + } +#endif return Qt::Key_unknown; } @@ -593,32 +604,52 @@ Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") bool handled = false; for (UIPress* press in presses) { - int key = [self mapPressTypeToKey:press]; + Qt::KeyboardModifiers qtModifiers = Qt::NoModifier; +#if QT_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__IPHONE_13_4) + if (@available(ios 13.4, *)) + qtModifiers = QAppleKeyMapper::fromUIKitModifiers(press.key.modifierFlags); +#endif + int key = [self mapPressTypeToKey:press withModifiers:qtModifiers]; if (key == Qt::Key_unknown) continue; - if (QWindowSystemInterface::handleKeyEvent(self.platformWindow->window(), type, key, Qt::NoModifier)) + if (QWindowSystemInterface::handleKeyEvent(self.platformWindow->window(), type, key, qtModifiers)) handled = true; } return handled; } +- (BOOL)handlePresses:(NSSet *)presses eventType:(QEvent::Type)type +{ + bool handlePress = false; + if (qApp->focusWindow()) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (qApp->focusObject() && QCoreApplication::sendEvent(qApp->focusObject(), &queryEvent)) + handlePress = queryEvent.value(Qt::ImEnabled).toBool(); + if (!handlePress && [self processPresses:presses withType:type]) + return true; + } + return false; +} + - (void)pressesBegan:(NSSet *)presses withEvent:(UIPressesEvent *)event { - if (![self processPresses:presses withType:QEvent::KeyPress]) + if (![self handlePresses:presses eventType:QEvent::KeyPress]) [super pressesBegan:presses withEvent:event]; } - (void)pressesChanged:(NSSet *)presses withEvent:(UIPressesEvent *)event { - if (![self processPresses:presses withType:QEvent::KeyPress]) - [super pressesChanged:presses withEvent:event]; + if (![self handlePresses:presses eventType:QEvent::KeyPress]) + [super pressesBegan:presses withEvent:event]; + [super pressesChanged:presses withEvent:event]; } - (void)pressesEnded:(NSSet *)presses withEvent:(UIPressesEvent *)event { - if (![self processPresses:presses withType:QEvent::KeyRelease]) - [super pressesEnded:presses withEvent:event]; + if (![self handlePresses:presses eventType:QEvent::KeyRelease]) + [super pressesBegan:presses withEvent:event]; + [super pressesEnded:presses withEvent:event]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender -- cgit v1.2.3