Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCom
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterResizeSynchronizer.h
Expand Down
2 changes: 2 additions & 0 deletions shell/platform/darwin/macos/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterGLCompositor.mm",
"framework/Source/FlutterIOSurfaceHolder.h",
"framework/Source/FlutterIOSurfaceHolder.mm",
"framework/Source/FlutterIntermediateKeyResponder.h",
"framework/Source/FlutterIntermediateKeyResponder.mm",
"framework/Source/FlutterMouseCursorPlugin.h",
"framework/Source/FlutterMouseCursorPlugin.mm",
"framework/Source/FlutterResizeSynchronizer.h",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <Cocoa/Cocoa.h>

/*
* An interface for a key responder that can declare itself as the final
* responder of the event, terminating the event propagation.
*
* It differs from an NSResponder in that it returns a boolean from the
* handleKeyUp and handleKeyDown calls, where true means it has handled the
* given event.
*/
@interface FlutterIntermediateKeyResponder : NSObject
/*
* Informs the receiver that the user has released a key.
*
* Default implementation returns NO.
*/
- (BOOL)handleKeyUp:(nonnull NSEvent*)event;
/*
* Informs the receiver that the user has pressed a key.
*
* Default implementation returns NO.
*/
- (BOOL)handleKeyDown:(nonnull NSEvent*)event;
@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h"

@implementation FlutterIntermediateKeyResponder {
}

#pragma mark - Default key handling methods

- (BOOL)handleKeyUp:(NSEvent*)event {
return NO;
}

- (BOOL)handleKeyDown:(NSEvent*)event {
return NO;
}
@end
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h"

/**
* A plugin to handle text input.
Expand All @@ -16,7 +17,7 @@
* This is not an FlutterPlugin since it needs access to FlutterViewController internals, so needs
* to be managed differently.
*/
@interface FlutterTextInputPlugin : NSResponder
@interface FlutterTextInputPlugin : FlutterIntermediateKeyResponder

/**
* Initializes a text input plugin that coordinates key event handling with |viewController|.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,17 +129,20 @@ - (void)updateEditState {
}

#pragma mark -
#pragma mark NSResponder
#pragma mark FlutterIntermediateKeyResponder

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

#pragma mark -
Expand Down
104 changes: 84 additions & 20 deletions shell/platform/darwin/macos/framework/Source/FlutterViewController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,14 @@ void Reset() {
@interface FlutterViewController () <FlutterViewReshapeListener>

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

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

/**
* Converts |event| to a key event channel message, and sends it to the engine.
* Sends |event| to all responders in additionalKeyResponders and then to the
* nextResponder if none of the additional responders handled the event.
*/
- (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type;

/**
* Converts |event| to a key event channel message, and sends it to the engine to
* hand to the framework. Once the framework responds, if the event was not handled,
* propagates the event to any additional key responders.
*/
- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type;

Expand Down Expand Up @@ -206,9 +219,11 @@ @implementation FlutterViewController {
* Performs initialization that's common between the different init paths.
*/
static void CommonInit(FlutterViewController* controller) {
controller->_engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
project:controller->_project
allowHeadlessExecution:NO];
if (!controller->_engine) {
controller->_engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
project:controller->_project
allowHeadlessExecution:NO];
}
controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init];
controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow;
}
Expand Down Expand Up @@ -238,6 +253,27 @@ - (instancetype)initWithProject:(nullable FlutterDartProject*)project {
return self;
}

- (instancetype)initWithEngine:(nonnull FlutterEngine*)engine
nibName:(nullable NSString*)nibName
bundle:(nullable NSBundle*)nibBundle {
NSAssert(engine != nil, @"Engine is required");
self = [super initWithNibName:nibName bundle:nibBundle];
if (self) {
if (engine.viewController) {
NSLog(@"The supplied FlutterEngine %@ is already used with FlutterViewController "
"instance %@. One instance of the FlutterEngine can only be attached to one "
"FlutterViewController at a time. Set FlutterEngine.viewController "
"to nil before attaching it to another FlutterViewController.",
[engine description], [engine.viewController description]);
}
_engine = engine;
CommonInit(self);
[engine setViewController:self];
}

return self;
}

- (void)loadView {
NSOpenGLContext* resourceContext = _engine.resourceContext;
if (!resourceContext) {
Expand Down Expand Up @@ -288,11 +324,12 @@ - (FlutterView*)flutterView {
return static_cast<FlutterView*>(self.view);
}

- (void)addKeyResponder:(NSResponder*)responder {
- (void)addKeyResponder:(FlutterIntermediateKeyResponder*)responder {
[self.additionalKeyResponders addObject:responder];
}

- (void)removeKeyResponder:(NSResponder*)responder {
- (void)removeKeyResponder:(FlutterIntermediateKeyResponder*)responder {
[self.additionalKeyResponders removeObject:responder];
}

#pragma mark - Private methods
Expand Down Expand Up @@ -460,19 +497,56 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
}
}

- (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type {
if ([type isEqual:@"keydown"]) {
for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) {
if ([responder handleKeyDown:event]) {
return;
}
}
if ([self.nextResponder respondsToSelector:@selector(keyDown:)]) {
[self.nextResponder keyDown:event];
}
} else if ([type isEqual:@"keyup"]) {
for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) {
if ([responder handleKeyUp:event]) {
return;
}
}
if ([self.nextResponder respondsToSelector:@selector(keyUp:)]) {
[self.nextResponder keyUp:event];
}
}
}

- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type {
if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp &&
event.type != NSEventTypeFlagsChanged) {
return;
}
NSMutableDictionary* keyMessage = [@{
@"keymap" : @"macos",
@"type" : type,
@"keyCode" : @(event.keyCode),
@"modifiers" : @(event.modifierFlags),
} mutableCopy];
// Calling these methods on any other type of event will raise an exception.
// Calling these methods on any other type of event
// (e.g NSEventTypeFlagsChanged) will raise an exception.
if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) {
keyMessage[@"characters"] = event.characters;
keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers;
}
[_keyEventChannel sendMessage:keyMessage];
__weak __typeof__(self) weakSelf = self;
FlutterReply replyHandler = ^(id _Nullable reply) {
if (!reply) {
return;
}
// Only re-dispatch the event to other responders if the framework didn't handle it.
if (![[reply valueForKey:@"handled"] boolValue]) {
[weakSelf propagateKeyEvent:event ofType:type];
}
};
[_keyEventChannel sendMessage:keyMessage reply:replyHandler];
}

- (void)onSettingsChanged:(NSNotification*)notification {
Expand Down Expand Up @@ -571,20 +645,10 @@ - (BOOL)acceptsFirstResponder {

- (void)keyDown:(NSEvent*)event {
[self dispatchKeyEvent:event ofType:@"keydown"];
for (NSResponder* responder in self.additionalKeyResponders) {
if ([responder respondsToSelector:@selector(keyDown:)]) {
[responder keyDown:event];
}
}
}

- (void)keyUp:(NSEvent*)event {
[self dispatchKeyEvent:event ofType:@"keyup"];
for (NSResponder* responder in self.additionalKeyResponders) {
if ([responder respondsToSelector:@selector(keyUp:)]) {
[responder keyUp:event];
}
}
}

- (void)flagsChanged:(NSEvent*)event {
Expand Down
Loading