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;
128 [
self handleKeyboardMethodCall:call result:result];
131 _primaryResponders = [[NSMutableArray alloc] init];
133 __weak __typeof__(
self) weakSelf =
self;
135 initWithSendEvent:^(const FlutterKeyEvent& event,
136 FlutterKeyEventCallback callback,
138 __strong __typeof__(weakSelf) strongSelf = weakSelf;
139 [strongSelf.viewDelegate sendKeyEvent:event
153 _pendingEvents = [[NSMutableArray alloc] init];
154 _layoutMap = [NSMutableDictionary<NSNumber*, NSNumber*> dictionary];
156 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
160 [_viewDelegate subscribeToKeyboardLayoutChange:^() {
161 [weakSelf buildLayout];
168 if ([[call method] isEqualToString:
@"getKeyboardState"]) {
169 result([
self getPressedState]);
176 [_primaryResponders addObject:responder];
179 - (void)handleEvent:(nonnull NSEvent*)event {
185 if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp &&
186 event.type != NSEventTypeFlagsChanged) {
190 [_pendingEvents addObject:event];
191 [
self processNextEvent];
194 - (BOOL)isDispatchingKeyEvent:(NSEvent*)event {
195 return _eventBeingDispatched == event;
198 #pragma mark - Private
200 - (void)processNextEvent {
201 @
synchronized(
self) {
202 if (_processingEvent || [_pendingEvents count] == 0) {
205 _processingEvent = TRUE;
208 NSEvent* pendingEvent = [_pendingEvents firstObject];
209 [_pendingEvents removeObjectAtIndex:0];
211 __weak __typeof__(
self) weakSelf = self;
212 VoidBlock onFinish = ^() {
213 weakSelf.processingEvent = FALSE;
214 [weakSelf processNextEvent];
216 [
self performProcessEvent:pendingEvent onFinish:onFinish];
219 - (void)performProcessEvent:(NSEvent*)event onFinish:(VoidBlock)onFinish {
223 NSAssert([_primaryResponders count] >= 0,
@"At least one primary responder must be added.");
225 __weak __typeof__(
self) weakSelf = self;
226 __block
int unreplied = [_primaryResponders count];
227 __block BOOL anyHandled = false;
231 NSAssert(unreplied >= 0,
@"More primary responders replied than possible.");
232 anyHandled = anyHandled || handled;
233 if (unreplied == 0) {
235 [weakSelf dispatchTextEvent:event];
241 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
246 - (void)dispatchTextEvent:(NSEvent*)event {
247 if ([_viewDelegate onTextInputKeyEvent:event]) {
250 NSResponder* nextResponder = _viewDelegate.nextResponder;
251 if (nextResponder == nil) {
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];
262 case NSEventTypeKeyUp:
263 if ([nextResponder respondsToSelector:
@selector(keyUp:)]) {
264 [nextResponder keyUp:event];
267 case NSEventTypeFlagsChanged:
268 if ([nextResponder respondsToSelector:
@selector(flagsChanged:)]) {
269 [nextResponder flagsChanged:event];
273 NSAssert(
false,
@"Unexpected key event type (got %lu).", event.type);
275 NSAssert(_eventBeingDispatched != nil,
@"_eventBeingDispatched was cleared unexpectedly.");
276 _eventBeingDispatched = nil;
279 - (void)buildLayout {
280 [_layoutMap removeAllObjects];
282 std::map<uint32_t, LayoutGoal> mandatoryGoalsByChar;
283 std::map<uint32_t, LayoutGoal> usLayoutGoalsByKeyCode;
285 if (goal.mandatory) {
286 mandatoryGoalsByChar[goal.keyChar] = goal;
288 usLayoutGoalsByKeyCode[goal.keyCode] = goal;
295 const uint16_t kMaxKeyCode = 0x32;
296 #ifdef DEBUG_PRINT_LAYOUT
297 NSString* debugLayoutData =
@"";
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
305 debugFormatLayoutData(debugLayoutData, keyCode, thisKeyClues[0], thisKeyClues[1]);
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()) {
319 NSAssert(_layoutMap[@(keyCode)] == nil,
@"Attempting to assign an assigned key code.");
320 _layoutMap[@(keyCode)] = @(keyChar);
321 mandatoryGoalsByChar.erase(matchingGoal);
325 bool hasAnyEascii = isEascii(thisKeyClues[0]) || isEascii(thisKeyClues[1]);
327 auto foundUsLayoutGoal = usLayoutGoalsByKeyCode.find(keyCode);
328 if (foundUsLayoutGoal != usLayoutGoalsByKeyCode.end() && _layoutMap[@(keyCode)] == nil &&
330 _layoutMap[@(keyCode)] = @(foundUsLayoutGoal->second.keyChar);
333 #ifdef DEBUG_PRINT_LAYOUT
334 NSLog(
@"%@", debugLayoutData);
338 for (
auto mandatoryGoalIter : mandatoryGoalsByChar) {
339 const LayoutGoal& goal = mandatoryGoalIter.second;
340 _layoutMap[@(goal.keyCode)] = @(goal.keyChar);
344 - (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
345 timestamp:(NSTimeInterval)timestamp {
346 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
357 - (nonnull NSDictionary*)getPressedState {