Flutter Engine
The Flutter Engine
Instance Methods | Properties | List of all members
FlutterKeyboardManager Class Reference

#import <FlutterKeyboardManager.h>

Inheritance diagram for FlutterKeyboardManager:

Instance Methods

(void) - addPrimaryResponder:
 
(void) - addSecondaryResponder:
 
(void) - handlePress:nextAction:
 
(nonnull instancetype) - initWithViewDelegate:
 
(void) - handleEvent:
 
(BOOL- isDispatchingKeyEvent:
 
(void) - syncModifiersIfNeeded:timestamp:
 
(nonnull NSDictionary *) - getPressedState
 
(nonnull instancetype) - init [implementation]
 
(void) - dispatchToSecondaryResponders:complete: [implementation]
 
(void) - handleKeyboardMethodCall:result: [implementation]
 
(BOOL- isDispatchingKeyEvent: [implementation]
 
(void) - processNextEvent [implementation]
 
(void) - performProcessEvent:onFinish: [implementation]
 
(void) - dispatchTextEvent: [implementation]
 
(void) - buildLayout [implementation]
 

Properties

NSMutableArray< id< FlutterKeyPrimaryResponder > > * primaryResponders [implementation]
 
NSMutableArray< id< FlutterKeySecondaryResponder > > * secondaryResponders [implementation]
 
id< FlutterKeyboardViewDelegateviewDelegate [implementation]
 
NSMutableArray< NSEvent * > * pendingEvents [implementation]
 
BOOL processingEvent [implementation]
 
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap [implementation]
 
NSEvent * eventBeingDispatched [implementation]
 

Detailed Description

A hub that manages how key events are dispatched to various Flutter key responders, and propagates it to the superclass if the Flutter key responders do not handle it.

This class manages one or more primary responders, as well as zero or more secondary responders.

An event that is received by |handlePresses| is first dispatched to all primary responders. Each primary responder responds asynchronously with a boolean, indicating whether it handles the event.

An event that is not handled by any primary responders is then passed to to the first secondary responder (in the chronological order of addition), which responds synchronously with a boolean, indicating whether it handles the event. If not, the event is passed to the next secondary responder, and so on.

The event is then handed back to the |completeCallback| from the original call to |handlePresses| so that it can respond synchronously to the OS if the event was not handled by the responders. The |completeCallback| is called on the platform thread because the platform thread is blocked by a nested event loop while the response from the framework is being collected, and it needs to be called on the platform thread to unblock the thread by exiting the nested event loop.

Preventing primary responders from receiving events is not supported, because in reality this class only supports two hardcoded responders (FlutterChannelKeyResponder and FlutterEmbedderKeyResponder), where the only purpose of supporting two is to maintain the legacy channel API during the deprecation window, after which the channel responder should be removed, and only one primary responder will exist.

Processes keyboard events and cooperate with |TextInputPlugin|.

A keyboard event goes through a few sections, each can choose to handled the event, and only unhandled events can move to the next section:

Definition at line 53 of file FlutterKeyboardManager.h.

Method Documentation

◆ addPrimaryResponder:

- (void) addPrimaryResponder: (nonnull id<FlutterKeyPrimaryResponder>)  responder

Add a primary responder, which asynchronously decides whether to handle an event.

Definition at line 26 of file FlutterKeyboardManager.mm.

45 :(nonnull id<FlutterKeyPrimaryResponder>)responder {
46 [_primaryResponders addObject:responder];
47}

◆ addSecondaryResponder:

- (void) addSecondaryResponder: (nonnull id<FlutterKeySecondaryResponder>)  responder

Add a secondary responder, which synchronously decides whether to handle an event in order if no earlier responders handle.

Definition at line 26 of file FlutterKeyboardManager.mm.

49 :(nonnull id<FlutterKeySecondaryResponder>)responder {
50 [_secondaryResponders addObject:responder];
51}

◆ buildLayout

- (void) buildLayout
implementation

Clears the current layout and build a new one based on the current layout.

Definition at line 73 of file FlutterKeyboardManager.mm.

279 {
280 [_layoutMap removeAllObjects];
281
282 std::map<uint32_t, LayoutGoal> mandatoryGoalsByChar;
283 std::map<uint32_t, LayoutGoal> usLayoutGoalsByKeyCode;
284 for (const LayoutGoal& goal : flutter::kLayoutGoals) {
285 if (goal.mandatory) {
286 mandatoryGoalsByChar[goal.keyChar] = goal;
287 } else {
288 usLayoutGoalsByKeyCode[goal.keyCode] = goal;
289 }
290 }
291
292 // Derive key mapping for each key code based on their layout clues.
293 // Key code 0x00 - 0x32 are typewriter keys (letters, digits, and symbols.)
294 // See keyCodeToPhysicalKey.
295 const uint16_t kMaxKeyCode = 0x32;
296#ifdef DEBUG_PRINT_LAYOUT
297 NSString* debugLayoutData = @"";
298#endif
299 for (uint16_t keyCode = 0; keyCode <= kMaxKeyCode; keyCode += 1) {
300 std::vector<LayoutClue> thisKeyClues = {
301 [_viewDelegate lookUpLayoutForKeyCode:keyCode shift:false],
302 [_viewDelegate lookUpLayoutForKeyCode:keyCode shift:true]};
303#ifdef DEBUG_PRINT_LAYOUT
304 debugLayoutData =
305 debugFormatLayoutData(debugLayoutData, keyCode, thisKeyClues[0], thisKeyClues[1]);
306#endif
307 // The logical key should be the first available clue from below:
308 //
309 // - Mandatory goal, if it matches any clue. This ensures that all alnum
310 // keys can be found somewhere.
311 // - US layout, if neither clue of the key is EASCII. This ensures that
312 // there are no non-latin logical keys.
313 // - Derived on the fly from keyCode & characters.
314 for (const LayoutClue& clue : thisKeyClues) {
315 uint32_t keyChar = clue.isDeadKey ? 0 : clue.character;
316 auto matchingGoal = mandatoryGoalsByChar.find(keyChar);
317 if (matchingGoal != mandatoryGoalsByChar.end()) {
318 // Found a key that produces a mandatory char. Use it.
319 NSAssert(_layoutMap[@(keyCode)] == nil, @"Attempting to assign an assigned key code.");
320 _layoutMap[@(keyCode)] = @(keyChar);
321 mandatoryGoalsByChar.erase(matchingGoal);
322 break;
323 }
324 }
325 bool hasAnyEascii = isEascii(thisKeyClues[0]) || isEascii(thisKeyClues[1]);
326 // See if any produced char meets the requirement as a logical key.
327 auto foundUsLayoutGoal = usLayoutGoalsByKeyCode.find(keyCode);
328 if (foundUsLayoutGoal != usLayoutGoalsByKeyCode.end() && _layoutMap[@(keyCode)] == nil &&
329 !hasAnyEascii) {
330 _layoutMap[@(keyCode)] = @(foundUsLayoutGoal->second.keyChar);
331 }
332 }
333#ifdef DEBUG_PRINT_LAYOUT
334 NSLog(@"%@", debugLayoutData);
335#endif
336
337 // Ensure all mandatory goals are assigned.
338 for (auto mandatoryGoalIter : mandatoryGoalsByChar) {
339 const LayoutGoal& goal = mandatoryGoalIter.second;
340 _layoutMap[@(goal.keyCode)] = @(goal.keyChar);
341 }
342}
const std::vector< LayoutGoal > kLayoutGoals

◆ dispatchTextEvent:

- (void) dispatchTextEvent: (NSEvent*)  event
implementation

Definition at line 73 of file FlutterKeyboardManager.mm.

246 :(NSEvent*)event {
247 if ([_viewDelegate onTextInputKeyEvent:event]) {
248 return;
249 }
250 NSResponder* nextResponder = _viewDelegate.nextResponder;
251 if (nextResponder == nil) {
252 return;
253 }
254 NSAssert(_eventBeingDispatched == nil, @"An event is already being dispached.");
255 _eventBeingDispatched = event;
256 switch (event.type) {
257 case NSEventTypeKeyDown:
258 if ([nextResponder respondsToSelector:@selector(keyDown:)]) {
259 [nextResponder keyDown:event];
260 }
261 break;
262 case NSEventTypeKeyUp:
263 if ([nextResponder respondsToSelector:@selector(keyUp:)]) {
264 [nextResponder keyUp:event];
265 }
266 break;
267 case NSEventTypeFlagsChanged:
268 if ([nextResponder respondsToSelector:@selector(flagsChanged:)]) {
269 [nextResponder flagsChanged:event];
270 }
271 break;
272 default:
273 NSAssert(false, @"Unexpected key event type (got %lu).", event.type);
274 }
275 NSAssert(_eventBeingDispatched != nil, @"_eventBeingDispatched was cleared unexpectedly.");
276 _eventBeingDispatched = nil;
277}
FlKeyEvent * event

◆ dispatchToSecondaryResponders:complete:

- (void) dispatchToSecondaryResponders: (nonnull FlutterUIPressProxy*)  press
complete: (ios(13.4))  API_AVAILABLE 
implementation

Definition at line 26 of file FlutterKeyboardManager.mm.

114 :(nonnull FlutterUIPressProxy*)press
115 complete:(nonnull KeyEventCompleteCallback)callback
116 API_AVAILABLE(ios(13.4)) {
117 if (@available(iOS 13.4, *)) {
118 // no-op
119 } else {
120 callback(false, press);
121 return;
122 }
123
124 for (id<FlutterKeySecondaryResponder> responder in _secondaryResponders) {
125 if ([responder handlePress:press]) {
126 callback(true, press);
127 return;
128 }
129 }
130 callback(false, press);
131}
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
void(^ KeyEventCompleteCallback)(bool, FlutterUIPressProxy *_Nonnull) API_AVAILABLE(ios(13.4))
SK_API sk_sp< SkSurface > ios(9.0)
API_AVAILABLE(ios(14.0), macos(11.0)) static NSString *MTLCommandEncoderErrorStateToString(MTLCommandEncoderErrorState state)

◆ getPressedState

- (nonnull NSDictionary *) getPressedState

Returns the keyboard pressed state.

Returns the keyboard pressed state. The dictionary contains one entry per pressed keys, mapping from the logical key to the physical key.

Definition at line 73 of file FlutterKeyboardManager.mm.

357 {
358 // The embedder responder is the first element in _primaryResponders.
359 FlutterEmbedderKeyResponder* embedderResponder =
360 (FlutterEmbedderKeyResponder*)_primaryResponders[0];
361 return [embedderResponder getPressedState];
362}

◆ handleEvent:

- (void) handleEvent: (nonnull NSEvent*)  event

Processes a key event.

Unhandled events will be dispatched to the text input system, and possibly the next responder afterwards.

Definition at line 73 of file FlutterKeyboardManager.mm.

179 :(nonnull NSEvent*)event {
180 // The `handleEvent` does not process the event immediately, but instead put
181 // events into a queue. Events are processed one by one by `processNextEvent`.
182
183 // Be sure to add a handling method in propagateKeyEvent when allowing more
184 // event types here.
185 if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp &&
186 event.type != NSEventTypeFlagsChanged) {
187 return;
188 }
189
190 [_pendingEvents addObject:event];
191 [self processNextEvent];
192}

◆ handleKeyboardMethodCall:result:

- (void) handleKeyboardMethodCall: (FlutterMethodCall*)  call
result: (FlutterResult result 
implementation

Definition at line 73 of file FlutterKeyboardManager.mm.

168 if ([[call method] isEqualToString:@"getKeyboardState"]) {
169 result([self getPressedState]);
170 } else {
172 }
173}
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
GAsyncResult * result
nonnull NSDictionary * getPressedState()
def call(args)
Definition: dom.py:159

◆ handlePress:nextAction:

- (void) handlePress: (nonnull FlutterUIPressProxy*)  press
nextAction: (ios(13.4))  API_AVAILABLE 

Dispatches a key press event to all responders, gathering their responses, and then calls the |nextAction| if the event was not handled.

Definition at line 26 of file FlutterKeyboardManager.mm.

53 :(nonnull FlutterUIPressProxy*)press
54 nextAction:(nonnull void (^)())next API_AVAILABLE(ios(13.4)) {
55 if (@available(iOS 13.4, *)) {
56 // no-op
57 } else {
58 return;
59 }
60
61 bool __block wasHandled = false;
62 KeyEventCompleteCallback completeCallback = ^void(bool handled, FlutterUIPressProxy* press) {
63 wasHandled = handled;
64 CFRunLoopStop(CFRunLoopGetCurrent());
65 };
66 switch (press.phase) {
67 case UIPressPhaseBegan:
68 case UIPressPhaseEnded: {
69 // Having no primary responders requires extra logic, but Flutter hard-codes
70 // all primary responders, so this is a situation that Flutter will never
71 // encounter.
72 NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
73
74 __block __weak __typeof(self) weakSelf = self;
75 __block NSUInteger unreplied = [self.primaryResponders count];
76 __block BOOL anyHandled = false;
77 FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
78 unreplied--;
79 NSAssert(unreplied >= 0, @"More primary responders replied than expected.");
80 anyHandled = anyHandled || handled;
81 if (unreplied == 0) {
82 if (!anyHandled && weakSelf) {
83 [weakSelf dispatchToSecondaryResponders:press complete:completeCallback];
84 } else {
85 completeCallback(true, press);
86 }
87 }
88 };
89 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
90 [responder handlePress:press callback:replyCallback];
91 }
92 // Create a nested run loop while we wait for a response from the
93 // framework. Once the completeCallback is called, this run loop will exit
94 // and the main one will resume. The completeCallback MUST be called, or
95 // the app will get stuck in this run loop indefinitely.
96 //
97 // We need to run in this mode so that UIKit doesn't give us new
98 // events until we are done processing this one.
100 break;
101 }
102 case UIPressPhaseChanged:
103 case UIPressPhaseCancelled:
104 case UIPressPhaseStationary:
105 break;
106 }
107 if (!wasHandled) {
108 next();
109 }
110}
int count
Definition: FontMgrTest.cpp:50
static float next(float f)
NSMutableArray< id< FlutterKeyPrimaryResponder > > * primaryResponders
static CFStringRef kMessageLoopCFRunLoopMode
void(^ FlutterAsyncKeyCallback)(BOOL handled)
static FLUTTER_ASSERT_ARC constexpr CFTimeInterval kDistantFuture
int BOOL
Definition: windows_types.h:37

