Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qnsview_keys.mm
Go to the documentation of this file.
1// Copyright (C) 2018 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// This file is included from qnsview.mm, and only used to organize the code
5
6/*
7 Determines if the text represents one of the "special keys" on macOS
8
9 As a legacy from OpenStep, macOS reserves the range 0xF700-0xF8FF of the
10 Unicode private use area for representing function keys on the keyboard:
11
12 http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
13
14 https://developer.apple.com/documentation/appkit/nsevent/specialkey
15
16 These code points are not supposed to have any glyphs associated with them,
17 but since we can't guarantee that the system doesn't have a font that does
18 provide glyphs for this range (Arial Unicode MS e.g.) we need to filter
19 the text of our key events up front.
20*/
21static bool isSpecialKey(const QString &text)
22{
23 if (text.length() != 1)
24 return false;
25
26 const char16_t unicode = text.at(0).unicode();
27 if (unicode >= 0xF700 && unicode <= 0xF8FF)
28 return true;
29
30 return false;
31}
32
33@implementation QNSView (Keys)
34
35- (bool)handleKeyEvent:(NSEvent *)nsevent
36{
37 qCDebug(lcQpaKeys) << "Handling" << nsevent;
38 KeyEvent keyEvent(nsevent);
39
40 // FIXME: Why is this the top level window and not m_platformWindow?
41 QWindow *window = [self topLevelWindow];
42
43 // We will send a key event unless the input method handles it
44 QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true);
45
46 // Assume we should send key events with text, unless told
47 // otherwise by doCommandBySelector.
49
50 bool didInterpretKeyEvent = false;
51
52 if (keyEvent.type == QEvent::KeyPress) {
53
55 KeyEvent shortcutEvent = keyEvent;
56 shortcutEvent.type = QEvent::Shortcut;
57 qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
58 << "for" << shortcutEvent;
59
60 if (shortcutEvent.sendWindowSystemEvent(window)) {
61 qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
62 return true;
63 } else {
64 qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery";
65 }
66 }
67
68 QObject *focusObject = m_platformWindow ? m_platformWindow->window()->focusObject() : nullptr;
69 if (m_sendKeyEvent && focusObject) {
70 if (auto queryResult = queryInputMethod(focusObject, Qt::ImHints)) {
71 auto hints = static_cast<Qt::InputMethodHints>(queryResult.value(Qt::ImHints).toUInt());
72
73 // Make sure we send dead keys and the next key to the input method for composition
74 const bool isDeadKey = !nsevent.characters.length;
75 const bool ignoreHidden = (hints & Qt::ImhHiddenText) && !isDeadKey && !m_lastKeyDead;
76
77 if (!(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || ignoreHidden)) {
78 // Pass the key event to the input method, and assume it handles the event,
79 // unless we explicit set m_sendKeyEvent to deliver as a normal key event.
80 m_sendKeyEvent = false;
81
82 // Match NSTextView's keyDown behavior of hiding the cursor before
83 // interpreting key events. Shortcuts should not trigger this though.
84 // Unfortunately many of our controls handle shortcuts by accepting
85 // the ShortcutOverride event and then handling the shortcut in the
86 // following key event, and QWSI::handleShortcutEvent doesn't reveal
87 // whether this will be the case. For NSTextView this is not an issue
88 // as shortcuts are handled via performKeyEquivalent, which happens
89 // prior to keyDown. To work around this until we can get the info
90 // we need from handleShortcutEvent we match AppKit and assume that
91 // any key press with a command or control modifier is a shortcut.
92 if (!(nsevent.modifierFlags & (NSEventModifierFlagCommand | NSEventModifierFlagControl)))
93 [NSCursor setHiddenUntilMouseMoves:YES];
94
95 qCDebug(lcQpaKeys) << "Interpreting key event for focus object" << focusObject;
97 [self interpretKeyEvents:@[nsevent]];
99 didInterpretKeyEvent = true;
100
101 // If the last key we sent was dead, then pass the next
102 // key to the IM as well to complete composition.
103 m_lastKeyDead = isDeadKey;
104 }
105
106 }
107 }
108 }
109
110 bool accepted = true;
112 // Trust text input system on whether to send the event with text or not,
113 // or otherwise apply heuristics to filter out private use symbols.
114 if (didInterpretKeyEvent ? m_sendKeyEventWithoutText : isSpecialKey(keyEvent.text))
115 keyEvent.text = {};
116 qCDebug(lcQpaKeys) << "Sending as" << keyEvent;
117 accepted = keyEvent.sendWindowSystemEvent(window);
118 }
119 return accepted;
120}
121
122- (void)keyDown:(NSEvent *)nsevent
123{
124 if ([self isTransparentForUserInput])
125 return [super keyDown:nsevent];
126
127 const bool accepted = [self handleKeyEvent:nsevent];
128
129 // When Qt is used to implement a plugin for a native application we
130 // want to propagate unhandled events to other native views. However,
131 // Qt does not always set the accepted state correctly (in particular
132 // for return key events), so do this for plugin applications only
133 // to prevent incorrect forwarding in the general case.
134 const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted;
135
136 // Track keyDown acceptance/forward state for later acceptance of the keyUp.
137 if (!shouldPropagate)
138 m_acceptedKeyDowns.insert(nsevent.keyCode);
139
140 if (shouldPropagate)
141 [super keyDown:nsevent];
142}
143
144- (void)keyUp:(NSEvent *)nsevent
145{
146 if ([self isTransparentForUserInput])
147 return [super keyUp:nsevent];
148
149 const bool keyUpAccepted = [self handleKeyEvent:nsevent];
150
151 // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was
152 // accepted. Qt text controls will often not use and ignore keyUp events, but we
153 // want to avoid propagating unmatched keyUps.
154 const bool keyDownAccepted = m_acceptedKeyDowns.remove(nsevent.keyCode);
155 if (!keyUpAccepted && !keyDownAccepted)
156 [super keyUp:nsevent];
157}
158
159- (void)cancelOperation:(id)sender
160{
161 Q_UNUSED(sender);
162
163 NSEvent *currentEvent = NSApp.currentEvent;
164 if (!currentEvent || currentEvent.type != NSEventTypeKeyDown)
165 return;
166
167 // Handling the key event may recurse back here through interpretKeyEvents
168 // (when IM is enabled), so we need to guard against that.
169 if (currentEvent == m_currentlyInterpretedKeyEvent) {
170 m_sendKeyEvent = true;
171 return;
172 }
173
174 // Send Command+Key_Period and Escape as normal keypresses so that
175 // the key sequence is delivered through Qt. That way clients can
176 // intercept the shortcut and override its effect.
177 [self handleKeyEvent:currentEvent];
178}
179
180- (void)flagsChanged:(NSEvent *)nsevent
181{
182 // FIXME: Why are we not checking isTransparentForUserInput here?
183
184 KeyEvent keyEvent(nsevent);
185 qCDebug(lcQpaKeys) << "Flags changed resulting in" << keyEvent.modifiers;
186
187 // Calculate the delta and remember the current modifiers for next time
188 static NSEventModifierFlags m_lastKnownModifiers;
189 NSEventModifierFlags lastKnownModifiers = m_lastKnownModifiers;
190 NSEventModifierFlags newModifiers = lastKnownModifiers ^ keyEvent.nativeModifiers;
191 m_lastKnownModifiers = keyEvent.nativeModifiers;
192
193 static constexpr std::tuple<NSEventModifierFlags, Qt::Key> modifierMap[] = {
194 { NSEventModifierFlagShift, Qt::Key_Shift },
195 { NSEventModifierFlagControl, Qt::Key_Meta },
196 { NSEventModifierFlagCommand, Qt::Key_Control },
197 { NSEventModifierFlagOption, Qt::Key_Alt },
198 { NSEventModifierFlagCapsLock, Qt::Key_CapsLock }
199 };
200
201 for (auto [macModifier, qtKey] : modifierMap) {
202 if (!(newModifiers & macModifier))
203 continue;
204
205 // FIXME: Use QAppleKeyMapper helper
206 if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) {
207 if (qtKey == Qt::Key_Meta)
209 else if (qtKey == Qt::Key_Control)
211 }
212
213 KeyEvent modifierEvent = keyEvent;
214 modifierEvent.type = lastKnownModifiers & macModifier
216
217 modifierEvent.key = qtKey;
218
219 // FIXME: Shouldn't this be based on lastKnownModifiers?
220 modifierEvent.modifiers ^= QAppleKeyMapper::fromCocoaModifiers(macModifier);
221 modifierEvent.nativeModifiers ^= macModifier;
222
223 // FIXME: Why are we sending to m_platformWindow here, but not for key events?
224 QWindow *window = m_platformWindow->window();
225
226 qCDebug(lcQpaKeys) << "Sending" << modifierEvent;
227 modifierEvent.sendWindowSystemEvent(window);
228 }
229}
230
231@end
232
233// -------------------------------------------------------------------------
234
235KeyEvent::KeyEvent(NSEvent *nsevent)
236{
237 timestamp = nsevent.timestamp * 1000;
238 nativeModifiers = nsevent.modifierFlags;
239 modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers);
240
241 switch (nsevent.type) {
242 case NSEventTypeKeyDown: type = QEvent::KeyPress; break;
243 case NSEventTypeKeyUp: type = QEvent::KeyRelease; break;
244 default: break; // Must be manually set
245 }
246
247 switch (nsevent.type) {
248 case NSEventTypeKeyDown:
249 case NSEventTypeKeyUp:
250 case NSEventTypeFlagsChanged:
251 nativeVirtualKey = nsevent.keyCode;
252 default:
253 break;
254 }
255
256 if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) {
257 NSString *charactersIgnoringModifiers = nsevent.charactersIgnoringModifiers;
258 NSString *characters = nsevent.characters;
259
261
262 // If a dead key occurs as a result of pressing a key combination then
263 // characters will have 0 length, but charactersIgnoringModifiers will
264 // have a valid character in it. This enables key combinations such as
265 // ALT+E to be used as a shortcut with an English keyboard even though
266 // pressing ALT+E will give a dead key while doing normal text input.
267 if (characters.length || charactersIgnoringModifiers.length) {
268 if (nativeModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)
269 && charactersIgnoringModifiers.length)
270 character = QChar([charactersIgnoringModifiers characterAtIndex:0]);
271 else if (characters.length)
272 character = QChar([characters characterAtIndex:0]);
273 key = QAppleKeyMapper::fromCocoaKey(character);
274 }
275
276 text = QString::fromNSString(characters);
277
278 isRepeat = nsevent.ARepeat;
279 }
280}
281
282bool KeyEvent::sendWindowSystemEvent(QWindow *window) const
283{
284 switch (type) {
285 case QEvent::Shortcut: {
287 key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
288 text, isRepeat);
289 }
290 case QEvent::KeyPress:
291 case QEvent::KeyRelease: {
292 static const int count = 1;
294 type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
295 text, isRepeat, count);
296 // FIXME: Make handleExtendedKeyEvent synchronous
298 }
299 default:
300 qCritical() << "KeyEvent can not send event type" << type;
301 return false;
302 }
303}
304
306{
307 QDebugStateSaver saver(debug);
308 debug.nospace().verbosity(0) << "KeyEvent("
309 << e.type << ", timestamp=" << e.timestamp
310 << ", key=" << e.key << ", modifiers=" << e.modifiers
311 << ", text="<< e.text << ", isRepeat=" << e.isRepeat
312 << ", nativeVirtualKey=" << e.nativeVirtualKey
313 << ", nativeModifiers=" << e.nativeModifiers
314 << ")";
315 return debug;
316}
\inmodule QtCore
Definition qchar.h:48
@ ReplacementCharacter
Definition qchar.h:59
constexpr char16_t unicode() const noexcept
Returns the numeric Unicode value of the QChar.
Definition qchar.h:458
static bool testAttribute(Qt::ApplicationAttribute attribute)
Returns true if attribute attribute is set; otherwise returns false.
\inmodule QtCore
\inmodule QtCore
@ KeyRelease
Definition qcoreevent.h:65
@ KeyPress
Definition qcoreevent.h:64
\inmodule QtCore
Definition qobject.h:90
bool remove(const T &value)
Definition qset.h:63
iterator insert(const T &value)
Definition qset.h:155
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1079
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
qsizetype length() const
Returns the number of characters in this string.
Definition qstring.h:187
static bool flushWindowSystemEvents(QEventLoop::ProcessEventsFlags flags=QEventLoop::AllEvents)
Make Qt Gui process all events on the event queue immediately.
static bool handleExtendedKeyEvent(QWindow *window, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, const QString &text=QString(), bool autorep=false, ushort count=1)
static bool handleShortcutEvent(QWindow *window, ulong timestamp, int k, Qt::KeyboardModifiers mods, quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, const QString &text=QString(), bool autorep=false, ushort count=1)
\inmodule QtGui
Definition qwindow.h:63
EGLImageKHR int int EGLuint64KHR * modifiers
QString text
double e
@ ImHints
@ ImhFormattedNumbersOnly
@ ImhDigitsOnly
@ ImhHiddenText
@ Key_Shift
Definition qnamespace.h:678
@ Key_Control
Definition qnamespace.h:679
@ Key_Alt
Definition qnamespace.h:681
@ Key_Meta
Definition qnamespace.h:680
@ Key_CapsLock
Definition qnamespace.h:682
@ AA_MacDontSwapCtrlAndMeta
Definition qnamespace.h:431
@ AA_PluginApplication
Definition qnamespace.h:429
InputMethodQueryResult queryInputMethod(QObject *object, Qt::InputMethodQueries queries)
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
#define qCritical
Definition qlogging.h:163
#define qCDebug(category,...)
bool m_sendKeyEvent
Definition qnsview.mm:111
NSEvent * m_currentlyInterpretedKeyEvent
Definition qnsview.mm:113
bool m_lastKeyDead
Definition qnsview.mm:110
QSet< quint32 > m_acceptedKeyDowns
Definition qnsview.mm:114
bool m_sendKeyEventWithoutText
Definition qnsview.mm:112
QString m_composingText
Definition qnsview.mm:117
static bool isSpecialKey(const QString &text)
GLuint64 key
GLenum GLenum GLsizei count
GLenum type
static QString qtKey(CFStringRef cfkey)
#define Q_UNUSED(x)
QList< QChar > characters
QDataStream & operator<<(QDataStream &out, const MyClass &myObj)
[4]
aWidget window() -> setWindowTitle("New Window Title")
[2]
EventType type
Definition qwasmevent.h:129
Qt::Key key
Definition qwasmevent.h:145
KeyEvent(EventType type, emscripten::val webEvent)
QFlags< Qt::KeyboardModifier > modifiers
Definition qwasmevent.h:146