24 #ifdef DEBUG_PRINT_LAYOUT
26 NSString* debugFormatLayoutData(NSString* debugLayoutData,
31 stringWithFormat:
@" %@%@0x%d%04x, 0x%d%04x,", debugLayoutData,
32 keyCode % 4 == 0 ? [NSString stringWithFormat:
@"\n/* 0x%02x */ ", keyCode]
34 clue1.isDeadKey, clue1.character, clue2.isDeadKey, clue2.character];
40 typedef NSResponder* _NSResponderPtr;
41 typedef _Nullable _NSResponderPtr (^NextResponderProvider)();
43 bool isEascii(
const LayoutClue& clue) {
44 return clue.character < 256 && !clue.isDeadKey;
47 typedef void (^VoidBlock)();
51 typedef NSResponder* _NSResponderPtr;
52 typedef _Nullable _NSResponderPtr (^NextResponderProvider)();
60 @property(nonatomic, weak) id<FlutterKeyboardViewDelegate> viewDelegate;
65 @property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
67 @property(nonatomic) NSMutableArray<NSEvent*>* pendingEvents;
69 @property(nonatomic) BOOL processingEvent;
71 @property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* layoutMap;
73 @property(nonatomic, nullable) NSEvent* eventBeingDispatched;
87 - (void)processNextEvent;
97 - (void)performProcessEvent:(NSEvent*)event onFinish:(nonnull VoidBlock)onFinish;
103 - (void)dispatchTextEvent:(nonnull NSEvent*)pendingEvent;
113 NextResponderProvider _getNextResponder;
119 _processingEvent = FALSE;
120 _viewDelegate = viewDelegate;
127 [
self handleKeyboardMethodCall:call result:result];
129 _primaryResponders = [[NSMutableArray alloc] init];
131 initWithSendEvent:^(const FlutterKeyEvent& event,
132 FlutterKeyEventCallback callback,
134 [_viewDelegate sendKeyEvent:event
146 _pendingEvents = [[NSMutableArray alloc] init];
147 _layoutMap = [NSMutableDictionary<NSNumber*, NSNumber*> dictionary];
149 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
153 __weak __typeof__(
self) weakSelf =
self;
154 [_viewDelegate subscribeToKeyboardLayoutChange:^() {
155 [weakSelf buildLayout];
162 if ([[call method] isEqualToString:
@"getKeyboardState"]) {
163 result([
self getPressedState]);
170 [_primaryResponders addObject:responder];
173 - (void)handleEvent:(nonnull NSEvent*)event {
179 if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp &&
180 event.type != NSEventTypeFlagsChanged) {
184 [_pendingEvents addObject:event];
185 [
self processNextEvent];
188 - (BOOL)isDispatchingKeyEvent:(NSEvent*)event {
189 return _eventBeingDispatched == event;
192 #pragma mark - Private
194 - (void)processNextEvent {
195 @
synchronized(
self) {
196 if (_processingEvent || [_pendingEvents count] == 0) {
199 _processingEvent = TRUE;
202 NSEvent* pendingEvent = [_pendingEvents firstObject];
203 [_pendingEvents removeObjectAtIndex:0];
205 __weak __typeof__(
self) weakSelf = self;
206 VoidBlock onFinish = ^() {
207 weakSelf.processingEvent = FALSE;
208 [weakSelf processNextEvent];
210 [
self performProcessEvent:pendingEvent onFinish:onFinish];
213 - (void)performProcessEvent:(NSEvent*)event onFinish:(VoidBlock)onFinish {
217 NSAssert([_primaryResponders count] >= 0,
@"At least one primary responder must be added.");
219 __weak __typeof__(
self) weakSelf = self;
220 __block
int unreplied = [_primaryResponders count];
221 __block BOOL anyHandled = false;
225 NSAssert(unreplied >= 0,
@"More primary responders replied than possible.");
226 anyHandled = anyHandled || handled;
227 if (unreplied == 0) {
229 [weakSelf dispatchTextEvent:event];
235 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
240 - (void)dispatchTextEvent:(NSEvent*)event {
241 if ([_viewDelegate onTextInputKeyEvent:event]) {
244 NSResponder* nextResponder = _viewDelegate.nextResponder;
245 if (nextResponder == nil) {
248 NSAssert(_eventBeingDispatched == nil,
@"An event is already being dispached.");
249 _eventBeingDispatched = event;
250 switch (event.type) {
251 case NSEventTypeKeyDown:
252 if ([nextResponder respondsToSelector:
@selector(keyDown:)]) {
253 [nextResponder keyDown:event];
256 case NSEventTypeKeyUp:
257 if ([nextResponder respondsToSelector:
@selector(keyUp:)]) {
258 [nextResponder keyUp:event];
261 case NSEventTypeFlagsChanged:
262 if ([nextResponder respondsToSelector:
@selector(flagsChanged:)]) {
263 [nextResponder flagsChanged:event];
267 NSAssert(
false,
@"Unexpected key event type (got %lu).", event.type);
269 NSAssert(_eventBeingDispatched != nil,
@"_eventBeingDispatched was cleared unexpectedly.");
270 _eventBeingDispatched = nil;
273 - (void)buildLayout {
274 [_layoutMap removeAllObjects];
276 std::map<uint32_t, LayoutGoal> mandatoryGoalsByChar;
277 std::map<uint32_t, LayoutGoal> usLayoutGoalsByKeyCode;
279 if (goal.mandatory) {
280 mandatoryGoalsByChar[goal.keyChar] = goal;
282 usLayoutGoalsByKeyCode[goal.keyCode] = goal;
289 const uint16_t kMaxKeyCode = 0x32;
290 #ifdef DEBUG_PRINT_LAYOUT
291 NSString* debugLayoutData =
@"";
293 for (uint16_t keyCode = 0; keyCode <= kMaxKeyCode; keyCode += 1) {
294 std::vector<LayoutClue> thisKeyClues = {
295 [_viewDelegate lookUpLayoutForKeyCode:keyCode shift:false],
296 [_viewDelegate lookUpLayoutForKeyCode:keyCode shift:true]};
297 #ifdef DEBUG_PRINT_LAYOUT
299 debugFormatLayoutData(debugLayoutData, keyCode, thisKeyClues[0], thisKeyClues[1]);
308 for (
const LayoutClue& clue : thisKeyClues) {
309 uint32_t keyChar = clue.isDeadKey ? 0 : clue.character;
310 auto matchingGoal = mandatoryGoalsByChar.find(keyChar);
311 if (matchingGoal != mandatoryGoalsByChar.end()) {
313 NSAssert(_layoutMap[@(keyCode)] == nil,
@"Attempting to assign an assigned key code.");
314 _layoutMap[@(keyCode)] = @(keyChar);
315 mandatoryGoalsByChar.erase(matchingGoal);
319 bool hasAnyEascii = isEascii(thisKeyClues[0]) || isEascii(thisKeyClues[1]);
321 auto foundUsLayoutGoal = usLayoutGoalsByKeyCode.find(keyCode);
322 if (foundUsLayoutGoal != usLayoutGoalsByKeyCode.end() && _layoutMap[@(keyCode)] == nil &&
324 _layoutMap[@(keyCode)] = @(foundUsLayoutGoal->second.keyChar);
327 #ifdef DEBUG_PRINT_LAYOUT
328 NSLog(
@"%@", debugLayoutData);
332 for (
auto mandatoryGoalIter : mandatoryGoalsByChar) {
333 const LayoutGoal& goal = mandatoryGoalIter.second;
334 _layoutMap[@(goal.keyCode)] = @(goal.keyChar);
338 - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
339 timestamp:(NSTimeInterval)timestamp {
340 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
351 - (nonnull NSDictionary*)getPressedState {