Skip to content

Commit

Permalink
[macOS] performKeyEquivalent cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
knopp committed Sep 17, 2023
1 parent 57a4ff3 commit 13c3e9b
Show file tree
Hide file tree
Showing 2 changed files with 14 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,6 @@ @interface FlutterViewWrapper : NSView

- (void)setBackgroundColor:(NSColor*)color;

- (BOOL)performKeyEquivalent:(NSEvent*)event;

@end

/**
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 13c3e9b

Please sign in to comment.