Flutter Engine
 
Loading...
Searching...
No Matches
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
12
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) {
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 };
63 NSEventModifierFlagShift);
65 NSEventModifierFlagControl);
67 NSEventModifierFlagOption);
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
109- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
110 // Remove the modifier bits that Flutter is not interested in.
111 NSEventModifierFlags modifierFlags = event.modifierFlags & ~0x100;
112 NSString* type;
113 switch (event.type) {
114 case NSEventTypeKeyDown:
115 type = @"keydown";
116 break;
117 case NSEventTypeKeyUp:
118 type = @"keyup";
119 break;
120 case NSEventTypeFlagsChanged:
121 if (modifierFlags < _previouslyPressedFlags) {
122 type = @"keyup";
123 } else if (modifierFlags > _previouslyPressedFlags) {
124 type = @"keydown";
125 } else {
126 // ignore duplicate modifiers; This can happen in situations like switching
127 // between application windows when MacOS only sends the up event to new window.
128 callback(true);
129 return;
130 }
131 break;
132 default:
133 [[unlikely]] {
134 NSAssert(false, @"Unexpected key event type (got %lu).", event.type);
135 callback(false);
136 // This should not happen. Return to suppress clang-tidy warning on `type` being nil.
137 return;
138 }
139 }
140 _previouslyPressedFlags = modifierFlags;
141 NSMutableDictionary* keyMessage = [@{
142 @"keymap" : @"macos",
143 @"type" : type,
144 @"keyCode" : @(event.keyCode),
145 @"modifiers" : @(modifierFlags),
146 } mutableCopy];
147 // Calling these methods on any other type of event
148 // (e.g NSEventTypeFlagsChanged) will raise an exception.
149 if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) {
150 keyMessage[@"characters"] = event.characters;
151 keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers;
152 }
153 NSNumber* specifiedLogicalKey = layoutMap[@(event.keyCode)];
154 if (specifiedLogicalKey != nil) {
155 keyMessage[@"specifiedLogicalKey"] = specifiedLogicalKey;
156 }
157 [self.channel sendMessage:keyMessage
158 reply:^(id reply) {
159 if (!reply) {
160 return callback(true);
161 }
162 // Only propagate the event to other responders if the framework didn't
163 // handle it.
164 callback([[reply valueForKey:@"handled"] boolValue]);
165 }];
166}
167
168#pragma mark - Private
169
170@end
FlutterMethodChannel * _channel
GLenum type
const gchar * channel
FlutterDesktopBinaryReply callback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap