Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit c365940

Browse files
committed
[macOS] Synchronize modifiers from mouse events for RawKeyboard
1 parent bb7cf1a commit c365940

File tree

4 files changed

+124
-8
lines changed

4 files changed

+124
-8
lines changed

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

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,73 @@ - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)cha
4040
return self;
4141
}
4242

43+
/// Checks single modifier flag from event flags and sends appropriate key event
44+
/// if it is different from the previous state.
45+
- (void)checkModifierFlag:(NSUInteger)modifierFlag
46+
forEventFlags:(NSEventModifierFlags)modifierFlags
47+
keyCode:(NSUInteger)keyCode
48+
timestamp:(NSTimeInterval)timestamp {
49+
if ((modifierFlags & modifierFlag) != (_previouslyPressedFlags & modifierFlag)) {
50+
uint64_t newFlags = (_previouslyPressedFlags & ~modifierFlag) | (modifierFlags & modifierFlag);
51+
52+
// Sets combined flag if either left or right modifier is pressed, unsets otherwise.
53+
auto updateCombinedFlag = [&](uint64_t side1, uint64_t side2, NSEventModifierFlags flag) {
54+
if (newFlags & (side1 | side2)) {
55+
newFlags |= flag;
56+
} else {
57+
newFlags &= ~flag;
58+
}
59+
};
60+
updateCombinedFlag(flutter::kModifierFlagShiftLeft, flutter::kModifierFlagShiftRight,
61+
NSEventModifierFlagShift);
62+
updateCombinedFlag(flutter::kModifierFlagControlLeft, flutter::kModifierFlagControlRight,
63+
NSEventModifierFlagControl);
64+
updateCombinedFlag(flutter::kModifierFlagAltLeft, flutter::kModifierFlagAltRight,
65+
NSEventModifierFlagOption);
66+
updateCombinedFlag(flutter::kModifierFlagMetaLeft, flutter::kModifierFlagMetaRight,
67+
NSEventModifierFlagCommand);
68+
69+
NSEvent* event = [NSEvent keyEventWithType:NSEventTypeFlagsChanged
70+
location:NSZeroPoint
71+
modifierFlags:newFlags
72+
timestamp:timestamp
73+
windowNumber:0
74+
context:nil
75+
characters:@""
76+
charactersIgnoringModifiers:@""
77+
isARepeat:NO
78+
keyCode:keyCode];
79+
[self handleEvent:event
80+
callback:^(BOOL){
81+
}];
82+
};
83+
}
84+
85+
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
86+
timestamp:(NSTimeInterval)timestamp {
87+
modifierFlags = modifierFlags & ~0x100;
88+
if (_previouslyPressedFlags == modifierFlags) {
89+
return;
90+
}
91+
92+
[flutter::modifierFlagToKeyCode
93+
enumerateKeysAndObjectsUsingBlock:^(NSNumber* flag, NSNumber* keyCode, BOOL* stop) {
94+
[self checkModifierFlag:[flag unsignedShortValue]
95+
forEventFlags:modifierFlags
96+
keyCode:[keyCode unsignedShortValue]
97+
timestamp:timestamp];
98+
}];
99+
100+
// Caps lock is not included in the modifierFlagToKeyCode map.
101+
[self checkModifierFlag:NSEventModifierFlagCapsLock
102+
forEventFlags:modifierFlags
103+
keyCode:0x00000039 // kVK_CapsLock
104+
timestamp:timestamp];
105+
106+
// At the end we should end up with the same modifier flags as the system.
107+
FML_DCHECK(_previouslyPressedFlags == modifierFlags);
108+
}
109+
43110
- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
44111
// Remove the modifier bits that Flutter is not interested in.
45112
NSEventModifierFlags modifierFlags = event.modifierFlags & ~0x100;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ typedef void (^FlutterAsyncKeyCallback)(BOOL handled);
2424
@required
2525
- (void)handleEvent:(nonnull NSEvent*)event callback:(nonnull FlutterAsyncKeyCallback)callback;
2626

27+
/**
28+
* Synchronize the modifier flags if necessary. The new modifier flag would usually come from mouse
29+
* event and may be out of sync with current keyboard state if the modifier flags have changed while
30+
* window was not key.
31+
*/
32+
@required
33+
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
34+
timestamp:(NSTimeInterval)timestamp;
35+
2736
/* A map from macOS key code to logical keyboard.
2837
*
2938
* The map is assigned on initialization, and updated when the user changes

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -337,10 +337,9 @@ - (void)buildLayout {
337337

338338
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
339339
timestamp:(NSTimeInterval)timestamp {
340-
// The embedder responder is the first element in _primaryResponders.
341-
FlutterEmbedderKeyResponder* embedderResponder =
342-
(FlutterEmbedderKeyResponder*)_primaryResponders[0];
343-
[embedderResponder syncModifiersIfNeeded:modifierFlags timestamp:timestamp];
340+
for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
341+
[responder syncModifiersIfNeeded:modifierFlags timestamp:timestamp];
342+
}
344343
}
345344

346345
/**

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

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -957,13 +957,17 @@ - (bool)testMouseDownUpEventsSentToNextResponder {
957957

958958
- (bool)testModifierKeysAreSynthesizedOnMouseMove {
959959
id engineMock = flutter::testing::CreateMockFlutterEngine(@"");
960+
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
961+
OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
962+
[engineMock binaryMessenger])
963+
.andReturn(binaryMessengerMock);
964+
960965
// Need to return a real renderer to allow view controller to load.
961966
FlutterRenderer* renderer_ = [[FlutterRenderer alloc] initWithFlutterEngine:engineMock];
962967
OCMStub([engineMock renderer]).andReturn(renderer_);
963968

964969
// Capture calls to sendKeyEvent
965-
__block NSMutableArray<KeyEventWrapper*>* events =
966-
[[NSMutableArray<KeyEventWrapper*> alloc] init];
970+
__block NSMutableArray<KeyEventWrapper*>* events = [NSMutableArray array];
967971
OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
968972
callback:nil
969973
userData:nil])
@@ -973,6 +977,17 @@ - (bool)testModifierKeysAreSynthesizedOnMouseMove {
973977
[events addObject:[[KeyEventWrapper alloc] initWithEvent:event]];
974978
}));
975979

980+
__block NSMutableArray<NSDictionary*>* channelEvents = [NSMutableArray array];
981+
OCMStub([binaryMessengerMock sendOnChannel:@"flutter/keyevent"
982+
message:[OCMArg any]
983+
binaryReply:[OCMArg any]])
984+
.andDo((^(NSInvocation* invocation) {
985+
NSData* data;
986+
[invocation getArgument:&data atIndex:3];
987+
id event = [[FlutterJSONMessageCodec sharedInstance] decode:data];
988+
[channelEvents addObject:event];
989+
}));
990+
976991
FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock
977992
nibName:@""
978993
bundle:nil];
@@ -987,12 +1002,27 @@ - (bool)testModifierKeysAreSynthesizedOnMouseMove {
9871002
// For each modifier key, check that key events are synthesized.
9881003
for (NSNumber* keyCode in flutter::keyCodeToModifierFlag) {
9891004
FlutterKeyEvent* event;
1005+
NSDictionary* channelEvent;
9901006
NSNumber* logicalKey;
9911007
NSNumber* physicalKey;
992-
NSNumber* flag = flutter::keyCodeToModifierFlag[keyCode];
1008+
NSEventModifierFlags flag = [flutter::keyCodeToModifierFlag[keyCode] unsignedLongValue];
1009+
1010+
// Cocoa event always contain combined flags.
1011+
if (flag & (flutter::kModifierFlagShiftLeft | flutter::kModifierFlagShiftRight)) {
1012+
flag |= NSEventModifierFlagShift;
1013+
}
1014+
if (flag & (flutter::kModifierFlagControlLeft | flutter::kModifierFlagControlRight)) {
1015+
flag |= NSEventModifierFlagControl;
1016+
}
1017+
if (flag & (flutter::kModifierFlagAltLeft | flutter::kModifierFlagAltRight)) {
1018+
flag |= NSEventModifierFlagOption;
1019+
}
1020+
if (flag & (flutter::kModifierFlagMetaLeft | flutter::kModifierFlagMetaRight)) {
1021+
flag |= NSEventModifierFlagCommand;
1022+
}
9931023

9941024
// Should synthesize down event.
995-
NSEvent* mouseEvent = flutter::testing::CreateMouseEvent([flag unsignedLongValue]);
1025+
NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(flag);
9961026
[viewController mouseMoved:mouseEvent];
9971027
EXPECT_EQ([events count], 1u);
9981028
event = events[0].data;
@@ -1003,6 +1033,11 @@ - (bool)testModifierKeysAreSynthesizedOnMouseMove {
10031033
EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
10041034
EXPECT_EQ(event->synthesized, true);
10051035

1036+
channelEvent = channelEvents[0];
1037+
EXPECT_TRUE([channelEvent[@"type"] isEqual:@"keydown"]);
1038+
EXPECT_TRUE([channelEvent[@"keyCode"] isEqual:keyCode]);
1039+
EXPECT_TRUE([channelEvent[@"modifiers"] isEqual:@(flag)]);
1040+
10061041
// Should synthesize up event.
10071042
mouseEvent = flutter::testing::CreateMouseEvent(0x00);
10081043
[viewController mouseMoved:mouseEvent];
@@ -1015,7 +1050,13 @@ - (bool)testModifierKeysAreSynthesizedOnMouseMove {
10151050
EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
10161051
EXPECT_EQ(event->synthesized, true);
10171052

1053+
channelEvent = channelEvents[1];
1054+
EXPECT_TRUE([channelEvent[@"type"] isEqual:@"keyup"]);
1055+
EXPECT_TRUE([channelEvent[@"keyCode"] isEqual:keyCode]);
1056+
EXPECT_TRUE([channelEvent[@"modifiers"] isEqual:@(0)]);
1057+
10181058
[events removeAllObjects];
1059+
[channelEvents removeAllObjects];
10191060
};
10201061

10211062
return true;

0 commit comments

Comments
 (0)