Flutter Engine
The Flutter Engine
Instance Methods | Properties | List of all members
FlutterEmbedderKeyResponder Class Reference

#import <FlutterEmbedderKeyResponder.h>

Inheritance diagram for FlutterEmbedderKeyResponder:
<FlutterKeyPrimaryResponder> <FlutterKeyPrimaryResponder>

Instance Methods

(nonnull instancetype) - initWithSendEvent:
 
(nonnull instancetype) - initWithSendEvent:
 
(void) - syncModifiersIfNeeded:timestamp:
 
(nonnull NSDictionary *) - getPressedState
 
(nonnull instancetype) - initWithSendEvent: [implementation]
 
(void) - handlePress:callback: [implementation]
 
(void) - synchronizeModifiers: [implementation]
 
(void) - synthesizeCapsLockTapWithTimestamp: [implementation]
 
(void) - updateKey:asPressed: [implementation]
 
(void) - sendPrimaryFlutterEvent:callback: [implementation]
 
(void) - sendEmptyEvent [implementation]
 
(void) - synthesizeModifierEventOfType:timestamp:keyCode: [implementation]
 
(void) - handlePressBegin:callback: [implementation]
 
(void) - handlePressEnd:callback: [implementation]
 
(void) - handleResponse:forId: [implementation]
 
(UInt32) - fixSidedFlags:withLeftFlag:withRightFlag:withLeftKey:withRightKey:withKeyCode:keyDown:forFlags: [implementation]
 
(UInt32) - adjustModifiers: [implementation]
 
(void) - handleEvent:callback: [implementation]
 
(void) - synchronizeModifiers:ignoringFlags:timestamp:guard: [implementation]
 
(void) - sendSynthesizedFlutterEvent:guard: [implementation]
 
(void) - sendCapsLockTapWithTimestamp:synthesizeDown:callback: [implementation]
 
(void) - sendModifierEventOfType:timestamp:keyCode:synthesized:callback: [implementation]
 
(void) - handleDownEvent:callback: [implementation]
 
(void) - handleUpEvent:callback: [implementation]
 
(void) - handleCapsLockEvent:callback: [implementation]
 
(void) - handleFlagEvent:callback: [implementation]
 
- Instance Methods inherited from <FlutterKeyPrimaryResponder>
(void) - handlePress:callback:
 
(void) - handleEvent:callback:
 
(void) - syncModifiersIfNeeded:timestamp:
 

Properties

FlutterSendKeyEvent sendEvent [implementation]
 
FlutterSendEmbedderKeyEvent sendEvent [implementation]
 
NSMutableDictionary< NSNumber *, NSNumber * > * pressingRecords [implementation]
 
NSUInteger modifierFlagOfInterestMask [implementation]
 
NSUInteger lastModifierFlagsOfInterest [implementation]
 
uint64_t responseId [implementation]
 
NSMutableDictionary< NSNumber *, FlutterAsyncKeyCallback > * pendingResponses [implementation]
 
- Properties inherited from <FlutterKeyPrimaryResponder>
NSMutableDictionary< NSNumber *, NSNumber * > * layoutMap
 

Detailed Description

A primary responder of |FlutterKeyboardManager| that handles events by sending the converted events through a Dart hook to the framework.

This class interfaces with the HardwareKeyboard API in the framework.

A primary responder of |FlutterKeyboardManager| that handles events by sending the converted events through the embedder API.

This class communicates with the HardwareKeyboard API in the framework.

Definition at line 23 of file FlutterEmbedderKeyResponder.h.

Method Documentation

◆ adjustModifiers:

- (UInt32) adjustModifiers: (ios(13.4))  API_AVAILABLE
implementation

Because iOS differs from other platforms in that the modifier flags still contain the flag for the key that is being released on the keyup event, we adjust the modifiers when the released key is a matching modifier key.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

