Flutter Engine
The Flutter Engine
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
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
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 layoutMap;
33
34- (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel {
35 self = [super init];
36 if (self != nil) {
38 _previouslyPressedFlags = 0;
39 }
40 return self;
41}
42
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)targetMask
46 forEventFlags:(NSEventModifierFlags)eventFlags
47 keyCode:(NSUInteger)keyCode
48 timestamp:(NSTimeInterval)timestamp {
49 NSAssert((targetMask & (targetMask - 1)) == 0, @"targetMask must only have one bit set");
50 if ((eventFlags & targetMask) != (_previouslyPressedFlags & targetMask)) {
51 uint64_t newFlags = (_previouslyPressedFlags & ~targetMask) | (eventFlags & targetMask);
52
53 // Sets combined flag if either left or right modifier is pressed, unsets otherwise.
54 auto updateCombinedFlag = [&](uint64_t side1, uint64_t side2, NSEventModifierFlags flag) {
55 if (newFlags & (side1 | side2)) {
56 newFlags |= flag;
57 } else {
58 newFlags &= ~flag;
59 }
60 };
62 NSEventModifierFlagShift);
64 NSEventModifierFlagControl);
66 NSEventModifierFlagOption);
68 NSEventModifierFlagCommand);
69
70 NSEvent* event = [NSEvent keyEventWithType:NSEventTypeFlagsChanged
71 location:NSZeroPoint
72 modifierFlags:newFlags
73 timestamp:timestamp
74 windowNumber:0
75 context:nil
76 characters:@""
77 charactersIgnoringModifiers:@""
78 isARepeat:NO
79 keyCode:keyCode];
80 [self handleEvent:event
81 callback:^(BOOL){
82 }];
83 };
84}
85
86- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
87 timestamp:(NSTimeInterval)timestamp {
88 modifierFlags = modifierFlags & ~0x100;
89 if (_previouslyPressedFlags == modifierFlags) {
90 return;
91 }
92
93 [flutter::modifierFlagToKeyCode
94 enumerateKeysAndObjectsUsingBlock:^(NSNumber* flag, NSNumber* keyCode, BOOL* stop) {
95 [self checkModifierFlag:[flag unsignedShortValue]
96 forEventFlags:modifierFlags
97 keyCode:[keyCode unsignedShortValue]
98 timestamp:timestamp];
99 }];
100
101 // Caps lock is not included in the modifierFlagToKeyCode map.
102 [self checkModifierFlag:NSEventModifierFlagCapsLock
103 forEventFlags:modifierFlags
104 keyCode:0x00000039 // kVK_CapsLock
105 timestamp:timestamp];
106
107 // At the end we should end up with the same modifier flags as the event.
108 FML_DCHECK(_previouslyPressedFlags == modifierFlags);
109}
110
111- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback {
112 // Remove the modifier bits that Flutter is not interested in.
113 NSEventModifierFlags modifierFlags = event.modifierFlags & ~0x100;
114 NSString* type;
115 switch (event.type) {
116 case NSEventTypeKeyDown:
117 type = @"keydown";
118 break;
119 case NSEventTypeKeyUp:
120 type = @"keyup";
121 break;
122 case NSEventTypeFlagsChanged:
123 if (modifierFlags < _previouslyPressedFlags) {
124 type = @"keyup";
125 } else if (modifierFlags > _previouslyPressedFlags) {
126 type = @"keydown";
127 } else {
128 // ignore duplicate modifiers; This can happen in situations like switching
129 // between application windows when MacOS only sends the up event to new window.
130 callback(true);
131 return;
132 }
133 break;
134 default: {
135 NSAssert(false, @"Unexpected key event type (got %lu).", event.type);
136 callback(false);
137 }
138 }
139 _previouslyPressedFlags = modifierFlags;
140 NSMutableDictionary* keyMessage = [@{
141 @"keymap" : @"macos",
142 @"type" : type,
143 @"keyCode" : @(event.keyCode),
144 @"modifiers" : @(modifierFlags),
145 } mutableCopy];
146 // Calling these methods on any other type of event
147 // (e.g NSEventTypeFlagsChanged) will raise an exception.
148 if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) {
149 keyMessage[@"characters"] = event.characters;
150 keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers;
151 }
152 NSNumber* specifiedLogicalKey = layoutMap[@(event.keyCode)];
153 if (specifiedLogicalKey != nil) {
154 keyMessage[@"specifiedLogicalKey"] = specifiedLogicalKey;
155 }
156 [self.channel sendMessage:keyMessage
157 reply:^(id reply) {
158 if (!reply) {
159 return callback(true);
160 }
161 // Only propagate the event to other responders if the framework didn't
162 // handle it.
163 callback([[reply valueForKey:@"handled"] boolValue]);
164 }];
165}
166
167#pragma mark - Private
168
169@end
FlutterMethodChannel * _channel
GLenum type
FlutterSemanticsFlag flag
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
#define FML_DCHECK(condition)
Definition: logging.h:103
FlutterBasicMessageChannel * channel
void(^ FlutterAsyncKeyCallback)(BOOL handled)
@ kModifierFlagControlLeft
@ kModifierFlagControlRight
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap