Skip to content

Commit 07503bb

Browse files
knoppdkwingsmt
andauthored
[macOS] Prepare FlutterKeyboardManager for multi-view (flutter#163962)
Notable changes: - Moved keyboard layout related code from `FlutterViewController` to `FlutterKeyboardLayout`. - `FlutterKeyboardManager` is now owned by the engine and shared between view controllers. The per view controller part, which is associated with event, has been moved from `FlutterKeyboardManager` delegate to `FlutterKeyboardManagerEventContext`. - The `FlutterKeyboardManagerDelegate` is implemented by `FlutterEngine`. - Some overall clean-up and dead code removal (i.e. `_NSResponderPtr` and `NextResponderProvider`) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Tong Mu <[email protected]>
1 parent b831b26 commit 07503bb

14 files changed

+439
-363
lines changed

engine/src/flutter/ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43450,10 +43450,11 @@ ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEng
4345043450
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTexture.h + ../../../flutter/LICENSE
4345143451
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTexture.mm + ../../../flutter/LICENSE
4345243452
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h + ../../../flutter/LICENSE
43453+
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardLayout.h + ../../../flutter/LICENSE
43454+
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardLayout.mm + ../../../flutter/LICENSE
4345343455
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h + ../../../flutter/LICENSE
4345443456
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm + ../../../flutter/LICENSE
4345543457
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerTest.mm + ../../../flutter/LICENSE
43456-
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h + ../../../flutter/LICENSE
4345743458
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h + ../../../flutter/LICENSE
4345843459
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.mm + ../../../flutter/LICENSE
4345943460
ORIGIN: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPluginTest.mm + ../../../flutter/LICENSE
@@ -46401,10 +46402,11 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngin
4640146402
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTexture.h
4640246403
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterExternalTexture.mm
4640346404
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h
46405+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardLayout.h
46406+
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardLayout.mm
4640446407
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h
4640546408
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm
4640646409
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerTest.mm
46407-
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h
4640846410
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h
4640946411
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.mm
4641046412
FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPluginTest.mm

engine/src/flutter/shell/platform/darwin/macos/BUILD.gn

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,10 @@ source_set("flutter_framework_source") {
7777
"framework/Source/FlutterExternalTexture.h",
7878
"framework/Source/FlutterExternalTexture.mm",
7979
"framework/Source/FlutterKeyPrimaryResponder.h",
80+
"framework/Source/FlutterKeyboardLayout.h",
81+
"framework/Source/FlutterKeyboardLayout.mm",
8082
"framework/Source/FlutterKeyboardManager.h",
8183
"framework/Source/FlutterKeyboardManager.mm",
82-
"framework/Source/FlutterKeyboardViewDelegate.h",
8384
"framework/Source/FlutterMenuPlugin.h",
8485
"framework/Source/FlutterMenuPlugin.mm",
8586
"framework/Source/FlutterMenuPlugin_Internal.h",

engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
104104
forEventFlags:modifierFlags
105105
keyCode:0x00000039 // kVK_CapsLock
106106
timestamp:timestamp];
107-
108-
// At the end we should end up with the same modifier flags as the event.
109-
FML_DCHECK(_previouslyPressedFlags == modifierFlags);
110107
}
111108

