diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index 62d81dbaa43bd..8e7cb911ccd22 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -164,8 +164,6 @@ @interface FlutterViewWrapper : NSView - (void)setBackgroundColor:(NSColor*)color; -- (BOOL)performKeyEquivalent:(NSEvent*)event; - @end /** @@ -242,37 +240,6 @@ - (void)onKeyboardLayoutChanged; @end -#pragma mark - NSEvent (KeyEquivalentMarker) protocol - -@interface NSEvent (KeyEquivalentMarker) - -// Internally marks that the event was received through performKeyEquivalent:. -// When text editing is active, keyboard events that have modifier keys pressed -// are received through performKeyEquivalent: instead of keyDown:. If such event -// is passed to TextInputContext but doesn't result in a text editing action it -// needs to be forwarded by FlutterKeyboardManager to the next responder. -- (void)markAsKeyEquivalent; - -// Returns YES if the event is marked as a key equivalent. -- (BOOL)isKeyEquivalent; - -@end - -@implementation NSEvent (KeyEquivalentMarker) - -// This field doesn't need a value because only its address is used as a unique identifier. -static char markerKey; - -- (void)markAsKeyEquivalent { - objc_setAssociatedObject(self, &markerKey, @true, OBJC_ASSOCIATION_RETAIN); -} - -- (BOOL)isKeyEquivalent { - return [objc_getAssociatedObject(self, &markerKey) boolValue] == YES; -} - -@end - #pragma mark - Private dependant functions namespace { @@ -312,19 +279,15 @@ - (void)setBackgroundColor:(NSColor*)color { } - (BOOL)performKeyEquivalent:(NSEvent*)event { - if ([_controller isDispatchingKeyEvent:event]) { - // When NSWindow is nextResponder, keyboard manager will send to it - // unhandled events (through [NSWindow keyDown:]). If event has both - // control and cmd modifiers set (i.e. cmd+control+space - emoji picker) - // NSWindow will then send this event as performKeyEquivalent: to first - // responder, which might be FlutterTextInputPlugin. If that's the case, the - // plugin must not handle the event, otherwise the emoji picker would not - // work (due to first responder returning YES from performKeyEquivalent:) - // and there would be an infinite loop, because FlutterViewController will - // send the event back to [keyboardManager handleEvent:]. + // Do not intercept the event if flutterView is not first responder, otherwise this would + // interfere with TextInputPlugin, which also handles key equivalents. + // + // Also do not intercept the event if key equivalent is a product of an event being + // redispatched by the TextInputPlugin, in which case it needs to bubble up so that menus + // can handle key equivalents. + if (self.window.firstResponder != _flutterView || [_controller isDispatchingKeyEvent:event]) { return NO; } - [event markAsKeyEquivalent]; [_flutterView keyDown:event]; return YES; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm index 1628aa004ef4d..f64d35f5896a1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm @@ -331,6 +331,13 @@ - (bool)testCtrlTabKeyEventIsPropagated { const uint64_t kPhysicalKeyTab = 0x7002b; [viewController viewWillAppear]; // Initializes the event channel. + // Creates a NSWindow so that FlutterView view can be first responder. + NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600) + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + window.contentView = viewController.view; + [window makeFirstResponder:viewController.flutterView]; [viewController.view performKeyEquivalent:event]; EXPECT_TRUE(called);