Thanks to visit codestin.com
Credit goes to github.com

Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[macOS] TextInputPlugin should not consume unhandled key equivalent events #34250

Merged
merged 9 commits into from
Jul 7, 2022

Conversation

knopp
Copy link
Member

@knopp knopp commented Jun 23, 2022

Fixes flutter/flutter#106354

If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on
    writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@@ -482,7 +512,16 @@ - (BOOL)handleKeyEvent:(NSEvent*)event {
return NO;
}

return [_textInputContext handleEvent:event];
_eventProducedOutput = NO;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the event is a key equivalent, should we let the application route it as a key equivalent first, if it's not handled, then we give the event to the currently focused text field?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not familiar with how key handling on macOS works so /cc @dkwingsmt

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By application do you mean the framework? Because if so that's already happening. On the other hand If you mean the rest of cocoa responder chain, then I'd say no - this would be completely different behavior that what happens without text input (framework first, cocoa second).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole purpose of this PR is to make sure that TextInputContext doesn't doesn't simply swallow keyEquivalent events that don't produce any text editing action so that they are forwarded to the rest of responder chain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the cocoa responder chain (potentially by sending it back to the -[FlutterViewController performKeyEquivalent:] method's super implementation).
-[FlutterTextInputPlugin handleKeyEvent:] is only called when the framework doesn't consume the key event iirc? So it's already "framework first"?

Copy link
Member Author

@knopp knopp Jun 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it's already "framework first"?

It is. This is the main keyboard event processing:

- (void)performProcessEvent:(NSEvent*)event onFinish:(VoidBlock)onFinish {
if (_viewDelegate.isComposing) {
[self dispatchTextEvent:event];
onFinish();
return;
}
// Having no primary responders require extra logic, but Flutter hard-codes
// all primary responders, so this is a situation that Flutter will never
// encounter.
NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
__weak __typeof__(self) weakSelf = self;
__block int unreplied = [_primaryResponders count];
__block BOOL anyHandled = false;
FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
unreplied -= 1;
NSAssert(unreplied >= 0, @"More primary responders replied than possible.");
anyHandled = anyHandled || handled;
if (unreplied == 0) {
if (!anyHandled) {
[weakSelf dispatchTextEvent:event];
}
onFinish();
}
};
for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
[responder handleEvent:event callback:replyCallback];
}
}

Primary responders are FlutterEmbedderKeyResponder and FlutterChannelKeyResponder (framework). If these don't handle keyboard event, the event is passed to [FlutterKeyboardManager dispatchTextEvent:]. This will let the TextInputPlugin process event, and if not handled will forward it to nextResponder.

The issue this PR fixes is that any event sent to TextInputPlugin (with active context) was marked as handled, even if it was keyboard shortcut that didn't result in any action.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, FlutterTextInputPlugin#handleKeyEvent is called only when the Framework's keyboard systems choose not to handle the event.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few small comments. The approach looks right to me, but curious about @LongCatIsLooong's response to the discussion at https://github.com/flutter/engine/pull/34250/files#r908880528.

@@ -482,7 +512,16 @@ - (BOOL)handleKeyEvent:(NSEvent*)event {
return NO;
}

return [_textInputContext handleEvent:event];
_eventProducedOutput = NO;
BOOL res = [_textInputContext handleEvent:event];
Copy link
Contributor

@dkwingsmt dkwingsmt Jul 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of know what you're changing here in the context of this PR, but if I'm reading the code the following comment confuses me. Can you add more information and describe it in the positive way, like:

NSTextInputContext#handleEvent returns YES if the context handles the event. One of the reasons the event is handled is because it's a key equivalent. But a key equivalent might produce a text command (indicated by calling doCommandBySelector) and might not (for example, Cmd+Q). In the latter case, this command somehow has not been executed yet and Flutter must dispatch it to the next responder. See flutter/flutter#106354 .

@dkwingsmt
Copy link
Contributor

I'm ok with this PR. I'm still confused by why NSTextInputContext:handleEvent returns YES for Cmd-Q while not handling it immediately, but I guess that's how it is. A few comment suggestion. Rephrase them as you see fit.

@knopp knopp force-pushed the cmd_q_in_text_field_not_working branch from 4a16700 to 9aa4a7b Compare July 5, 2022 13:02
@knopp
Copy link
Member Author

knopp commented Jul 5, 2022

@dkwingsmt, I've updated the comment according to your suggestion. Is there anything else or is this good to go?

Copy link
Contributor

@dkwingsmt dkwingsmt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@knopp knopp force-pushed the cmd_q_in_text_field_not_working branch from 46a20ea to 3079907 Compare July 6, 2022 19:08
@knopp knopp merged commit 628735f into flutter:main Jul 7, 2022
@knopp knopp deleted the cmd_q_in_text_field_not_working branch July 7, 2022 13:00
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jul 7, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[macOS] Cannot quit app with Cmd+Q when text field is focused
4 participants