From 05ef97b15db5a62a3fb4d7c53b1adc1e0c5dc7dc Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Wed, 31 May 2023 15:15:06 +0200 Subject: [PATCH] [macOS] Return keyboard pressed state --- .../Source/FlutterEmbedderKeyResponder.h | 8 ++ .../Source/FlutterEmbedderKeyResponder.mm | 3 + .../framework/Source/FlutterKeyboardManager.h | 8 ++ .../Source/FlutterKeyboardManager.mm | 28 ++++++ .../Source/FlutterKeyboardManagerTest.mm | 91 +++++++++++++++++-- .../Source/FlutterKeyboardViewDelegate.h | 8 ++ .../framework/Source/FlutterViewController.mm | 4 + 7 files changed, 144 insertions(+), 6 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h index 7a3b9ee51bdca..117b8c3113696 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h @@ -39,4 +39,12 @@ typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */, - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags timestamp:(NSTimeInterval)timestamp; +/** + * Returns the keyboard pressed state. + * + * Returns the keyboard pressed state. The dictionary contains one entry per + * pressed keys, mapping from the logical key to the physical key. + */ +- (nonnull NSDictionary*)getPressedState; + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 4a5d14f4d847f..37f67f94ee8f4 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -793,6 +793,9 @@ - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags guard:guardedCallback]; } +- (nonnull NSDictionary*)getPressedState { + return [NSDictionary dictionaryWithDictionary:_pressingRecords]; +} @end namespace { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index 64ca1a35f65b0..ae29439f9ba46 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -57,4 +57,12 @@ - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags timestamp:(NSTimeInterval)timestamp; +/** + * Returns the keyboard pressed state. + * + * Returns the keyboard pressed state. The dictionary contains one entry per + * pressed keys, mapping from the logical key to the physical key. + */ +- (nonnull NSDictionary*)getPressedState; + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index ad50476a47675..2238c300bf9a3 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -119,6 +119,13 @@ - (nonnull instancetype)initWithViewDelegate:(nonnull id)responder { [_primaryResponders addObject:responder]; } @@ -328,4 +343,17 @@ - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags [embedderResponder syncModifiersIfNeeded:modifierFlags timestamp:timestamp]; } +/** + * Returns the keyboard pressed state. + * + * Returns the keyboard pressed state. The dictionary contains one entry per + * pressed keys, mapping from the logical key to the physical key. + */ +- (nonnull NSDictionary*)getPressedState { + // The embedder responder is the first element in _primaryResponders. + FlutterEmbedderKeyResponder* embedderResponder = + (FlutterEmbedderKeyResponder*)_primaryResponders[0]; + return [embedderResponder getPressedState]; +} + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerTest.mm index d9b2ba994d7f2..4bb5766791102 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerTest.mm @@ -22,6 +22,7 @@ using flutter::testing::keycodes::kLogicalKeyM; using flutter::testing::keycodes::kLogicalKeyQ; using flutter::testing::keycodes::kLogicalKeyT; +using flutter::testing::keycodes::kPhysicalKeyA; using flutter::LayoutClue; @@ -211,6 +212,10 @@ - (void)respondTextInputWith:(BOOL)response; - (void)recordCallTypesTo:(nonnull NSMutableArray*)typeStorage forTypes:(uint32_t)typeMask; +- (id)lastKeyboardChannelResult; + +- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message; + @property(readonly, nonatomic, strong) FlutterKeyboardManager* manager; @property(nonatomic, nullable, strong) NSResponder* nextResponder; @@ -237,6 +242,10 @@ @implementation KeyboardTester { flutter::KeyboardLayoutNotifier _keyboardLayoutNotifier; const MockLayoutData* _currentLayout; + + id _keyboardChannelResult; + NSObject* _messengerMock; + FlutterBinaryMessageHandler _keyboardHandler; } - (nonnull instancetype)init { @@ -252,17 +261,21 @@ - (nonnull instancetype)init { _currentLayout = &kUsLayout; - id messengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger)); - OCMStub([messengerMock sendOnChannel:@"flutter/keyevent" - message:[OCMArg any] - binaryReply:[OCMArg any]]) + _messengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger)); + OCMStub([_messengerMock sendOnChannel:@"flutter/keyevent" + message:[OCMArg any] + binaryReply:[OCMArg any]]) .andCall(self, @selector(handleChannelMessage:message:binaryReply:)); - + OCMStub([_messengerMock setMessageHandlerOnChannel:@"flutter/keyboard" + binaryMessageHandler:[OCMArg any]]) + .andCall(self, @selector(setKeyboardChannelHandler:handler:)); + OCMStub([_messengerMock sendOnChannel:@"flutter/keyboard" message:[OCMArg any]]) + .andCall(self, @selector(handleKeyboardChannelMessage:message:)); id viewDelegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardViewDelegate)); OCMStub([viewDelegateMock nextResponder]).andReturn(_nextResponder); OCMStub([viewDelegateMock onTextInputKeyEvent:[OCMArg any]]) .andCall(self, @selector(handleTextInputKeyEvent:)); - OCMStub([viewDelegateMock getBinaryMessenger]).andReturn(messengerMock); + OCMStub([viewDelegateMock getBinaryMessenger]).andReturn(_messengerMock); OCMStub([viewDelegateMock sendKeyEvent:FlutterKeyEvent {} callback:nil userData:nil]) .ignoringNonObjectArgs() .andCall(self, @selector(handleEmbedderEvent:callback:userData:)); @@ -276,6 +289,10 @@ - (nonnull instancetype)init { return self; } +- (id)lastKeyboardChannelResult { + return _keyboardChannelResult; +} + - (void)respondEmbedderCallsWith:(BOOL)response { _embedderHandler = ^(const FlutterKeyEvent* event, FlutterAsyncKeyCallback callback) { callback(response); @@ -327,6 +344,10 @@ - (void)recordCallTypesTo:(nonnull NSMutableArray*)typeStorage _typeStorageMask = typeMask; } +- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message { + [_messengerMock sendOnChannel:@"flutter/keyboard" message:message]; +} + - (void)setLayout:(const MockLayoutData&)layout { _currentLayout = &layout; if (_keyboardLayoutNotifier != nil) { @@ -364,6 +385,12 @@ - (void)handleChannelMessage:(NSString*)channel }); } +- (void)handleKeyboardChannelMessage:(NSString*)channel message:(NSData* _Nullable)message { + _keyboardHandler(message, ^(id result) { + _keyboardChannelResult = result; + }); +} + - (BOOL)handleTextInputKeyEvent:(NSEvent*)event { if (_typeStorage != nil && (_typeStorageMask & kTextCall) != 0) { [_typeStorage addObject:@(kTextCall)]; @@ -382,6 +409,10 @@ - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift { return LayoutClue{cluePair & kCharMask, (cluePair & kDeadKeyMask) != 0}; } +- (void)setKeyboardChannelHandler:(NSString*)channel handler:(FlutterBinaryMessageHandler)handler { + _keyboardHandler = handler; +} + @end @interface FlutterKeyboardManagerUnittestsObjC : NSObject @@ -389,6 +420,8 @@ - (bool)singlePrimaryResponder; - (bool)doublePrimaryResponder; - (bool)textInputPlugin; - (bool)emptyNextResponder; +- (bool)getPressedState; +- (bool)keyboardChannelGetPressedState; - (bool)racingConditionBetweenKeyAndText; - (bool)correctLogicalKeyForLayouts; @end @@ -410,6 +443,14 @@ - (bool)correctLogicalKeyForLayouts; ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] emptyNextResponder]); } +TEST(FlutterKeyboardManagerUnittests, GetPressedState) { + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] getPressedState]); +} + +TEST(FlutterKeyboardManagerUnittests, KeyboardChannelGetPressedState) { + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] keyboardChannelGetPressedState]); +} + TEST(FlutterKeyboardManagerUnittests, RacingConditionBetweenKeyAndText) { ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] racingConditionBetweenKeyAndText]); } @@ -563,6 +604,44 @@ - (bool)emptyNextResponder { return true; } +- (bool)getPressedState { + KeyboardTester* tester = [[KeyboardTester alloc] init]; + + [tester respondEmbedderCallsWith:false]; + [tester respondChannelCallsWith:false]; + [tester respondTextInputWith:false]; + [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)]; + + NSDictionary* pressingRecords = [tester.manager getPressedState]; + EXPECT_EQ([pressingRecords count], 1u); + EXPECT_EQ(pressingRecords[@(kPhysicalKeyA)], @(kLogicalKeyA)); + + return true; +} + +- (bool)keyboardChannelGetPressedState { + KeyboardTester* tester = [[KeyboardTester alloc] init]; + + [tester respondEmbedderCallsWith:false]; + [tester respondChannelCallsWith:false]; + [tester respondTextInputWith:false]; + [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)]; + + FlutterMethodCall* getKeyboardStateMethodCall = + [FlutterMethodCall methodCallWithMethodName:@"getKeyboardState" arguments:nil]; + NSData* getKeyboardStateMessage = + [[FlutterStandardMethodCodec sharedInstance] encodeMethodCall:getKeyboardStateMethodCall]; + [tester sendKeyboardChannelMessage:getKeyboardStateMessage]; + + id encodedResult = [tester lastKeyboardChannelResult]; + id decoded = [[FlutterStandardMethodCodec sharedInstance] decodeEnvelope:encodedResult]; + + EXPECT_EQ([decoded count], 1u); + EXPECT_EQ(decoded[@(kPhysicalKeyA)], @(kLogicalKeyA)); + + return true; +} + // Regression test for https://github.com/flutter/flutter/issues/82673. - (bool)racingConditionBetweenKeyAndText { KeyboardTester* tester = [[KeyboardTester alloc] init]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h index cc559fe3f063e..f051b95613326 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardViewDelegate.h @@ -86,4 +86,12 @@ typedef struct { */ - (flutter::LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift; +/** + * Returns the keyboard pressed state. + * + * Returns the keyboard pressed state. The dictionary contains one entry per + * pressed keys, mapping from the logical key to the physical key. + */ +- (nonnull NSDictionary*)getPressedState; + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index 5129ef4ebe0f0..209eb32d71162 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -955,6 +955,10 @@ - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift { return LayoutClue{0, false}; } +- (nonnull NSDictionary*)getPressedState { + return [_keyboardManager getPressedState]; +} + #pragma mark - NSResponder - (BOOL)acceptsFirstResponder {