Skip to content

Commit b3cd1c5

Browse files
authored
[macOS] Return keyboard pressed state (flutter#42878)
## Description This PR updates the macOS engine in order to answer to keyboard pressed state queries from the framework (as implemented in flutter#122885). ## Related Issue macOS engine implementation for flutter#87391 Similar to: - Linux: flutter/engine#42346 - Android: flutter/engine#42758 ## Tests Adds 2 tests.
1 parent 4bdcecc commit b3cd1c5

7 files changed

Lines changed: 144 additions & 6 deletions

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,12 @@ typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */,
3939
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
4040
timestamp:(NSTimeInterval)timestamp;
4141

42+
/**
43+
* Returns the keyboard pressed state.
44+
*
45+
* Returns the keyboard pressed state. The dictionary contains one entry per
46+
* pressed keys, mapping from the logical key to the physical key.
47+
*/
48+
- (nonnull NSDictionary*)getPressedState;
49+
4250
@end

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,9 @@ - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
793793
guard:guardedCallback];
794794
}
795795

796+
- (nonnull NSDictionary*)getPressedState {
797+
return [NSDictionary dictionaryWithDictionary:_pressingRecords];
798+
}
796799
@end
797800

798801
namespace {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,12 @@
5757
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
5858
timestamp:(NSTimeInterval)timestamp;
5959

60+
/**
61+
* Returns the keyboard pressed state.
62+
*
63+
* Returns the keyboard pressed state. The dictionary contains one entry per
64+
* pressed keys, mapping from the logical key to the physical key.
65+
*/
66+
- (nonnull NSDictionary*)getPressedState;
67+
6068
@end

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ - (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDele
119119
_processingEvent = FALSE;
120120
_viewDelegate = viewDelegate;
121121

122+
FlutterMethodChannel* keyboardChannel =
123+
[FlutterMethodChannel methodChannelWithName:@"flutter/keyboard"
124+
binaryMessenger:[_viewDelegate getBinaryMessenger]
125+
codec:[FlutterStandardMethodCodec sharedInstance]];
126+
[keyboardChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
127+
[self handleKeyboardMethodCall:call result:result];
128+
}];
122129
_primaryResponders = [[NSMutableArray alloc] init];
123130
[self addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc]
124131
initWithSendEvent:^(const FlutterKeyEvent& event,
@@ -151,6 +158,14 @@ - (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDele
151158
return self;
152159
}
153160

161+
- (void)handleKeyboardMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
162+
if ([[call method] isEqualToString:@"getKeyboardState"]) {
163+
result([self getPressedState]);
164+
} else {
165+
result(FlutterMethodNotImplemented);
166+
}
167+
}
168+
154169
- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder {
155170
[_primaryResponders addObject:responder];
156171
}
@@ -328,4 +343,17 @@ - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
328343
[embedderResponder syncModifiersIfNeeded:modifierFlags timestamp:timestamp];
329344
}
330345

346+
/**
347+
* Returns the keyboard pressed state.
348+
*
349+
* Returns the keyboard pressed state. The dictionary contains one entry per
350+
* pressed keys, mapping from the logical key to the physical key.
351+
*/
352+
- (nonnull NSDictionary*)getPressedState {
353+
// The embedder responder is the first element in _primaryResponders.
354+
FlutterEmbedderKeyResponder* embedderResponder =
355+
(FlutterEmbedderKeyResponder*)_primaryResponders[0];
356+
return [embedderResponder getPressedState];
357+
}
358+
331359
@end

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

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
using flutter::testing::keycodes::kLogicalKeyM;
2323
using flutter::testing::keycodes::kLogicalKeyQ;
2424
using flutter::testing::keycodes::kLogicalKeyT;
25+
using flutter::testing::keycodes::kPhysicalKeyA;
2526

2627
using flutter::LayoutClue;
2728

