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