Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qapplekeymapper.mm
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <qglobal.h>
5
6#ifdef Q_OS_MACOS
7#include <AppKit/AppKit.h>
8#endif
9
10#if defined(QT_PLATFORM_UIKIT)
11#include <UIKit/UIKit.h>
12#endif
13
15
16#include <QtCore/qloggingcategory.h>
17#include <QtGui/QGuiApplication>
18
20
21#ifdef Q_OS_MACOS
22Q_STATIC_LOGGING_CATEGORY(lcQpaKeyMapperKeys, "qt.qpa.keymapper.keys");
23#endif
24
25static Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers)
26{
27 if (QCoreApplication::testAttribute(Qt::AA_MacDontSwapCtrlAndMeta))
28 return modifiers;
29
30 Qt::KeyboardModifiers swappedModifiers = modifiers;
31 swappedModifiers &= ~(Qt::MetaModifier | Qt::ControlModifier);
32
33 if (modifiers & Qt::ControlModifier)
34 swappedModifiers |= Qt::MetaModifier;
35 if (modifiers & Qt::MetaModifier)
36 swappedModifiers |= Qt::ControlModifier;
37
38 return swappedModifiers;
39}
40
41#ifdef Q_OS_MACOS
42static constexpr std::tuple<NSEventModifierFlags, Qt::KeyboardModifier> cocoaModifierMap[] = {
43 { NSEventModifierFlagShift, Qt::ShiftModifier },
44 { NSEventModifierFlagControl, Qt::ControlModifier },
45 { NSEventModifierFlagCommand, Qt::MetaModifier },
46 { NSEventModifierFlagOption, Qt::AltModifier },
47 { NSEventModifierFlagNumericPad, Qt::KeypadModifier }
48};
49
50Qt::KeyboardModifiers QAppleKeyMapper::fromCocoaModifiers(NSEventModifierFlags cocoaModifiers)
51{
52 Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
53 for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) {
54 if (cocoaModifiers & cocoaModifier)
55 qtModifiers |= qtModifier;
56 }
57
58 return swapModifiersIfNeeded(qtModifiers);
59}
60
61NSEventModifierFlags QAppleKeyMapper::toCocoaModifiers(Qt::KeyboardModifiers qtModifiers)
62{
63 qtModifiers = swapModifiersIfNeeded(qtModifiers);
64
65 NSEventModifierFlags cocoaModifiers = 0;
66 for (const auto &[cocoaModifier, qtModifier] : cocoaModifierMap) {
67 if (qtModifiers & qtModifier)
68 cocoaModifiers |= cocoaModifier;
69 }
70
71 return cocoaModifiers;
72}
73
74using CarbonModifiers = UInt32; // As opposed to EventModifiers which is UInt16
75
76static CarbonModifiers toCarbonModifiers(Qt::KeyboardModifiers qtModifiers)
77{
78 qtModifiers = swapModifiersIfNeeded(qtModifiers);
79
80 static constexpr std::tuple<int, Qt::KeyboardModifier> carbonModifierMap[] = {
81 { shiftKey, Qt::ShiftModifier },
82 { controlKey, Qt::ControlModifier },
83 { cmdKey, Qt::MetaModifier },
84 { optionKey, Qt::AltModifier },
85 { kEventKeyModifierNumLockMask, Qt::KeypadModifier }
86 };
87
88 CarbonModifiers carbonModifiers = 0;
89 for (const auto &[carbonModifier, qtModifier] : carbonModifierMap) {
90 if (qtModifiers & qtModifier)
91 carbonModifiers |= carbonModifier;
92 }
93
94 return carbonModifiers;
95}
96
97// Keyboard keys (non-modifiers)
98static QHash<char16_t, Qt::Key> standardKeys = {
99 { kHomeCharCode, Qt::Key_Home },
100 { kEnterCharCode, Qt::Key_Enter },
101 { kEndCharCode, Qt::Key_End },
102 { kBackspaceCharCode, Qt::Key_Backspace },
103 { kTabCharCode, Qt::Key_Tab },
104 { kPageUpCharCode, Qt::Key_PageUp },
105 { kPageDownCharCode, Qt::Key_PageDown },
106 { kReturnCharCode, Qt::Key_Return },
107 { kEscapeCharCode, Qt::Key_Escape },
108 { kLeftArrowCharCode, Qt::Key_Left },
109 { kRightArrowCharCode, Qt::Key_Right },
110 { kUpArrowCharCode, Qt::Key_Up },
111 { kDownArrowCharCode, Qt::Key_Down },
112 { kHelpCharCode, Qt::Key_Help },
113 { kDeleteCharCode, Qt::Key_Delete },
114 // ASCII maps, for debugging
115 { ':', Qt::Key_Colon },
116 { ';', Qt::Key_Semicolon },
117 { '<', Qt::Key_Less },
118 { '=', Qt::Key_Equal },
119 { '>', Qt::Key_Greater },
120 { '?', Qt::Key_Question },
121 { '@', Qt::Key_At },
122 { ' ', Qt::Key_Space },
123 { '!', Qt::Key_Exclam },
124 { '"', Qt::Key_QuoteDbl },
125 { '#', Qt::Key_NumberSign },
126 { '$', Qt::Key_Dollar },
127 { '%', Qt::Key_Percent },
128 { '&', Qt::Key_Ampersand },
129 { '\'', Qt::Key_Apostrophe },
130 { '(', Qt::Key_ParenLeft },
131 { ')', Qt::Key_ParenRight },
132 { '*', Qt::Key_Asterisk },
133 { '+', Qt::Key_Plus },
134 { ',', Qt::Key_Comma },
135 { '-', Qt::Key_Minus },
136 { '.', Qt::Key_Period },
137 { '/', Qt::Key_Slash },
138 { '[', Qt::Key_BracketLeft },
139 { ']', Qt::Key_BracketRight },
140 { '\\', Qt::Key_Backslash },
141 { '_', Qt::Key_Underscore },
142 { '`', Qt::Key_QuoteLeft },
143 { '{', Qt::Key_BraceLeft },
144 { '}', Qt::Key_BraceRight },
145 { '|', Qt::Key_Bar },
146 { '~', Qt::Key_AsciiTilde },
147 { '^', Qt::Key_AsciiCircum }
148};
149
150static QHash<char16_t, Qt::Key> virtualKeys = {
151 { kVK_F1, Qt::Key_F1 },
152 { kVK_F2, Qt::Key_F2 },
153 { kVK_F3, Qt::Key_F3 },
154 { kVK_F4, Qt::Key_F4 },
155 { kVK_F5, Qt::Key_F5 },
156 { kVK_F6, Qt::Key_F6 },
157 { kVK_F7, Qt::Key_F7 },
158 { kVK_F8, Qt::Key_F8 },
159 { kVK_F9, Qt::Key_F9 },
160 { kVK_F10, Qt::Key_F10 },
161 { kVK_F11, Qt::Key_F11 },
162 { kVK_F12, Qt::Key_F12 },
163 { kVK_F13, Qt::Key_F13 },
164 { kVK_F14, Qt::Key_F14 },
165 { kVK_F15, Qt::Key_F15 },
166 { kVK_F16, Qt::Key_F16 },
167 { kVK_Return, Qt::Key_Return },
168 { kVK_Tab, Qt::Key_Tab },
169 { kVK_Escape, Qt::Key_Escape },
170 { kVK_Help, Qt::Key_Help },
171 { kVK_UpArrow, Qt::Key_Up },
172 { kVK_DownArrow, Qt::Key_Down },
173 { kVK_LeftArrow, Qt::Key_Left },
174 { kVK_RightArrow, Qt::Key_Right },
175 { kVK_PageUp, Qt::Key_PageUp },
176 { kVK_PageDown, Qt::Key_PageDown }
177};
178
179static QHash<char16_t, Qt::Key> functionKeys = {
180 { NSUpArrowFunctionKey, Qt::Key_Up },
181 { NSDownArrowFunctionKey, Qt::Key_Down },
182 { NSLeftArrowFunctionKey, Qt::Key_Left },
183 { NSRightArrowFunctionKey, Qt::Key_Right },
184 // F1-35 function keys handled manually below
185 { NSInsertFunctionKey, Qt::Key_Insert },
186 { NSDeleteFunctionKey, Qt::Key_Delete },
187 { NSHomeFunctionKey, Qt::Key_Home },
188 { NSEndFunctionKey, Qt::Key_End },
189 { NSPageUpFunctionKey, Qt::Key_PageUp },
190 { NSPageDownFunctionKey, Qt::Key_PageDown },
191 { NSPrintScreenFunctionKey, Qt::Key_Print },
192 { NSScrollLockFunctionKey, Qt::Key_ScrollLock },
193 { NSPauseFunctionKey, Qt::Key_Pause },
194 { NSSysReqFunctionKey, Qt::Key_SysReq },
195 { NSMenuFunctionKey, Qt::Key_Menu },
196 { NSPrintFunctionKey, Qt::Key_Printer },
197 { NSClearDisplayFunctionKey, Qt::Key_Clear },
198 { NSInsertCharFunctionKey, Qt::Key_Insert },
199 { NSDeleteCharFunctionKey, Qt::Key_Delete },
200 { NSSelectFunctionKey, Qt::Key_Select },
201 { NSExecuteFunctionKey, Qt::Key_Execute },
202 { NSUndoFunctionKey, Qt::Key_Undo },
203 { NSRedoFunctionKey, Qt::Key_Redo },
204 { NSFindFunctionKey, Qt::Key_Find },
205 { NSHelpFunctionKey, Qt::Key_Help },
206 { NSModeSwitchFunctionKey, Qt::Key_Mode_switch }
207};
208
209static int toKeyCode(const QChar &key, int virtualKey, int modifiers)
210{
211 qCDebug(lcQpaKeyMapperKeys, "Mapping key: %d (0x%04x) / vk %d (0x%04x)",
212 key.unicode(), key.unicode(), virtualKey, virtualKey);
213
214 if (key == char16_t(kClearCharCode) && virtualKey == 0x47)
215 return Qt::Key_Clear;
216
217 if (key.isDigit()) {
218 qCDebug(lcQpaKeyMapperKeys, "Got digit key: %d", key.digitValue());
219 return key.digitValue() + Qt::Key_0;
220 }
221
222 if (key.isLetter()) {
223 qCDebug(lcQpaKeyMapperKeys, "Got letter key: %d", (key.toUpper().unicode() - 'A'));
224 return (key.toUpper().unicode() - 'A') + Qt::Key_A;
225 }
226 if (key.isSymbol()) {
227 qCDebug(lcQpaKeyMapperKeys, "Got symbol key: %d", (key.unicode()));
228 return key.unicode();
229 }
230
231 if (auto qtKey = standardKeys.value(key.unicode())) {
232 // To work like Qt for X11 we issue Backtab when Shift + Tab are pressed
233 if (qtKey == Qt::Key_Tab && (modifiers & Qt::ShiftModifier)) {
234 qCDebug(lcQpaKeyMapperKeys, "Got key: Qt::Key_Backtab");
235 return Qt::Key_Backtab;
236 }
237
238 qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey;
239 return qtKey;
240 }
241
242 // Last ditch try to match the scan code
243 if (auto qtKey = virtualKeys.value(virtualKey)) {
244 qCDebug(lcQpaKeyMapperKeys) << "Got scancode" << qtKey;
245 return qtKey;
246 }
247
248 // Check if they belong to key codes in private unicode range
249 if (key >= char16_t(NSUpArrowFunctionKey) && key <= char16_t(NSModeSwitchFunctionKey)) {
250 if (auto qtKey = functionKeys.value(key.unicode())) {
251 qCDebug(lcQpaKeyMapperKeys) << "Got" << qtKey;
252 return qtKey;
253 } else if (key >= char16_t(NSF1FunctionKey) && key <= char16_t(NSF35FunctionKey)) {
254 auto functionKey = Qt::Key_F1 + (key.unicode() - NSF1FunctionKey) ;
255 qCDebug(lcQpaKeyMapperKeys) << "Got" << functionKey;
256 return functionKey;
257 }
258 }
259
260 qCDebug(lcQpaKeyMapperKeys, "Unknown case.. %d[%d] %d", key.unicode(), key.toLatin1(), virtualKey);
261 return Qt::Key_unknown;
262}
263
264// --------- Cocoa key mapping moved from Qt Core ---------
265
266static const int NSEscapeCharacter = 27; // not defined by Cocoa headers
267
268static const QHash<char16_t, Qt::Key> cocoaKeys = {
269 { NSEnterCharacter, Qt::Key_Enter },
270 { NSBackspaceCharacter, Qt::Key_Backspace },
271 { NSTabCharacter, Qt::Key_Tab },
272 { NSNewlineCharacter, Qt::Key_Return },
273 { NSCarriageReturnCharacter, Qt::Key_Return },
274 { NSBackTabCharacter, Qt::Key_Backtab },
275 { NSEscapeCharacter, Qt::Key_Escape },
276 { NSDeleteCharacter, Qt::Key_Backspace },
277 { NSUpArrowFunctionKey, Qt::Key_Up },
278 { NSDownArrowFunctionKey, Qt::Key_Down },
279 { NSLeftArrowFunctionKey, Qt::Key_Left },
280 { NSRightArrowFunctionKey, Qt::Key_Right },
281 { NSF1FunctionKey, Qt::Key_F1 },
282 { NSF2FunctionKey, Qt::Key_F2 },
283 { NSF3FunctionKey, Qt::Key_F3 },
284 { NSF4FunctionKey, Qt::Key_F4 },
285 { NSF5FunctionKey, Qt::Key_F5 },
286 { NSF6FunctionKey, Qt::Key_F6 },
287 { NSF7FunctionKey, Qt::Key_F7 },
288 { NSF8FunctionKey, Qt::Key_F8 },
289 { NSF9FunctionKey, Qt::Key_F9 },
290 { NSF10FunctionKey, Qt::Key_F10 },
291 { NSF11FunctionKey, Qt::Key_F11 },
292 { NSF12FunctionKey, Qt::Key_F12 },
293 { NSF13FunctionKey, Qt::Key_F13 },
294 { NSF14FunctionKey, Qt::Key_F14 },
295 { NSF15FunctionKey, Qt::Key_F15 },
296 { NSF16FunctionKey, Qt::Key_F16 },
297 { NSF17FunctionKey, Qt::Key_F17 },
298 { NSF18FunctionKey, Qt::Key_F18 },
299 { NSF19FunctionKey, Qt::Key_F19 },
300 { NSF20FunctionKey, Qt::Key_F20 },
301 { NSF21FunctionKey, Qt::Key_F21 },
302 { NSF22FunctionKey, Qt::Key_F22 },
303 { NSF23FunctionKey, Qt::Key_F23 },
304 { NSF24FunctionKey, Qt::Key_F24 },
305 { NSF25FunctionKey, Qt::Key_F25 },
306 { NSF26FunctionKey, Qt::Key_F26 },
307 { NSF27FunctionKey, Qt::Key_F27 },
308 { NSF28FunctionKey, Qt::Key_F28 },
309 { NSF29FunctionKey, Qt::Key_F29 },
310 { NSF30FunctionKey, Qt::Key_F30 },
311 { NSF31FunctionKey, Qt::Key_F31 },
312 { NSF32FunctionKey, Qt::Key_F32 },
313 { NSF33FunctionKey, Qt::Key_F33 },
314 { NSF34FunctionKey, Qt::Key_F34 },
315 { NSF35FunctionKey, Qt::Key_F35 },
316 { NSInsertFunctionKey, Qt::Key_Insert },
317 { NSDeleteFunctionKey, Qt::Key_Delete },
318 { NSHomeFunctionKey, Qt::Key_Home },
319 { NSEndFunctionKey, Qt::Key_End },
320 { NSPageUpFunctionKey, Qt::Key_PageUp },
321 { NSPageDownFunctionKey, Qt::Key_PageDown },
322 { NSPrintScreenFunctionKey, Qt::Key_Print },
323 { NSScrollLockFunctionKey, Qt::Key_ScrollLock },
324 { NSPauseFunctionKey, Qt::Key_Pause },
325 { NSSysReqFunctionKey, Qt::Key_SysReq },
326 { NSMenuFunctionKey, Qt::Key_Menu },
327 { NSHelpFunctionKey, Qt::Key_Help },
328};
329
330QChar QAppleKeyMapper::toCocoaKey(Qt::Key key)
331{
332 // Prioritize overloaded keys
333 if (key == Qt::Key_Return)
334 return char16_t(NSCarriageReturnCharacter);
335 if (key == Qt::Key_Backspace)
336 return char16_t(NSBackspaceCharacter);
337
338 Q_CONSTINIT static QHash<Qt::Key, char16_t> reverseCocoaKeys;
339 if (reverseCocoaKeys.isEmpty()) {
340 reverseCocoaKeys.reserve(cocoaKeys.size());
341 for (auto it = cocoaKeys.begin(); it != cocoaKeys.end(); ++it)
342 reverseCocoaKeys.insert(it.value(), it.key());
343 }
344
345 return reverseCocoaKeys.value(key);
346}
347
348Qt::Key QAppleKeyMapper::fromCocoaKey(QChar keyCode)
349{
350 if (auto key = cocoaKeys.value(keyCode.unicode()))
351 return key;
352
353 return Qt::Key(keyCode.toUpper().unicode());
354}
355
356// ------------------------------------------------
357
358Qt::KeyboardModifiers QAppleKeyMapper::queryKeyboardModifiers() const
359{
360 return fromCocoaModifiers(NSEvent.modifierFlags);
361}
362
363bool QAppleKeyMapper::updateKeyboard()
364{
365 QCFType<TISInputSourceRef> source = TISCopyInputMethodKeyboardLayoutOverride();
366 if (!source)
367 source = TISCopyCurrentKeyboardInputSource();
368
369 if (m_keyboardMode != NullMode && source == m_currentInputSource)
370 return false;
371
372 Q_ASSERT(source);
373 m_currentInputSource = source;
374 m_keyboardKind = LMGetKbdType();
375
376 m_keyMap.clear();
377
378 if (auto data = CFDataRef(TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData))) {
379 const UCKeyboardLayout *uchrData = reinterpret_cast<const UCKeyboardLayout *>(CFDataGetBytePtr(data));
380 Q_ASSERT(uchrData);
381 m_keyboardLayoutFormat = uchrData;
382 m_keyboardMode = UnicodeMode;
383 } else {
384 m_keyboardLayoutFormat = nullptr;
385 m_keyboardMode = NullMode;
386 }
387
388 qCDebug(lcQpaKeyMapper) << "Updated keyboard to"
389 << QString::fromCFString(CFStringRef(TISGetInputSourceProperty(
390 m_currentInputSource, kTISPropertyLocalizedName)));
391
392 return true;
393}
394
395static constexpr Qt::KeyboardModifiers modifierCombinations[] = {
396 Qt::NoModifier, // 0
397 Qt::ShiftModifier, // 1
398 Qt::ControlModifier, // 2
399 Qt::ControlModifier | Qt::ShiftModifier, // 3
400 Qt::AltModifier, // 4
401 Qt::AltModifier | Qt::ShiftModifier, // 5
402 Qt::AltModifier | Qt::ControlModifier, // 6
403 Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 7
404 Qt::MetaModifier, // 8
405 Qt::MetaModifier | Qt::ShiftModifier, // 9
406 Qt::MetaModifier | Qt::ControlModifier, // 10
407 Qt::MetaModifier | Qt::ControlModifier | Qt::ShiftModifier, // 11
408 Qt::MetaModifier | Qt::AltModifier, // 12
409 Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier, // 13
410 Qt::MetaModifier | Qt::AltModifier | Qt::ControlModifier, // 14
411 Qt::MetaModifier | Qt::AltModifier | Qt::ShiftModifier | Qt::ControlModifier, // 15
412};
413
414/*
415 Returns a key map for the given \virtualKey based on all
416 possible modifier combinations.
417*/
418const QAppleKeyMapper::KeyMap &QAppleKeyMapper::keyMapForKey(VirtualKeyCode virtualKey) const
419{
420 static_assert(sizeof(modifierCombinations) / sizeof(Qt::KeyboardModifiers) == kNumModifierCombinations);
421
422 const_cast<QAppleKeyMapper *>(this)->updateKeyboard();
423
424 auto &keyMap = m_keyMap[virtualKey];
425 if (keyMap[Qt::NoModifier] != Qt::Key_unknown)
426 return keyMap; // Already filled
427
428 qCDebug(lcQpaKeyMapper, "Updating key map for virtual key 0x%02x", (uint)virtualKey);
429
430 // Key mapping via [NSEvent charactersByApplyingModifiers:] only works for key down
431 // events, but we might (wrongly) get into this code path for other key events such
432 // as NSEventTypeFlagsChanged.
433 const bool canMapCocoaEvent = NSApp.currentEvent.type == NSEventTypeKeyDown;
434
435 if (!canMapCocoaEvent)
436 qCWarning(lcQpaKeyMapper) << "Could not map key to character for event" << NSApp.currentEvent;
437
438 for (int i = 0; i < kNumModifierCombinations; ++i) {
439 Q_ASSERT(!i || keyMap[i] == 0);
440
441 auto qtModifiers = modifierCombinations[i];
442 auto carbonModifiers = toCarbonModifiers(qtModifiers);
443 const UInt32 modifierKeyState = (carbonModifiers >> 8) & 0xFF;
444
445 UInt32 deadKeyState = 0;
446 static const UniCharCount maxStringLength = 10;
447 static UniChar unicodeString[maxStringLength];
448 UniCharCount actualStringLength = 0;
449 OSStatus err = UCKeyTranslate(m_keyboardLayoutFormat, virtualKey,
450 kUCKeyActionDown, modifierKeyState, m_keyboardKind,
451 kUCKeyTranslateNoDeadKeysMask, &deadKeyState,
452 maxStringLength, &actualStringLength,
453 unicodeString);
454
455 // Use translated Unicode key if valid
456 QChar carbonUnicodeKey;
457 if (err == noErr && actualStringLength)
458 carbonUnicodeKey = QChar(unicodeString[0]);
459
460 if (canMapCocoaEvent) {
461 // Until we've verified that the Cocoa API works as expected
462 // we first run the event through the Carbon APIs and then
463 // compare the results to Cocoa.
464 auto cocoaModifiers = toCocoaModifiers(qtModifiers);
465 auto *charactersWithModifiers = [NSApp.currentEvent charactersByApplyingModifiers:cocoaModifiers];
466
467 QChar cocoaUnicodeKey;
468 if (charactersWithModifiers.length > 0)
469 cocoaUnicodeKey = QChar([charactersWithModifiers characterAtIndex:0]);
470
471 if (cocoaUnicodeKey != carbonUnicodeKey) {
472 qCWarning(lcQpaKeyMapper) << "Mismatch between Cocoa" << cocoaUnicodeKey
473 << "and Carbon" << carbonUnicodeKey << "for virtual key" << virtualKey
474 << "with" << qtModifiers;
475 }
476 }
477
478 int qtKey = toKeyCode(carbonUnicodeKey, virtualKey, qtModifiers);
479 if (qtKey == Qt::Key_unknown)
480 qtKey = carbonUnicodeKey.unicode();
481
482 keyMap[i] = qtKey;
483
484 qCDebug(lcQpaKeyMapper).verbosity(0) << "\t" << qtModifiers
485 << "+" << qUtf8Printable(QString::asprintf("0x%02x", virtualKey))
486 << "=" << qUtf8Printable(QString::asprintf("%d / 0x%02x /", qtKey, qtKey))
487 << QKeySequence(qtKey).toString();
488 }
489
490 return keyMap;
491}
492
493/*
494 Compute the possible key combinations that can map to the event's
495 virtual key and modifiers, in the current keyboard layout.
496
497 For example, given a normal US keyboard layout, the virtual key
498 23 combined with the Alt (⌥) and Shift (⇧) modifiers, can map
499 to the following key combinations:
500
501 - Alt+Shift+5
502 - Alt+%
503 - Shift+∞
504 - fi
505
506 The function builds on a key map produced by keyMapForKey(),
507 where each modifier-key combination has been mapped to the
508 key it will produce.
509*/
510QList<QKeyCombination> QAppleKeyMapper::possibleKeyCombinations(const QKeyEvent *event) const
511{
512 QList<QKeyCombination> ret;
513
514 const auto nativeVirtualKey = event->nativeVirtualKey();
515 if (!nativeVirtualKey)
516 return ret;
517
518 auto keyMap = keyMapForKey(nativeVirtualKey);
519
520 auto unmodifiedKey = keyMap[Qt::NoModifier];
521 Q_ASSERT(unmodifiedKey != Qt::Key_unknown);
522
523 auto eventModifiers = event->modifiers();
524
525 int startingModifierLayer = 0;
526 if (toCocoaModifiers(eventModifiers) & NSEventModifierFlagCommand) {
527 // When the Command key is pressed AppKit seems to do key equivalent
528 // matching using a Latin/Roman interpretation of the current keyboard
529 // layout. For example, for a Greek layout, pressing Option+Command+C
530 // produces a key event with chars="ç" and unmodchars="ψ", but AppKit
531 // still treats this as a match for a key equivalent of Option+Command+C.
532 // We can't do the same by just applying the modifiers to our key map,
533 // as that too contains "ψ" for the Option+Command combination. What we
534 // can do instead is take advantage of the fact that the Command
535 // modifier layer in all/most keyboard layouts contains a Latin
536 // layer. We then combine that with the modifiers of the event
537 // to produce the resulting "Latin" key combination.
538
539 // Depending on whether Qt::AA_MacDontSwapCtrlAndMeta is set or not
540 // the index of the Command layer in modifierCombinations will differ.
541 const int commandLayer = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? 8 : 2;
542 // FIXME: We don't clear the key map when Qt::AA_MacDontSwapCtrlAndMeta
543 // is set, so changing Qt::AA_MacDontSwapCtrlAndMeta at runtime will
544 // fail once we've built the key map.
545 ret << QKeyCombination::fromCombined(int(eventModifiers) + int(keyMap[commandLayer]));
546
547 // If the unmodified key is outside of Latin1, we also treat
548 // that as a valid key combination, even if AppKit natively
549 // does not. For example, for a Greek layout, we still want
550 // to support Option+Command+ψ as a key combination, as it's
551 // unlikely to clash with the Latin key combination we added
552 // above.
553
554 // However, if the unmodified key is within Latin1, we skip
555 // it, to avoid these types of conflicts. For example, in
556 // the same Greek layout, pressing the key next to Tab will
557 // produce a Latin ';' symbol, but we've already treated that
558 // as 'q' above, thanks to the Command modifier, so we skip
559 // the potential Command+; key combination. This is also in
560 // line with what AppKit natively does.
561
562 // Skipping Latin1 unmodified keys also handles the case of
563 // a Latin layout, where the unmodified and modified keys
564 // are the same.
565
566 if (unmodifiedKey <= 0xff)
567 startingModifierLayer = 1;
568 }
569
570 // FIXME: We only compute the first 8 combinations. Why?
571 for (int i = startingModifierLayer; i < 15; ++i) {
572 auto keyAfterApplyingModifiers = keyMap[i];
573 if (!keyAfterApplyingModifiers)
574 continue;
575
576 // Include key if the event modifiers match exactly,
577 // or are a superset of the current candidate modifiers.
578 auto candidateModifiers = modifierCombinations[i];
579 if ((eventModifiers & candidateModifiers) == candidateModifiers) {
580 // If the event includes more modifiers than the candidate they
581 // will need to be included in the resulting key combination.
582 auto additionalModifiers = eventModifiers & ~candidateModifiers;
583
584 auto keyCombination = QKeyCombination::fromCombined(
585 int(additionalModifiers) + int(keyAfterApplyingModifiers));
586
587 // If there's an existing key combination with the same key,
588 // but a different set of modifiers, we want to choose only
589 // one of them, by priority (see below).
590 const auto existingCombination = std::find_if(
591 ret.begin(), ret.end(), [&](auto existingCombination) {
592 return existingCombination.key() == keyAfterApplyingModifiers;
593 });
594
595 if (existingCombination != ret.end()) {
596 // We prioritize the combination with the more specific
597 // modifiers. In the case where the number of modifiers
598 // are the same, we want to prioritize Command over Option
599 // over Control over Shift. Unfortunately the order (and
600 // hence value) of the modifiers in Qt::KeyboardModifier
601 // does not match our preferred order when Control and
602 // Meta is switched, but we can work around that by
603 // explicitly swapping the modifiers and using that
604 // for the comparison. This also works when the
605 // Qt::AA_MacDontSwapCtrlAndMeta application attribute
606 // is set, as the incoming modifiers are then left
607 // as is, and we can still trust the order.
608 auto existingModifiers = swapModifiersIfNeeded(existingCombination->keyboardModifiers());
609 auto replacementModifiers = swapModifiersIfNeeded(additionalModifiers);
610 if (replacementModifiers > existingModifiers)
611 *existingCombination = keyCombination;
612 } else {
613 // All is good, no existing combination has this key
614 ret << keyCombination;
615 }
616 }
617 }
618
619 return ret;
620}
621
622
623
624#else // iOS
625
626Qt::Key QAppleKeyMapper::fromNSString(Qt::KeyboardModifiers qtModifiers, NSString *characters,
627 NSString *charactersIgnoringModifiers, QString &text)
628{
629 if ([characters isEqualToString:@"\t"]) {
630 if (qtModifiers & Qt::ShiftModifier)
631 return Qt::Key_Backtab;
632 return Qt::Key_Tab;
633 } else if ([characters isEqualToString:@"\r"]) {
634 if (qtModifiers & Qt::KeypadModifier)
635 return Qt::Key_Enter;
636 return Qt::Key_Return;
637 }
638 if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) {
639 QChar ch;
640 if (((qtModifiers & Qt::MetaModifier) || (qtModifiers & Qt::AltModifier)) &&
641 ([charactersIgnoringModifiers length] != 0)) {
642 ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
643 } else if ([characters length] != 0) {
644 ch = QChar([characters characterAtIndex:0]);
645 }
646 if (!(qtModifiers & (Qt::ControlModifier | Qt::MetaModifier)) &&
647 (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) {
648 text = QString::fromNSString(characters);
649 }
650 if (!ch.isNull())
651 return Qt::Key(ch.toUpper().unicode());
652 }
653 return Qt::Key_unknown;
654}
655
656// Keyboard keys (non-modifiers)
657API_AVAILABLE(ios(13.4)) Qt::Key QAppleKeyMapper::fromUIKitKey(NSString *keyCode)
658{
659 static QHash<NSString *, Qt::Key> uiKitKeys = {
660 { UIKeyInputF1, Qt::Key_F1 },
661 { UIKeyInputF2, Qt::Key_F2 },
662 { UIKeyInputF3, Qt::Key_F3 },
663 { UIKeyInputF4, Qt::Key_F4 },
664 { UIKeyInputF5, Qt::Key_F5 },
665 { UIKeyInputF6, Qt::Key_F6 },
666 { UIKeyInputF7, Qt::Key_F7 },
667 { UIKeyInputF8, Qt::Key_F8 },
668 { UIKeyInputF9, Qt::Key_F9 },
669 { UIKeyInputF10, Qt::Key_F10 },
670 { UIKeyInputF11, Qt::Key_F11 },
671 { UIKeyInputF12, Qt::Key_F12 },
672 { UIKeyInputHome, Qt::Key_Home },
673 { UIKeyInputEnd, Qt::Key_End },
674 { UIKeyInputPageUp, Qt::Key_PageUp },
675 { UIKeyInputPageDown, Qt::Key_PageDown },
676 { UIKeyInputEscape, Qt::Key_Escape },
677 { UIKeyInputUpArrow, Qt::Key_Up },
678 { UIKeyInputDownArrow, Qt::Key_Down },
679 { UIKeyInputLeftArrow, Qt::Key_Left },
680 { UIKeyInputRightArrow, Qt::Key_Right }
681 };
682
683 if (auto key = uiKitKeys.value(keyCode))
684 return key;
685
686 return Qt::Key_unknown;
687}
688
690 { UIKeyModifierShift, Qt::ShiftModifier },
691 { UIKeyModifierControl, Qt::ControlModifier },
692 { UIKeyModifierCommand, Qt::MetaModifier },
693 { UIKeyModifierAlternate, Qt::AltModifier },
694 { UIKeyModifierNumericPad, Qt::KeypadModifier }
695};
696
697ulong QAppleKeyMapper::toUIKitModifiers(Qt::KeyboardModifiers qtModifiers)
698{
699 qtModifiers = swapModifiersIfNeeded(qtModifiers);
700
701 ulong nativeModifiers = 0;
702 for (const auto &[nativeModifier, qtModifier] : uiKitModifierMap) {
703 if (qtModifiers & qtModifier)
704 nativeModifiers |= nativeModifier;
705 }
706
707 return nativeModifiers;
708}
709
710Qt::KeyboardModifiers QAppleKeyMapper::fromUIKitModifiers(ulong nativeModifiers)
711{
712 Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
713 for (const auto &[nativeModifier, qtModifier] : uiKitModifierMap) {
714 if (nativeModifiers & nativeModifier)
715 qtModifiers |= qtModifier;
716 }
717
718 return swapModifiersIfNeeded(qtModifiers);
719}
720#endif
721
722QT_END_NAMESPACE
static constexpr std::tuple< ulong, Qt::KeyboardModifier > uiKitModifierMap[]
API_AVAILABLE(ios(13.4)) Qt
static QT_BEGIN_NAMESPACE Qt::KeyboardModifiers swapModifiersIfNeeded(const Qt::KeyboardModifiers modifiers)