@@ -211,6 +212,10 @@ - (void)respondTextInputWith:(BOOL)response;
211212
- (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
212213
forTypes:(uint32_t)typeMask;
213214

215+
- (id)lastKeyboardChannelResult;
216+
217+
- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message;
218+
214219
@property(readonly, nonatomic, strong) FlutterKeyboardManager* manager;
215220
@property(nonatomic, nullable, strong) NSResponder* nextResponder;
216221

@@ -237,6 +242,10 @@ @implementation KeyboardTester {
237242

238243
flutter::KeyboardLayoutNotifier _keyboardLayoutNotifier;
239244
const MockLayoutData* _currentLayout;
245+
246+
id _keyboardChannelResult;
247+
NSObject<FlutterBinaryMessenger>* _messengerMock;
248+
FlutterBinaryMessageHandler _keyboardHandler;
240249
}
241250

242251
- (nonnull instancetype)init {
@@ -252,17 +261,21 @@ - (nonnull instancetype)init {
252261

253262
_currentLayout = &kUsLayout;
254263

255-
id messengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger));
256-
OCMStub([messengerMock sendOnChannel:@"flutter/keyevent"
257-
message:[OCMArg any]
258-
binaryReply:[OCMArg any]])
264+
_messengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger));
265+
OCMStub([_messengerMock sendOnChannel:@"flutter/keyevent"
266+
message:[OCMArg any]
267+
binaryReply:[OCMArg any]])
259268
.andCall(self, @selector(handleChannelMessage:message:binaryReply:));
260-
269+
OCMStub([_messengerMock setMessageHandlerOnChannel:@"flutter/keyboard"
270+
binaryMessageHandler:[OCMArg any]])
271+
.andCall(self, @selector(setKeyboardChannelHandler:handler:));
272+
OCMStub([_messengerMock sendOnChannel:@"flutter/keyboard" message:[OCMArg any]])
273+
.andCall(self, @selector(handleKeyboardChannelMessage:message:));
261274
id viewDelegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardViewDelegate));
262275
OCMStub([viewDelegateMock nextResponder]).andReturn(_nextResponder);
263276
OCMStub([viewDelegateMock onTextInputKeyEvent:[OCMArg any]])
264277
.andCall(self, @selector(handleTextInputKeyEvent:));
265-
OCMStub([viewDelegateMock getBinaryMessenger]).andReturn(messengerMock);
278+
OCMStub([viewDelegateMock getBinaryMessenger]).andReturn(_messengerMock);
266279
OCMStub([viewDelegateMock sendKeyEvent:FlutterKeyEvent {} callback:nil userData:nil])
267280
.ignoringNonObjectArgs()
268281
.andCall(self, @selector(handleEmbedderEvent:callback:userData:));
@@ -276,6 +289,10 @@ - (nonnull instancetype)init {
276289
return self;
277290
}
278291

292+
- (id)lastKeyboardChannelResult {
293+
return _keyboardChannelResult;
294+
}
295+
279296
- (void)respondEmbedderCallsWith:(BOOL)response {
280297
_embedderHandler = ^(const FlutterKeyEvent* event, FlutterAsyncKeyCallback callback) {
281298
callback(response);
@@ -327,6 +344,10 @@ - (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
327344
_typeStorageMask = typeMask;
328345
}
329346

347+
- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message {
348+
[_messengerMock sendOnChannel:@"flutter/keyboard" message:message];
349+
}
350+
330351
- (void)setLayout:(const MockLayoutData&)layout {
331352
_currentLayout = &layout;
332353
if (_keyboardLayoutNotifier != nil) {
@@ -364,6 +385,12 @@ - (void)handleChannelMessage:(NSString*)channel
364385
});
365386
}
366387

388+
- (void)handleKeyboardChannelMessage:(NSString*)channel message:(NSData* _Nullable)message {
389+
_keyboardHandler(message, ^(id result) {
390+
_keyboardChannelResult = result;
391+
});
392+
}
393+
367394
- (BOOL)handleTextInputKeyEvent:(NSEvent*)event {
368395
if (_typeStorage != nil && (_typeStorageMask & kTextCall) != 0) {
369396
[_typeStorage addObject:@(kTextCall)];
@@ -382,13 +409,19 @@ - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
382409
return LayoutClue{cluePair & kCharMask, (cluePair & kDeadKeyMask) != 0};
383410
}
384411

412+
- (void)setKeyboardChannelHandler:(NSString*)channel handler:(FlutterBinaryMessageHandler)handler {
413+
_keyboardHandler = handler;
414+
}
415+
385416
@end
386417

387418
@interface FlutterKeyboardManagerUnittestsObjC : NSObject
388419
- (bool)singlePrimaryResponder;
389420
- (bool)doublePrimaryResponder;
390421
- (bool)textInputPlugin;
391422
- (bool)emptyNextResponder;
423+
- (bool)getPressedState;
424+
- (bool)keyboardChannelGetPressedState;
392425
- (bool)racingConditionBetweenKeyAndText;
393426
- (bool)correctLogicalKeyForLayouts;
394427
@end
@@ -410,6 +443,14 @@ - (bool)correctLogicalKeyForLayouts;
410443
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] emptyNextResponder]);
411444
}
412445

