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

Commit 2e8bb9c

Browse files
committed
[macos] Synthesize modifier keys events on pointer events
1 parent e6d9fff commit 2e8bb9c

6 files changed

+92
-0
lines changed

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,13 @@ typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */,
3030
*/
3131
- (nonnull instancetype)initWithSendEvent:(_Nonnull FlutterSendEmbedderKeyEvent)sendEvent;
3232

33+
/**
34+
* Synthesize modifier keys events.
35+
*
36+
* If needed, synthesize modifier keys up and down events by comparing their
37+
* current pressing states with the given modifier flags.
38+
*/
39+
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
40+
timestamp:(NSTimeInterval)timestamp;
41+
3342
@end

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,19 @@ - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId {
780780
[_pendingResponses removeObjectForKey:@(responseId)];
781781
}
782782

783+
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
784+
timestamp:(NSTimeInterval)timestamp {
785+
FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
786+
// Do nothing.
787+
};
788+
FlutterKeyCallbackGuard* guardedCallback =
789+
[[FlutterKeyCallbackGuard alloc] initWithCallback:replyCallback];
790+
[self synchronizeModifiers:modifierFlags
791+
ignoringFlags:0
792+
timestamp:timestamp
793+
guard:guardedCallback];
794+
}
795+
783796
@end
784797

785798
namespace {

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#import <Foundation/Foundation.h>
66
#import <OCMock/OCMock.h>
77

8+
#import "KeyCodeMap_Internal.h"
89
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h"
910
#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
1011
#import "flutter/testing/testing.h"
@@ -1216,4 +1217,54 @@ - (void)dealloc {
12161217
[events removeAllObjects];
12171218
}
12181219

1220+
// Synthesize modifier keys events if needed.
1221+
TEST(FlutterEmbedderKeyResponderUnittests, SynchronizeModifiersIfNeeded) {
1222+
__block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
1223+
1224+
FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc]
1225+
initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
1226+
_Nullable _VoidPtr user_data) {
1227+
[events addObject:[[TestKeyEvent alloc] initWithEvent:&event
1228+
callback:callback
1229+
userData:user_data]];
1230+
}];
1231+
1232+
// Zeroed modifier flag should not synthesize events.
1233+
[responder syncModifiersIfNeeded:0x00 timestamp:0];
1234+
EXPECT_EQ([events count], 0u);
1235+
[events removeAllObjects];
1236+
1237+
// For each modifier key, check that key events are synthesized.
1238+
[flutter::keyCodeToModifierFlag
1239+
enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL* stop) {
1240+
FlutterKeyEvent* event;
1241+
NSNumber* logicalKey;
1242+
NSNumber* physicalKey;
1243+
1244+
// Should synthesize down event.
1245+
[responder syncModifiersIfNeeded:[flag unsignedLongValue] timestamp:0];
1246+
EXPECT_EQ([events count], 1u);
1247+
event = events[0].data;
1248+
logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1249+
physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1250+
EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
1251+
EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1252+
EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1253+
EXPECT_EQ(event->synthesized, true);
1254+
1255+
// Should synthesize up event.
1256+
[responder syncModifiersIfNeeded:0x00 timestamp:0];
1257+
EXPECT_EQ([events count], 2u);
1258+
event = events[1].data;
1259+
logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1260+
physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1261+
EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
1262+
EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1263+
EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1264+
EXPECT_EQ(event->synthesized, true);
1265+
1266+
[events removeAllObjects];
1267+
}];
1268+
}
1269+
12191270
} // namespace flutter::testing

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,13 @@
4848
*/
4949
- (BOOL)isDispatchingKeyEvent:(nonnull NSEvent*)event;
5050

51+
/**
52+
* Synthesize modifier keys events.
53+
*
54+
* If needed, synthesize modifier keys up and down events by comparing their
55+
* current pressing states with the given modifier flags.
56+
*/
57+
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
58+
timestamp:(NSTimeInterval)timestamp;
59+
5160
@end

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,4 +320,12 @@ - (void)buildLayout {
320320
}
321321
}
322322

323+
- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
324+
timestamp:(NSTimeInterval)timestamp {
325+
// The embedder responder is the first element in _primaryResponders.
326+
FlutterEmbedderKeyResponder* embedderResponder =
327+
(FlutterEmbedderKeyResponder*)_primaryResponders[0];
328+
[embedderResponder syncModifiersIfNeeded:modifierFlags timestamp:timestamp];
329+
}
330+
323331
@end

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,8 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase {
646646
flutterEvent.scroll_delta_x = -event.scrollingDeltaX * pixelsPerLine * scaleFactor;
647647
flutterEvent.scroll_delta_y = -event.scrollingDeltaY * pixelsPerLine * scaleFactor;
648648
}
649+
650+
[_keyboardManager syncModifiersIfNeeded:event.modifierFlags timestamp:event.timestamp];
649651
[_engine sendPointerEvent:flutterEvent];
650652

651653
// Update tracking of state as reported to Flutter.

0 commit comments

Comments
 (0)