811 :(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4)) {
812 if (@available(iOS 13.4, *)) {
813 // no-op
814 } else {
815 return press.key.modifierFlags;
816 }
817
818 bool keyDown = isKeyDown(press);
819
820 // Start with the current modifier flags, along with any sided flags that we
821 // already know are down.
822 UInt32 pressedModifiers =
823 press.key.modifierFlags | (_lastModifierFlagsOfInterest & kModifierFlagSidedMask);
824
825 pressedModifiers = [self fixSidedFlags:kModifierFlagShiftAny
826 withLeftFlag:kModifierFlagShiftLeft
827 withRightFlag:kModifierFlagShiftRight
828 withLeftKey:UIKeyboardHIDUsageKeyboardLeftShift
829 withRightKey:UIKeyboardHIDUsageKeyboardRightShift
830 withKeyCode:press.key.keyCode
831 keyDown:keyDown
832 forFlags:pressedModifiers];
833 pressedModifiers = [self fixSidedFlags:kModifierFlagControlAny
834 withLeftFlag:kModifierFlagControlLeft
835 withRightFlag:kModifierFlagControlRight
836 withLeftKey:UIKeyboardHIDUsageKeyboardLeftControl
837 withRightKey:UIKeyboardHIDUsageKeyboardRightControl
838 withKeyCode:press.key.keyCode
839 keyDown:keyDown
840 forFlags:pressedModifiers];
841 pressedModifiers = [self fixSidedFlags:kModifierFlagAltAny
842 withLeftFlag:kModifierFlagAltLeft
843 withRightFlag:kModifierFlagAltRight
844 withLeftKey:UIKeyboardHIDUsageKeyboardLeftAlt
845 withRightKey:UIKeyboardHIDUsageKeyboardRightAlt
846 withKeyCode:press.key.keyCode
847 keyDown:keyDown
848 forFlags:pressedModifiers];
849 pressedModifiers = [self fixSidedFlags:kModifierFlagMetaAny
850 withLeftFlag:kModifierFlagMetaLeft
851 withRightFlag:kModifierFlagMetaRight
852 withLeftKey:UIKeyboardHIDUsageKeyboardLeftGUI
853 withRightKey:UIKeyboardHIDUsageKeyboardRightGUI
854 withKeyCode:press.key.keyCode
855 keyDown:keyDown
856 forFlags:pressedModifiers];
857
858 if (press.key.keyCode == UIKeyboardHIDUsageKeyboardCapsLock) {
859 // The caps lock modifier needs to be unset only if it was already on
860 // and this is a key up. This is because it indicates the lock state, and
861 // not the key press state. The caps lock state should be on between the
862 // first down, and the second up (i.e. while the lock in effect), and
863 // this code turns it off at the second up event. The OS leaves it on still
864 // because of iOS's weird late processing of modifier states. Synthesis of
865 // the appropriate synthesized key events happens in synchronizeModifiers.
866 if (!keyDown && _lastModifierFlagsOfInterest & kModifierFlagCapsLock) {
867 pressedModifiers &= ~kModifierFlagCapsLock;
868 }
869 }
870 return pressedModifiers;
871}
@ kModifierFlagCapsLock
constexpr uint32_t kModifierFlagSidedMask
static bool isKeyDown(FlutterUIPressProxy *press) API_AVAILABLE(ios(13.4))
SK_API sk_sp< SkSurface > ios(9.0)
API_AVAILABLE(ios(14.0), macos(11.0)) static NSString *MTLCommandEncoderErrorStateToString(MTLCommandEncoderErrorState state)

◆ fixSidedFlags:withLeftFlag:withRightFlag:withLeftKey:withRightKey:withKeyCode:keyDown:forFlags:

- (UInt32) fixSidedFlags: (ModifierFlag anyFlag
withLeftFlag: (ModifierFlag leftSide
withRightFlag: (ModifierFlag rightSide
withLeftKey: (UInt16)  leftKeyCode
withRightKey: (UInt16)  rightKeyCode
withKeyCode: (UInt16)  keyCode
keyDown: (BOOL isKeyDown
forFlags: (ios(13.4))  API_AVAILABLE 
implementation

Fix up the modifiers for a particular type of modifier key.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

753 :(ModifierFlag)anyFlag
754 withLeftFlag:(ModifierFlag)leftSide
755 withRightFlag:(ModifierFlag)rightSide
756 withLeftKey:(UInt16)leftKeyCode
757 withRightKey:(UInt16)rightKeyCode
758 withKeyCode:(UInt16)keyCode
759 keyDown:(BOOL)isKeyDown
760 forFlags:(UInt32)modifiersPressed API_AVAILABLE(ios(13.4)) {
761 UInt32 newModifiers = modifiersPressed;
762 if (isKeyDown) {
763 // Add in the modifier flags that correspond to this key code, if any.
764 if (keyCode == leftKeyCode) {
765 newModifiers |= leftSide | anyFlag;
766 } else if (keyCode == rightKeyCode) {
767 newModifiers |= rightSide | anyFlag;
768 }
769 } else {
770 // If this is a key up, then remove any modifier that matches the keycode in
771 // the event from the flags, and the anyFlag if the other side isn't also
772 // pressed.
773 if (keyCode == leftKeyCode) {
774 newModifiers &= ~leftSide;
775 if (!(newModifiers & rightSide)) {
776 newModifiers &= ~anyFlag;
777 }
778 } else if (keyCode == rightKeyCode) {
779 newModifiers &= ~rightSide;
780 if (!(newModifiers & leftSide)) {
781 newModifiers &= ~anyFlag;
782 }
783 }
784 }
785
786 if (!(newModifiers & anyFlag)) {
787 // Turn off any sided flags, since the "any" flag is gone.
788 newModifiers &= ~(leftSide | rightSide);
789 }
790
791 return newModifiers;
792}
ModifierFlag
int BOOL
Definition: windows_types.h:37

◆ getPressedState

- (nonnull NSDictionary *) getPressedState

Returns the keyboard pressed state.

Returns the keyboard pressed state. The dictionary contains one entry per pressed keys, mapping from the logical key to the physical key.

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

797 {
798 return [NSDictionary dictionaryWithDictionary:_pressingRecords];
799}

◆ handleCapsLockEvent:callback:

- (void) handleCapsLockEvent: (NSEvent*)  event
callback: (FlutterKeyCallbackGuard*)  callback 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

727 [self synchronizeModifiers:event.modifierFlags
728 ignoringFlags:NSEventModifierFlagCapsLock
729 timestamp:event.timestamp
730 guard:callback];
731 if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) !=
732 (event.modifierFlags & NSEventModifierFlagCapsLock)) {
733 [self sendCapsLockTapWithTimestamp:event.timestamp synthesizeDown:false callback:callback];
734 _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock;
735 } else {
736 [callback resolveTo:TRUE];
737 }
738}
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event

◆ handleDownEvent:callback:

- (void) handleDownEvent: (NSEvent*)  event
callback: (FlutterKeyCallbackGuard*)  callback 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

647 uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
648 NSNumber* logicalKeyFromMap = self.layoutMap[@(event.keyCode)];
649 uint64_t logicalKey = logicalKeyFromMap != nil ? [logicalKeyFromMap unsignedLongLongValue]
650 : GetLogicalKeyForEvent(event, physicalKey);
651 [self synchronizeModifiers:event.modifierFlags
652 ignoringFlags:0
653 timestamp:event.timestamp
654 guard:callback];
655
656 bool isARepeat = event.isARepeat;
657 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
658 if (pressedLogicalKey != nil && !isARepeat) {
659 // This might happen in add-to-app scenarios if the focus is changed
660 // from the native view to the Flutter view amid the key tap.
661 //
662 // This might also happen when a key event is forged (such as by an
663 // IME) using the same keyCode as an unreleased key. See
664 // https://github.com/flutter/flutter/issues/82673#issuecomment-988661079
665 FlutterKeyEvent flutterEvent = {
666 .struct_size = sizeof(FlutterKeyEvent),
667 .timestamp = GetFlutterTimestampFrom(event.timestamp),
669 .physical = physicalKey,
670 .logical = [pressedLogicalKey unsignedLongLongValue],
671 .character = nil,
672 .synthesized = true,
673 };
674 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
675 pressedLogicalKey = nil;
676 }
677
678 if (pressedLogicalKey == nil) {
679 [self updateKey:physicalKey asPressed:logicalKey];
680 }
681
682 FlutterKeyEvent flutterEvent = {
683 .struct_size = sizeof(FlutterKeyEvent),
684 .timestamp = GetFlutterTimestampFrom(event.timestamp),
685 .type = pressedLogicalKey == nil ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeRepeat,
686 .physical = physicalKey,
687 .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
688 .character = getEventString(event.characters),
689 .synthesized = false,
690 };
691 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
692}
@ kFlutterKeyEventTypeDown
Definition: embedder.h:1076
@ kFlutterKeyEventTypeUp
Definition: embedder.h:1075
@ kFlutterKeyEventTypeRepeat
Definition: embedder.h:1077
static double GetFlutterTimestampFrom(NSTimeInterval timestamp)
static uint64_t GetPhysicalKeyForKeyCode(UInt32 keyCode)
static uint64_t GetLogicalKeyForEvent(FlutterUIPressProxy *press, NSNumber *maybeSpecialKey) API_AVAILABLE(ios(13.4))
size_t struct_size
The size of this struct. Must be sizeof(FlutterKeyEvent).
Definition: embedder.h:1112

◆ handleEvent:callback:

- (void) handleEvent: (NSEvent*)  event
callback: (FlutterAsyncKeyCallback callback 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

492 // The conversion algorithm relies on a non-nil callback to properly compute
493 // `synthesized`.
494 NSAssert(callback != nil, @"The callback must not be nil.");
495 FlutterKeyCallbackGuard* guardedCallback =
496 [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
497 switch (event.type) {
498 case NSEventTypeKeyDown:
499 [self handleDownEvent:event callback:guardedCallback];
500 break;
501 case NSEventTypeKeyUp:
502 [self handleUpEvent:event callback:guardedCallback];
503 break;
504 case NSEventTypeFlagsChanged:
505 [self handleFlagEvent:event callback:guardedCallback];
506 break;
507 default:
508 NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type));
509 }
510 NSAssert(guardedCallback.handled, @"The callback is returned without being handled.");
511 if (!guardedCallback.sentAnyEvents) {
512 FlutterKeyEvent flutterEvent = {
513 .struct_size = sizeof(FlutterKeyEvent),
514 .timestamp = 0,
516 .physical = 0,
517 .logical = 0,
518 .character = nil,
519 .synthesized = false,
520 };
521 _sendEvent(flutterEvent, nullptr, nullptr);
522 }
523 NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask),
524 @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx",
525 _lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask);
526}
GLenum type
void(^ FlutterAsyncKeyCallback)(BOOL handled)

◆ handleFlagEvent:callback:

- (void) handleFlagEvent: (NSEvent*)  event
callback: (FlutterKeyCallbackGuard*)  callback 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

741 NSNumber* targetModifierFlagObj = flutter::keyCodeToModifierFlag[@(event.keyCode)];
742 NSUInteger targetModifierFlag =
743 targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue];
744 uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode);
745 if (targetKey == flutter::kCapsLockPhysicalKey) {
746 return [self handleCapsLockEvent:event callback:callback];
747 }
748
749 [self synchronizeModifiers:event.modifierFlags
750 ignoringFlags:targetModifierFlag
751 timestamp:event.timestamp
752 guard:callback];
753
754 NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)];
755 BOOL lastTargetPressed = pressedLogicalKey != nil;
756 NSAssert(targetModifierFlagObj == nil ||
757 (_lastModifierFlagsOfInterest & targetModifierFlag) != 0 == lastTargetPressed,
758 @"Desynchronized state between lastModifierFlagsOfInterest (0x%lx) on bit 0x%lx "
759 @"for keyCode 0x%hx, whose pressing state is %@.",
760 _lastModifierFlagsOfInterest, targetModifierFlag, event.keyCode,
761 lastTargetPressed
762 ? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]]
763 : @"empty");
764
765 BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0;
766 if (lastTargetPressed == shouldBePressed) {
767 [callback resolveTo:TRUE];
768 return;
769 }
770 _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ targetModifierFlag;
771 [self sendModifierEventOfType:shouldBePressed
772 timestamp:event.timestamp
773 keyCode:event.keyCode
774 synthesized:false
775 callback:callback];
776}
const NSDictionary * keyCodeToModifierFlag
const uint64_t kCapsLockPhysicalKey

◆ handlePress:callback:

- (void) handlePress: (nonnull FlutterUIPressProxy *)  press
callback: (ios(13.4))  API_AVAILABLE 
implementation

Process the event.

The |callback| should be called with a value that indicates whether the responder has handled the given press event. The |callback| must be called exactly once, and can be called before the return of this method, or after.

Reimplemented from <FlutterKeyPrimaryResponder>.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

503 :(nonnull FlutterUIPressProxy*)press
505 if (@available(iOS 13.4, *)) {
506 } else {
507 return;
508 }
509 // The conversion algorithm relies on a non-nil callback to properly compute
510 // `synthesized`.
511 NSAssert(callback != nil, @"The callback must not be nil.");
512
513 FlutterKeyCallbackGuard* guardedCallback = nil;
514 switch (press.phase) {
515 case UIPressPhaseBegan:
516 guardedCallback = [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
517 [self handlePressBegin:press callback:guardedCallback];
518 break;
519 case UIPressPhaseEnded:
520 guardedCallback = [[FlutterKeyCallbackGuard alloc] initWithCallback:callback];
521 [self handlePressEnd:press callback:guardedCallback];
522 break;
523 case UIPressPhaseChanged:
524 case UIPressPhaseCancelled:
525 // TODO(gspencergoog): Handle cancelled events as synthesized up events.
526 case UIPressPhaseStationary:
527 NSAssert(false, @"Unexpected press phase receieved in handlePress");
528 return;
529 }
530 NSAssert(guardedCallback.handled, @"The callback returned without being handled.");
531 NSAssert(
532 (_lastModifierFlagsOfInterest & ~kModifierFlagCapsLock) ==
533 ([self adjustModifiers:press] & (_modifierFlagOfInterestMask & ~kModifierFlagCapsLock)),
534 @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx",
535 static_cast<unsigned long>(_lastModifierFlagsOfInterest & ~kModifierFlagCapsLock),
536 static_cast<unsigned long>([self adjustModifiers:press] &
537 (_modifierFlagOfInterestMask & ~kModifierFlagCapsLock)));
538}

◆ handlePressBegin:callback:

- (void) handlePressBegin: (nonnull FlutterUIPressProxy *)  press
callback: (ios(13.4))  API_AVAILABLE 
implementation

Processes a down event from the system.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

667 :(nonnull FlutterUIPressProxy*)press
669 if (@available(iOS 13.4, *)) {
670 } else {
671 return;
672 }
673 uint64_t physicalKey = GetPhysicalKeyForKeyCode(press.key.keyCode);
674 // Some unprintable keys on iOS have literal names on their key label, such as
675 // @"UIKeyInputEscape". They are called the "special keys" and have predefined
676 // logical keys and empty characters.
677 NSNumber* specialKey = [specialKeyMapping objectForKey:press.key.charactersIgnoringModifiers];
678 uint64_t logicalKey = GetLogicalKeyForEvent(press, specialKey);
679 [self synchronizeModifiers:press];
680
681 NSNumber* pressedLogicalKey = nil;
682 if ([_pressingRecords count] > 0) {
683 pressedLogicalKey = _pressingRecords[@(physicalKey)];
684 if (pressedLogicalKey != nil) {
685 // Normally the key up events won't be missed since iOS always sends the
686 // key up event to the view where the corresponding key down occurred.
687 // However this might happen in add-to-app scenarios if the focus is changed
688 // from the native view to the Flutter view amid the key tap.
689 [callback resolveTo:TRUE];
690 [self sendEmptyEvent];
691 return;
692 }
693 }
694
695 if (pressedLogicalKey == nil) {
696 [self updateKey:physicalKey asPressed:logicalKey];
697 }
698
699 FlutterKeyEvent flutterEvent = {
700 .struct_size = sizeof(FlutterKeyEvent),
701 .timestamp = GetFlutterTimestampFrom(press.timestamp),
703 .physical = physicalKey,
704 .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue],
705 .character =
706 specialKey != nil ? nil : getEventCharacters(press.key.characters, press.key.keyCode),
707 .synthesized = false,
709 };
710 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
711}
int count
Definition: FontMgrTest.cpp:50
@ kFlutterKeyEventDeviceTypeKeyboard
Definition: embedder.h:1081
static const char * getEventCharacters(NSString *characters, UIKeyboardHIDUsage keyCode) API_AVAILABLE(ios(13.4))