◆ init

- (nonnull instancetype) init
implementation

Definition at line 26 of file FlutterKeyboardManager.mm.

36 {
37 self = [super init];
38 if (self != nil) {
39 _primaryResponders = [[NSMutableArray alloc] init];
40 _secondaryResponders = [[NSMutableArray alloc] init];
41 }
42 return self;
43}

◆ initWithViewDelegate:

- (nonnull instancetype) initWithViewDelegate: (nonnull id<FlutterKeyboardViewDelegate>)  viewDelegate
Initial value:
{
NextResponderProvider _getNextResponder

Create a keyboard manager.

The |viewDelegate| is a weak reference, typically implemented by |FlutterViewController|.

Definition at line 73 of file FlutterKeyboardManager.mm.

116 :(nonnull id<FlutterKeyboardViewDelegate>)viewDelegate {
117 self = [super init];
118 if (self != nil) {
119 _processingEvent = FALSE;
120 _viewDelegate = viewDelegate;
121
122 FlutterMethodChannel* keyboardChannel =
124 binaryMessenger:[_viewDelegate getBinaryMessenger]
126
127 [keyboardChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
128 [self handleKeyboardMethodCall:call result:result];
129 }];
130
131 _primaryResponders = [[NSMutableArray alloc] init];
132
133 __weak __typeof__(self) weakSelf = self;
134 [self addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc]
135 initWithSendEvent:^(const FlutterKeyEvent& event,
137 void* userData) {
138 __strong __typeof__(weakSelf) strongSelf = weakSelf;
139 [strongSelf.viewDelegate sendKeyEvent:event
140 callback:callback
141 userData:userData];
142 }]];
143
144 [self
145 addPrimaryResponder:[[FlutterChannelKeyResponder alloc]
146 initWithChannel:[FlutterBasicMessageChannel
147 messageChannelWithName:@"flutter/keyevent"
148 binaryMessenger:[_viewDelegate
149 getBinaryMessenger]
151 sharedInstance]]]];
152
153 _pendingEvents = [[NSMutableArray alloc] init];
154 _layoutMap = [NSMutableDictionary<NSNumber*, NSNumber*> dictionary];
155 [self buildLayout];
156 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
157 responder.layoutMap = _layoutMap;
158 }
159
160 [_viewDelegate subscribeToKeyboardLayoutChange:^() {
161 [weakSelf buildLayout];
162 }];
163 }
164 return self;
165}
void(* FlutterKeyEventCallback)(bool, void *)
Definition: embedder.h:1155
instancetype messageChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMessageCodec > *codec)
instancetype sharedInstance()
id< FlutterKeyboardViewDelegate > viewDelegate
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
return FALSE

