25#ifdef DEBUG_PRINT_LAYOUT
27NSString* debugFormatLayoutData(NSString* debugLayoutData,
32 stringWithFormat:
@" %@%@0x%d%04x, 0x%d%04x,", debugLayoutData,
33 keyCode % 4 == 0 ? [NSString stringWithFormat:
@"\n/* 0x%02x */ ", keyCode]
35 clue1.isDeadKey, clue1.character, clue2.isDeadKey, clue2.character];
39bool isEascii(
const LayoutClue& clue) {
40 return clue.character < 256 && !clue.isDeadKey;
43typedef void (^VoidBlock)();
49@property(nonatomic, readonly) NSEvent*
event;
50@property(nonatomic, readonly) id<FlutterKeyboardManagerEventContext>
context;
52- (instancetype)initWithEvent:(NSEvent*)event
53 context:(nonnull
id<FlutterKeyboardManagerEventContext>)context;
59 id<FlutterKeyboardManagerEventContext>
_context;
62- (instancetype)initWithEvent:(NSEvent*)event
63 context:(
id<FlutterKeyboardManagerEventContext>)context {
79@property(nonatomic, weak) id<FlutterKeyboardManagerDelegate> delegate;
84@property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>* primaryResponders;
86@property(nonatomic) NSMutableArray<FlutterEventWithContext*>* pendingEvents;
88@property(nonatomic)
BOOL processingEvent;
90@property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>* layoutMap;
92@property(nonatomic, nullable) NSEvent* eventBeingDispatched;
98- (void)addPrimaryResponder:(nonnull
id<FlutterKeyPrimaryResponder>)responder;
106- (void)processNextEvent;
116- (void)performProcessEvent:(NSEvent*)event
117 withContext:(nonnull
id<FlutterKeyboardManagerEventContext>)context
118 onFinish:(nonnull VoidBlock)onFinish;
124- (void)dispatchTextEvent:(nonnull NSEvent*)pendingEvent
125 withContext:(nonnull
id<FlutterKeyboardManagerEventContext>)context;
138- (nonnull instancetype)initWithDelegate:(nonnull
id<FlutterKeyboardManagerDelegate>)delegate {
142- (nonnull instancetype)initWithDelegate:(nonnull
id<FlutterKeyboardManagerDelegate>)delegate
146 _processingEvent = FALSE;
147 _delegate = delegate;
155 [
self handleKeyboardMethodCall:call result:result];
158 _primaryResponders = [[NSMutableArray alloc] init];
160 __weak __typeof__(
self) weakSelf =
self;
162 initWithSendEvent:^(const FlutterKeyEvent& event,
163 FlutterKeyEventCallback callback,
165 __strong __typeof__(weakSelf) strongSelf = weakSelf;
166 [strongSelf.delegate sendKeyEvent:event
179 _pendingEvents = [[NSMutableArray alloc] init];
180 _layoutMap = [NSMutableDictionary<NSNumber*, NSNumber*> dictionary];
182 _keyboardLayout = keyboardLayout;
185 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
186 responder.layoutMap = _layoutMap;
193 if ([[call method] isEqualToString:
@"getKeyboardState"]) {
194 result([
self getPressedState]);
200- (void)addPrimaryResponder:(nonnull
id<FlutterKeyPrimaryResponder>)responder {
201 [_primaryResponders addObject:responder];
204- (void)handleEvent:(nonnull NSEvent*)event
205 withContext:(nonnull
id<FlutterKeyboardManagerEventContext>)context {
211 if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp &&
212 event.type != NSEventTypeFlagsChanged) {
217 [
self processNextEvent];
220- (
BOOL)isDispatchingKeyEvent:(NSEvent*)event {
221 return _eventBeingDispatched == event;
224#pragma mark - Private
226- (void)processNextEvent {
227 @
synchronized(
self) {
228 if (_processingEvent || [_pendingEvents count] == 0) {
231 _processingEvent =
TRUE;
235 [_pendingEvents removeObjectAtIndex:0];
237 __weak __typeof__(
self) weakSelf =
self;
238 VoidBlock onFinish = ^() {
239 weakSelf.processingEvent = FALSE;
240 [weakSelf processNextEvent];
242 [
self performProcessEvent:pendingEvent.event withContext:pendingEvent.context onFinish:onFinish];
245- (void)performProcessEvent:(NSEvent*)event
246 withContext:(
id<FlutterKeyboardManagerEventContext>)context
247 onFinish:(VoidBlock)onFinish {
251 NSAssert([_primaryResponders count] >= 0,
@"At least one primary responder must be added.");
253 __weak __typeof__(
self) weakSelf =
self;
254 __block
int unreplied = [_primaryResponders count];
255 __block
BOOL anyHandled =
false;
259 NSAssert(unreplied >= 0,
@"More primary responders replied than possible.");
260 anyHandled = anyHandled || handled;
261 if (unreplied == 0) {
263 [weakSelf dispatchTextEvent:event withContext:context];
269 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
270 [responder handleEvent:event callback:replyCallback];
274- (void)dispatchTextEvent:(NSEvent*)event
275 withContext:(
id<FlutterKeyboardManagerEventContext>)context {
276 if ([context onTextInputKeyEvent:event]) {
279 NSResponder* nextResponder = context.nextResponder;
280 if (nextResponder == nil) {
283 NSAssert(_eventBeingDispatched == nil,
@"An event is already being dispached.");
284 _eventBeingDispatched = event;
285 switch (event.type) {
286 case NSEventTypeKeyDown:
287 if ([nextResponder respondsToSelector:@selector(keyDown:)]) {
288 [nextResponder keyDown:event];
291 case NSEventTypeKeyUp:
292 if ([nextResponder respondsToSelector:@selector(keyUp:)]) {
293 [nextResponder keyUp:event];
296 case NSEventTypeFlagsChanged:
297 if ([nextResponder respondsToSelector:@selector(flagsChanged:)]) {
298 [nextResponder flagsChanged:event];
302 NSAssert(
false,
@"Unexpected key event type (got %lu).", event.type);
304 NSAssert(_eventBeingDispatched != nil,
@"_eventBeingDispatched was cleared unexpectedly.");
305 _eventBeingDispatched = nil;
309 [_layoutMap removeAllObjects];
311 std::map<uint32_t, LayoutGoal> mandatoryGoalsByChar;
312 std::map<uint32_t, LayoutGoal> usLayoutGoalsByKeyCode;
314 if (goal.mandatory) {
315 mandatoryGoalsByChar[goal.keyChar] = goal;
317 usLayoutGoalsByKeyCode[goal.keyCode] = goal;
324 const uint16_t kMaxKeyCode = 0x32;
325#ifdef DEBUG_PRINT_LAYOUT
326 NSString* debugLayoutData =
@"";
328 for (uint16_t keyCode = 0; keyCode <= kMaxKeyCode; keyCode += 1) {
329 std::vector<LayoutClue> thisKeyClues = {
330 [_keyboardLayout lookUpLayoutForKeyCode:keyCode shift:false],
331 [_keyboardLayout lookUpLayoutForKeyCode:keyCode shift:true]};
332#ifdef DEBUG_PRINT_LAYOUT
334 debugFormatLayoutData(debugLayoutData, keyCode, thisKeyClues[0], thisKeyClues[1]);
343 for (
const LayoutClue& clue : thisKeyClues) {
344 uint32_t keyChar = clue.isDeadKey ? 0 : clue.character;
345 auto matchingGoal = mandatoryGoalsByChar.find(keyChar);
346 if (matchingGoal != mandatoryGoalsByChar.end()) {
348 NSAssert(_layoutMap[@(keyCode)] == nil,
@"Attempting to assign an assigned key code.");
349 _layoutMap[@(keyCode)] = @(keyChar);
350 mandatoryGoalsByChar.erase(matchingGoal);
354 bool hasAnyEascii = isEascii(thisKeyClues[0]) || isEascii(thisKeyClues[1]);
356 auto foundUsLayoutGoal = usLayoutGoalsByKeyCode.find(keyCode);
357 if (foundUsLayoutGoal != usLayoutGoalsByKeyCode.end() && _layoutMap[@(keyCode)] == nil &&
359 _layoutMap[@(keyCode)] = @(foundUsLayoutGoal->second.keyChar);
362#ifdef DEBUG_PRINT_LAYOUT
363 NSLog(
@"%@", debugLayoutData);
367 for (
auto mandatoryGoalIter : mandatoryGoalsByChar) {
368 const LayoutGoal& goal = mandatoryGoalIter.second;
369 _layoutMap[@(goal.keyCode)] = @(goal.keyChar);
373- (void)syncModifiersIfNeeded:(NSEventModifierFlags)modifierFlags
374 timestamp:(NSTimeInterval)timestamp {
375 for (id<FlutterKeyPrimaryResponder> responder in _primaryResponders) {
376 [responder syncModifiersIfNeeded:modifierFlags timestamp:timestamp];
381 _processingEvent = FALSE;
382 [_pendingEvents removeAllObjects];
391- (nonnull NSDictionary*)getPressedState {
398- (void)keyboardLayoutDidChange {
403 _keyboardLayout = keyboardLayout;
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
instancetype messageChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMessageCodec > *codec)
nonnull NSDictionary * getPressedState()
id< FlutterKeyboardManagerEventContext > context
id< FlutterKeyboardLayoutDelegate > delegate
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
void(^ FlutterAsyncKeyCallback)(BOOL handled)
id< FlutterKeyboardManagerEventContext > _context
const std::vector< LayoutGoal > kLayoutGoals
instancetype sharedInstance()