macOS: Assume input method handles key event

When input methods are enabled for the focus object we send key events
through interpretKeyEvents, which will involve the input method in the
key event processing. The input method will get back to us with callbacks
such as insertText, setMarkedText, or doCommandBySelector.

In the case of insertText, when the inserted text matches the originating
key event's text, we opt to not send the text as an QInputMethodEvent,
and instead fall back to sending it as a normal QKeyEvent. The reason
for this is that Qt's IM protocol was designed to handle composited
text, so sending non-composited (but IM-initiated) text input as IM
events is unexpected (see 2d05d3bd28).

However, we cannot assume that the input method will always call us
back with one of the above mentioned methods. The input method can
very well eat the event as part of its own operation. This happens
for example when pressing and holding 'a' in a US English keyboard
layout, which will pop up an input panel for the various accents
available. Or it may happen when using the AquaSKK third party IM,
which uses the 'l' key to switch the input mode to latin without
producing any characters.

To allow these input methods the freedom to control the processing
of key events we need to reverse the logic for when we send key
events as QKeyEvent. We now assume that the IM will handle the
event, and only trigger QKeyEvent in two cases where we explicitly
were called back by the IM, but decided that a QKeyEvent is needed:

 - If the IM calls insertText and we consider the text simple text
 - If the IM calls doCommandBySelector and we can't find a matching
   selector for the command. We only implement insertNewline and
   cancel, so in all other cases we want to pass on the key event
   to let the focus object handle it, for example for 'Select All'
   and similar key combinations.

Fixes: QTBUG-46300
Fixes: QTBUG-71394
Pick-to: 6.2
Inspired-by: Vladimir Belyavsky
Change-Id: I9a73a8e1baa2ebe0c5df1166a9ec3d9843632bb1
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Tor Arne Vestbø 2021-08-27 14:41:10 +02:00
parent 587d64507a
commit 9e1875483c
2 changed files with 15 additions and 12 deletions

View File

@ -68,7 +68,7 @@
qCDebug(lcQpaKeys).nospace() << "Inserting \"" << text << "\"" qCDebug(lcQpaKeys).nospace() << "Inserting \"" << text << "\""
<< ", replacing range " << replacementRange; << ", replacing range " << replacementRange;
if (m_sendKeyEvent && m_composingText.isEmpty()) { if (m_composingText.isEmpty()) {
// The input method may have transformed the incoming key event // The input method may have transformed the incoming key event
// to text that doesn't match what the original key event would // to text that doesn't match what the original key event would
// have produced, for example when 'Pinyin - Simplified' does smart // have produced, for example when 'Pinyin - Simplified' does smart
@ -83,6 +83,7 @@
// We do not send input method events for simple text input, // We do not send input method events for simple text input,
// and instead let handleKeyEvent send the key event. // and instead let handleKeyEvent send the key event.
qCDebug(lcQpaKeys) << "Ignoring text insertion for simple text"; qCDebug(lcQpaKeys) << "Ignoring text insertion for simple text";
m_sendKeyEvent = true;
return; return;
} }
} }
@ -111,9 +112,6 @@
} }
QCoreApplication::sendEvent(focusObject, &inputMethodEvent); QCoreApplication::sendEvent(focusObject, &inputMethodEvent);
// prevent handleKeyEvent from sending a key event
m_sendKeyEvent = false;
} }
m_composingText.clear(); m_composingText.clear();
@ -153,8 +151,6 @@
newlineEvent.nativeVirtualKey = kVK_Return; newlineEvent.nativeVirtualKey = kVK_Return;
qCDebug(lcQpaKeys) << "Inserting newline via" << newlineEvent; qCDebug(lcQpaKeys) << "Inserting newline via" << newlineEvent;
newlineEvent.sendWindowSystemEvent(m_platformWindow->window()); newlineEvent.sendWindowSystemEvent(m_platformWindow->window());
m_sendKeyEvent = false;
} }
// ------------- Text composition ------------- // ------------- Text composition -------------
@ -281,8 +277,6 @@
event.setCommitString(QString(), replaceFrom, replaceLength); event.setCommitString(QString(), replaceFrom, replaceLength);
} }
QCoreApplication::sendEvent(focusObject, &event); QCoreApplication::sendEvent(focusObject, &event);
// prevent handleKeyEvent from sending a key event
m_sendKeyEvent = false;
} }
} }
} }
@ -388,8 +382,15 @@
- (void)doCommandBySelector:(SEL)selector - (void)doCommandBySelector:(SEL)selector
{ {
// Note: if the selector cannot be invoked, then doCommandBySelector:
// should not pass this message up the responder chain (nor should it
// call super, as the NSResponder base class would in that case pass
// the message up the responder chain, which we don't want). We will
// pass the originating key event up the responder chain if applicable.
qCDebug(lcQpaKeys) << "Trying to perform command" << selector; qCDebug(lcQpaKeys) << "Trying to perform command" << selector;
[self tryToPerform:selector with:self]; if (![self tryToPerform:selector with:self])
m_sendKeyEvent = true;
} }
// ------------- Various text properties ------------- // ------------- Various text properties -------------

View File

@ -55,7 +55,7 @@
window = popup->window(); window = popup->window();
} }
// 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 handles it
QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true); QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true);
if (keyEvent.type == QEvent::KeyPress) { if (keyEvent.type == QEvent::KeyPress) {
@ -84,8 +84,10 @@
const bool ignoreHidden = (hints & Qt::ImhHiddenText) && !isDeadKey && !m_lastKeyDead; const bool ignoreHidden = (hints & Qt::ImhHiddenText) && !isDeadKey && !m_lastKeyDead;
if (!(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || ignoreHidden)) { if (!(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || ignoreHidden)) {
// Pass the key event to the input method. Note that m_sendKeyEvent may be set // Pass the key event to the input method, and assume it handles the event,
// to false during this call // unless we explicit set m_sendKeyEvent to deliver as a normal key event.
m_sendKeyEvent = false;
qCDebug(lcQpaKeys) << "Interpreting key event for focus object" << focusObject; qCDebug(lcQpaKeys) << "Interpreting key event for focus object" << focusObject;
m_currentlyInterpretedKeyEvent = nsevent; m_currentlyInterpretedKeyEvent = nsevent;
[self interpretKeyEvents:@[nsevent]]; [self interpretKeyEvents:@[nsevent]];