446+
TEST(FlutterKeyboardManagerUnittests, GetPressedState) {
447+
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] getPressedState]);
448+
}
449+
450+
TEST(FlutterKeyboardManagerUnittests, KeyboardChannelGetPressedState) {
451+
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] keyboardChannelGetPressedState]);
452+
}
453+
413454
TEST(FlutterKeyboardManagerUnittests, RacingConditionBetweenKeyAndText) {
414455
ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] racingConditionBetweenKeyAndText]);
415456
}
@@ -563,6 +604,44 @@ - (bool)emptyNextResponder {
563604
return true;
564605
}
565606

607+
- (bool)getPressedState {
608+
KeyboardTester* tester = [[KeyboardTester alloc] init];
609+
610+
[tester respondEmbedderCallsWith:false];
611+
[tester respondChannelCallsWith:false];
612+
[tester respondTextInputWith:false];
613+
[tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)];
614+
615+
NSDictionary* pressingRecords = [tester.manager getPressedState];
616+
EXPECT_EQ([pressingRecords count], 1u);
617+
EXPECT_EQ(pressingRecords[@(kPhysicalKeyA)], @(kLogicalKeyA));
618+
619+
return true;
620+
}
621+
622+
- (bool)keyboardChannelGetPressedState {
623+
KeyboardTester* tester = [[KeyboardTester alloc] init];
624+
625+
[tester respondEmbedderCallsWith:false];
626+
[tester respondChannelCallsWith:false];
627+
[tester respondTextInputWith:false];
628+
[tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)];
629+
630+
FlutterMethodCall* getKeyboardStateMethodCall =
631+
[FlutterMethodCall methodCallWithMethodName:@"getKeyboardState" arguments:nil];
632+
NSData* getKeyboardStateMessage =
633+
[[FlutterStandardMethodCodec sharedInstance] encodeMethodCall:getKeyboardStateMethodCall];
634+
[tester sendKeyboardChannelMessage:getKeyboardStateMessage];
635+
636+
id encodedResult = [tester lastKeyboardChannelResult];
637+
id decoded = [[FlutterStandardMethodCodec sharedInstance] decodeEnvelope:encodedResult];
638+
639+
EXPECT_EQ([decoded count], 1u);
640+
EXPECT_EQ(decoded[@(kPhysicalKeyA)], @(kLogicalKeyA));
641+
642+
return true;
643+
}
644+
566645
// Regression test for https://github.com/flutter/flutter/issues/82673.
567646
- (bool)racingConditionBetweenKeyAndText {
568647
KeyboardTester* tester = [[KeyboardTester alloc] init];

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,12 @@ typedef struct {
8686
*/
8787
- (flutter::LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift;
8888

89+
/**
90+
* Returns the keyboard pressed state.
91+
*
92+
* Returns the keyboard pressed state. The dictionary contains one entry per
93+
* pressed keys, mapping from the logical key to the physical key.
94+
*/
95+
- (nonnull NSDictionary*)getPressedState;
96+
8997
@end

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,10 @@ - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
955955
return LayoutClue{0, false};
956956
}
957957

958+
- (nonnull NSDictionary*)getPressedState {
959+
return [_keyboardManager getPressedState];
960+
}
961+
958962
#pragma mark - NSResponder
959963

960964
- (BOOL)acceptsFirstResponder {

0 commit comments

Comments
 (0)