macOS: Factor out key event handling into helper struct

This allows us to share code between handleKeyEvent: and flagsChanged:
for parsing the incoming NSEvent, and allows for sending key events
from other call sites in the future without duplicating the parsing
and sending logic.

Pick-to: 6.2
Change-Id: Ic63f740523496a9432e439663a20f78b5bc234c5
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Tor Arne Vestbø 2021-08-26 15:24:42 +02:00
parent 2945c6223b
commit f563203f60
2 changed files with 140 additions and 72 deletions

View File

@ -371,6 +371,31 @@ InputMethodQueryResult queryInputMethod(QObject *object, Qt::InputMethodQueries
// -------------------------------------------------------------------------
struct KeyEvent
{
ulong timestamp = 0;
QEvent::Type type = QEvent::None;
Qt::Key key = Qt::Key_unknown;
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
QString text;
bool isRepeat = false;
// Scan codes are hardware dependent codes for each key. There is no way to get these
// from Carbon or Cocoa, so leave it 0, as documented in QKeyEvent::nativeScanCode().
static const quint32 nativeScanCode = 0;
quint32 nativeVirtualKey = 0;
NSEventModifierFlags nativeModifiers = 0;
KeyEvent(NSEvent *nsevent);
bool sendWindowSystemEvent(QWindow *window) const;
};
QDebug operator<<(QDebug debug, const KeyEvent &e);
// -------------------------------------------------------------------------
QDebug operator<<(QDebug, const NSRange &);
QDebug operator<<(QDebug, SEL);

View File

@ -41,45 +41,10 @@
@implementation QNSView (Keys)
- (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(QEvent::Type)eventType
- (bool)handleKeyEvent:(NSEvent *)nsevent
{
ulong timestamp = nsevent.timestamp * 1000;
NSEventModifierFlags nativeModifiers = nsevent.modifierFlags;
Qt::KeyboardModifiers modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers);
NSString *charactersIgnoringModifiers = nsevent.charactersIgnoringModifiers;
NSString *characters = nsevent.characters;
// Scan codes are hardware dependent codes for each key. There is no way to get these
// from Carbon or Cocoa, so leave it 0, as documented in QKeyEvent::nativeScanCode().
const quint32 nativeScanCode = 0;
// Virtual keys on the other hand are mapped to be the same keys on any system
const quint32 nativeVirtualKey = nsevent.keyCode;
QChar ch = QChar::ReplacementCharacter;
Qt::Key qtKey = Qt::Key_unknown;
// If a dead key occurs as a result of pressing a key combination then
// characters will have 0 length, but charactersIgnoringModifiers will
// have a valid character in it. This enables key combinations such as
// ALT+E to be used as a shortcut with an English keyboard even though
// pressing ALT+E will give a dead key while doing normal text input.
if (characters.length || charactersIgnoringModifiers.length) {
if (nativeModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)
&& charactersIgnoringModifiers.length)
ch = QChar([charactersIgnoringModifiers characterAtIndex:0]);
else if (characters.length)
ch = QChar([characters characterAtIndex:0]);
qtKey = QAppleKeyMapper::fromCocoaKey(ch);
}
QString text;
// ignore text for the U+F700-U+F8FF range. This is used by Cocoa when
// delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.)
if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff))
text = QString::fromNSString(characters);
qCDebug(lcQpaKeys) << "Handling" << nsevent;
KeyEvent keyEvent(nsevent);
// FIXME: Why is this the top level window and not m_platformWindow?
QWindow *window = [self topLevelWindow];
@ -90,19 +55,19 @@
window = popup->window();
}
qCDebug(lcQpaKeys) << "Handling" << nsevent << "as" << qtKey
<< "with" << modifiers << "and resulting text" << text;
QBoolBlocker resendKeyEventGuard(m_resendKeyEvent, false);
// We will send a key event unless the input method sets m_sendKeyEvent to false
QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true);
if (eventType == QEvent::KeyPress) {
if (keyEvent.type == QEvent::KeyPress) {
if (m_composingText.isEmpty()) {
qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window;
if (QWindowSystemInterface::handleShortcutEvent(window, timestamp, qtKey, modifiers,
nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1)) {
KeyEvent shortcutEvent = keyEvent;
shortcutEvent.type = QEvent::Shortcut;
qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
<< "for" << shortcutEvent;
if (shortcutEvent.sendWindowSystemEvent(window)) {
qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
return true;
} else {
@ -150,10 +115,9 @@
bool accepted = true;
if (m_sendKeyEvent && m_composingText.isEmpty()) {
qCDebug(lcQpaKeys) << "Sending as regular key event";
QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), qtKey, modifiers,
nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false);
accepted = QWindowSystemInterface::flushWindowSystemEvents();
KeyEvent keyEvent(nsevent);
qCDebug(lcQpaKeys) << "Sending as" << keyEvent;
accepted = keyEvent.sendWindowSystemEvent(window);
}
return accepted;
}
@ -163,7 +127,7 @@
if ([self isTransparentForUserInput])
return [super keyDown:nsevent];
const bool accepted = [self handleKeyEvent:nsevent eventType:QEvent::KeyPress];
const bool accepted = [self handleKeyEvent:nsevent];
// When Qt is used to implement a plugin for a native application we
// want to propagate unhandled events to other native views. However,
@ -185,7 +149,7 @@
if ([self isTransparentForUserInput])
return [super keyUp:nsevent];
const bool keyUpAccepted = [self handleKeyEvent:nsevent eventType:QEvent::KeyRelease];
const bool keyUpAccepted = [self handleKeyEvent:nsevent];
// Propagate the keyUp if neither Qt accepted it nor the corresponding KeyDown was
// accepted. Qt text controls wil often not use and ignore keyUp events, but we
@ -211,32 +175,21 @@
// Send Command+Key_Period and Escape as normal keypresses so that
// the key sequence is delivered through Qt. That way clients can
// intercept the shortcut and override its effect.
[self handleKeyEvent:currentEvent eventType:QEvent::KeyPress];
[self handleKeyEvent:currentEvent];
}
- (void)flagsChanged:(NSEvent *)nsevent
{
// FIXME: Why are we not checking isTransparentForUserInput here?
ulong timestamp = nsevent.timestamp * 1000;
NSEventModifierFlags nativeModifiers = nsevent.modifierFlags;
Qt::KeyboardModifiers modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers);
qCDebug(lcQpaKeys) << "Flags changed with" << nsevent
<< "resulting in" << modifiers;
// Scan codes are hardware dependent codes for each key. There is no way to get these
// from Carbon or Cocoa, so leave it 0, as documented in QKeyEvent::nativeScanCode().
const quint32 nativeScanCode = 0;
// Virtual keys on the other hand are mapped to be the same keys on any system
const quint32 nativeVirtualKey = nsevent.keyCode;
KeyEvent keyEvent(nsevent);
qCDebug(lcQpaKeys) << "Flags changed resulting in" << keyEvent.modifiers;
// Calculate the delta and remember the current modifiers for next time
static NSEventModifierFlags m_lastKnownModifiers;
NSEventModifierFlags lastKnownModifiers = m_lastKnownModifiers;
NSEventModifierFlags newModifiers = lastKnownModifiers ^ nativeModifiers;
m_lastKnownModifiers = nativeModifiers;
NSEventModifierFlags newModifiers = lastKnownModifiers ^ keyEvent.nativeModifiers;
m_lastKnownModifiers = keyEvent.nativeModifiers;
static constexpr std::tuple<NSEventModifierFlags, Qt::Key> modifierMap[] = {
{ NSEventModifierFlagShift, Qt::Key_Shift },
@ -258,16 +211,106 @@
qtKey = Qt::Key_Meta;
}
auto eventType = lastKnownModifiers & macModifier
? QEvent::KeyRelease : QEvent::KeyPress;
KeyEvent modifierEvent = keyEvent;
modifierEvent.type = lastKnownModifiers & macModifier
? QEvent::KeyRelease : QEvent::KeyPress;
modifierEvent.key = qtKey;
// FIXME: Shouldn't this be based on lastKnownModifiers?
modifierEvent.modifiers ^= QAppleKeyMapper::fromCocoaModifiers(macModifier);
modifierEvent.nativeModifiers ^= macModifier;
// FIXME: Why are we sending to m_platformWindow here, but not for key events?
QWindow *window = m_platformWindow->window();
QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, eventType,
qtKey, modifiers ^ QAppleKeyMapper::fromCocoaModifiers(macModifier),
nativeScanCode, nativeVirtualKey, nativeModifiers ^ macModifier);
qCDebug(lcQpaKeys) << "Sending" << modifierEvent;
modifierEvent.sendWindowSystemEvent(window);
}
}
@end
// -------------------------------------------------------------------------
KeyEvent::KeyEvent(NSEvent *nsevent)
{
timestamp = nsevent.timestamp * 1000;
switch (nsevent.type) {
case NSEventTypeKeyDown: type = QEvent::KeyPress; break;
case NSEventTypeKeyUp: type = QEvent::KeyRelease; break;
default: break; // Must be manually set
}
if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) {
NSString *charactersIgnoringModifiers = nsevent.charactersIgnoringModifiers;
NSString *characters = nsevent.characters;
QChar character = QChar::ReplacementCharacter;
// If a dead key occurs as a result of pressing a key combination then
// characters will have 0 length, but charactersIgnoringModifiers will
// have a valid character in it. This enables key combinations such as
// ALT+E to be used as a shortcut with an English keyboard even though
// pressing ALT+E will give a dead key while doing normal text input.
if (characters.length || charactersIgnoringModifiers.length) {
if (nativeModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption)
&& charactersIgnoringModifiers.length)
character = QChar([charactersIgnoringModifiers characterAtIndex:0]);
else if (characters.length)
character = QChar([characters characterAtIndex:0]);
key = QAppleKeyMapper::fromCocoaKey(character);
}
// Ignore text for the U+F700-U+F8FF range. This is used by Cocoa when
// delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.)
if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier))
&& (character.unicode() < 0xf700 || character.unicode() > 0xf8ff))
text = QString::fromNSString(characters);
isRepeat = nsevent.ARepeat;
}
nativeVirtualKey = nsevent.keyCode;
nativeModifiers = nsevent.modifierFlags;
modifiers = QAppleKeyMapper::fromCocoaModifiers(nativeModifiers);
}
bool KeyEvent::sendWindowSystemEvent(QWindow *window) const
{
switch (type) {
case QEvent::Shortcut: {
return QWindowSystemInterface::handleShortcutEvent(window, timestamp,
key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
text, isRepeat);
}
case QEvent::KeyPress:
case QEvent::KeyRelease: {
static const int count = 1;
static const bool tryShortcutOverride = false;
QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp,
type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
text, isRepeat, count, tryShortcutOverride);
// FIXME: Make handleExtendedKeyEvent synchronous
return QWindowSystemInterface::flushWindowSystemEvents();
}
default:
qCritical() << "KeyEvent can not send event type" << type;
return false;
}
}
QDebug operator<<(QDebug debug, const KeyEvent &e)
{
QDebugStateSaver saver(debug);
debug.nospace().verbosity(0) << "KeyEvent("
<< e.type << ", timestamp=" << e.timestamp
<< ", key=" << e.key << ", modifiers=" << e.modifiers
<< ", text="<< e.text << ", isRepeat=" << e.isRepeat
<< ", nativeVirtualKey=" << e.nativeVirtualKey
<< ", nativeModifiers=" << e.nativeModifiers
<< ")";
return debug;
}