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 
8 #import "KeyCodeMap_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 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  };
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  // 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
FlutterBasicMessageChannel
Definition: FlutterChannels.h:37
flutter::kModifierFlagMetaLeft
@ kModifierFlagMetaLeft
Definition: KeyCodeMap_Internal.h:83
flutter::kModifierFlagAltRight
@ kModifierFlagAltRight
Definition: KeyCodeMap_Internal.h:86
FlutterChannelKeyResponder.h
flutter::kModifierFlagMetaRight
@ kModifierFlagMetaRight
Definition: KeyCodeMap_Internal.h:84
flutter::kModifierFlagControlLeft
@ kModifierFlagControlLeft
Definition: KeyCodeMap_Internal.h:80
flutter::kModifierFlagAltLeft
@ kModifierFlagAltLeft
Definition: KeyCodeMap_Internal.h:85
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:10
FlutterCodecs.h
flutter::kModifierFlagShiftRight
@ kModifierFlagShiftRight
Definition: KeyCodeMap_Internal.h:82
FlutterKeyPrimaryResponder-p::layoutMap
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap
Definition: FlutterKeyPrimaryResponder.h:46
FlutterChannelKeyResponder
Definition: FlutterChannelKeyResponder.h:20
FlutterViewController_Internal.h
KeyCodeMap_Internal.h
flutter::kModifierFlagShiftLeft
@ kModifierFlagShiftLeft
Definition: KeyCodeMap_Internal.h:81
flutter::kModifierFlagControlRight
@ kModifierFlagControlRight
Definition: KeyCodeMap_Internal.h:87