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:
parent
2945c6223b
commit
f563203f60
@ -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, const NSRange &);
|
||||||
QDebug operator<<(QDebug, SEL);
|
QDebug operator<<(QDebug, SEL);
|
||||||
|
|
||||||
|
@ -41,45 +41,10 @@
|
|||||||
|
|
||||||
@implementation QNSView (Keys)
|
@implementation QNSView (Keys)
|
||||||
|
|
||||||
- (bool)handleKeyEvent:(NSEvent *)nsevent eventType:(QEvent::Type)eventType
|
- (bool)handleKeyEvent:(NSEvent *)nsevent
|
||||||
{
|
{
|
||||||
ulong timestamp = nsevent.timestamp * 1000;
|
qCDebug(lcQpaKeys) << "Handling" << nsevent;
|
||||||
|
KeyEvent keyEvent(nsevent);
|
||||||
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);
|
|
||||||
|
|
||||||
// FIXME: Why is this the top level window and not m_platformWindow?
|
// FIXME: Why is this the top level window and not m_platformWindow?
|
||||||
QWindow *window = [self topLevelWindow];
|
QWindow *window = [self topLevelWindow];
|
||||||
@ -90,19 +55,19 @@
|
|||||||
window = popup->window();
|
window = popup->window();
|
||||||
}
|
}
|
||||||
|
|
||||||
qCDebug(lcQpaKeys) << "Handling" << nsevent << "as" << qtKey
|
|
||||||
<< "with" << modifiers << "and resulting text" << text;
|
|
||||||
|
|
||||||
QBoolBlocker resendKeyEventGuard(m_resendKeyEvent, false);
|
QBoolBlocker resendKeyEventGuard(m_resendKeyEvent, false);
|
||||||
// We will send a key event unless the input method sets m_sendKeyEvent to false
|
// We will send a key event unless the input method sets m_sendKeyEvent to false
|
||||||
QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true);
|
QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true);
|
||||||
|
|
||||||
if (eventType == QEvent::KeyPress) {
|
if (keyEvent.type == QEvent::KeyPress) {
|
||||||
|
|
||||||
if (m_composingText.isEmpty()) {
|
if (m_composingText.isEmpty()) {
|
||||||
qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window;
|
KeyEvent shortcutEvent = keyEvent;
|
||||||
if (QWindowSystemInterface::handleShortcutEvent(window, timestamp, qtKey, modifiers,
|
shortcutEvent.type = QEvent::Shortcut;
|
||||||
nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1)) {
|
qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
|
||||||
|
<< "for" << shortcutEvent;
|
||||||
|
|
||||||
|
if (shortcutEvent.sendWindowSystemEvent(window)) {
|
||||||
qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
|
qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -150,10 +115,9 @@
|
|||||||
|
|
||||||
bool accepted = true;
|
bool accepted = true;
|
||||||
if (m_sendKeyEvent && m_composingText.isEmpty()) {
|
if (m_sendKeyEvent && m_composingText.isEmpty()) {
|
||||||
qCDebug(lcQpaKeys) << "Sending as regular key event";
|
KeyEvent keyEvent(nsevent);
|
||||||
QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), qtKey, modifiers,
|
qCDebug(lcQpaKeys) << "Sending as" << keyEvent;
|
||||||
nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false);
|
accepted = keyEvent.sendWindowSystemEvent(window);
|
||||||
accepted = QWindowSystemInterface::flushWindowSystemEvents();
|
|
||||||
}
|
}
|
||||||
return accepted;
|
return accepted;
|
||||||
}
|
}
|
||||||
@ -163,7 +127,7 @@
|
|||||||
if ([self isTransparentForUserInput])
|
if ([self isTransparentForUserInput])
|
||||||
return [super keyDown:nsevent];
|
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
|
// When Qt is used to implement a plugin for a native application we
|
||||||
// want to propagate unhandled events to other native views. However,
|
// want to propagate unhandled events to other native views. However,
|
||||||
@ -185,7 +149,7 @@
|
|||||||
if ([self isTransparentForUserInput])
|
if ([self isTransparentForUserInput])
|
||||||
return [super keyUp:nsevent];
|
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
|
// 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
|
// 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
|
// Send Command+Key_Period and Escape as normal keypresses so that
|
||||||
// the key sequence is delivered through Qt. That way clients can
|
// the key sequence is delivered through Qt. That way clients can
|
||||||
// intercept the shortcut and override its effect.
|
// intercept the shortcut and override its effect.
|
||||||
[self handleKeyEvent:currentEvent eventType:QEvent::KeyPress];
|
[self handleKeyEvent:currentEvent];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)flagsChanged:(NSEvent *)nsevent
|
- (void)flagsChanged:(NSEvent *)nsevent
|
||||||
{
|
{
|
||||||
// FIXME: Why are we not checking isTransparentForUserInput here?
|
// FIXME: Why are we not checking isTransparentForUserInput here?
|
||||||
|
|
||||||
ulong timestamp = nsevent.timestamp * 1000;
|
KeyEvent keyEvent(nsevent);
|
||||||
NSEventModifierFlags nativeModifiers = nsevent.modifierFlags;
|
qCDebug(lcQpaKeys) << "Flags changed resulting in" << keyEvent.modifiers;
|
||||||
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;
|
|
||||||
|
|
||||||
// Calculate the delta and remember the current modifiers for next time
|
// Calculate the delta and remember the current modifiers for next time
|
||||||
static NSEventModifierFlags m_lastKnownModifiers;
|
static NSEventModifierFlags m_lastKnownModifiers;
|
||||||
NSEventModifierFlags lastKnownModifiers = m_lastKnownModifiers;
|
NSEventModifierFlags lastKnownModifiers = m_lastKnownModifiers;
|
||||||
NSEventModifierFlags newModifiers = lastKnownModifiers ^ nativeModifiers;
|
NSEventModifierFlags newModifiers = lastKnownModifiers ^ keyEvent.nativeModifiers;
|
||||||
m_lastKnownModifiers = nativeModifiers;
|
m_lastKnownModifiers = keyEvent.nativeModifiers;
|
||||||
|
|
||||||
static constexpr std::tuple<NSEventModifierFlags, Qt::Key> modifierMap[] = {
|
static constexpr std::tuple<NSEventModifierFlags, Qt::Key> modifierMap[] = {
|
||||||
{ NSEventModifierFlagShift, Qt::Key_Shift },
|
{ NSEventModifierFlagShift, Qt::Key_Shift },
|
||||||
@ -258,16 +211,106 @@
|
|||||||
qtKey = Qt::Key_Meta;
|
qtKey = Qt::Key_Meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto eventType = lastKnownModifiers & macModifier
|
KeyEvent modifierEvent = keyEvent;
|
||||||
? QEvent::KeyRelease : QEvent::KeyPress;
|
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?
|
// FIXME: Why are we sending to m_platformWindow here, but not for key events?
|
||||||
QWindow *window = m_platformWindow->window();
|
QWindow *window = m_platformWindow->window();
|
||||||
|
|
||||||
QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, eventType,
|
qCDebug(lcQpaKeys) << "Sending" << modifierEvent;
|
||||||
qtKey, modifiers ^ QAppleKeyMapper::fromCocoaModifiers(macModifier),
|
modifierEvent.sendWindowSystemEvent(window);
|
||||||
nativeScanCode, nativeVirtualKey, nativeModifiers ^ macModifier);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@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;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user