◆ isDispatchingKeyEvent: [1/2]

- (BOOL) isDispatchingKeyEvent: (nonnull NSEvent *)  event

Returns yes if is event currently being redispatched.

In some instances (i.e. emoji shortcut) the event may be redelivered by cocoa as key equivalent to FlutterTextInput, in which case it shouldn't be processed again.

◆ isDispatchingKeyEvent: [2/2]

- (BOOL) isDispatchingKeyEvent: (NSEvent*)  event
implementation

Definition at line 73 of file FlutterKeyboardManager.mm.

194 :(NSEvent*)event {
195 return _eventBeingDispatched == event;
196}

◆ performProcessEvent:onFinish:

- (void) performProcessEvent: (NSEvent*)  event
onFinish: (VoidBlock)  onFinish 
implementation

Definition at line 73 of file FlutterKeyboardManager.mm.

219 :(NSEvent*)event onFinish:(VoidBlock)onFinish {
220 // Having no primary responders require extra logic, but Flutter hard-codes
221 // all primary responders, so this is a situation that Flutter will never
222 // encounter.
223 NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added.");
224
225 __weak __typeof__(self) weakSelf = self;
226 __block int unreplied = [_primaryResponders count];
227 __block BOOL anyHandled = false;
228
229 FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
230 unreplied -= 1;
231 NSAssert(unreplied >= 0, @"More primary responders replied than possible.");
232 anyHandled = anyHandled || handled;
233 if (unreplied == 0) {
234 if (!anyHandled) {
235 [weakSelf dispatchTextEvent:event];
236 }
237 onFinish();
238 }
239 };
240
241 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
242 [responder handleEvent:event callback:replyCallback];
243 }
244}