◆ handlePressEnd:callback:

- (void) handlePressEnd: (nonnull FlutterUIPressProxy *)  press
callback: (ios(13.4))  API_AVAILABLE 
implementation

Processes an up event from the system.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

713 :(nonnull FlutterUIPressProxy*)press
715 if (@available(iOS 13.4, *)) {
716 } else {
717 return;
718 }
719 [self synchronizeModifiers:press];
720
721 uint64_t physicalKey = GetPhysicalKeyForKeyCode(press.key.keyCode);
722 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
723 if (pressedLogicalKey == nil) {
724 // Normally the key up events won't be missed since iOS always sends the
725 // key up event to the view where the corresponding key down occurred.
726 // However this might happen in add-to-app scenarios if the focus is changed
727 // from the native view to the Flutter view amid the key tap.
728 [callback resolveTo:TRUE];
729 [self sendEmptyEvent];
730 return;
731 }
732 [self updateKey:physicalKey asPressed:0];
733
734 FlutterKeyEvent flutterEvent = {
735 .struct_size = sizeof(FlutterKeyEvent),
736 .timestamp = GetFlutterTimestampFrom(press.timestamp),
738 .physical = physicalKey,
739 .logical = [pressedLogicalKey unsignedLongLongValue],
740 .character = nil,
741 .synthesized = false,
743 };
744 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
745}

◆ handleResponse:forId:

- (void) handleResponse: (BOOL handled
forId: (uint64_t)  responseId 
implementation

Processes the response from the framework.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

747 :(BOOL)handled forId:(uint64_t)responseId {
748 FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)];
749 callback(handled);
750 [_pendingResponses removeObjectForKey:@(responseId)];
751}

◆ handleUpEvent:callback:

- (void) handleUpEvent: (NSEvent*)  event
callback: (FlutterKeyCallbackGuard*)  callback 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

695 NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@",
696 event.keyCode, event.characters, event.charactersIgnoringModifiers);
697 [self synchronizeModifiers:event.modifierFlags
698 ignoringFlags:0
699 timestamp:event.timestamp
700 guard:callback];
701
702 uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode);
703 NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)];
704 if (pressedLogicalKey == nil) {
705 // Normally the key up events won't be missed since macOS always sends the
706 // key up event to the window where the corresponding key down occurred.
707 // However this might happen in add-to-app scenarios if the focus is changed
708 // from the native view to the Flutter view amid the key tap.
709 [callback resolveTo:TRUE];
710 return;
711 }
712 [self updateKey:physicalKey asPressed:0];
713
714 FlutterKeyEvent flutterEvent = {
715 .struct_size = sizeof(FlutterKeyEvent),
716 .timestamp = GetFlutterTimestampFrom(event.timestamp),
718 .physical = physicalKey,
719 .logical = [pressedLogicalKey unsignedLongLongValue],
720 .character = nil,
721 .synthesized = false,
722 };
723 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
724}

◆ initWithSendEvent: [1/3]