112109
- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {

engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ - (instancetype)initWithConnection:(NSNumber*)connection
8989
/**
9090
* Private interface declaration for FlutterEngine.
9191
*/
92-
@interface FlutterEngine () <FlutterBinaryMessenger, FlutterMouseCursorPluginDelegate>
92+
@interface FlutterEngine () <FlutterBinaryMessenger,
93+
FlutterMouseCursorPluginDelegate,
94+
FlutterKeyboardManagerDelegate>
9395

9496
/**
9597
* A mutable array that holds one bool value that determines if responses to platform messages are
@@ -473,6 +475,9 @@ @implementation FlutterEngine {
473475
// Weak reference to last view that received a pointer event. This is used to
474476
// pair cursor change with a view.
475477
__weak FlutterView* _lastViewWithPointerEvent;
478+
479+
// Pointer to a keyboard manager.
480+
FlutterKeyboardManager* _keyboardManager;
476481
}
477482

478483
- (instancetype)initWithName:(NSString*)labelPrefix project:(FlutterDartProject*)project {
@@ -513,6 +518,7 @@ - (instancetype)initWithName:(NSString*)labelPrefix
513518
_binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
514519
_isResponseValid = [[NSMutableArray alloc] initWithCapacity:1];
515520
[_isResponseValid addObject:@YES];
521+
_keyboardManager = [[FlutterKeyboardManager alloc] initWithDelegate:self];
516522

517523
_embedderAPI.struct_size = sizeof(FlutterEngineProcTable);
518524
FlutterEngineGetProcAddresses(&_embedderAPI);
@@ -1024,12 +1030,6 @@ - (void)sendPointerEvent:(const FlutterPointerEvent&)event {
10241030
_lastViewWithPointerEvent = [self viewControllerForIdentifier:kFlutterImplicitViewId].flutterView;
10251031
}
10261032

1027-
- (void)sendKeyEvent:(const FlutterKeyEvent&)event
1028-
callback:(FlutterKeyEventCallback)callback
1029-
userData:(void*)userData {
1030-
_embedderAPI.SendKeyEvent(_engine, &event, callback, userData);
1031-
}
1032-
10331033
- (void)setSemanticsEnabled:(BOOL)enabled {
10341034
if (_semanticsEnabled == enabled) {
10351035
return;
@@ -1127,6 +1127,7 @@ - (void)engineCallbackOnPreEngineRestart {
11271127
[nextViewController onPreEngineRestart];
11281128
}
11291129
[_platformViewController reset];
1130+
_keyboardManager = [[FlutterKeyboardManager alloc] initWithDelegate:self];
11301131
}
11311132

11321133
- (void)onVSync:(uintptr_t)baton {
@@ -1564,4 +1565,15 @@ - (void)postMainThreadTask:(FlutterTask)task targetTimeInNanoseconds:(uint64_t)t
15641565
return _macOSCompositor.get();
15651566
}
15661567

1568+
#pragma mark - FlutterKeyboardManagerDelegate
1569+
1570+
/**
1571+
* Dispatches the given pointer event data to engine.
1572+
*/
1573+
- (void)sendKeyEvent:(const FlutterKeyEvent&)event
1574+
callback:(FlutterKeyEventCallback)callback
1575+
userData:(void*)userData {
1576+
_embedderAPI.SendKeyEvent(_engine, &event, callback, userData);
1577+
}
1578+
15671579
@end

engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "flutter/shell/platform/common/app_lifecycle_state.h"
1515

1616
#import "flutter/shell/platform/darwin/macos/framework/Source/AccessibilityBridgeMac.h"
17+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h"
1718
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterPlatformViewController.h"
1819
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
1920

@@ -165,13 +166,6 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
165166
*/
166167
- (void)sendPointerEvent:(const FlutterPointerEvent&)event;
167168

168-
/**
169-
* Dispatches the given pointer event data to engine.
170-
*/
171-
- (void)sendKeyEvent:(const FlutterKeyEvent&)event
172-
callback:(nullable FlutterKeyEventCallback)callback
173-
userData:(nullable void*)userData;
174-
175169
/**
176170
* Registers an external texture with the given id. Returns YES on success.
177171
*/
@@ -218,6 +212,11 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
218212
- (void)announceAccessibilityMessage:(NSString*)message
219213
withPriority:(NSAccessibilityPriorityLevel)priority;
220214

215+
/**
216+
* Returns keyboard manager for the engine.
217+
*/
218+
@property(nonatomic, readonly) FlutterKeyboardManager* keyboardManager;
219+
221220
/**
222221
* Returns an array of screen objects representing all of the screens available on the system.
223222
*/
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERKEYBOARDLAYOUT_H_
6+
#define FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERKEYBOARDLAYOUT_H_
7+
8+
#import <AppKit/AppKit.h>
9+
10+
namespace flutter {
11+
12+
// The printable result of a key under certain modifiers, used to derive key
13+
// mapping.
14+
typedef struct {
15+
// The printable character.
16+
//
17+
// If `isDeadKey` is true, then this is the character when pressing the same
18+
// dead key twice.
19+
uint32_t character;
20+
21+
// Whether this character is a dead key.
22+
//
23+
// A dead key is a key that is not counted as text per se, but holds a
24+
// diacritics to be added to the next key.
25+
bool isDeadKey;
26+
} LayoutClue;
27+
28+
} // namespace flutter
29+
30+
/**
31+
* A delegate protocol for FlutterKeyboardLayout. Implemented by FlutterKeyboardManager.
32+
*/
33+
@protocol FlutterKeyboardLayoutDelegate
34+
35+
/**
36+
* Called when the active keyboard input source changes.
37+
*
38+
* Input sources may be simple keyboard layouts, or more complex input methods involving an IME,
39+
* such as Chinese, Japanese, and Korean.
40+
*/
41+
- (void)keyboardLayoutDidChange;
42+
43+
@end
44+
45+
/**
46+
* A class that allows querying the printable result of a key with a modifier state according to the
47+
* current keyboard layout. It also provides a delegate protocol for clients interested in
48+
* listening to keyboard layout changes.
49+
*/
50+
@interface FlutterKeyboardLayout : NSObject
51+
52+
@property(readwrite, nonatomic, weak) id<FlutterKeyboardLayoutDelegate> delegate;
53+
54+
/**
55+
* Querying the printable result of a key under the given modifier state.
56+
*/
57+
- (flutter::LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift;
58+
59+
@end
60+
61+
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_MACOS_FRAMEWORK_SOURCE_FLUTTERKEYBOARDLAYOUT_H_
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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/FlutterKeyboardLayout.h"
6+
7+
#include <Carbon/Carbon.h>
8+
#include <cctype>
9+
#include "flutter/fml/platform/darwin/cf_utils.h"
10+
11+
@implementation FlutterKeyboardLayout {
12+
NSData* _keyboardLayoutData;
13+
}
14+
15+
@synthesize delegate = _delegate;
16+
17+
/**
18+
* Returns the current Unicode layout data (kTISPropertyUnicodeKeyLayoutData).
19+
*
20+
* To use the returned data, convert it to CFDataRef first, finds its bytes
21+
* with CFDataGetBytePtr, then reinterpret it into const UCKeyboardLayout*.
22+
* It's returned in NSData* to enable auto reference count.
23+
*/
24+
static NSData* CurrentKeyboardLayoutData() {
25+
fml::CFRef<TISInputSourceRef> source(TISCopyCurrentKeyboardInputSource());
26+
CFTypeRef layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
27+
if (layout_data == nil) {
28+
// TISGetInputSourceProperty returns null with Japanese keyboard layout.
29+
// Using TISCopyCurrentKeyboardLayoutInputSource to fix NULL return.
30+
// https://github.com/microsoft/node-native-keymap/blob/5f0699ded00179410a14c0e1b0e089fe4df8e130/src/keyboard_mac.mm#L91
31+
source.Reset(TISCopyCurrentKeyboardLayoutInputSource());
32+
layout_data = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData);
33+
}
34+
return (__bridge NSData*)layout_data;
35+
}
36+
37+
/**
38+
* NotificationCenter callback invoked on kTISNotifySelectedKeyboardInputSourceChanged events.
39+
*/
40+
static void OnKeyboardLayoutChanged(CFNotificationCenterRef center,
41+
void* observer,
42+
CFStringRef name,
43+
const void* object,
44+
CFDictionaryRef userInfo) {
45+
FlutterKeyboardLayout* controller = (__bridge FlutterKeyboardLayout*)observer;
46+
if (controller != nil) {
47+
[controller onKeyboardLayoutChanged];
48+
}
49+
}
50+
51+
- (instancetype)initWithDelegate:(id<FlutterKeyboardLayoutDelegate>)delegate {
52+
self = [super init];
53+
if (self) {
54+
_delegate = delegate;
55+
// macOS fires this message when changing IMEs.
56+
CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
57+
__weak FlutterKeyboardLayout* weakSelf = self;
58+
CFNotificationCenterAddObserver(cfCenter, (__bridge void*)weakSelf, OnKeyboardLayoutChanged,
59+
kTISNotifySelectedKeyboardInputSourceChanged, NULL,
60+
CFNotificationSuspensionBehaviorDeliverImmediately);
61+
}
62+
return self;
63+
}
64+
65+
- (void)dealloc {
66+
CFNotificationCenterRef cfCenter = CFNotificationCenterGetDistributedCenter();
67+
CFNotificationCenterRemoveEveryObserver(cfCenter, (__bridge void*)self);
68+
}
69+
70+
- (void)onKeyboardLayoutChanged {
71+
_keyboardLayoutData = nil;
72+
[_delegate keyboardLayoutDidChange];
73+
}
74+
75+
- (flutter::LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
76+
if (_keyboardLayoutData == nil) {
77+
_keyboardLayoutData = CurrentKeyboardLayoutData();
78+
}
79+
const UCKeyboardLayout* layout = reinterpret_cast<const UCKeyboardLayout*>(
80+
CFDataGetBytePtr((__bridge CFDataRef)_keyboardLayoutData));
81+
82+
UInt32 deadKeyState = 0;
83+
UniCharCount stringLength = 0;
84+
UniChar resultChar;
85+
86+
UInt32 modifierState = ((shift ? shiftKey : 0) >> 8) & 0xFF;
87+
UInt32 keyboardType = LMGetKbdLast();
88+
89+
bool isDeadKey = false;
90+
OSStatus status =
91+
UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
92+
kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
93+
// For dead keys, press the same key again to get the printable representation of the key.
94+
if (status == noErr && stringLength == 0 && deadKeyState != 0) {
95+
isDeadKey = true;
96+
status =
97+
UCKeyTranslate(layout, keyCode, kUCKeyActionDown, modifierState, keyboardType,
98+
kUCKeyTranslateNoDeadKeysBit, &deadKeyState, 1, &stringLength, &resultChar);
99+
}
100+
101+
if (status == noErr && stringLength == 1 && !std::iscntrl(resultChar)) {
102+
return flutter::LayoutClue{resultChar, isDeadKey};
103+
}
104+
return flutter::LayoutClue{0, false};
105+
}
106+
107+
@end

0 commit comments

Comments
 (0)