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