- (nonnull instancetype) initWithSendEvent: (_Nonnull FlutterSendEmbedderKeyEvent sendEvent

Create an instance by specifying the function to send converted events to.

The |sendEvent| is typically |FlutterEngine|'s |sendKeyEvent|.

◆ initWithSendEvent: [2/3]

- (nonnull instancetype) initWithSendEvent: (FlutterSendKeyEvent sendEvent
implementation

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

491 self = [super init];
492 if (self != nil) {
493 _sendEvent = [sendEvent copy];
494 _pressingRecords = [[NSMutableDictionary alloc] init];
495 _pendingResponses = [[NSMutableDictionary alloc] init];
496 _responseId = 1;
497 _lastModifierFlagsOfInterest = 0;
498 _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask();
499 }
500 return self;
501}
void(^ FlutterSendKeyEvent)(const FlutterKeyEvent &, _Nullable FlutterKeyEventCallback, void *_Nullable)
static NSUInteger computeModifierFlagOfInterestMask()

◆ initWithSendEvent: [3/3]

- (nonnull instancetype) initWithSendEvent: (nonnull FlutterSendKeyEvent sendEvent

Create an instance by specifying the function to send converted events to.

The |sendEvent| is typically |FlutterEngine|'s |sendKeyEvent|.

◆ sendCapsLockTapWithTimestamp:synthesizeDown:callback:

- (void) sendCapsLockTapWithTimestamp: (NSTimeInterval)  timestamp
synthesizeDown: (bool)  synthesizeDown
callback: (FlutterKeyCallbackGuard*)  callback 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

589 :(NSTimeInterval)timestamp
590 synthesizeDown:(bool)synthesizeDown
592 // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on
593 // even taps and odd taps. A CapsLock down or CapsLock up should always be
594 // converted to a down *and* an up, and the up should always be a synthesized
595 // event, since the FlutterEmbedderKeyResponder will never know when the
596 // button is released.
597 FlutterKeyEvent flutterEvent = {
598 .struct_size = sizeof(FlutterKeyEvent),
599 .timestamp = GetFlutterTimestampFrom(timestamp),
603 .character = nil,
604 .synthesized = synthesizeDown,
605 };
606 if (!synthesizeDown) {
607 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
608 } else {
609 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
610 }
611
612 flutterEvent.type = kFlutterKeyEventTypeUp;
613 flutterEvent.synthesized = true;
614 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
615}
const uint64_t kCapsLockLogicalKey
FlutterKeyEventType type
The event kind.
Definition: embedder.h:1118

◆ sendEmptyEvent

- (void) sendEmptyEvent
implementation

Send an empty key event.

The event is never synthesized, and never expects an event result. An empty event is sent when no other events should be sent, such as upon back-to-back keydown events of the same key.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

631 {
632 FlutterKeyEvent event = {
633 .struct_size = sizeof(FlutterKeyEvent),
634 .timestamp = 0,
636 .physical = 0,
637 .logical = 0,
638 .character = nil,
639 .synthesized = false,
641 };
642 _sendEvent(event, nil, nil);
643}

◆ sendModifierEventOfType:timestamp:keyCode:synthesized:callback:

- (void) sendModifierEventOfType: (BOOL isDownEvent
timestamp: (NSTimeInterval)  timestamp
keyCode: (unsigned short)  keyCode
synthesized: (bool)  synthesized
callback: (FlutterKeyCallbackGuard*)  callback 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

617 :(BOOL)isDownEvent
618 timestamp:(NSTimeInterval)timestamp
619 keyCode:(unsigned short)keyCode
620 synthesized:(bool)synthesized
622 uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
623 uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
624 if (physicalKey == 0 || logicalKey == 0) {
625 NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey);
626 [callback resolveTo:TRUE];
627 return;
628 }
629 FlutterKeyEvent flutterEvent = {
630 .struct_size = sizeof(FlutterKeyEvent),
631 .timestamp = GetFlutterTimestampFrom(timestamp),
632 .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
633 .physical = physicalKey,
634 .logical = logicalKey,
635 .character = nil,
636 .synthesized = synthesized,
637 };
638 [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
639 if (!synthesized) {
640 [self sendPrimaryFlutterEvent:flutterEvent callback:callback];
641 } else {
642 [self sendSynthesizedFlutterEvent:flutterEvent guard:callback];
643 }
644}
static uint64_t GetLogicalKeyForModifier(UInt32 keyCode, uint64_t hidCode)

◆ sendPrimaryFlutterEvent:callback:

- (void) sendPrimaryFlutterEvent: (const FlutterKeyEvent&)  event
callback: (FlutterKeyCallbackGuard*)  callback 
implementation

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

621 :(const FlutterKeyEvent&)event
623 _responseId += 1;
624 uint64_t responseId = _responseId;
626 [[FlutterKeyPendingResponse alloc] initWithHandler:self responseId:responseId];
627 [callback pendTo:_pendingResponses withId:responseId];
628 _sendEvent(event, HandleResponse, (__bridge_retained void* _Nullable)pending);
629}
void pendTo:withId:(nonnull NSMutableDictionary< NSNumber *, FlutterAsyncKeyCallback > *pendingResponses,[withId] uint64_t responseId)
void HandleResponse(bool handled, void *user_data)

◆ sendSynthesizedFlutterEvent:guard:

- (void) sendSynthesizedFlutterEvent: (const FlutterKeyEvent &)  event
guard: (FlutterKeyCallbackGuard *)  guard 
implementation

Send a synthesized key event, never expecting its event result.

The |guard| is basically a regular guarded callback, but instead of being called, it is only used to record whether an event is sent.

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

583 :(const FlutterKeyEvent&)event
584 guard:(FlutterKeyCallbackGuard*)guard {
585 _sendEvent(event, nullptr, nullptr);
586 guard.sentAnyEvents = TRUE;
587}

◆ synchronizeModifiers:

- (void) synchronizeModifiers: (ios(13.4))  API_AVAILABLE
implementation

Compare the last modifier flags and the current, and dispatch synthesized key events for each different modifier flag bit.

The flags compared are all flags after masking with |modifierFlagOfInterestMask| and excluding |ignoringFlags|.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

542 :(nonnull FlutterUIPressProxy*)press API_AVAILABLE(ios(13.4)) {
543 if (@available(iOS 13.4, *)) {
544 } else {
545 return;
546 }
547
548 const UInt32 lastFlagsOfInterest = _lastModifierFlagsOfInterest & _modifierFlagOfInterestMask;
549 const UInt32 pressedModifiers = [self adjustModifiers:press];
550 const UInt32 currentFlagsOfInterest = pressedModifiers & _modifierFlagOfInterestMask;
551 UInt32 flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;
552 if (flagDifference & kModifierFlagCapsLock) {
553 // If the caps lock changed, and we didn't expect that, then send a
554 // synthesized down and an up to simulate a toggle of the state.
555 if (press.key.keyCode != UIKeyboardHIDUsageKeyboardCapsLock) {
556 [self synthesizeCapsLockTapWithTimestamp:press.timestamp];
557 }
558 flagDifference &= ~kModifierFlagCapsLock;
559 }
560 while (true) {
561 const UInt32 currentFlag = lowestSetBit(flagDifference);
562 if (currentFlag == 0) {
563 break;
564 }
565 flagDifference &= ~currentFlag;
566 if (currentFlag & kModifierFlagAnyMask) {
567 // Skip synthesizing keys for the "any" flags, since their synthesis will
568 // be handled when we do the sided flags. We still want them in the flags
569 // of interest, though, so we can keep their state.
570 continue;
571 }
572 auto keyCode = modifierFlagToKeyCode.find(static_cast<ModifierFlag>(currentFlag));
573 NSAssert(keyCode != modifierFlagToKeyCode.end(), @"Invalid modifier flag of interest 0x%lx",
574 static_cast<unsigned long>(currentFlag));
575 if (keyCode == modifierFlagToKeyCode.end()) {
576 continue;
577 }
578 // If this press matches the modifier key in question, then don't synthesize
579 // it, because it's already a "real" keypress.
580 if (keyCode->second == static_cast<UInt32>(press.key.keyCode)) {
581 continue;
582 }
583 BOOL isDownEvent = currentFlagsOfInterest & currentFlag;
584 [self synthesizeModifierEventOfType:isDownEvent
585 timestamp:press.timestamp
586 keyCode:keyCode->second];
587 }
588 _lastModifierFlagsOfInterest =
589 (_lastModifierFlagsOfInterest & ~_modifierFlagOfInterestMask) | currentFlagsOfInterest;
590}
const std::map< ModifierFlag, uint32_t > modifierFlagToKeyCode
constexpr uint32_t kModifierFlagAnyMask
static NSUInteger lowestSetBit(NSUInteger bitmask)

◆ synchronizeModifiers:ignoringFlags:timestamp:guard:

- (void) synchronizeModifiers: (NSUInteger)  currentFlags
ignoringFlags: (NSUInteger)  ignoringFlags
timestamp: (NSTimeInterval)  timestamp
guard: (FlutterKeyCallbackGuard*)  guard 
implementation

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

530 :(NSUInteger)currentFlags
531 ignoringFlags:(NSUInteger)ignoringFlags
532 timestamp:(NSTimeInterval)timestamp
533 guard:(FlutterKeyCallbackGuard*)guard {
534 const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags;
535 const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask;
536 const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask;
537 NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest;
538 if (flagDifference & NSEventModifierFlagCapsLock) {
539 [self sendCapsLockTapWithTimestamp:timestamp synthesizeDown:true callback:guard];
540 flagDifference = flagDifference & ~NSEventModifierFlagCapsLock;
541 }
542 while (true) {
543 const NSUInteger currentFlag = lowestSetBit(flagDifference);
544 if (currentFlag == 0) {
545 break;
546 }
547 flagDifference = flagDifference & ~currentFlag;
548 NSNumber* keyCode = [flutter::modifierFlagToKeyCode objectForKey:@(currentFlag)];
549 NSAssert(keyCode != nil, @"Invalid modifier flag 0x%lx", currentFlag);
550 if (keyCode == nil) {
551 continue;
552 }
553 BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0;
554 [self sendModifierEventOfType:isDownEvent
555 timestamp:timestamp
556 keyCode:[keyCode unsignedShortValue]
557 synthesized:true
558 callback:guard];
559 }
560 _lastModifierFlagsOfInterest =
561 (_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest;
562}

◆ syncModifiersIfNeeded:timestamp:

- (void) syncModifiersIfNeeded: (NSEventModifierFlags)  modifierFlags
timestamp: (NSTimeInterval)  timestamp 

Synthesize modifier keys events.

If needed, synthesize modifier keys up and down events by comparing their current pressing states with the given modifier flags.

Reimplemented from <FlutterKeyPrimaryResponder>.

Definition at line 386 of file FlutterEmbedderKeyResponder.mm.

784 :(NSEventModifierFlags)modifierFlags
785 timestamp:(NSTimeInterval)timestamp {
786 FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) {
787 // Do nothing.
788 };
789 FlutterKeyCallbackGuard* guardedCallback =
790 [[FlutterKeyCallbackGuard alloc] initWithCallback:replyCallback];
791 [self synchronizeModifiers:modifierFlags
792 ignoringFlags:0
793 timestamp:timestamp
794 guard:guardedCallback];
795}

◆ synthesizeCapsLockTapWithTimestamp:

- (void) synthesizeCapsLockTapWithTimestamp: (NSTimeInterval)  timestamp
implementation

Synthesize a CapsLock down event, then a CapsLock up event.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

