73@property(nonatomic, nullable) NSEvent* eventBeingDispatched;
79- (void)addPrimaryResponder:(nonnull
id<FlutterKeyPrimaryResponder>)responder;
97- (void)performProcessEvent:(NSEvent*)event onFinish:(nonnull VoidBlock)onFinish;
103- (void)dispatchTextEvent:(nonnull NSEvent*)pendingEvent;
113 NextResponderProvider _getNextResponder;
116- (nonnull instancetype)initWithViewDelegate:(nonnull
id<FlutterKeyboardViewDelegate>)viewDelegate {
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) {
157 responder.layoutMap = _layoutMap;
160 [_viewDelegate subscribeToKeyboardLayoutChange:^() {
161 [weakSelf buildLayout];
168 if ([[call method] isEqualToString:
@"getKeyboardState"]) {
175- (void)addPrimaryResponder:(nonnull
id<FlutterKeyPrimaryResponder>)responder {
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) {
242 [responder handleEvent:event callback:replyCallback];
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;
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);