5#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h"
10#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h"
11#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h"
12#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
13#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h"
14#import "flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap_Internal.h"
24#ifdef DEBUG_PRINT_LAYOUT
26NSString* 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];
40typedef NSResponder* _NSResponderPtr;
41typedef _Nullable _NSResponderPtr (^NextResponderProvider)();
43bool isEascii(
const LayoutClue& clue) {
44 return clue.character < 256 && !clue.isDeadKey;
47typedef void (^VoidBlock)();
51typedef NSResponder* _NSResponderPtr;
52typedef _Nullable _NSResponderPtr (^NextResponderProvider)();
60@property(nonatomic, weak) id<FlutterKeyboardViewDelegate>
viewDelegate;
65@property(nonatomic) NSMutableArray<id<FlutterKeyPrimaryResponder>>*
primaryResponders;
71@property(nonatomic) NSMutableDictionary<NSNumber*, NSNumber*>*
layoutMap;
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"]) {
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;
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 {
void(^ FlutterResult)(id _Nullable result)
FLUTTER_DARWIN_EXPORT NSObject const * FlutterMethodNotImplemented
NSMutableArray< id< FlutterKeyPrimaryResponder > > * primaryResponders
instancetype messageChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMessageCodec > *codec)
nonnull NSDictionary * getPressedState()
instancetype sharedInstance()
NSMutableArray< NSEvent * > * pendingEvents
NSEvent * eventBeingDispatched
id< FlutterKeyboardViewDelegate > viewDelegate
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap
void setMethodCallHandler:(FlutterMethodCallHandler _Nullable handler)
instancetype methodChannelWithName:binaryMessenger:codec:(NSString *name,[binaryMessenger] NSObject< FlutterBinaryMessenger > *messenger,[codec] NSObject< FlutterMethodCodec > *codec)
instancetype sharedInstance()
void(^ FlutterAsyncKeyCallback)(BOOL handled)
const std::vector< LayoutGoal > kLayoutGoals
void syncModifiersIfNeeded:timestamp:(NSEventModifierFlags modifierFlags,[timestamp] NSTimeInterval timestamp)
void handleEvent:callback:(nonnull NSEvent *event,[callback] nonnull FlutterAsyncKeyCallback callback)
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap