Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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
33static bool sendAsShortcut(const KeyEvent &keyEvent, QWindow *window)
34{
35 KeyEvent shortcutEvent = keyEvent;
36 shortcutEvent.type = QEvent::Shortcut;
37 qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
38 << "for" << shortcutEvent;
39
40 if (shortcutEvent.sendWindowSystemEvent(window)) {
41 qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
42 return true;
43 }
44 qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery";
45 return false;
46}
47
48@implementation QNSView (Keys)
49
50- (bool)performKeyEquivalent:(NSEvent *)nsevent
51{
52 // Implemented to handle shortcuts for modified Tab keys, which are
53 // handled by Cocoa and not delivered to your keyDown implementation.
54 if (nsevent.type == NSEventTypeKeyDown && m_composingText.isEmpty()) {
55 const bool ctrlDown = [nsevent modifierFlags] & NSEventModifierFlagControl;
56 const bool isTabKey = nsevent.keyCode == kVK_Tab;
57 if (ctrlDown && isTabKey && sendAsShortcut(KeyEvent(nsevent), [self topLevelWindow]))
58 return YES;
59 }
60 return NO;
61}
62
63- (bool)handleKeyEvent:(NSEvent *)nsevent
64{
65 qCDebug(lcQpaKeys) << "Handling" << nsevent;
66 KeyEvent keyEvent(nsevent);
67
68 // FIXME: Why is this the top level window and not m_platformWindow?
69 QWindow *window = [self topLevelWindow];
70
71 // We will send a key event unless the input method handles it
72 QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true);
73
74 // Assume we should send key events with text, unless told
75 // otherwise by doCommandBySelector.
77
78 bool didInterpretKeyEvent = false;
79
80 if (keyEvent.type == QEvent::KeyPress) {
81
83 if (sendAsShortcut(keyEvent, window))
84 return true;
85 }
86
87 QObject *focusObject = m_platformWindow ? m_platformWindow->window()->focusObject() : nullptr;
88 if (m_sendKeyEvent && focusObject) {
89 if (auto queryResult = queryInputMethod(focusObject, Qt::ImHints)) {
90 auto hints = static_cast<Qt::InputMethodHints>(queryResult.value(Qt::ImHints).toUInt());
91
92 // Make sure we send dead keys and the next key to the input method for composition
93 const bool isDeadKey = !nsevent.characters.length;
94 const bool ignoreHidden = (hints & Qt::ImhHiddenText) && !isDeadKey && !m_lastKeyDead;
95
96 if (!(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || ignoreHidden)) {
97 // Pass the key event to the input method, and assume it handles the event,
98 // unless we explicit set m_sendKeyEvent to deliver as a normal key event.
99 m_sendKeyEvent = false;
100
101 // Match NSTextView's keyDown behavior of hiding the cursor before
102 // interpreting key events. Shortcuts should not trigger this though.
103 // Unfortunately many of our controls handle shortcuts by accepting
104 // the ShortcutOverride event and then handling the shortcut in the
105 // following key event, and QWSI::handleShortcutEvent doesn't reveal
106 // whether this will be the case. For NSTextView this is not an issue
107 // as shortcuts are handled via performKeyEquivalent, which happens
108 // prior to keyDown. To work around this until we can get the info
109 // we need from handleShortcutEvent we match AppKit and assume that
110 // any key press with a command or control modifier is a shortcut.
111 if (!(nsevent.modifierFlags & (NSEventModifierFlagCommand | NSEventModifierFlagControl)))
112 [NSCursor setHiddenUntilMouseMoves:YES];
113
114 qCDebug(lcQpaKeys) << "Interpreting key event for focus object" << focusObject;
116 if (![self.inputContext handleEvent:nsevent]) {
117 qCDebug(lcQpaKeys) << "Input context did not consume event";
118 m_sendKeyEvent = true;
119 }
121 didInterpretKeyEvent = true;
122
123 // If the last key we sent was dead, then pass the next
124 // key to the IM as well to complete composition.
125 m_lastKeyDead = isDeadKey;
126 }
127
128 }
129 }
130 }
131
132 bool accepted = true;
134 // Trust text input system on whether to send the event with text or not,
135 // or otherwise apply heuristics to filter out private use symbols.
136 if (didInterpretKeyEvent ? m_sendKeyEventWithoutText : isSpecialKey(keyEvent.text))
137 keyEvent.text = {};
138 qCDebug(lcQpaKeys) << "Sending as" << keyEvent;
139 accepted = keyEvent.sendWindowSystemEvent(window);
140 }
141 return accepted;
142}
143
144- (void)keyDown:(NSEvent *)nsevent
145{
146 if ([self isTransparentForUserInput])
147 return [super keyDown:nsevent];
148
149 const bool accepted = [self handleKeyEvent:nsevent];
150
151 // When Qt is used to implement a plugin for a native application we
152 // want to propagate unhandled events to other native views. However,
153 // Qt does not always set the accepted state correctly (in particular
154 // for return key events), so do this for plugin applications only
155 // to prevent incorrect forwarding in the general case.
156 const bool shouldPropagate = QCoreApplication::testAttribute(Qt::AA_PluginApplication) && !accepted;
157
158 // Track keyDown acceptance/forward state for later acceptance of the keyUp.
159 if (!shouldPropagate)
160 m_acceptedKeyDowns.insert(nsevent.keyCode);
161
162 if (shouldPropagate)
163 [super keyDown:nsevent];
164}
165
166- (void)keyUp:(NSEvent *)nsevent
167{
168 if ([self isTransparentForUserInput])
169 return [super keyUp:nsevent];
170
171 const bool keyUpAccepted = [self handleKeyEvent:nsevent];
172
173 // Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was
174 // accepted. Qt text controls will often not use and ignore keyUp events, but we
175 // want to avoid propagating unmatched keyUps.
176 const bool keyDownAccepted = m_acceptedKeyDowns.remove(nsevent.keyCode);
177 if (!keyUpAccepted && !keyDownAccepted)
178 [super keyUp:nsevent];
179}
180
181- (void)cancelOperation:(id)sender
182{
183 Q_UNUSED(sender);
184
185 NSEvent *currentEvent = NSApp.currentEvent;
186 if (!currentEvent || currentEvent.type != NSEventTypeKeyDown)
187 return;
188
189 // Handling the key event may recurse back here through interpretKeyEvents
190 // (when IM is enabled), so we need to guard against that.
191 if (currentEvent == m_currentlyInterpretedKeyEvent) {
192 m_sendKeyEvent = true;
193 return;
194 }
195
196 // Send Command+Key_Period and Escape as normal keypresses so that
197 // the key sequence is delivered through Qt. That way clients can
198 // intercept the shortcut and override its effect.
199 [self handleKeyEvent:currentEvent];
200}
201
202- (void)flagsChanged:(NSEvent *)nsevent
203{
204 // FIXME: Why are we not checking isTransparentForUserInput here?
205
206 KeyEvent keyEvent(nsevent);
207 qCDebug(lcQpaKeys) << "Flags changed resulting in" << keyEvent.modifiers;
208
209 // Calculate the delta and remember the current modifiers for next time
210 static NSEventModifierFlags m_lastKnownModifiers;
211 NSEventModifierFlags lastKnownModifiers = m_lastKnownModifiers;
212 NSEventModifierFlags newModifiers = lastKnownModifiers ^ keyEvent.nativeModifiers;
213 m_lastKnownModifiers = keyEvent.nativeModifiers;
214
215 static constexpr std::tuple<NSEventModifierFlags, Qt::Key> modifierMap[] = {
216 { NSEventModifierFlagShift, Qt::Key_Shift },
217 { NSEventModifierFlagControl, Qt::Key_Meta },
218 { NSEventModifierFlagCommand, Qt::Key_Control },
219 { NSEventModifierFlagOption, Qt::Key_Alt },
220 { NSEventModifierFlagCapsLock, Qt::Key_CapsLock }
221 };
222
223 for (auto [macModifier, qtKey] : modifierMap) {
224 if (!(newModifiers & macModifier))
225 continue;
226
227 // FIXME: Use QAppleKeyMapper helper
228 if (qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta)) {
229 if (qtKey == Qt::Key_Meta)
231 else if (qtKey == Qt::Key_Control)
233 }
234
235 KeyEvent modifierEvent = keyEvent;
236 modifierEvent.type = lastKnownModifiers & macModifier
238
239 modifierEvent.key = qtKey;
240
241 // FIXME: Shouldn't this be based on lastKnownModifiers?
242 modifierEvent.modifiers ^= QAppleKeyMapper::fromCocoaModifiers(macModifier);
243 modifierEvent.nativeModifiers ^= macModifier;
244
245 // FIXME: Why are we sending to m_platformWindow here, but not for key events?
246 QWindow *window = m_platformWindow->window();
247
248 qCDebug(lcQpaKeys) << "Sending" << modifierEvent;
249 modifierEvent.sendWindowSystemEvent(window);
250 }
251}
252
253@end
254
255// -------------------------------------------------------------------------
256
257KeyEvent::KeyEvent(NSEvent *nsevent)
258{
259 timestamp = nsevent.timestamp * 1000;
260 nativeModifiers = nsevent.modifierFlags;
261 modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers);
262
263 switch (nsevent.type) {
264 case NSEventTypeKeyDown: type = QEvent::KeyPress; break;
265 case NSEventTypeKeyUp: type = QEvent::KeyRelease; break;
266 default: break; // Must be manually set
267 }
268
269 switch (nsevent.type) {
270 case NSEventTypeKeyDown:
271 case NSEventTypeKeyUp:
272 case NSEventTypeFlagsChanged:
273 nativeVirtualKey = nsevent.keyCode;
274 default:
275 break;
276 }
277
278 if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) {
279 NSString *charactersIgnoringModifiers = nsevent.charactersIgnoringModifiers;
280 NSString *characters = nsevent.characters;
281
282 QChar character = QChar::ReplacementCharacter;
283
284 // If a dead key occurs as a result of pressing a key combination then
285 // characters will have 0 length, but charactersIgnoringModifiers will
286 // have a valid character in it. This enables key combinations such as
287 // ALT+E to be used as a shortcut with an English keyboard even though
288 // pressing ALT+E will give a dead key while doing normal text input.
289 if (characters.length || charactersIgnoringModifiers.length) {
290 if (nativeModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)
291 && charactersIgnoringModifiers.length)
292 character = QChar([charactersIgnoringModifiers characterAtIndex:0]);
293 else if (characters.length)
294 character = QChar([characters characterAtIndex:0]);
295 key = QAppleKeyMapper::fromCocoaKey(character);
296 }
297
298 text = QString::fromNSString(characters);
299
300 isRepeat = nsevent.ARepeat;
301 }
302}
303
304bool KeyEvent::sendWindowSystemEvent(QWindow *window) const
305{
306 switch (type) {
307 case QEvent::Shortcut: {
309 key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
310 text, isRepeat);
311 }
312 case QEvent::KeyPress:
313 case QEvent::KeyRelease: {
314 static const int count = 1;
316 type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
317 text, isRepeat, count);
318 // FIXME: Make handleExtendedKeyEvent synchronous
320 }
321 default:
322 qCritical() << "KeyEvent can not send event type" << type;
323 return false;
324 }
325}
326
328{
329 QDebugStateSaver saver(debug);
330 debug.nospace().verbosity(0) << "KeyEvent("
331 << e.type << ", timestamp=" << e.timestamp
332 << ", key=" << e.key << ", modifiers=" << e.modifiers
333 << ", text="<< e.text << ", isRepeat=" << e.isRepeat
334 << ", nativeVirtualKey=" << e.nativeVirtualKey
335 << ", nativeModifiers=" << e.nativeModifiers
336 << ")";
337 return debug;
338}
\inmodule QtCore
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:103
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:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
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
@ ImHints
@ ImhFormattedNumbersOnly
@ ImhDigitsOnly
@ ImhHiddenText
@ Key_Shift
Definition qnamespace.h:683
@ Key_Control
Definition qnamespace.h:684
@ Key_Alt
Definition qnamespace.h:686
@ Key_Meta
Definition qnamespace.h:685
@ Key_CapsLock
Definition qnamespace.h:687
@ AA_MacDontSwapCtrlAndMeta
Definition qnamespace.h:432
@ AA_PluginApplication
Definition qnamespace.h:430
QString self
Definition language.cpp:58
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:167
#define qCDebug(category,...)
bool m_sendKeyEvent
Definition qnsview.mm:113
NSEvent * m_currentlyInterpretedKeyEvent
Definition qnsview.mm:115
bool m_lastKeyDead
Definition qnsview.mm:112
QSet< quint32 > m_acceptedKeyDowns
Definition qnsview.mm:116
bool m_sendKeyEventWithoutText
Definition qnsview.mm:114
QString m_composingText
Definition qnsview.mm:119
static bool sendAsShortcut(const KeyEvent &keyEvent, QWindow *window)
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:136
QString text
Definition qwasmevent.h:155
Qt::Key key
Definition qwasmevent.h:152
KeyEvent(EventType type, emscripten::val webEvent)
QFlags< Qt::KeyboardModifier > modifiers
Definition qwasmevent.h:153