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

Commit 21691f1

Browse files
authored
Enable delayed event delivery for macOS (#21231)
This enables delayed event delivery for macOS, so that shortcuts can handle keys that are headed for a text field and intercept them. This fixes the problem where pressing TAB (or other shortcuts) in a text field also inserts a tab character into the text field.
1 parent 4679f7b commit 21691f1

10 files changed

Lines changed: 348 additions & 34 deletions

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCom
10731073
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm
10741074
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h
10751075
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm
1076+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h
1077+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm
10761078
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
10771079
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
10781080
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h

shell/platform/darwin/macos/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ source_set("flutter_framework_source") {
6060
"framework/Source/FlutterGLCompositor.mm",
6161
"framework/Source/FlutterIOSurfaceHolder.h",
6262
"framework/Source/FlutterIOSurfaceHolder.mm",
63+
"framework/Source/FlutterIntermediateKeyResponder.h",
64+
"framework/Source/FlutterIntermediateKeyResponder.mm",
6365
"framework/Source/FlutterMouseCursorPlugin.h",
6466
"framework/Source/FlutterMouseCursorPlugin.mm",
6567
"framework/Source/FlutterResizeSynchronizer.h",
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import <Cocoa/Cocoa.h>
6+
7+
/*
8+
* An interface for a key responder that can declare itself as the final
9+
* responder of the event, terminating the event propagation.
10+
*
11+
* It differs from an NSResponder in that it returns a boolean from the
12+
* handleKeyUp and handleKeyDown calls, where true means it has handled the
13+
* given event.
14+
*/
15+
@interface FlutterIntermediateKeyResponder : NSObject
16+
/*
17+
* Informs the receiver that the user has released a key.
18+
*
19+
* Default implementation returns NO.
20+
*/
21+
- (BOOL)handleKeyUp:(nonnull NSEvent*)event;
22+
/*
23+
* Informs the receiver that the user has pressed a key.
24+
*
25+
* Default implementation returns NO.
26+
*/
27+
- (BOOL)handleKeyDown:(nonnull NSEvent*)event;
28+
@end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h"
6+
7+
@implementation FlutterIntermediateKeyResponder {
8+
}
9+
10+
#pragma mark - Default key handling methods
11+
12+
- (BOOL)handleKeyUp:(NSEvent*)event {
13+
return NO;
14+
}
15+
16+
- (BOOL)handleKeyDown:(NSEvent*)event {
17+
return NO;
18+
}
19+
@end

shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
88
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
9+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h"
910

1011
/**
1112
* A plugin to handle text input.
@@ -16,7 +17,7 @@
1617
* This is not an FlutterPlugin since it needs access to FlutterViewController internals, so needs
1718
* to be managed differently.
1819
*/
19-
@interface FlutterTextInputPlugin : NSResponder
20+
@interface FlutterTextInputPlugin : FlutterIntermediateKeyResponder
2021

2122
/**
2223
* Initializes a text input plugin that coordinates key event handling with |viewController|.

shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,17 +129,20 @@ - (void)updateEditState {
129129
}
130130

131131
#pragma mark -
132-
#pragma mark NSResponder
132+
#pragma mark FlutterIntermediateKeyResponder
133133

134134
/**
135+
* Handles key down events received from the view controller, responding TRUE if
136+
* the event was handled.
137+
*
135138
* Note, the Apple docs suggest that clients should override essentially all the
136139
* mouse and keyboard event-handling methods of NSResponder. However, experimentation
137140
* indicates that only key events are processed by the native layer; Flutter processes
138141
* mouse events. Additionally, processing both keyUp and keyDown results in duplicate
139-
* processing of the same keys. So for now, limit processing to just keyDown.
142+
* processing of the same keys. So for now, limit processing to just handleKeyDown.
140143
*/
141-
- (void)keyDown:(NSEvent*)event {
142-
[_textInputContext handleEvent:event];
144+
- (BOOL)handleKeyDown:(NSEvent*)event {
145+
return [_textInputContext handleEvent:event];
143146
}
144147

145148
#pragma mark -

shell/platform/darwin/macos/framework/Source/FlutterViewController.mm

Lines changed: 84 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,14 @@ void Reset() {
8282
@interface FlutterViewController () <FlutterViewReshapeListener>
8383

8484
/**
85-
* A list of additional responders to keyboard events. Keybord events are forwarded to all of them.
85+
* A list of additional responders to keyboard events.
86+
*
87+
* Keyboard events received by FlutterViewController are first dispatched to
88+
* each additional responder in order. If any of them handle the event (by
89+
* returning true), the event is not dispatched to later additional responders
90+
* or to the nextResponder.
8691
*/
87-
@property(nonatomic) NSMutableOrderedSet<NSResponder*>* additionalKeyResponders;
92+
@property(nonatomic) NSMutableOrderedSet<FlutterIntermediateKeyResponder*>* additionalKeyResponders;
8893

8994
/**
9095
* The tracking area used to generate hover events, if enabled.
@@ -135,7 +140,15 @@ - (void)dispatchMouseEvent:(nonnull NSEvent*)event;
135140
- (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase;
136141

137142
/**
138-
* Converts |event| to a key event channel message, and sends it to the engine.
143+
* Sends |event| to all responders in additionalKeyResponders and then to the
144+
* nextResponder if none of the additional responders handled the event.
145+
*/
146+
- (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type;
147+
148+
/**
149+
* Converts |event| to a key event channel message, and sends it to the engine to
150+
* hand to the framework. Once the framework responds, if the event was not handled,
151+
* propagates the event to any additional key responders.
139152
*/
140153
- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type;
141154

@@ -206,9 +219,11 @@ @implementation FlutterViewController {
206219
* Performs initialization that's common between the different init paths.
207220
*/
208221
static void CommonInit(FlutterViewController* controller) {
209-
controller->_engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
210-
project:controller->_project
211-
allowHeadlessExecution:NO];
222+
if (!controller->_engine) {
223+
controller->_engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
224+
project:controller->_project
225+
allowHeadlessExecution:NO];
226+
}
212227
controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init];
213228
controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;
214229
}
@@ -238,6 +253,27 @@ - (instancetype)initWithProject:(nullable FlutterDartProject*)project {
238253
return self;
239254
}
240255

256+
- (instancetype)initWithEngine:(nonnull FlutterEngine*)engine
257+
nibName:(nullable NSString*)nibName
258+
bundle:(nullable NSBundle*)nibBundle {
259+
NSAssert(engine != nil, @"Engine is required");
260+
self = [super initWithNibName:nibName bundle:nibBundle];
261+
if (self) {
262+
if (engine.viewController) {
263+
NSLog(@"The supplied FlutterEngine %@ is already used with FlutterViewController "
264+
"instance %@. One instance of the FlutterEngine can only be attached to one "
265+
"FlutterViewController at a time. Set FlutterEngine.viewController "
266+
"to nil before attaching it to another FlutterViewController.",
267+
[engine description], [engine.viewController description]);
268+
}
269+
_engine = engine;
270+
CommonInit(self);
271+
[engine setViewController:self];
272+
}
273+
274+
return self;
275+
}
276+
241277
- (void)loadView {
242278
NSOpenGLContext* resourceContext = _engine.resourceContext;
243279
if (!resourceContext) {
@@ -288,11 +324,12 @@ - (FlutterView*)flutterView {
288324
return static_cast<FlutterView*>(self.view);
289325
}
290326

291-
- (void)addKeyResponder:(NSResponder*)responder {
327+
- (void)addKeyResponder:(FlutterIntermediateKeyResponder*)responder {
292328
[self.additionalKeyResponders addObject:responder];
293329
}
294330

295-
- (void)removeKeyResponder:(NSResponder*)responder {
331+
- (void)removeKeyResponder:(FlutterIntermediateKeyResponder*)responder {
332+
[self.additionalKeyResponders removeObject:responder];
296333
}
297334

298335
#pragma mark - Private methods
@@ -460,19 +497,56 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
460497
}
461498
}
462499

500+
- (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type {
501+
if ([type isEqual:@"keydown"]) {
502+
for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) {
503+
if ([responder handleKeyDown:event]) {
504+
return;
505+
}
506+
}
507+
if ([self.nextResponder respondsToSelector:@selector(keyDown:)]) {
508+
[self.nextResponder keyDown:event];
509+
}
510+
} else if ([type isEqual:@"keyup"]) {
511+
for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) {
512+
if ([responder handleKeyUp:event]) {
513+
return;
514+
}
515+
}
516+
if ([self.nextResponder respondsToSelector:@selector(keyUp:)]) {
517+
[self.nextResponder keyUp:event];
518+
}
519+
}
520+
}
521+
463522
- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type {
523+
if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp &&
524+
event.type != NSEventTypeFlagsChanged) {
525+
return;
526+
}
464527
NSMutableDictionary* keyMessage = [@{
465528
@"keymap" : @"macos",
466529
@"type" : type,
467530
@"keyCode" : @(event.keyCode),
468531
@"modifiers" : @(event.modifierFlags),
469532
} mutableCopy];
470-
// Calling these methods on any other type of event will raise an exception.
533+
// Calling these methods on any other type of event
534+
// (e.g NSEventTypeFlagsChanged) will raise an exception.
471535
if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) {
472536
keyMessage[@"characters"] = event.characters;
473537
keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers;
474538
}
475-
[_keyEventChannel sendMessage:keyMessage];
539+
__weak __typeof__(self) weakSelf = self;
540+
FlutterReply replyHandler = ^(id _Nullable reply) {
541+
if (!reply) {
542+
return;
543+
}
544+
// Only re-dispatch the event to other responders if the framework didn't handle it.
545+
if (![[reply valueForKey:@"handled"] boolValue]) {
546+
[weakSelf propagateKeyEvent:event ofType:type];
547+
}
548+
};
549+
[_keyEventChannel sendMessage:keyMessage reply:replyHandler];
476550
}
477551

478552
- (void)onSettingsChanged:(NSNotification*)notification {
@@ -571,20 +645,10 @@ - (BOOL)acceptsFirstResponder {
571645

572646
- (void)keyDown:(NSEvent*)event {
573647
[self dispatchKeyEvent:event ofType:@"keydown"];
574-
for (NSResponder* responder in self.additionalKeyResponders) {
575-
if ([responder respondsToSelector:@selector(keyDown:)]) {
576-
[responder keyDown:event];
577-
}
578-
}
579648
}
580649

581650
- (void)keyUp:(NSEvent*)event {
582651
[self dispatchKeyEvent:event ofType:@"keyup"];
583-
for (NSResponder* responder in self.additionalKeyResponders) {
584-
if ([responder respondsToSelector:@selector(keyUp:)]) {
585-
[responder keyUp:event];
586-
}
587-
}
588652
}
589653

590654
- (void)flagsChanged:(NSEvent*)event {

0 commit comments

Comments
 (0)