diff options
Diffstat (limited to 'src/gui/platform/darwin/qapplekeymapper.mm')
-rw-r--r-- | src/gui/platform/darwin/qapplekeymapper.mm | 312 |
1 files changed, 188 insertions, 124 deletions
diff --git a/src/gui/platform/darwin/qapplekeymapper.mm b/src/gui/platform/darwin/qapplekeymapper.mm index c66fe784ed..b8ff5c9d6d 100644 --- a/src/gui/platform/darwin/qapplekeymapper.mm +++ b/src/gui/platform/darwin/qapplekeymapper.mm @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** 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$ -** -****************************************************************************/ +// 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> @@ -54,7 +18,6 @@ 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) @@ -73,32 +36,6 @@ static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers m 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<NSEventModifierFlags, Qt::KeyboardModifier> cocoaModifierMap[] = { { NSEventModifierFlagShift, Qt::ShiftModifier }, @@ -392,11 +329,11 @@ QChar QAppleKeyMapper::toCocoaKey(Qt::Key key) { // Prioritize overloaded keys if (key == Qt::Key_Return) - return QChar(NSNewlineCharacter); + return QChar(NSCarriageReturnCharacter); if (key == Qt::Key_Backspace) return QChar(NSBackspaceCharacter); - static QHash<Qt::Key, char16_t> reverseCocoaKeys; + 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) @@ -416,7 +353,7 @@ Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode) // ------------------------------------------------ -Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() +Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() const { return fromCocoaModifiers(NSEvent.modifierFlags); } @@ -433,7 +370,6 @@ bool QAppleKeyMapper::updateKeyboard() Q_ASSERT(source); m_currentInputSource = source; m_keyboardKind = LMGetKbdType(); - m_deadKeyState = 0; m_keyMap.clear(); @@ -477,7 +413,7 @@ static constexpr Qt::KeyboardModifiers modifierCombinations[] = { Returns a key map for the given \virtualKey based on all possible modifier combinations. */ -const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virtualKey, QChar unicodeKey) const +const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virtualKey) const { static_assert(sizeof(modifierCombinations) / sizeof(Qt::KeyboardModifiers) == kNumModifierCombinations); @@ -487,7 +423,7 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt if (keyMap[Qt::NoModifier] != Qt::Key_unknown) return keyMap; // Already filled - qCDebug(lcQpaKeyMapper, "Updating key map for virtual key = 0x%02x!", (uint)virtualKey); + 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 @@ -504,16 +440,20 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt 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, OptionBits(0), - &m_deadKeyState, maxStringLength, &actualStringLength, unicodeString); + kUCKeyActionDown, modifierKeyState, m_keyboardKind, + kUCKeyTranslateNoDeadKeysMask, &deadKeyState, + maxStringLength, &actualStringLength, + unicodeString); - // Use translated unicode key if valid + // Use translated Unicode key if valid + QChar carbonUnicodeKey; if (err == noErr && actualStringLength) - unicodeKey = QChar(unicodeString[0]); + carbonUnicodeKey = QChar(unicodeString[0]); if (@available(macOS 10.15, *)) { if (canMapCocoaEvent) { @@ -522,58 +462,153 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt // 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) { + + QChar cocoaUnicodeKey; + if (charactersWithModifiers.length > 0) + cocoaUnicodeKey = QChar([charactersWithModifiers characterAtIndex:0]); + + if (cocoaUnicodeKey != carbonUnicodeKey) { qCWarning(lcQpaKeyMapper) << "Mismatch between Cocoa" << cocoaUnicodeKey - << "and Carbon" << unicodeKey << "for virtual key" << virtualKey + << "and Carbon" << carbonUnicodeKey << "for virtual key" << virtualKey << "with" << qtModifiers; } } } - int qtkey = toKeyCode(unicodeKey, virtualKey, qtModifiers); - if (qtkey == Qt::Key_unknown) - qtkey = unicodeKey.unicode(); + int qtKey = toKeyCode(carbonUnicodeKey, virtualKey, qtModifiers); + if (qtKey == Qt::Key_unknown) + qtKey = carbonUnicodeKey.unicode(); - keyMap[i] = qtkey; + keyMap[i] = qtKey; - qCDebug(lcQpaKeyMapper, " [%d] (%d,0x%02x,'%c')", i, qtkey, qtkey, 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; } -QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const +/* + 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<int> ret; + QList<QKeyCombination> ret; const auto nativeVirtualKey = event->nativeVirtualKey(); if (!nativeVirtualKey) return ret; - auto keyMap = keyMapForKey(nativeVirtualKey, QChar(event->key())); + auto keyMap = keyMapForKey(nativeVirtualKey); 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); + 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 = 1; i < 8; ++i) { + for (int i = startingModifierLayer; i < 15; ++i) { auto keyAfterApplyingModifiers = keyMap[i]; - if (keyAfterApplyingModifiers == unmodifiedKey) - continue; + if (!keyAfterApplyingModifiers) + continue; - // Include key if event modifiers includes, or matches - // perfectly, the current candidate modifiers. + // 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) - ret << int(keyAfterApplyingModifiers) + int(eventModifiers & ~candidateModifiers); + 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; @@ -581,36 +616,65 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const -#else -// Keyboard keys (non-modifiers) -API_AVAILABLE(ios(13.4)) static QHash<NSString *, Qt::Key> 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 } -}; +#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; |