summaryrefslogtreecommitdiffstats
path: root/src/gui/platform/darwin/qapplekeymapper.mm
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/platform/darwin/qapplekeymapper.mm')
-rw-r--r--src/gui/platform/darwin/qapplekeymapper.mm717
1 files changed, 717 insertions, 0 deletions
diff --git a/src/gui/platform/darwin/qapplekeymapper.mm b/src/gui/platform/darwin/qapplekeymapper.mm
new file mode 100644
index 0000000000..b8ff5c9d6d
--- /dev/null
+++ b/src/gui/platform/darwin/qapplekeymapper.mm
@@ -0,0 +1,717 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <qglobal.h>
+
+#ifdef Q_OS_MACOS
+#include <AppKit/AppKit.h>
+#endif
+
+#if defined(QT_PLATFORM_UIKIT)
+#include <UIKit/UIKit.h>
+#endif
+
+#include "qapplekeymapper_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtGui/QGuiApplication>
+
+QT_BEGIN_NAMESPACE
+
+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;
+}
+
+#ifdef Q_OS_MACOS
+static constexpr std::tuple<NSEventModifierFlags, Qt::KeyboardModifier> 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<int, Qt::KeyboardModifier> 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<char16_t, Qt::Key> 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<char16_t, Qt::Key> 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<char16_t, Qt::Key> 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<char16_t, Qt::Key> 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(NSCarriageReturnCharacter);
+ if (key == Qt::Key_Backspace)
+ return QChar(NSBackspaceCharacter);
+
+ Q_CONSTINIT static QHash<Qt::Key, char16_t> 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() const
+{
+ return fromCocoaModifiers(NSEvent.modifierFlags);
+}
+
+bool QAppleKeyMapper::updateKeyboard()
+{
+ QCFType<TISInputSourceRef> 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_keyMap.clear();
+
+ if (auto data = CFDataRef(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData))) {
+ const UCKeyboardLayout *uchrData = reinterpret_cast<const UCKeyboardLayout *>(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) const
+{
+ static_assert(sizeof(modifierCombinations) / sizeof(Qt::KeyboardModifiers) == kNumModifierCombinations);
+
+ const_cast<QAppleKeyMapper *>(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;
+
+ UInt32 deadKeyState = 0;
+ static const UniCharCount maxStringLength = 10;
+ static UniChar unicodeString[maxStringLength];
+ UniCharCount actualStringLength = 0;
+ OSStatus err = UCKeyTranslate(m_keyboardLayoutFormat, virtualKey,
+ kUCKeyActionDown, modifierKeyState, m_keyboardKind,
+ kUCKeyTranslateNoDeadKeysMask, &deadKeyState,
+ maxStringLength, &actualStringLength,
+ unicodeString);
+
+ // Use translated Unicode key if valid
+ QChar carbonUnicodeKey;
+ if (err == noErr && actualStringLength)
+ carbonUnicodeKey = 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];
+
+ QChar cocoaUnicodeKey;
+ if (charactersWithModifiers.length > 0)
+ cocoaUnicodeKey = QChar([charactersWithModifiers characterAtIndex:0]);
+
+ if (cocoaUnicodeKey != carbonUnicodeKey) {
+ qCWarning(lcQpaKeyMapper) << "Mismatch between Cocoa" << cocoaUnicodeKey
+ << "and Carbon" << carbonUnicodeKey << "for virtual key" << virtualKey
+ << "with" << qtModifiers;
+ }
+ }
+ }
+
+ int qtKey = toKeyCode(carbonUnicodeKey, virtualKey, qtModifiers);
+ if (qtKey == Qt::Key_unknown)
+ qtKey = carbonUnicodeKey.unicode();
+
+ keyMap[i] = qtKey;
+
+ qCDebug(lcQpaKeyMapper).verbosity(0) << "\t" << qtModifiers
+ << "+" << qUtf8Printable(QString::asprintf("0x%02x", virtualKey))
+ << "=" << qUtf8Printable(QString::asprintf("%d / 0x%02x /", qtKey, qtKey))
+ << QKeySequence(qtKey).toString();
+ }
+
+ return keyMap;
+}
+
+/*
+ Compute the possible key combinations that can map to the event's
+ virtual key and modifiers, in the current keyboard layout.
+
+ For example, given a normal US keyboard layout, the virtual key
+ 23 combined with the Alt (⌥) and Shift (⇧) modifiers, can map
+ to the following key combinations:
+
+ - Alt+Shift+5
+ - Alt+%
+ - Shift+∞
+ - fi
+
+ The function builds on a key map produced by keyMapForKey(),
+ where each modifier-key combination has been mapped to the
+ key it will produce.
+*/
+QList<QKeyCombination> QAppleKeyMapper::possibleKeyCombinations(const QKeyEvent *event) const
+{
+ QList<QKeyCombination> ret;
+
+ const auto nativeVirtualKey = event->nativeVirtualKey();
+ if (!nativeVirtualKey)
+ return ret;
+
+ auto keyMap = keyMapForKey(nativeVirtualKey);
+
+ auto unmodifiedKey = keyMap[Qt::NoModifier];
+ Q_ASSERT(unmodifiedKey != Qt::Key_unknown);
+
+ auto eventModifiers = event->modifiers();
+
+ int startingModifierLayer = 0;
+ if (toCocoaModifiers(eventModifiers) & NSEventModifierFlagCommand) {
+ // When the Command key is pressed AppKit seems to do key equivalent
+ // matching using a Latin/Roman interpretation of the current keyboard
+ // layout. For example, for a Greek layout, pressing Option+Command+C
+ // produces a key event with chars="ç" and unmodchars="ψ", but AppKit
+ // still treats this as a match for a key equivalent of Option+Command+C.
+ // We can't do the same by just applying the modifiers to our key map,
+ // as that too contains "ψ" for the Option+Command combination. What we
+ // can do instead is take advantage of the fact that the Command
+ // modifier layer in all/most keyboard layouts contains a Latin
+ // layer. We then combine that with the modifiers of the event
+ // to produce the resulting "Latin" key combination.
+ static constexpr int kCommandLayer = 2;
+ ret << QKeyCombination::fromCombined(
+ int(eventModifiers) + int(keyMap[kCommandLayer]));
+
+ // If the unmodified key is outside of Latin1, we also treat
+ // that as a valid key combination, even if AppKit natively
+ // does not. For example, for a Greek layout, we still want
+ // to support Option+Command+ψ as a key combination, as it's
+ // unlikely to clash with the Latin key combination we added
+ // above.
+
+ // However, if the unmodified key is within Latin1, we skip
+ // it, to avoid these types of conflicts. For example, in
+ // the same Greek layout, pressing the key next to Tab will
+ // produce a Latin ';' symbol, but we've already treated that
+ // as 'q' above, thanks to the Command modifier, so we skip
+ // the potential Command+; key combination. This is also in
+ // line with what AppKit natively does.
+
+ // Skipping Latin1 unmodified keys also handles the case of
+ // a Latin layout, where the unmodified and modified keys
+ // are the same.
+
+ if (unmodifiedKey <= 0xff)
+ startingModifierLayer = 1;
+ }
+
+ // FIXME: We only compute the first 8 combinations. Why?
+ for (int i = startingModifierLayer; i < 15; ++i) {
+ auto keyAfterApplyingModifiers = keyMap[i];
+ if (!keyAfterApplyingModifiers)
+ continue;
+
+ // Include key if the event modifiers match exactly,
+ // or are a superset of the current candidate modifiers.
+ auto candidateModifiers = modifierCombinations[i];
+ if ((eventModifiers & candidateModifiers) == candidateModifiers) {
+ // If the event includes more modifiers than the candidate they
+ // will need to be included in the resulting key combination.
+ auto additionalModifiers = eventModifiers & ~candidateModifiers;
+
+ auto keyCombination = QKeyCombination::fromCombined(
+ int(additionalModifiers) + int(keyAfterApplyingModifiers));
+
+ // If there's an existing key combination with the same key,
+ // but a different set of modifiers, we want to choose only
+ // one of them, by priority (see below).
+ const auto existingCombination = std::find_if(
+ ret.begin(), ret.end(), [&](auto existingCombination) {
+ return existingCombination.key() == keyAfterApplyingModifiers;
+ });
+
+ if (existingCombination != ret.end()) {
+ // We prioritize the combination with the more specific
+ // modifiers. In the case where the number of modifiers
+ // are the same, we want to prioritize Command over Option
+ // over Control over Shift. Unfortunately the order (and
+ // hence value) of the modifiers in Qt::KeyboardModifier
+ // does not match our preferred order when Control and
+ // Meta is switched, but we can work around that by
+ // explicitly swapping the modifiers and using that
+ // for the comparison. This also works when the
+ // Qt::AA_MacDontSwapCtrlAndMeta application attribute
+ // is set, as the incoming modifiers are then left
+ // as is, and we can still trust the order.
+ auto existingModifiers = swapModifiersIfNeeded(existingCombination->keyboardModifiers());
+ auto replacementModifiers = swapModifiersIfNeeded(additionalModifiers);
+ if (replacementModifiers > existingModifiers)
+ *existingCombination = keyCombination;
+ } else {
+ // All is good, no existing combination has this key
+ ret << keyCombination;
+ }
+ }
+ }
+
+ return ret;
+}
+
+
+
+#else // iOS
+
+Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters,
+ NSString *charactersIgnoringModifiers, QString &text)
+{
+ 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 (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) &&
+ (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) {
+ text = QString::fromNSString(characters);
+ }
+ if (!ch.isNull())
+ return Qt::Key(ch.toUpper().unicode());
+ }
+ return Qt::Key_unknown;
+}
+
+// Keyboard keys (non-modifiers)
+API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode)
+{
+ static QHash<NSString *, Qt::Key> uiKitKeys = {
+ { 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 },
+ { UIKeyInputEscape, Qt::Key_Escape },
+ { UIKeyInputUpArrow, Qt::Key_Up },
+ { UIKeyInputDownArrow, Qt::Key_Down },
+ { UIKeyInputLeftArrow, Qt::Key_Left },
+ { UIKeyInputRightArrow, Qt::Key_Right }
+ };
+
+ if (auto key = uiKitKeys.value(keyCode))
+ return key;
+
+ return Qt::Key_unknown;
+}
+
+static constexpr std::tuple<ulong, Qt::KeyboardModifier> 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