592 :(NSTimeInterval)timestamp {
593 // The assumption when the app starts is that caps lock is off, but if that
594 // turns out to be untrue (according to the modifier flags), then this is used
595 // to simulate a key down and a key up of the caps lock key, to simulate
596 // toggling of that state in the framework.
597 FlutterKeyEvent flutterEvent = {
598 .struct_size = sizeof(FlutterKeyEvent),
599 .timestamp = GetFlutterTimestampFrom(timestamp),
601 .physical = kCapsLockPhysicalKey,
602 .logical = kCapsLockLogicalKey,
603 .character = nil,
604 .synthesized = true,
606 };
607 _sendEvent(flutterEvent, nullptr, nullptr);
608
609 flutterEvent.type = kFlutterKeyEventTypeUp;
610 _sendEvent(flutterEvent, nullptr, nullptr);
611}
const uint64_t kCapsLockLogicalKey
const uint64_t kCapsLockPhysicalKey

◆ synthesizeModifierEventOfType:timestamp:keyCode:

- (void) synthesizeModifierEventOfType: (BOOL isDownEvent
timestamp: (NSTimeInterval)  timestamp
keyCode: (UInt32)  keyCode 
implementation

Send a key event for a modifier key.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

645 :(BOOL)isDownEvent
646 timestamp:(NSTimeInterval)timestamp
647 keyCode:(UInt32)keyCode {
648 uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode);
649 uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey);
650 if (physicalKey == 0 || logicalKey == 0) {
651 return;
652 }
653 FlutterKeyEvent flutterEvent = {
654 .struct_size = sizeof(FlutterKeyEvent),
655 .timestamp = GetFlutterTimestampFrom(timestamp),
656 .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp,
657 .physical = physicalKey,
658 .logical = logicalKey,
659 .character = nil,
660 .synthesized = true,
662 };
663 [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0];
664 _sendEvent(flutterEvent, nullptr, nullptr);
665}

◆ updateKey:asPressed:

- (void) updateKey: (uint64_t)  physicalKey
asPressed: (uint64_t)  logicalKey 
implementation

Update the pressing state.

If logicalKey is not 0, physicalKey is pressed as logicalKey. Otherwise, physicalKey is released.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

613 :(uint64_t)physicalKey asPressed:(uint64_t)logicalKey {
614 if (logicalKey == 0) {
615 [_pressingRecords removeObjectForKey:@(physicalKey)];
616 } else {
617 _pressingRecords[@(physicalKey)] = @(logicalKey);
618 }
619}

Property Documentation

◆ lastModifierFlagsOfInterest

- (NSUInteger) lastModifierFlagsOfInterest
readwritenonatomicassignimplementation

The modifier flags of the last received key event, excluding uninterested bits.

This should be kept synchronized with the last |NSEvent.modifierFlags| after masking with |modifierFlagOfInterestMask|. This should also be kept synchronized with the corresponding keys of |pressingRecords|.

This is used by |synchronizeModifiers| to quickly find out modifier keys that are desynchronized.

Definition at line 391 of file FlutterEmbedderKeyResponder.mm.

◆ modifierFlagOfInterestMask

- (NSUInteger) modifierFlagOfInterestMask
readwritenonatomicassignimplementation

A constant mask for NSEvent.modifierFlags that Flutter synchronizes with.

Flutter keeps track of the last |modifierFlags| and compares it with the incoming one. Any bit within |modifierFlagOfInterestMask| that is different (except for the one that corresponds to the event key) indicates that an event for this modifier was missed, and Flutter synthesizes an event to make up for the state difference.

It is computed by computeModifierFlagOfInterestMask.

Definition at line 378 of file FlutterEmbedderKeyResponder.mm.

◆ pendingResponses

- (NSMutableDictionary<NSNumber*, FlutterAsyncKeyCallback>*) pendingResponses
readwritenonatomicassignimplementation

A map of unresponded key events sent to the framework.

Its values are |responseId|s, and keys are the callback that was received along with the event.

Definition at line 405 of file FlutterEmbedderKeyResponder.mm.

◆ pressingRecords

- (NSMutableDictionary<NSNumber*, NSNumber*>*) pressingRecords
readwritenonatomicassignimplementation

A map of pressed keys.

The keys of the dictionary are physical keys, while the values are the logical keys of the key down event.

A map of presessd keys.

The keys of the dictionary are physical keys, while the values are the logical keys of the key down event.

Definition at line 365 of file FlutterEmbedderKeyResponder.mm.

◆ responseId

- (uint64_t) responseId
readwritenonatomicassignimplementation

A self-incrementing ID used to label key events sent to the framework.

Definition at line 396 of file FlutterEmbedderKeyResponder.mm.

◆ sendEvent [1/2]

- (FlutterSendKeyEvent) sendEvent
readnonatomiccopyimplementation

The function to send converted events to.

Set by the initializer.

Provided by category FlutterEmbedderKeyResponder(Tests).

Definition at line 357 of file FlutterEmbedderKeyResponder.mm.

◆ sendEvent [2/2]

- (FlutterSendEmbedderKeyEvent) sendEvent
readwritenonatomiccopyimplementation

The function to send converted events to.

Set by the initializer.

Definition at line 339 of file FlutterEmbedderKeyResponder.mm.


The documentation for this class was generated from the following files: