| Flutter macOS Embedder |

FlutterChannelKeyResponder.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import <objc/message.h>
6
7 #import "FlutterChannelKeyResponder.h"
8 #import "KeyCodeMap_Internal.h"
9 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h"
10 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
11 #import "flutter/shell/platform/embedder/embedder.h"
12
13 @interface FlutterChannelKeyResponder ()
14
15 /**
16 * The channel used to communicate with Flutter.
17 */
18 @property(nonatomic) FlutterBasicMessageChannel* channel;
19
20 /**
21 * The |NSEvent.modifierFlags| of the last event received.
22 *
23 * Used to determine whether a FlagsChanged event should count as a keydown or
24 * a keyup event.
25 */
26 @property(nonatomic) uint64_t previouslyPressedFlags;
27
28 @end
29
30 @implementation FlutterChannelKeyResponder
31
32 // Synthesize properties declared in FlutterKeyPrimaryResponder protocol.
33 @synthesize layoutMap;
34
35 - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel {
36 self = [super init];
37 if (self != nil) {
38 _channel = channel;
39 _previouslyPressedFlags = 0;
40 }
41 return self;
42 }
43
44 /// Checks single modifier flag from event flags and sends appropriate key event
45 /// if it is different from the previous state.
46 - (void)checkModifierFlag:(NSUInteger)targetMask
47 forEventFlags:(NSEventModifierFlags)eventFlags
48 keyCode:(NSUInteger)keyCode
49 timestamp:(NSTimeInterval)timestamp {
50 NSAssert((targetMask & (targetMask - 1)) == 0, @"targetMask must only have one bit set");
51 if ((eventFlags & targetMask) != (_previouslyPressedFlags & targetMask)) {
52 uint64_t newFlags = (_previouslyPressedFlags & ~targetMask) | (eventFlags & targetMask);
53
54 // Sets combined flag if either left or right modifier is pressed, unsets otherwise.
55 auto updateCombinedFlag = [&](uint64_t side1, uint64_t side2, NSEventModifierFlags flag) {
56 if (newFlags & (side1 | side2)) {
57 newFlags |= flag;
58 } else {
59 newFlags &= ~flag;
60 }
61 };
62 updateCombinedFlag(flutter::kModifierFlagShiftLeft, flutter::kModifierFlagShiftRight,
63 NSEventModifierFlagShift);
64 updateCombinedFlag(flutter::kModifierFlagControlLeft, flutter::kModifierFlagControlRight,
65 NSEventModifierFlagControl);
66 updateCombinedFlag(flutter::kModifierFlagAltLeft, flutter::kModifierFlagAltRight,
67 NSEventModifierFlagOption);
68 updateCombinedFlag(flutter::kModifierFlagMetaLeft, flutter::kModifierFlagMetaRight,
69 NSEventModifierFlagCommand);
70
71 NSEvent* event = [NSEvent keyEventWithType:NSEventTypeFlagsChanged
72 location:NSZeroPoint
73 modifierFlags:newFlags
74 timestamp:timestamp
75 windowNumber:0
76 context:nil
77 characters:@""
78 charactersIgnoringModifiers:@""
79 isARepeat:NO
80 keyCode:keyCode];
81 [self handleEvent:event
82 callback:^(BOOL){
83 }];
84 };
85 }
86
87 - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
88 timestamp:(NSTimeInterval)timestamp {
89 modifierFlags = modifierFlags & ~0x100;
90 if (_previouslyPressedFlags == modifierFlags) {
91 return;
92 }
93
94 [flutter::modifierFlagToKeyCode
95 enumerateKeysAndObjectsUsingBlock:^(NSNumber* flag, NSNumber* keyCode, BOOL* stop) {
96 [self checkModifierFlag:[flag unsignedShortValue]
97 forEventFlags:modifierFlags
98 keyCode:[keyCode unsignedShortValue]
99 timestamp:timestamp];
100 }];
101
102 // Caps lock is not included in the modifierFlagToKeyCode map.
103 [self checkModifierFlag:NSEventModifierFlagCapsLock
104 forEventFlags:modifierFlags
105 keyCode:0x00000039 // kVK_CapsLock
106 timestamp:timestamp];
107
108 // At the end we should end up with the same modifier flags as the event.
109 FML_DCHECK(_previouslyPressedFlags == modifierFlags);
110 }
111
112 - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
113 // Remove the modifier bits that Flutter is not interested in.
114 NSEventModifierFlags modifierFlags = event.modifierFlags & ~0x100;
115 NSString* type;
116 switch (event.type) {
117 case NSEventTypeKeyDown:
118 type = @"keydown";
119 break;
120 case NSEventTypeKeyUp:
121 type = @"keyup";
122 break;
123 case NSEventTypeFlagsChanged:
124 if (modifierFlags < _previouslyPressedFlags) {
125 type = @"keyup";
126 } else if (modifierFlags > _previouslyPressedFlags) {
127 type = @"keydown";
128 } else {
129 // ignore duplicate modifiers; This can happen in situations like switching
130 // between application windows when MacOS only sends the up event to new window.
131 callback(true);
132 return;
133 }
134 break;
135 default:
136 [[unlikely]] {
137 NSAssert(false, @"Unexpected key event type (got %lu).", event.type);
138 callback(false);
139 // This should not happen. Return to suppress clang-tidy warning on `type` being nil.
140 return;
141 }
142 }
143 _previouslyPressedFlags = modifierFlags;
144 NSMutableDictionary* keyMessage = [@{
145 @"keymap" : @"macos",
146 @"type" : type,
147 @"keyCode" : @(event.keyCode),
148 @"modifiers" : @(modifierFlags),
149 } mutableCopy];
150 // Calling these methods on any other type of event
151 // (e.g NSEventTypeFlagsChanged) will raise an exception.
152 if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) {
153 keyMessage[@"characters"] = event.characters;
154 keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers;
155 }
156 NSNumber* specifiedLogicalKey = layoutMap[@(event.keyCode)];
157 if (specifiedLogicalKey != nil) {
158 keyMessage[@"specifiedLogicalKey"] = specifiedLogicalKey;
159 }
160 [self.channel sendMessage:keyMessage
161 reply:^(id reply) {
162 if (!reply) {
163 return callback(true);
164 }
165 // Only propagate the event to other responders if the framework didn't
166 // handle it.
167 callback([[reply valueForKey:@"handled"] boolValue]);
168 }];
169 }
170
171 #pragma mark - Private
172
173 @end
FlutterChannelKeyResponder.h
FlutterCodecs.h
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:10
FlutterViewController_Internal.h
KeyCodeMap_Internal.h
FlutterBasicMessageChannel
Definition: FlutterChannels.h:38
FlutterChannelKeyResponder
Definition: FlutterChannelKeyResponder.h:20
flutter::kModifierFlagShiftLeft
@ kModifierFlagShiftLeft
Definition: KeyCodeMap_Internal.h:81
flutter::kModifierFlagAltLeft
@ kModifierFlagAltLeft
Definition: KeyCodeMap_Internal.h:85
flutter::kModifierFlagMetaRight
@ kModifierFlagMetaRight
Definition: KeyCodeMap_Internal.h:84
flutter::kModifierFlagMetaLeft
@ kModifierFlagMetaLeft
Definition: KeyCodeMap_Internal.h:83
flutter::kModifierFlagControlLeft
@ kModifierFlagControlLeft
Definition: KeyCodeMap_Internal.h:80
flutter::kModifierFlagAltRight
@ kModifierFlagAltRight
Definition: KeyCodeMap_Internal.h:86
flutter::kModifierFlagControlRight
@ kModifierFlagControlRight
Definition: KeyCodeMap_Internal.h:87
flutter::kModifierFlagShiftRight
@ kModifierFlagShiftRight
Definition: KeyCodeMap_Internal.h:82
FlutterKeyPrimaryResponder-p::layoutMap
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap
Definition: FlutterKeyPrimaryResponder.h:46