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.mm283
1 files changed, 168 insertions, 115 deletions
diff --git a/src/gui/platform/darwin/qapplekeymapper.mm b/src/gui/platform/darwin/qapplekeymapper.mm
index 738fab3863..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,36 +36,6 @@ static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers m
return swappedModifiers;
}
-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;
-}
-
#ifdef Q_OS_MACOS
static constexpr std::tuple<NSEventModifierFlags, Qt::KeyboardModifier> cocoaModifierMap[] = {
{ NSEventModifierFlagShift, Qt::ShiftModifier },
@@ -396,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)
@@ -420,7 +353,7 @@ Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode)
// ------------------------------------------------
-Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers()
+Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() const
{
return fromCocoaModifiers(NSEvent.modifierFlags);
}
@@ -437,7 +370,6 @@ bool QAppleKeyMapper::updateKeyboard()
Q_ASSERT(source);
m_currentInputSource = source;
m_keyboardKind = LMGetKbdType();
- m_deadKeyState = 0;
m_keyMap.clear();
@@ -508,12 +440,15 @@ 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
QChar carbonUnicodeKey;
@@ -549,15 +484,32 @@ const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virt
qCDebug(lcQpaKeyMapper).verbosity(0) << "\t" << qtModifiers
<< "+" << qUtf8Printable(QString::asprintf("0x%02x", virtualKey))
<< "=" << qUtf8Printable(QString::asprintf("%d / 0x%02x /", qtKey, qtKey))
- << QString::asprintf("%c", 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)
@@ -570,21 +522,93 @@ QList<int> QAppleKeyMapper::possibleKeys(const QKeyEvent *event) const
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;
@@ -592,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;