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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,9 @@ - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
guard:guardedCallback];
}

- (nonnull NSDictionary*)getPressedState {
return [NSDictionary dictionaryWithDictionary:_pressingRecords];
}
@end

namespace {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ - (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDele
_processingEvent = FALSE;
_viewDelegate = viewDelegate;

FlutterMethodChannel* keyboardChannel =
[FlutterMethodChannel methodChannelWithName:@"flutter/keyboard"
binaryMessenger:[_viewDelegate getBinaryMessenger]
codec:[FlutterStandardMethodCodec sharedInstance]];
[keyboardChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[self handleKeyboardMethodCall:call result:result];
}];
_primaryResponders = [[NSMutableArray alloc] init];
[self addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc]
initWithSendEvent:^(const FlutterKeyEvent& event,
Expand Down Expand Up @@ -151,6 +158,14 @@ - (nonnull instancetype)initWithViewDelegate:(nonnull id<FlutterKeyboardViewDele
return self;
}

- (void)handleKeyboardMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
if ([[call method] isEqualToString:@"getKeyboardState"]) {
result([self getPressedState]);
} else {
result(FlutterMethodNotImplemented);
}
}

- (void)addPrimaryResponder:(nonnull id<FlutterKeyPrimaryResponder>)responder {
[_primaryResponders addObject:responder];
}
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

- (id)lastKeyboardChannelResult;

- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message;

@property(readonly, nonatomic, strong) FlutterKeyboardManager* manager;
@property(nonatomic, nullable, strong) NSResponder* nextResponder;

Expand All @@ -237,6 +242,10 @@ @implementation KeyboardTester {

flutter::KeyboardLayoutNotifier _keyboardLayoutNotifier;
const MockLayoutData* _currentLayout;

id _keyboardChannelResult;
NSObject<FlutterBinaryMessenger>* _messengerMock;
FlutterBinaryMessageHandler _keyboardHandler;
}

- (nonnull instancetype)init {
Expand All @@ -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:));
Expand All @@ -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);
Expand Down Expand Up @@ -327,6 +344,10 @@ - (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
_typeStorageMask = typeMask;
}

- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message {
[_messengerMock sendOnChannel:@"flutter/keyboard" message:message];
}

- (void)setLayout:(const MockLayoutData&)layout {
_currentLayout = &layout;
if (_keyboardLayoutNotifier != nil) {
Expand Down Expand Up @@ -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)];
Expand All @@ -382,13 +409,19 @@ - (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
- (bool)singlePrimaryResponder;
- (bool)doublePrimaryResponder;
- (bool)textInputPlugin;
- (bool)emptyNextResponder;
- (bool)getPressedState;
- (bool)keyboardChannelGetPressedState;
- (bool)racingConditionBetweenKeyAndText;
- (bool)correctLogicalKeyForLayouts;
@end
Expand All @@ -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]);
}
Expand Down Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down