◆ processNextEvent

- (void) processNextEvent
implementation

Start processing the next event if not started already.

This function might initiate an async process, whose callback calls this function again.

Definition at line 73 of file FlutterKeyboardManager.mm.

200 {
201 @synchronized(self) {
202 if (_processingEvent || [_pendingEvents count] == 0) {
203 return;
204 }
205 _processingEvent = TRUE;
206 }
207
208 NSEvent* pendingEvent = [_pendingEvents firstObject];
209 [_pendingEvents removeObjectAtIndex:0];
210
211 __weak __typeof__(self) weakSelf = self;
212 VoidBlock onFinish = ^() {
213 weakSelf.processingEvent = FALSE;
214 [weakSelf processNextEvent];
215 };
216 [self performProcessEvent:pendingEvent onFinish:onFinish];
217}

◆ syncModifiersIfNeeded:timestamp:

- (void) syncModifiersIfNeeded: (NSEventModifierFlags)  modifierFlags
timestamp: (NSTimeInterval)  timestamp 

Synthesize modifier keys events.

If needed, synthesize modifier keys up and down events by comparing their current pressing states with the given modifier flags.

Definition at line 73 of file FlutterKeyboardManager.mm.

344 :(NSEventModifierFlags)modifierFlags
345 timestamp:(NSTimeInterval)timestamp {
346 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
347 [responder syncModifiersIfNeeded:modifierFlags timestamp:timestamp];
348 }
349}

