Skip to content

Commit

Permalink
[macOS, Keyboard] Refactor: Clean up keyboard initialization, connect…
Browse files Browse the repository at this point in the history
…ion, and unit test framework (#31940)
  • Loading branch information
dkwingsmt committed Mar 11, 2022
1 parent 2c94cc5 commit e21a58d
Show file tree
Hide file tree
Showing 11 changed files with 378 additions and 254 deletions.
2 changes: 1 addition & 1 deletion ci/licenses_golden/licenses_flutter
Expand Up @@ -1582,10 +1582,10 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCom
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/FlutterKeyPrimaryResponder.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMacOSExternalTexture.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.h
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalCompositor.mm
Expand Down
2 changes: 1 addition & 1 deletion shell/platform/darwin/macos/BUILD.gn
Expand Up @@ -82,9 +82,9 @@ source_set("flutter_framework_source") {
"framework/Source/FlutterIOSurfaceHolder.h",
"framework/Source/FlutterIOSurfaceHolder.mm",
"framework/Source/FlutterKeyPrimaryResponder.h",
"framework/Source/FlutterKeySecondaryResponder.h",
"framework/Source/FlutterKeyboardManager.h",
"framework/Source/FlutterKeyboardManager.mm",
"framework/Source/FlutterKeyboardViewDelegate.h",
"framework/Source/FlutterMacOSExternalTexture.h",
"framework/Source/FlutterMacOSExternalTexture.h",
"framework/Source/FlutterMetalCompositor.h",
Expand Down

This file was deleted.

Expand Up @@ -2,68 +2,50 @@
// 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/FlutterKeyPrimaryResponder.h"

#import <Cocoa/Cocoa.h>

#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h"

namespace {
// Someohow this pointer type must be defined as a single type for the compiler
// to compile the function pointer type (due to _Nullable).
typedef NSResponder* _NSResponderPtr;
}

typedef _Nullable _NSResponderPtr (^NextResponderProvider)();

/**
* A hub that manages how key events are dispatched to various Flutter key
* responders, and whether the event is propagated to the next NSResponder.
*
* This class manages one or more primary responders, as well as zero or more
* secondary responders.
* Processes keyboard events and cooperate with |TextInputPlugin|.
*
* An event that is received by |handleEvent| is first dispatched to *all*
* primary responders. Each primary responder responds *asynchronously* with a
* boolean, indicating whether it handles the event.
* A keyboard event goes through a few sections, each can choose to handled the
* event, and only unhandled events can move to the next section:
*
* An event that is not handled by any primary responders is then passed to to
* the first secondary responder (in the chronological order of addition),
* which responds *synchronously* with a boolean, indicating whether it handles
* the event. If not, the event is passed to the next secondary responder, and
* so on.
*
* If no responders handle the event, the event is then handed over to the
* owner's |nextResponder| if not nil, dispatching to method |keyDown|,
* |keyUp|, or |flagsChanged| depending on the event's type. If the
* |nextResponder| is nil, then the event will be propagated no further.
*
* Preventing primary responders from receiving events is not supported,
* because in reality this class will only support 2 hardcoded ones (channel
* and embedder), where the only purpose of supporting two is to support the
* legacy API (channel) during the deprecation window, after which the channel
* responder should be removed.
* - Pre-filtering: Events during IME are sent to the system immediately
* (to be implemented).
* - Keyboard: Dispatch to the embedder responder and the channel responder
* simultaneously. After both responders have responded (asynchronously), the
* event is considered handled if either responder handles.
* - Text input: Events are sent to |TextInputPlugin| and are handled
* synchronously.
* - Next responder: Events are sent to the next responder as specified by
* |viewDelegate|.
*/
@interface FlutterKeyboardManager : NSObject

/**
* Create a manager by specifying the owner.
* Create a keyboard manager.
*
* The owner should be an object that handles the lifecycle of this instance.
* The |owner.nextResponder| can be nil, but if it isn't, it will be where the
* key events are propagated to if no responders handle the event. The owner
* is typically a |FlutterViewController|.
* The |viewDelegate| is a weak reference, typically implemented by
* |FlutterViewController|.
*/
- (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner;
- (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDelegate>)viewDelegate;

/**
* Add a primary responder, which asynchronously decides whether to handle an
* event.
*/
- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder;

/**
* Add a secondary responder, which synchronously decides whether to handle an
* event in order if no earlier responders handle.
*/
- (void)addSecondaryResponder:(nonnull id<FlutterKeySecondaryResponder>)responder;

/**
* Dispatch a key event to all responders, and possibly the next |NSResponder|
* afterwards.
* Processes a key event.
*
* Unhandled events will be dispatched to the text input system, and possibly
* the next responder afterwards.
*/
- (void)handleEvent:(nonnull NSEvent*)event;

Expand Down
Expand Up @@ -4,35 +4,59 @@

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

#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h"

@interface FlutterKeyboardManager ()

/**
* The owner set by initWithOwner.
* The text input plugin set by initialization.
*/
@property(nonatomic, weak) NSResponder* owner;
@property(nonatomic) id<FlutterKeyboardViewDelegate> viewDelegate;

/**
* The primary responders added by addPrimaryResponder.
*/
@property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;

/**
* The secondary responders added by addSecondaryResponder.
* Add a primary responder, which asynchronously decides whether to handle an
* event.
*/
@property(nonatomic) NSMutableArray<id<FlutterKeySecondaryResponder>>* secondaryResponders;
- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder;

- (void)dispatchToSecondaryResponders:(NSEvent*)event;

@end

@implementation FlutterKeyboardManager
@implementation FlutterKeyboardManager {
NextResponderProvider _getNextResponder;
}

- (nonnull instancetype)initWithOwner:(NSResponder*)weakOwner {
- (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDelegate>)viewDelegate {
self = [super init];
if (self != nil) {
_owner = weakOwner;
_viewDelegate = viewDelegate;

_primaryResponders = [[NSMutableArray alloc] init];
_secondaryResponders = [[NSMutableArray alloc] init];
[self addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc]
initWithSendEvent:^(const FlutterKeyEvent& event,
FlutterKeyEventCallback callback,
void* userData) {
[_viewDelegate sendKeyEvent:event
callback:callback
userData:userData];
}]];
[self
addPrimaryResponder:[[FlutterChannelKeyResponder alloc]
initWithChannel:[FlutterBasicMessageChannel
messageChannelWithName:@"flutter/keyevent"
binaryMessenger:[_viewDelegate
getBinaryMessenger]
codec:[FlutterJSONMessageCodec
sharedInstance]]]];
}
return self;
}
Expand All @@ -41,10 +65,6 @@ - (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder {
[_primaryResponders addObject:responder];
}

- (void)addSecondaryResponder:(nonnull id<FlutterKeySecondaryResponder>)responder {
[_secondaryResponders addObject:responder];
}

- (void)handleEvent:(nonnull NSEvent*)event {
// Be sure to add a handling method in propagateKeyEvent when allowing more
// event types here.
Expand Down Expand Up @@ -77,25 +97,27 @@ - (void)handleEvent:(nonnull NSEvent*)event {
#pragma mark - Private

- (void)dispatchToSecondaryResponders:(NSEvent*)event {
for (id<FlutterKeySecondaryResponder> responder in _secondaryResponders) {
if ([responder handleKeyEvent:event]) {
return;
}
if ([_viewDelegate onTextInputKeyEvent:event]) {
return;
}
NSResponder* nextResponder = _viewDelegate.nextResponder;
if (nextResponder == nil) {
return;
}
switch (event.type) {
case NSEventTypeKeyDown:
if ([_owner.nextResponder respondsToSelector:@selector(keyDown:)]) {
[_owner.nextResponder keyDown:event];
if ([nextResponder respondsToSelector:@selector(keyDown:)]) {
[nextResponder keyDown:event];
}
break;
case NSEventTypeKeyUp:
if ([_owner.nextResponder respondsToSelector:@selector(keyUp:)]) {
[_owner.nextResponder keyUp:event];
if ([nextResponder respondsToSelector:@selector(keyUp:)]) {
[nextResponder keyUp:event];
}
break;
case NSEventTypeFlagsChanged:
if ([_owner.nextResponder respondsToSelector:@selector(flagsChanged:)]) {
[_owner.nextResponder flagsChanged:event];
if ([nextResponder respondsToSelector:@selector(flagsChanged:)]) {
[nextResponder flagsChanged:event];
}
break;
default:
Expand Down

0 comments on commit e21a58d

Please sign in to comment.