Property Documentation

◆ eventBeingDispatched

- (NSEvent*) eventBeingDispatched
readwritenonatomicassignimplementation

Definition at line 73 of file FlutterKeyboardManager.mm.

◆ layoutMap

- (NSMutableDictionary<NSNumber*, NSNumber*>*) layoutMap
readwritenonatomicassignimplementation

Definition at line 71 of file FlutterKeyboardManager.mm.

◆ pendingEvents

- (NSMutableArray<NSEvent*>*) pendingEvents
readwritenonatomicassignimplementation

Definition at line 67 of file FlutterKeyboardManager.mm.

◆ primaryResponders

- (NSMutableArray<id<FlutterKeyPrimaryResponder> >*) primaryResponders
readwritenonatomicassignimplementation

The primary responders added by addPrimaryResponder.

Provided by category FlutterKeyboardManager(Tests).

Definition at line 20 of file FlutterKeyboardManager.mm.

◆ processingEvent

- (BOOL) processingEvent
readwritenonatomicassignimplementation

Definition at line 69 of file FlutterKeyboardManager.mm.

◆ secondaryResponders

- (NSMutableArray<id<FlutterKeySecondaryResponder> >*) secondaryResponders
readnonatomiccopyimplementation

The secondary responders added by addSecondaryResponder.

Definition at line 26 of file FlutterKeyboardManager.mm.

◆ viewDelegate

- (id<FlutterKeyboardViewDelegate>) viewDelegate
readwritenonatomicweakimplementation

The text input plugin set by initialization.

Definition at line 60 of file FlutterKeyboardManager.mm.


The documentation for this class was generated from the following files: