From 011ea611cc7456ae4eeed423e75d1e94c1f02b5b Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 6 Jan 2021 15:07:00 -0800 Subject: [PATCH 01/46] Split macos changes out --- .../darwin/ios/keycodes/keyboard_map_ios.h | 202 +++++++++ shell/platform/darwin/macos/BUILD.gn | 3 + .../macos/framework/Source/FlutterEngine.mm | 4 + .../framework/Source/FlutterEngine_Internal.h | 5 + .../framework/Source/FlutterKeyboardPlugin.h | 28 ++ .../framework/Source/FlutterKeyboardPlugin.mm | 391 ++++++++++++++++++ .../framework/Source/FlutterViewController.mm | 36 +- .../Source/FlutterViewController_Internal.h | 10 + .../macos/framework/Source/KeyCodeMap.mm | 276 +++++++++++++ .../framework/Source/KeyCodeMap_internal.h | 57 +++ 10 files changed, 1004 insertions(+), 8 deletions(-) create mode 100644 shell/platform/darwin/ios/keycodes/keyboard_map_ios.h create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.mm create mode 100644 shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm create mode 100644 shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h diff --git a/shell/platform/darwin/ios/keycodes/keyboard_map_ios.h b/shell/platform/darwin/ios/keycodes/keyboard_map_ios.h new file mode 100644 index 0000000000000..37e993ebee88e --- /dev/null +++ b/shell/platform/darwin/ios/keycodes/keyboard_map_ios.h @@ -0,0 +1,202 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include + +// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT +// This file is generated by flutter/flutter@dev/tools/gen_keycodes/bin/gen_keycodes.dart and +// should not be edited directly. +// +// Edit the template dev/tools/gen_keycodes/data/keyboard_map_ios_cc.tmpl instead. +// See dev/tools/gen_keycodes/README.md for more information. + +// Maps macOS-specific key code values representing [PhysicalKeyboardKey]. +// +// iOS doesn't provide a scan code, but a virtual keycode to represent a physical key. +const std::map g_ios_to_physical_key = { + { 0x00000000, 0x00070000 }, // usbReserved + { 0x00000001, 0x00070001 }, // usbErrorRollOver + { 0x00000002, 0x00070002 }, // usbPostFail + { 0x00000003, 0x00070003 }, // usbErrorUndefined + { 0x00000004, 0x00070004 }, // keyA + { 0x00000005, 0x00070005 }, // keyB + { 0x00000006, 0x00070006 }, // keyC + { 0x00000007, 0x00070007 }, // keyD + { 0x00000008, 0x00070008 }, // keyE + { 0x00000009, 0x00070009 }, // keyF + { 0x0000000a, 0x0007000a }, // keyG + { 0x0000000b, 0x0007000b }, // keyH + { 0x0000000c, 0x0007000c }, // keyI + { 0x0000000d, 0x0007000d }, // keyJ + { 0x0000000e, 0x0007000e }, // keyK + { 0x0000000f, 0x0007000f }, // keyL + { 0x00000010, 0x00070010 }, // keyM + { 0x00000011, 0x00070011 }, // keyN + { 0x00000012, 0x00070012 }, // keyO + { 0x00000013, 0x00070013 }, // keyP + { 0x00000014, 0x00070014 }, // keyQ + { 0x00000015, 0x00070015 }, // keyR + { 0x00000016, 0x00070016 }, // keyS + { 0x00000017, 0x00070017 }, // keyT + { 0x00000018, 0x00070018 }, // keyU + { 0x00000019, 0x00070019 }, // keyV + { 0x0000001a, 0x0007001a }, // keyW + { 0x0000001b, 0x0007001b }, // keyX + { 0x0000001c, 0x0007001c }, // keyY + { 0x0000001d, 0x0007001d }, // keyZ + { 0x0000001e, 0x0007001e }, // digit1 + { 0x0000001f, 0x0007001f }, // digit2 + { 0x00000020, 0x00070020 }, // digit3 + { 0x00000021, 0x00070021 }, // digit4 + { 0x00000022, 0x00070022 }, // digit5 + { 0x00000023, 0x00070023 }, // digit6 + { 0x00000024, 0x00070024 }, // digit7 + { 0x00000025, 0x00070025 }, // digit8 + { 0x00000026, 0x00070026 }, // digit9 + { 0x00000027, 0x00070027 }, // digit0 + { 0x00000028, 0x00070028 }, // enter + { 0x00000029, 0x00070029 }, // escape + { 0x0000002a, 0x0007002a }, // backspace + { 0x0000002b, 0x0007002b }, // tab + { 0x0000002c, 0x0007002c }, // space + { 0x0000002d, 0x0007002d }, // minus + { 0x0000002e, 0x0007002e }, // equal + { 0x0000002f, 0x0007002f }, // bracketLeft + { 0x00000030, 0x00070030 }, // bracketRight + { 0x00000031, 0x00070031 }, // backslash + { 0x00000033, 0x00070033 }, // semicolon + { 0x00000034, 0x00070034 }, // quote + { 0x00000035, 0x00070035 }, // backquote + { 0x00000036, 0x00070036 }, // comma + { 0x00000037, 0x00070037 }, // period + { 0x00000038, 0x00070038 }, // slash + { 0x00000039, 0x00070039 }, // capsLock + { 0x0000003a, 0x0007003a }, // f1 + { 0x0000003b, 0x0007003b }, // f2 + { 0x0000003c, 0x0007003c }, // f3 + { 0x0000003d, 0x0007003d }, // f4 + { 0x0000003e, 0x0007003e }, // f5 + { 0x0000003f, 0x0007003f }, // f6 + { 0x00000040, 0x00070040 }, // f7 + { 0x00000041, 0x00070041 }, // f8 + { 0x00000042, 0x00070042 }, // f9 + { 0x00000043, 0x00070043 }, // f10 + { 0x00000044, 0x00070044 }, // f11 + { 0x00000045, 0x00070045 }, // f12 + { 0x00000046, 0x00070046 }, // printScreen + { 0x00000047, 0x00070047 }, // scrollLock + { 0x00000048, 0x00070048 }, // pause + { 0x00000049, 0x00070049 }, // insert + { 0x0000004a, 0x0007004a }, // home + { 0x0000004b, 0x0007004b }, // pageUp + { 0x0000004c, 0x0007004c }, // delete + { 0x0000004d, 0x0007004d }, // end + { 0x0000004e, 0x0007004e }, // pageDown + { 0x0000004f, 0x0007004f }, // arrowRight + { 0x00000050, 0x00070050 }, // arrowLeft + { 0x00000051, 0x00070051 }, // arrowDown + { 0x00000052, 0x00070052 }, // arrowUp + { 0x00000053, 0x00070053 }, // numLock + { 0x00000054, 0x00070054 }, // numpadDivide + { 0x00000055, 0x00070055 }, // numpadMultiply + { 0x00000056, 0x00070056 }, // numpadSubtract + { 0x00000057, 0x00070057 }, // numpadAdd + { 0x00000058, 0x00070058 }, // numpadEnter + { 0x00000059, 0x00070059 }, // numpad1 + { 0x0000005a, 0x0007005a }, // numpad2 + { 0x0000005b, 0x0007005b }, // numpad3 + { 0x0000005c, 0x0007005c }, // numpad4 + { 0x0000005d, 0x0007005d }, // numpad5 + { 0x0000005e, 0x0007005e }, // numpad6 + { 0x0000005f, 0x0007005f }, // numpad7 + { 0x00000060, 0x00070060 }, // numpad8 + { 0x00000061, 0x00070061 }, // numpad9 + { 0x00000062, 0x00070062 }, // numpad0 + { 0x00000063, 0x00070063 }, // numpadDecimal + { 0x00000064, 0x00070064 }, // intlBackslash + { 0x00000065, 0x00070065 }, // contextMenu + { 0x00000066, 0x00070066 }, // power + { 0x00000067, 0x00070067 }, // numpadEqual + { 0x00000068, 0x00070068 }, // f13 + { 0x00000069, 0x00070069 }, // f14 + { 0x0000006a, 0x0007006a }, // f15 + { 0x0000006b, 0x0007006b }, // f16 + { 0x0000006c, 0x0007006c }, // f17 + { 0x0000006d, 0x0007006d }, // f18 + { 0x0000006e, 0x0007006e }, // f19 + { 0x0000006f, 0x0007006f }, // f20 + { 0x00000070, 0x00070070 }, // f21 + { 0x00000071, 0x00070071 }, // f22 + { 0x00000072, 0x00070072 }, // f23 + { 0x00000073, 0x00070073 }, // f24 + { 0x00000074, 0x00070074 }, // open + { 0x00000075, 0x00070075 }, // help + { 0x00000077, 0x00070077 }, // select + { 0x00000079, 0x00070079 }, // again + { 0x0000007a, 0x0007007a }, // undo + { 0x0000007b, 0x0007007b }, // cut + { 0x0000007c, 0x0007007c }, // copy + { 0x0000007d, 0x0007007d }, // paste + { 0x0000007e, 0x0007007e }, // find + { 0x0000007f, 0x0007007f }, // audioVolumeMute + { 0x00000080, 0x00070080 }, // audioVolumeUp + { 0x00000081, 0x00070081 }, // audioVolumeDown + { 0x00000085, 0x00070085 }, // numpadComma + { 0x00000087, 0x00070087 }, // intlRo + { 0x00000088, 0x00070088 }, // kanaMode + { 0x00000089, 0x00070089 }, // intlYen + { 0x0000008a, 0x0007008a }, // convert + { 0x0000008b, 0x0007008b }, // nonConvert + { 0x00000090, 0x00070090 }, // lang1 + { 0x00000091, 0x00070091 }, // lang2 + { 0x00000092, 0x00070092 }, // lang3 + { 0x00000093, 0x00070093 }, // lang4 + { 0x00000094, 0x00070094 }, // lang5 + { 0x0000009b, 0x0007009b }, // abort + { 0x000000a3, 0x000700a3 }, // props + { 0x000000b6, 0x000700b6 }, // numpadParenLeft + { 0x000000b7, 0x000700b7 }, // numpadParenRight + { 0x000000bb, 0x000700bb }, // numpadBackspace + { 0x000000d0, 0x000700d0 }, // numpadMemoryStore + { 0x000000d1, 0x000700d1 }, // numpadMemoryRecall + { 0x000000d2, 0x000700d2 }, // numpadMemoryClear + { 0x000000d3, 0x000700d3 }, // numpadMemoryAdd + { 0x000000d4, 0x000700d4 }, // numpadMemorySubtract + { 0x000000d7, 0x000700d7 }, // numpadSignChange + { 0x000000d8, 0x000700d8 }, // numpadClear + { 0x000000d9, 0x000700d9 }, // numpadClearEntry + { 0x000000e0, 0x000700e0 }, // controlLeft + { 0x000000e1, 0x000700e1 }, // shiftLeft + { 0x000000e2, 0x000700e2 }, // altLeft + { 0x000000e3, 0x000700e3 }, // metaLeft + { 0x000000e4, 0x000700e4 }, // controlRight + { 0x000000e5, 0x000700e5 }, // shiftRight + { 0x000000e6, 0x000700e6 }, // altRight + { 0x000000e7, 0x000700e7 }, // metaRight +}; + +// A map of iOS key codes which have printable representations, but appear +// on the number pad. Used to provide different key objects for keys like +// KEY_EQUALS and NUMPAD_EQUALS. +const std::map g_ios_numpad_map = { + { 0x00000054, 0x0100070054 }, // numpadDivide + { 0x00000055, 0x0100070055 }, // numpadMultiply + { 0x00000056, 0x0100070056 }, // numpadSubtract + { 0x00000057, 0x0100070057 }, // numpadAdd + { 0x00000059, 0x0100070059 }, // numpad1 + { 0x0000005a, 0x010007005a }, // numpad2 + { 0x0000005b, 0x010007005b }, // numpad3 + { 0x0000005c, 0x010007005c }, // numpad4 + { 0x0000005d, 0x010007005d }, // numpad5 + { 0x0000005e, 0x010007005e }, // numpad6 + { 0x0000005f, 0x010007005f }, // numpad7 + { 0x00000060, 0x0100070060 }, // numpad8 + { 0x00000061, 0x0100070061 }, // numpad9 + { 0x00000062, 0x0100070062 }, // numpad0 + { 0x00000063, 0x0100070063 }, // numpadDecimal + { 0x00000067, 0x0100070067 }, // numpadEqual + { 0x00000085, 0x0100070085 }, // numpadComma + { 0x000000b6, 0x01000700b6 }, // numpadParenLeft + { 0x000000b7, 0x01000700b7 }, // numpadParenRight +}; diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 88a65da11f07c..87a71bafb1727 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -58,6 +58,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterFrameBufferProvider.mm", "framework/Source/FlutterGLCompositor.h", "framework/Source/FlutterGLCompositor.mm", + "framework/Source/FlutterKeyboardPlugin.h", + "framework/Source/FlutterKeyboardPlugin.mm", "framework/Source/FlutterIOSurfaceHolder.h", "framework/Source/FlutterIOSurfaceHolder.mm", "framework/Source/FlutterIntermediateKeyResponder.h", @@ -76,6 +78,7 @@ source_set("flutter_framework_source") { "framework/Source/FlutterView.mm", "framework/Source/FlutterViewController.mm", "framework/Source/FlutterViewController_Internal.h", + "framework/Source/KeyCodeMap.mm", "framework/Source/MacOSGLContextSwitch.h", "framework/Source/MacOSGLContextSwitch.mm", ] diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index 4bf9e3f2b1845..11806cf5a4001 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -493,6 +493,10 @@ - (void)sendPointerEvent:(const FlutterPointerEvent&)event { _embedderAPI.SendPointerEvent(_engine, &event, 1); } +- (void)sendKeyEvent:(const FlutterKeyEvent&)event { + _embedderAPI.SendKeyEvent(_engine, &event); +} + #pragma mark - Private methods - (void)sendUserLocales { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index 48a27da52c5c7..82405542b9e68 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -36,4 +36,9 @@ */ - (void)sendPointerEvent:(const FlutterPointerEvent&)event; +/** + * Dispatches the given pointer event data to engine. + */ +- (void)sendKeyEvent:(const FlutterKeyEvent&)event; + @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h new file mode 100644 index 0000000000000..32851a9cb4028 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h @@ -0,0 +1,28 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" + +/** + * A plugin to handle hardward keyboard. + * + * Responsible for bridging the native macOS key event system with the + * Flutter framework hardware key event classes, via embedder. + */ +@interface FlutterKeyboardPlugin : NSObject + +- (nonnull instancetype)initWithViewController:(nonnull FlutterViewController*)viewController; + +/** + * Handles the method call that activates a system cursor. + * + * Returns a FlutterError if the arguments can not be recognized. Otherwise + * returns nil. + */ +- (bool)dispatchEvent:(nonnull NSEvent*)event; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.mm new file mode 100644 index 0000000000000..5824305d64900 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.mm @@ -0,0 +1,391 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" +#import "flutter/shell/platform/embedder/embedder.h" +#import "FlutterKeyboardPlugin.h" +#import "KeyCodeMap_internal.h" + +// Values of `characterIgnoringModifiers` that must not be directly converted to +// a logical key value, but should look up `keyCodeToLogical` by `keyCode`. +// This is because each of these of character codes is mapped from multiple +// logical keys, usually because of numpads. +// static const NSSet* kAmbiguousCharacters = @{ +// } + +// Whether a string represents a control character. +static bool IsControlCharacter(NSUInteger length, NSString *label) { + if (length > 1) { + return false; + } + unichar codeUnit = [label characterAtIndex:0]; + return (codeUnit <= 0x1f && codeUnit >= 0x00) || (codeUnit >= 0x7f && codeUnit <= 0x9f); +} + +// Whether a string represents an unprintable key. +static bool IsUnprintableKey(NSUInteger length, NSString *label) { + if (length > 1) { + return false; + } + unichar codeUnit = [label characterAtIndex:0]; + return codeUnit >= 0xF700 && codeUnit <= 0xF8FF; +} + +// Returns a key code composited by a base key and a plane. +static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) { + return plane | (baseKey & kValueMask); +} + +// Find the sibling key for a physical key by looking up `siblingKeyCodes`. +// +// Returns 0 if not found. +static uint64_t GetSiblingKeyCodeForKey(uint64_t physicalKey) { + NSNumber* siblingKey = [siblingKeyCodes objectForKey:@(physicalKey)]; + if (siblingKey == nil) + return 0; + return siblingKey.unsignedLongLongValue; +} + +// Find the modifer flag for a physical key by looking up `modiferFlags`. +// +// Returns 0 if not found. +static uint64_t GetModifierFlagForKey(uint64_t physicalKey) { + NSNumber* modifierFlag = [modiferFlags objectForKey:@(physicalKey)]; + if (modifierFlag == nil) + return 0; + return modifierFlag.unsignedLongLongValue; +} + +// Returns the physical key for a key code. +static uint64_t GetPhysicalKeyForKeyCode(uint64_t keyCode) { + NSNumber* physicalKeyKey = [keyCodeToPhysicalKey objectForKey:@(keyCode)]; + if (physicalKeyKey == nil) + return 0; + return physicalKeyKey.unsignedLongLongValue; +} + +// Returns the logical key for a modifier physical key. +static uint64_t GetLogicalKeyForModifier(uint64_t keyCode, uint64_t hidCode) { + NSNumber* fromKeyCode = [keyCodeToLogicalKey objectForKey:@(keyCode)]; + if (fromKeyCode != nil) + return fromKeyCode.unsignedLongLongValue; + return KeyOfPlane(hidCode, kHidPlane); +} + +// Returns the logical key of a KeyUp or KeyDown event. +// +// For FlagsChanged event, use GetLogicalKeyForModifier. +static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { + // Look to see if the keyCode can be mapped from keycode. + NSNumber* fromKeyCode = [keyCodeToLogicalKey objectForKey:@(event.keyCode)]; + if (fromKeyCode != nil) + return fromKeyCode.unsignedLongLongValue; + + NSString* keyLabel = event.charactersIgnoringModifiers; + NSUInteger keyLabelLength = [keyLabel length]; + // If this key is printable, generate the logical key from its Unicode + // value. Control keys such as ESC, CRTL, and SHIFT are not printable. HOME, + // DEL, arrow keys, and function keys are considered modifier function keys, + // which generate invalid Unicode scalar values. + if (keyLabelLength != 0 && + !IsControlCharacter(keyLabelLength, keyLabel) && + !IsUnprintableKey(keyLabelLength, keyLabel)) { + // Given that charactersIgnoringModifiers can contain a string of arbitrary + // length, limit to a maximum of two Unicode scalar values. It is unlikely + // that a keyboard would produce a code point bigger than 32 bits, but it is + // still worth defending against this case. + NSCAssert((keyLabelLength < 2), + @"Unexpected long key label: |%@|. Please report this to Flutter.", keyLabel); + + uint64_t codeUnit = (uint64_t)[keyLabel characterAtIndex:0]; + if (keyLabelLength == 2) { + uint64_t secondCode = (uint64_t)[keyLabel characterAtIndex:1]; + codeUnit = (codeUnit << 16) | secondCode; + } + if (codeUnit < 256) { + if (isupper(codeUnit)) { + return tolower(codeUnit); + } + return codeUnit; + } + return KeyOfPlane(codeUnit, kUnicodePlane); + } + + // Control keys like "backspace" and movement keys like arrow keys don't have + // a printable representation, but are present on the physical keyboard. Since + // there is no logical keycode map for macOS (macOS uses the keycode to + // reference physical keys), a LogicalKeyboardKey is created with the physical + // key's HID usage and debugName. This avoids duplicating the physical key + // map. + if (physicalKey != 0) { + return KeyOfPlane(physicalKey, kHidPlane); + } + + // This is a non-printable key that is unrecognized, so a new code is minted + // with the autogenerated bit set. + return KeyOfPlane(event.keyCode, kMacosPlane | kAutogeneratedMask); +} + +// Returns the timestamp for an event. +static double GetFlutterTimestampFrom(NSEvent* event) { + // Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision. + return event.timestamp * 1000000.0; +} + +@interface FlutterKeyboardPlugin () + +/** + * The FlutterViewController to manage input for. + */ +@property(nonatomic, weak) FlutterViewController* flutterViewController; + +/** + * A map of presessd keys. + * + * The keys of the dictionary are physical keys, while the values are the logical keys + * on pressing. + */ +@property(nonatomic) NSMutableDictionary* pressingRecords; + +/** + * Update the pressing state. + * + * If `logicalKey` is not 0, `physicalKey` is pressed as `logicalKey`. + * Otherwise, `physicalKey` is released. + */ +- (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; + +/** + * Processes a down event. + */ +- (void)dispatchDownEvent:(NSEvent*)event; + +/** + * Processes an up event. + */ +- (void)dispatchUpEvent:(NSEvent*)event; + +/** + * Processes a flags changed event, where modifier keys are pressed or released. + */ +- (void)dispatchFlagEvent:(NSEvent*)event; + +/** + * Processes all kinds of events. + */ +- (bool)dispatchEvent:(NSEvent*)event; + +@end + +@implementation FlutterKeyboardPlugin + +- (instancetype)initWithViewController:(FlutterViewController*)viewController { + self = [super init]; + if (self != nil) { + _flutterViewController = viewController; + _pressingRecords = [NSMutableDictionary dictionary]; + } + return self; +} + +- (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { + if (logicalKey == 0) { + [_pressingRecords removeObjectForKey:@(physicalKey)]; + } else { + _pressingRecords[@(physicalKey)] = @(logicalKey); + } +} + +- (void)dispatchDownEvent:(NSEvent*)event { + printf("Down event keyCode %d cIM %s c %s\n", [event keyCode], [[event charactersIgnoringModifiers] UTF8String], [[event characters] UTF8String]); + uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); + uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); + + NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; + bool isARepeat = false; + bool isSynthesized = false; + + if (pressedLogicalKey) { + // This physical key is being pressed according to the record. + if (event.isARepeat) { + // A normal repeated key. + isARepeat = true; + } else { + // A non-repeated key has been pressed that has the exact physical key as + // a currently pressed one, usually indicating multiple keyboards are + // pressing keys with the same physical key, or the up event was lost + // during a loss of focus. The down event is ignored. + return; + } + } else { + // This physical key is not being pressed according to the record. It's a + // normal down event, whether the system event is a repeat or not. + } + if (pressedLogicalKey == nil) { + [self updateKey:physicalKey asPressed:logicalKey]; + } + + FlutterKeyEvent flutterEvent = { + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = GetFlutterTimestampFrom(event), + .kind = isARepeat ? kFlutterKeyEventKindRepeat : kFlutterKeyEventKindDown, + .physical = physicalKey, + .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue], + .character = event.characters.UTF8String, + .synthesized = isSynthesized, + }; + [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; +} + +- (void)dispatchUpEvent:(NSEvent*)event { + NSAssert(!event.isARepeat, + @"Unexpected repeated Up event. Please report this to Flutter.", event.characters); + + uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); + NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; + if (!pressedLogicalKey) { + // The physical key has been released before. It indicates multiple + // keyboards pressed keys with the same physical key. Ignore the up event. + return; + } + [self updateKey:physicalKey asPressed:0]; + + FlutterKeyEvent flutterEvent = { + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = GetFlutterTimestampFrom(event), + .kind = kFlutterKeyEventKindUp, + .physical = physicalKey, + .logical = [pressedLogicalKey unsignedLongLongValue], + .character = nil, + .synthesized = false, + }; + [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; +} + +- (void)dispatchCapsLockEvent:(NSEvent*)event { + NSNumber* logicalKey = [keyCodeToLogicalKey objectForKey:@(event.keyCode)]; + if (logicalKey == nil) + return; + uint64_t logical = logicalKey.unsignedLongLongValue; + + FlutterKeyEvent flutterEvent = { + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = GetFlutterTimestampFrom(event), + .kind = kFlutterKeyEventKindDown, + .physical = kCapsLockPhysicalKey, + .logical = logical, + .character = nil, + .synthesized = false, + }; + [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + + // MacOS sends a Down or an Up when CapsLock is pressed, depending on whether + // the lock is enabled or disabled. This event should always be converted to + // a Down and a cancel, since we don't know how long it will be pressed. + flutterEvent.kind = kFlutterKeyEventKindUp; + flutterEvent.synthesized = true; + [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; +} + +- (void)dispatchFlagEvent:(NSEvent*)event { + // NSEvent only tells us the key that triggered the event and the resulting + // flag, but not whether the change is a down or up. For keys such as + // CapsLock, the change type can be inferred from the key and the flag. + // + // But some other modifier keys come in paris, such as shift or control, and + // both of the pair share one flag, which indicates either key is pressed. + // If the pressing states of paired keys are desynchronized due to loss of + // focus, the change might have to be guessed and synchronized. + // + // For convenience, the key corresponding to `event.keycode` is called + // `targetKey`. If the key is among a pair, the other key is called + // `siblingKey`. + // + // The logic of guessing is shown as follows, based on whether the key pairs + // were recorded as pressed and whether the incoming flag is set: ("*" + // indicates synthesized event.) + // + // LastPressed \ NowEither | | + // (Tgt,Sbl) \ Pressed | 1 | 0 + // -----------------------|-------------------|------------------- + // (1, 0) | - | TgtUp + // (0, 0) | TgtDown | - + // (0, 1) | TgtDown | SblUp* + // (1, 1) | TgtUp | SblUp*,TgtUp + // + // For non-pair keys, lastSiblingPressed is always set to 0, resulting in the + // top half of the table. + + uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode); + if (targetKey == kCapsLockPhysicalKey) { + return [self dispatchCapsLockEvent:event]; + } + uint64_t modifierFlag = GetModifierFlagForKey(targetKey); + if (targetKey == 0 || modifierFlag == 0) { + // Unrecognized modifier. + return; + } + // The `siblingKeyCode` may be 0, which means it doesn't have a sibling key. + uint64_t siblingKeyCode = GetSiblingKeyCodeForKey(event.keyCode); + uint64_t siblingKeyPhysical = siblingKeyCode == 0 ? 0 : GetPhysicalKeyForKeyCode(siblingKeyCode); + uint64_t siblingKeyLogical = siblingKeyCode == 0 ? 0 : GetLogicalKeyForModifier(siblingKeyCode, siblingKeyPhysical); + + bool lastTargetPressed = [_pressingRecords objectForKey:@(targetKey)] != nil; + bool lastSiblingPressed = siblingKeyCode == 0 ? false : [_pressingRecords objectForKey:@(siblingKeyPhysical)] != nil; + bool nowEitherPressed = (event.modifierFlags & modifierFlag) != 0; + + bool targetKeyShouldDown = !lastTargetPressed && nowEitherPressed; + bool targetKeyShouldUp = lastTargetPressed && (lastSiblingPressed || !nowEitherPressed); + bool siblingKeyShouldUp = lastSiblingPressed && !nowEitherPressed; + + FlutterKeyEvent flutterEvent = { + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = GetFlutterTimestampFrom(event), + .character = nil, + }; + if (siblingKeyShouldUp) { + flutterEvent.kind = kFlutterKeyEventKindUp; + flutterEvent.physical = siblingKeyPhysical; + flutterEvent.logical = siblingKeyLogical; + flutterEvent.synthesized = true; + [self updateKey:siblingKeyPhysical asPressed:0]; + [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + } + + if (targetKeyShouldDown || targetKeyShouldUp) { + uint64_t logicalKey = GetLogicalKeyForModifier(event.keyCode, targetKey); + flutterEvent.kind = targetKeyShouldDown ? kFlutterKeyEventKindDown : kFlutterKeyEventKindUp; + flutterEvent.physical = targetKey; + flutterEvent.logical = logicalKey; + flutterEvent.synthesized = false; + [self updateKey:targetKey asPressed:(targetKeyShouldDown ? logicalKey : 0)]; + [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + } +} + +- (bool)dispatchEvent:(NSEvent*)event { + switch (event.type) { + case NSEventTypeKeyDown: + [self dispatchDownEvent:event]; + break; + case NSEventTypeKeyUp: + [self dispatchUpEvent:event]; + break; + case NSEventTypeFlagsChanged: + [self dispatchFlagEvent:event]; + break; + default: + NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); + } + + return true; // TODO +} + +#pragma mark - Private + + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index 6944041a1bf8d..b8c8810971287 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -9,6 +9,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" @@ -202,6 +203,10 @@ @implementation FlutterViewController { // separately. FlutterTextInputPlugin* _textInputPlugin; + // The plugin used to handle key input. This is not an FlutterPlugin, so must be owned + // separately. + FlutterKeyboardPlugin* _keyboardPlugin; + // A message channel for passing key events to the Flutter engine. This should be replaced with // an embedding API; see Issue #47. FlutterBasicMessageChannel* _keyEventChannel; @@ -401,6 +406,7 @@ - (void)configureTrackingArea { - (void)addInternalPlugins { [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]]; + _keyboardPlugin = [[FlutterKeyboardPlugin alloc] initWithViewController:self]; _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self]; _keyEventChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent" @@ -497,6 +503,10 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase { } } +- (void)dispatchFlutterKeyEvent:(const FlutterKeyEvent&)event { + [_engine sendKeyEvent:event]; +} + - (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type { if ([type isEqual:@"keydown"]) { for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) { @@ -525,13 +535,12 @@ - (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type { } } -- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { - // Be sure to add a handler in propagateKeyEvent if you allow more event - // types here. - if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && - event.type != NSEventTypeFlagsChanged) { - return; - } +- (void)handleNSKeyEvent:(NSEvent*)event ofType:(NSString*)type reply:(FlutterReply)callback { + // Dispatch using new method + bool handled = [_keyboardPlugin dispatchEvent:event]; + // TODO: Handle `handled`. + + // Dispatch using legacy method NSMutableDictionary* keyMessage = [@{ @"keymap" : @"macos", @"type" : type, @@ -544,6 +553,17 @@ - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { keyMessage[@"characters"] = event.characters; keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers; } + [_keyEventChannel sendMessage:keyMessage reply:replyHandler]; +} + +- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { + // Be sure to add a handler in propagateKeyEvent if you allow more event + // types here. + if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && + event.type != NSEventTypeFlagsChanged) { + return; + } + __weak __typeof__(self) weakSelf = self; FlutterReply replyHandler = ^(id _Nullable reply) { if (!reply) { @@ -554,7 +574,7 @@ - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { [weakSelf propagateKeyEvent:event ofType:type]; } }; - [_keyEventChannel sendMessage:keyMessage reply:replyHandler]; + [self handleNSKeyEvent:event ofType:type reply:replyHandler] } - (void)onSettingsChanged:(NSNotification*)notification { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index acef9f23bae5e..a3d0945b28967 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -4,6 +4,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/embedder/embedder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" @@ -28,6 +29,15 @@ */ - (void)removeKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; +- (void)handleNSKeyEvent:(NSEvent*)event reply:(FlutterReply)callback; + +/** + * Send a FlutterKeyEvent to the framework using embedder API. + * + * Called by FlutterKeyboardPlugin. + */ +- (void)dispatchFlutterKeyEvent:(const FlutterKeyEvent&)event; + /** * Initializes this FlutterViewController with the specified `FlutterEngine`. * diff --git a/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm b/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm new file mode 100644 index 0000000000000..49f80c6a241e1 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm @@ -0,0 +1,276 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import +#include "./KeyCodeMap_internal.h" + +// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT +// This file is generated by flutter/flutter@dev/tools/gen_keycodes/bin/gen_keycodes.dart and +// should not be edited directly. +// +// Edit the template dev/tools/gen_keycodes/data/keyboard_map_macos_cc.tmpl instead. +// See dev/tools/gen_keycodes/README.md for more information. + +/** + * Mask for the 32-bit value portion of the key code. + * + * This is used by platform-specific code to generate Flutter key codes. + */ +const uint64_t kValueMask = 0x000FFFFFFFF; + +/** + * Mask for the platform prefix portion of the key code. + * + * This is used by platform-specific code to generate Flutter key codes. + */ +const uint64_t kPlatformMask = 0x0FF00000000; + +/** + * The code prefix for keys which have a Unicode representation. + * + * This is used by platform-specific code to generate Flutter key codes. + */ +const uint64_t kUnicodePlane = 0x00000000000; + +/** + * Mask for the auto-generated bit portion of the key code. + * + * This is used by platform-specific code to generate new Flutter key codes for + * keys which are not recognized. + */ +const uint64_t kAutogeneratedMask = 0x10000000000; + +/** + * Mask for the synonym pseudo-keys generated for keys which appear in more than + * one place on the keyboard. + * + * IDs in this range are used to represent keys which appear in multiple places + * on the keyboard, such as the SHIFT, ALT, CTRL, and numeric keypad keys. These + * key codes will never be generated by the key event system, but may be used in + * key maps to represent the union of all the keys of each type in order to + * match them. + * + * To look up the synonyms that are defined, look in the [synonyms] map. + */ +const uint64_t kSynonymMask = 0x20000000000; + +/** + * The code prefix for keys which do not have a Unicode representation. + * + * This is used by platform-specific code to generate Flutter key codes using + * HID Usage codes. + */ +const uint64_t kHidPlane = 0x00100000000; + +const NSDictionary* keyCodeToPhysicalKey = @{ + @0x00000000 : @0x00070004, // keyA + @0x0000000b : @0x00070005, // keyB + @0x00000008 : @0x00070006, // keyC + @0x00000002 : @0x00070007, // keyD + @0x0000000e : @0x00070008, // keyE + @0x00000003 : @0x00070009, // keyF + @0x00000005 : @0x0007000a, // keyG + @0x00000004 : @0x0007000b, // keyH + @0x00000022 : @0x0007000c, // keyI + @0x00000026 : @0x0007000d, // keyJ + @0x00000028 : @0x0007000e, // keyK + @0x00000025 : @0x0007000f, // keyL + @0x0000002e : @0x00070010, // keyM + @0x0000002d : @0x00070011, // keyN + @0x0000001f : @0x00070012, // keyO + @0x00000023 : @0x00070013, // keyP + @0x0000000c : @0x00070014, // keyQ + @0x0000000f : @0x00070015, // keyR + @0x00000001 : @0x00070016, // keyS + @0x00000011 : @0x00070017, // keyT + @0x00000020 : @0x00070018, // keyU + @0x00000009 : @0x00070019, // keyV + @0x0000000d : @0x0007001a, // keyW + @0x00000007 : @0x0007001b, // keyX + @0x00000010 : @0x0007001c, // keyY + @0x00000006 : @0x0007001d, // keyZ + @0x00000012 : @0x0007001e, // digit1 + @0x00000013 : @0x0007001f, // digit2 + @0x00000014 : @0x00070020, // digit3 + @0x00000015 : @0x00070021, // digit4 + @0x00000017 : @0x00070022, // digit5 + @0x00000016 : @0x00070023, // digit6 + @0x0000001a : @0x00070024, // digit7 + @0x0000001c : @0x00070025, // digit8 + @0x00000019 : @0x00070026, // digit9 + @0x0000001d : @0x00070027, // digit0 + @0x00000024 : @0x00070028, // enter + @0x00000035 : @0x00070029, // escape + @0x00000033 : @0x0007002a, // backspace + @0x00000030 : @0x0007002b, // tab + @0x00000031 : @0x0007002c, // space + @0x0000001b : @0x0007002d, // minus + @0x00000018 : @0x0007002e, // equal + @0x00000021 : @0x0007002f, // bracketLeft + @0x0000001e : @0x00070030, // bracketRight + @0x0000002a : @0x00070031, // backslash + @0x00000029 : @0x00070033, // semicolon + @0x00000027 : @0x00070034, // quote + @0x00000032 : @0x00070035, // backquote + @0x0000002b : @0x00070036, // comma + @0x0000002f : @0x00070037, // period + @0x0000002c : @0x00070038, // slash + @0x00000039 : @0x00070039, // capsLock + @0x0000007a : @0x0007003a, // f1 + @0x00000078 : @0x0007003b, // f2 + @0x00000063 : @0x0007003c, // f3 + @0x00000076 : @0x0007003d, // f4 + @0x00000060 : @0x0007003e, // f5 + @0x00000061 : @0x0007003f, // f6 + @0x00000062 : @0x00070040, // f7 + @0x00000064 : @0x00070041, // f8 + @0x00000065 : @0x00070042, // f9 + @0x0000006d : @0x00070043, // f10 + @0x00000067 : @0x00070044, // f11 + @0x0000006f : @0x00070045, // f12 + @0x00000072 : @0x00070049, // insert + @0x00000073 : @0x0007004a, // home + @0x00000074 : @0x0007004b, // pageUp + @0x00000075 : @0x0007004c, // delete + @0x00000077 : @0x0007004d, // end + @0x00000079 : @0x0007004e, // pageDown + @0x0000007c : @0x0007004f, // arrowRight + @0x0000007b : @0x00070050, // arrowLeft + @0x0000007d : @0x00070051, // arrowDown + @0x0000007e : @0x00070052, // arrowUp + @0x00000047 : @0x00070053, // numLock + @0x0000004b : @0x00070054, // numpadDivide + @0x00000043 : @0x00070055, // numpadMultiply + @0x0000004e : @0x00070056, // numpadSubtract + @0x00000045 : @0x00070057, // numpadAdd + @0x0000004c : @0x00070058, // numpadEnter + @0x00000053 : @0x00070059, // numpad1 + @0x00000054 : @0x0007005a, // numpad2 + @0x00000055 : @0x0007005b, // numpad3 + @0x00000056 : @0x0007005c, // numpad4 + @0x00000057 : @0x0007005d, // numpad5 + @0x00000058 : @0x0007005e, // numpad6 + @0x00000059 : @0x0007005f, // numpad7 + @0x0000005b : @0x00070060, // numpad8 + @0x0000005c : @0x00070061, // numpad9 + @0x00000052 : @0x00070062, // numpad0 + @0x00000041 : @0x00070063, // numpadDecimal + @0x0000000a : @0x00070064, // intlBackslash + @0x0000006e : @0x00070065, // contextMenu + @0x00000051 : @0x00070067, // numpadEqual + @0x00000069 : @0x00070068, // f13 + @0x0000006b : @0x00070069, // f14 + @0x00000071 : @0x0007006a, // f15 + @0x0000006a : @0x0007006b, // f16 + @0x00000040 : @0x0007006c, // f17 + @0x0000004f : @0x0007006d, // f18 + @0x00000050 : @0x0007006e, // f19 + @0x0000005a : @0x0007006f, // f20 + @0x0000004a : @0x0007007f, // audioVolumeMute + @0x00000048 : @0x00070080, // audioVolumeUp + @0x00000049 : @0x00070081, // audioVolumeDown + @0x0000005f : @0x00070085, // numpadComma + @0x0000005e : @0x00070087, // intlRo + @0x0000005d : @0x00070089, // intlYen + @0x00000068 : @0x00070090, // lang1 + @0x00000066 : @0x00070091, // lang2 + @0x0000003b : @0x000700e0, // controlLeft + @0x00000038 : @0x000700e1, // shiftLeft + @0x0000003a : @0x000700e2, // altLeft + @0x00000037 : @0x000700e3, // metaLeft + @0x0000003e : @0x000700e4, // controlRight + @0x0000003c : @0x000700e5, // shiftRight + @0x0000003d : @0x000700e6, // altRight + @0x00000036 : @0x000700e7, // metaRight + @0x0000003f : @0x00000012, // fn +}; + +const NSDictionary* keyCodeToLogicalKey = @{ + @0x00000033 : @0x0000000008, // Backspace + @0x00000035 : @0x000000001b, // Escape + @0x00000039 : @0x0000000104, // CapsLock + @0x0000003f : @0x0000000106, // Fn + @0x00000047 : @0x000000010a, // NumLock + @0x0000007d : @0x0000000301, // ArrowDown + @0x0000007b : @0x0000000302, // ArrowLeft + @0x0000007c : @0x0000000303, // ArrowRight + @0x0000007e : @0x0000000304, // ArrowUp + @0x00000077 : @0x0000000305, // End + @0x00000073 : @0x0000000306, // Home + @0x00000079 : @0x0000000307, // PageDown + @0x00000074 : @0x0000000308, // PageUp + @0x00000072 : @0x0000000407, // Insert + @0x0000006e : @0x0000000505, // ContextMenu + @0x0000007a : @0x0000000801, // F1 + @0x00000078 : @0x0000000802, // F2 + @0x00000063 : @0x0000000803, // F3 + @0x00000076 : @0x0000000804, // F4 + @0x00000060 : @0x0000000805, // F5 + @0x00000061 : @0x0000000806, // F6 + @0x00000062 : @0x0000000807, // F7 + @0x00000064 : @0x0000000808, // F8 + @0x00000065 : @0x0000000809, // F9 + @0x0000006d : @0x000000080a, // F10 + @0x00000067 : @0x000000080b, // F11 + @0x0000006f : @0x000000080c, // F12 + @0x00000069 : @0x000000080d, // F13 + @0x0000006b : @0x000000080e, // F14 + @0x00000071 : @0x000000080f, // F15 + @0x0000006a : @0x0000000810, // F16 + @0x00000040 : @0x0000000811, // F17 + @0x0000004f : @0x0000000812, // F18 + @0x00000050 : @0x0000000813, // F19 + @0x0000005a : @0x0000000814, // F20 + @0x0000004c : @0x020000000d, // NumpadEnter + @0x00000043 : @0x020000002a, // NumpadMultiply + @0x00000045 : @0x020000002b, // NumpadAdd + @0x0000005f : @0x020000002c, // NumpadComma + @0x0000004e : @0x020000002d, // NumpadSubtract + @0x00000041 : @0x020000002e, // NumpadDecimal + @0x0000004b : @0x020000002f, // NumpadDivide + @0x00000052 : @0x0200000030, // Numpad0 + @0x00000053 : @0x0200000031, // Numpad1 + @0x00000054 : @0x0200000032, // Numpad2 + @0x00000055 : @0x0200000033, // Numpad3 + @0x00000056 : @0x0200000034, // Numpad4 + @0x00000057 : @0x0200000035, // Numpad5 + @0x00000058 : @0x0200000036, // Numpad6 + @0x00000059 : @0x0200000037, // Numpad7 + @0x0000005b : @0x0200000038, // Numpad8 + @0x0000005c : @0x0200000039, // Numpad9 + @0x00000051 : @0x020000003d, // NumpadEqual + @0x0000003a : @0x0300000102, // AltLeft + @0x0000003b : @0x0300000105, // ControlLeft + @0x00000037 : @0x0300000109, // MetaLeft + @0x00000038 : @0x030000010d, // ShiftLeft + @0x0000003d : @0x0400000102, // AltRight + @0x0000003e : @0x0400000105, // ControlRight + @0x00000036 : @0x0400000109, // MetaRight + @0x0000003c : @0x040000010d, // ShiftRight +}; + +const NSDictionary* siblingKeyCodes = @{ + @0x00000038 : @0x0000003c, // shift + @0x0000003c : @0x00000038, // shift + @0x00000037 : @0x00000036, // meta + @0x00000036 : @0x00000037, // meta + @0x0000003a : @0x0000003d, // alt + @0x0000003d : @0x0000003a, // alt + @0x0000003b : @0x0000003e, // control + @0x0000003e : @0x0000003b, // control +}; + +const NSDictionary* modiferFlags = @{ + @0x000700e1 : @(NSEventModifierFlagShift), + @0x000700e5 : @(NSEventModifierFlagShift), + @0x000700e0 : @(NSEventModifierFlagControl), + @0x000700e4 : @(NSEventModifierFlagControl), + @0x000700e2 : @(NSEventModifierFlagOption), + @0x000700e6 : @(NSEventModifierFlagOption), + @0x000700e3 : @(NSEventModifierFlagCommand), + @0x000700e7 : @(NSEventModifierFlagCommand), +}; + +const uint64_t kCapsLockPhysicalKey = 0x00070039; diff --git a/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h b/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h new file mode 100644 index 0000000000000..ee5ed503d8033 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h @@ -0,0 +1,57 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * Maps macOS-specific key code values representing [PhysicalKeyboardKey]. + * + * MacOS doesn't provide a scan code, but a virtual keycode to represent a physical key. + */ +extern const NSDictionary* keyCodeToPhysicalKey; + +/** + * A map from macOS key codes to Flutter's logical key values. + * + * This is used to derive logical keys that can't or shouldn't be derived from + * `charactersIgnoringModifiers`. + */ +extern const NSDictionary* keyCodeToLogicalKey; + +// Several mask constants. See KeyCodeMap.mm for their descriptions. + +extern const uint64_t kValueMask; +extern const uint64_t kPlatformMask; +extern const uint64_t kUnicodePlane; +extern const uint64_t kHidPlane; +extern const uint64_t kAutogeneratedMask; +extern const uint64_t kSynonymMask; + +/** + * The code prefix for keys from macOS which do not have a Unicode + * representation. + */ +static const uint64_t kMacosPlane = 0x00500000000; + +/** + * Map the key code of a key to that of its sibling key. + * + * A sibling key is the other key of a pair of keys that share the same modifier + * flag, such as left and right shift keys. + */ +extern const NSDictionary* siblingKeyCodes; + +/** + * Map the physical key code of a key to its corresponding bitmask of + * NSEventModifierFlags. + * + * This does not include CapsLock, for it is handled specially. Other modifier + * keys do not seem to be needed either. + */ +extern const NSDictionary* modiferFlags; + +/** + * The physical key for CapsLock, which needs special handling. + * + * This should be kept up to date with KeyCodeMap.mm + */ +extern const uint64_t kCapsLockPhysicalKey; From 9f17107710004c64bf8e28d039a86d517d5d3be4 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 24 Feb 2021 22:44:14 -0800 Subject: [PATCH 02/46] Compile --- shell/platform/darwin/macos/BUILD.gn | 6 +- .../macos/framework/Source/FlutterEngine.mm | 6 +- .../framework/Source/FlutterEngine_Internal.h | 5 +- .../Source/FlutterKeyEmbedderHandler.h | 21 + ...Plugin.mm => FlutterKeyEmbedderHandler.mm} | 100 ++--- .../FlutterKeyEmbedderHandlerUnittests.mm | 75 ++++ .../framework/Source/FlutterKeyHandlerBase.h | 15 + .../framework/Source/FlutterKeyboardPlugin.h | 28 -- .../framework/Source/FlutterViewController.mm | 26 +- .../Source/FlutterViewController_Internal.h | 13 +- .../macos/framework/Source/KeyCodeMap.mm | 380 +++++++++--------- 11 files changed, 381 insertions(+), 294 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h rename shell/platform/darwin/macos/framework/Source/{FlutterKeyboardPlugin.mm => FlutterKeyEmbedderHandler.mm} (81%) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h delete mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 7a7c54f3d191c..cf0859d0ee172 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -69,12 +69,13 @@ source_set("flutter_framework_source") { "framework/Source/FlutterFrameBufferProvider.mm", "framework/Source/FlutterGLCompositor.h", "framework/Source/FlutterGLCompositor.mm", - "framework/Source/FlutterKeyboardPlugin.h", - "framework/Source/FlutterKeyboardPlugin.mm", "framework/Source/FlutterIOSurfaceHolder.h", "framework/Source/FlutterIOSurfaceHolder.mm", "framework/Source/FlutterIntermediateKeyResponder.h", "framework/Source/FlutterIntermediateKeyResponder.mm", + "framework/Source/FlutterKeyEmbedderHandler.h", + "framework/Source/FlutterKeyEmbedderHandler.mm", + "framework/Source/FlutterKeyHandlerBase.h", "framework/Source/FlutterMacOSExternalTexture.h", "framework/Source/FlutterMacOSExternalTexture.h", "framework/Source/FlutterMetalRenderer.h", @@ -160,6 +161,7 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/FlutterEmbedderExternalTextureUnittests.mm", "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterGLCompositorUnittests.mm", + "framework/Source/FlutterKeyEmbedderHandlerUnittests.mm", "framework/Source/FlutterMetalRendererTest.mm", "framework/Source/FlutterMetalSurfaceManagerTest.mm", "framework/Source/FlutterOpenGLRendererTest.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm index b149fb7562459..925219991e584 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm @@ -421,8 +421,10 @@ - (void)sendPointerEvent:(const FlutterPointerEvent&)event { _embedderAPI.SendPointerEvent(_engine, &event, 1); } -- (void)sendKeyEvent:(const FlutterKeyEvent&)event { - _embedderAPI.SendKeyEvent(_engine, &event); +- (void)sendKeyEvent:(const FlutterKeyEvent&)event + callback:(FlutterKeyEventCallback)callback + userData:(void*)userData { + _embedderAPI.SendKeyEvent(_engine, &event, callback, userData); } #pragma mark - Private methods diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h index 56f9f079c0894..c4fae1e9d8a76 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -40,8 +40,11 @@ /** * Dispatches the given pointer event data to engine. */ -- (void)sendKeyEvent:(const FlutterKeyEvent&)event; +- (void)sendKeyEvent:(const FlutterKeyEvent&)event + callback:(nullable FlutterKeyEventCallback)callback + userData:(nullable void*)userData; +/** * Registers an external texture with the given id. Returns YES on success. */ - (BOOL)registerTextureWithID:(int64_t)textureId; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h new file mode 100644 index 0000000000000..ab336ed7dfcc1 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h @@ -0,0 +1,21 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" + +namespace { +typedef void* _VoidPtr; +} + +typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */, + _Nullable FlutterKeyEventCallback /* callback */, + _Nullable _VoidPtr /* user_data */); + +@interface FlutterKeyEmbedderHandler : NSObject + +- (nonnull instancetype)initWithSendEvent:(_Nonnull FlutterSendEmbedderKeyEvent)sendEvent; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm similarity index 81% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.mm rename to shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 5824305d64900..a2c31e1b0c142 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -4,11 +4,11 @@ #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" +#import "FlutterKeyEmbedderHandler.h" +#import "KeyCodeMap_internal.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/embedder/embedder.h" -#import "FlutterKeyboardPlugin.h" -#import "KeyCodeMap_internal.h" // Values of `characterIgnoringModifiers` that must not be directly converted to // a logical key value, but should look up `keyCodeToLogical` by `keyCode`. @@ -18,7 +18,7 @@ // } // Whether a string represents a control character. -static bool IsControlCharacter(NSUInteger length, NSString *label) { +static bool IsControlCharacter(NSUInteger length, NSString* label) { if (length > 1) { return false; } @@ -27,7 +27,7 @@ static bool IsControlCharacter(NSUInteger length, NSString *label) { } // Whether a string represents an unprintable key. -static bool IsUnprintableKey(NSUInteger length, NSString *label) { +static bool IsUnprintableKey(NSUInteger length, NSString* label) { if (length > 1) { return false; } @@ -91,15 +91,14 @@ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { // value. Control keys such as ESC, CRTL, and SHIFT are not printable. HOME, // DEL, arrow keys, and function keys are considered modifier function keys, // which generate invalid Unicode scalar values. - if (keyLabelLength != 0 && - !IsControlCharacter(keyLabelLength, keyLabel) && + if (keyLabelLength != 0 && !IsControlCharacter(keyLabelLength, keyLabel) && !IsUnprintableKey(keyLabelLength, keyLabel)) { // Given that charactersIgnoringModifiers can contain a string of arbitrary // length, limit to a maximum of two Unicode scalar values. It is unlikely // that a keyboard would produce a code point bigger than 32 bits, but it is // still worth defending against this case. NSCAssert((keyLabelLength < 2), - @"Unexpected long key label: |%@|. Please report this to Flutter.", keyLabel); + @"Unexpected long key label: |%@|. Please report this to Flutter.", keyLabel); uint64_t codeUnit = (uint64_t)[keyLabel characterAtIndex:0]; if (keyLabelLength == 2) { @@ -136,12 +135,12 @@ static double GetFlutterTimestampFrom(NSEvent* event) { return event.timestamp * 1000000.0; } -@interface FlutterKeyboardPlugin () +@interface FlutterKeyEmbedderHandler () /** - * The FlutterViewController to manage input for. + * */ -@property(nonatomic, weak) FlutterViewController* flutterViewController; +@property(nonatomic, copy) FlutterSendEmbedderKeyEvent sendEvent; /** * A map of presessd keys. @@ -162,31 +161,30 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; /** * Processes a down event. */ -- (void)dispatchDownEvent:(NSEvent*)event; +- (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; /** * Processes an up event. */ -- (void)dispatchUpEvent:(NSEvent*)event; +- (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; /** * Processes a flags changed event, where modifier keys are pressed or released. */ -- (void)dispatchFlagEvent:(NSEvent*)event; +- (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; -/** - * Processes all kinds of events. - */ -- (bool)dispatchEvent:(NSEvent*)event; +- (void)handleEvent:(NSEvent*)event + ofType:(NSString*)type + callback:(FlutterKeyHandlerCallback)callback; @end -@implementation FlutterKeyboardPlugin +@implementation FlutterKeyEmbedderHandler -- (instancetype)initWithViewController:(FlutterViewController*)viewController { +- (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent { self = [super init]; if (self != nil) { - _flutterViewController = viewController; + _sendEvent = sendEvent; _pressingRecords = [NSMutableDictionary dictionary]; } return self; @@ -200,8 +198,9 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { } } -- (void)dispatchDownEvent:(NSEvent*)event { - printf("Down event keyCode %d cIM %s c %s\n", [event keyCode], [[event charactersIgnoringModifiers] UTF8String], [[event characters] UTF8String]); +- (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { + printf("Down event keyCode %d cIM %s c %s\n", [event keyCode], + [[event charactersIgnoringModifiers] UTF8String], [[event characters] UTF8String]); uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); @@ -232,18 +231,18 @@ - (void)dispatchDownEvent:(NSEvent*)event { FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), .timestamp = GetFlutterTimestampFrom(event), - .kind = isARepeat ? kFlutterKeyEventKindRepeat : kFlutterKeyEventKindDown, + .type = isARepeat ? kFlutterKeyEventTypeRepeat : kFlutterKeyEventTypeDown, .physical = physicalKey, .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue], .character = event.characters.UTF8String, .synthesized = isSynthesized, }; - [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + _sendEvent(flutterEvent, nullptr, nullptr); } -- (void)dispatchUpEvent:(NSEvent*)event { - NSAssert(!event.isARepeat, - @"Unexpected repeated Up event. Please report this to Flutter.", event.characters); +- (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { + NSAssert(!event.isARepeat, @"Unexpected repeated Up event. Please report this to Flutter.", + event.characters); uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; @@ -257,16 +256,16 @@ - (void)dispatchUpEvent:(NSEvent*)event { FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), .timestamp = GetFlutterTimestampFrom(event), - .kind = kFlutterKeyEventKindUp, + .type = kFlutterKeyEventTypeUp, .physical = physicalKey, .logical = [pressedLogicalKey unsignedLongLongValue], .character = nil, .synthesized = false, }; - [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + _sendEvent(flutterEvent, nullptr, nullptr); } -- (void)dispatchCapsLockEvent:(NSEvent*)event { +- (void)dispatchCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { NSNumber* logicalKey = [keyCodeToLogicalKey objectForKey:@(event.keyCode)]; if (logicalKey == nil) return; @@ -275,23 +274,23 @@ - (void)dispatchCapsLockEvent:(NSEvent*)event { FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), .timestamp = GetFlutterTimestampFrom(event), - .kind = kFlutterKeyEventKindDown, + .type = kFlutterKeyEventTypeDown, .physical = kCapsLockPhysicalKey, .logical = logical, .character = nil, .synthesized = false, }; - [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + _sendEvent(flutterEvent, nullptr, nullptr); // MacOS sends a Down or an Up when CapsLock is pressed, depending on whether // the lock is enabled or disabled. This event should always be converted to // a Down and a cancel, since we don't know how long it will be pressed. - flutterEvent.kind = kFlutterKeyEventKindUp; + flutterEvent.type = kFlutterKeyEventTypeUp; flutterEvent.synthesized = true; - [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + _sendEvent(flutterEvent, nullptr, nullptr); } -- (void)dispatchFlagEvent:(NSEvent*)event { +- (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { // NSEvent only tells us the key that triggered the event and the resulting // flag, but not whether the change is a down or up. For keys such as // CapsLock, the change type can be inferred from the key and the flag. @@ -322,7 +321,7 @@ - (void)dispatchFlagEvent:(NSEvent*)event { uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode); if (targetKey == kCapsLockPhysicalKey) { - return [self dispatchCapsLockEvent:event]; + return [self dispatchCapsLockEvent:event callback:callback]; } uint64_t modifierFlag = GetModifierFlagForKey(targetKey); if (targetKey == 0 || modifierFlag == 0) { @@ -332,10 +331,12 @@ - (void)dispatchFlagEvent:(NSEvent*)event { // The `siblingKeyCode` may be 0, which means it doesn't have a sibling key. uint64_t siblingKeyCode = GetSiblingKeyCodeForKey(event.keyCode); uint64_t siblingKeyPhysical = siblingKeyCode == 0 ? 0 : GetPhysicalKeyForKeyCode(siblingKeyCode); - uint64_t siblingKeyLogical = siblingKeyCode == 0 ? 0 : GetLogicalKeyForModifier(siblingKeyCode, siblingKeyPhysical); + uint64_t siblingKeyLogical = + siblingKeyCode == 0 ? 0 : GetLogicalKeyForModifier(siblingKeyCode, siblingKeyPhysical); bool lastTargetPressed = [_pressingRecords objectForKey:@(targetKey)] != nil; - bool lastSiblingPressed = siblingKeyCode == 0 ? false : [_pressingRecords objectForKey:@(siblingKeyPhysical)] != nil; + bool lastSiblingPressed = + siblingKeyCode == 0 ? false : [_pressingRecords objectForKey:@(siblingKeyPhysical)] != nil; bool nowEitherPressed = (event.modifierFlags & modifierFlag) != 0; bool targetKeyShouldDown = !lastTargetPressed && nowEitherPressed; @@ -348,44 +349,43 @@ - (void)dispatchFlagEvent:(NSEvent*)event { .character = nil, }; if (siblingKeyShouldUp) { - flutterEvent.kind = kFlutterKeyEventKindUp; + flutterEvent.type = kFlutterKeyEventTypeUp; flutterEvent.physical = siblingKeyPhysical; flutterEvent.logical = siblingKeyLogical; flutterEvent.synthesized = true; [self updateKey:siblingKeyPhysical asPressed:0]; - [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + _sendEvent(flutterEvent, nullptr, nullptr); } if (targetKeyShouldDown || targetKeyShouldUp) { uint64_t logicalKey = GetLogicalKeyForModifier(event.keyCode, targetKey); - flutterEvent.kind = targetKeyShouldDown ? kFlutterKeyEventKindDown : kFlutterKeyEventKindUp; + flutterEvent.type = targetKeyShouldDown ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp; flutterEvent.physical = targetKey; flutterEvent.logical = logicalKey; flutterEvent.synthesized = false; [self updateKey:targetKey asPressed:(targetKeyShouldDown ? logicalKey : 0)]; - [_flutterViewController dispatchFlutterKeyEvent:flutterEvent]; + _sendEvent(flutterEvent, nullptr, nullptr); } } -- (bool)dispatchEvent:(NSEvent*)event { +- (void)handleEvent:(NSEvent*)event + ofType:(NSString*)type + callback:(FlutterKeyHandlerCallback)callback { switch (event.type) { case NSEventTypeKeyDown: - [self dispatchDownEvent:event]; + [self dispatchDownEvent:event callback:callback]; break; case NSEventTypeKeyUp: - [self dispatchUpEvent:event]; + [self dispatchUpEvent:event callback:callback]; break; case NSEventTypeFlagsChanged: - [self dispatchFlagEvent:event]; + [self dispatchFlagEvent:event callback:callback]; break; default: NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); } - - return true; // TODO } #pragma mark - Private - @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm new file mode 100644 index 0000000000000..801d3ffa10dad --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -0,0 +1,75 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#include +#include + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" +#include "flutter/shell/platform/embedder/embedder.h" +#import "flutter/testing/testing.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace flutter::testing { + +// @implementation FlutterViewController + +// - (void)addKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; + +// /** +// * Removes an intermediate responder for keyboard events. +// */ +// - (void)removeKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; + +// - (void)handleNSKeyEvent:(NSEvent*)event reply:(FlutterReply)callback; + +// /** +// * Send a FlutterKeyEvent to the framework using embedder API. +// * +// * Called by FlutterKeyboardPlugin. +// */ +// - (void)dispatchFlutterKeyEvent:(const FlutterKeyEvent&)event; + +// /** +// * Initializes this FlutterViewController with the specified `FlutterEngine`. +// * +// * The initialized viewcontroller will attach itself to the engine as part of this process. +// * +// * @param engine The `FlutterEngine` instance to attach to. Cannot be nil. +// * @param nibName The NIB name to initialize this controller with. +// * @param nibBundle The NIB bundle. +// */ +// - (nonnull instancetype)initWithEngine:(nonnull FlutterEngine*)engine +// nibName:(nullable NSString*)nibName +// bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER; +// @end + +TEST(FlutterKeybaordPluginUnitTests, TestTextureResolution) { + __block std::vector calls; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + calls.push_back(event.physical); + }]; + [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + characters:@"a" + charactersIgnoringModifiers:@"a" + isARepeat:FALSE + keyCode:0x00000000] + ofType:@"keydown" + callback:^(BOOL handled){ + }]; + + EXPECT_TRUE(calls.size() == 1); +} + +} // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h new file mode 100644 index 0000000000000..24246f388d69e --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h @@ -0,0 +1,15 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +typedef void (^FlutterKeyHandlerCallback)(BOOL handled); + +@protocol FlutterKeyHandlerBase + +- (void)handleEvent:(NSEvent*)event + ofType:(NSString*)type + callback:(FlutterKeyHandlerCallback)callback; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h deleted file mode 100644 index 32851a9cb4028..0000000000000 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" -#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" - -/** - * A plugin to handle hardward keyboard. - * - * Responsible for bridging the native macOS key event system with the - * Flutter framework hardware key event classes, via embedder. - */ -@interface FlutterKeyboardPlugin : NSObject - -- (nonnull instancetype)initWithViewController:(nonnull FlutterViewController*)viewController; - -/** - * Handles the method call that activates a system cursor. - * - * Returns a FlutterError if the arguments can not be recognized. Otherwise - * returns nil. - */ -- (bool)dispatchEvent:(nonnull NSEvent*)event; - -@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index d492b76be452b..e52c5096d03b1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -9,7 +9,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardPlugin.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h" @@ -208,7 +208,7 @@ @implementation FlutterViewController { // The plugin used to handle key input. This is not an FlutterPlugin, so must be owned // separately. - FlutterKeyboardPlugin* _keyboardPlugin; + FlutterKeyEmbedderHandler* _keyboardPlugin; // A message channel for passing key events to the Flutter engine. This should be replaced with // an embedding API; see Issue #47. @@ -423,8 +423,13 @@ - (void)configureTrackingArea { } - (void)addInternalPlugins { + __weak FlutterViewController* weakSelf = self; [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]]; - _keyboardPlugin = [[FlutterKeyboardPlugin alloc] initWithViewController:self]; + _keyboardPlugin = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, + void* userData) { + [weakSelf.engine sendKeyEvent:event callback:callback userData:userData]; + }]; _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self]; _keyEventChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent" @@ -438,7 +443,6 @@ - (void)addInternalPlugins { [FlutterMethodChannel methodChannelWithName:@"flutter/platform" binaryMessenger:_engine.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; - __weak FlutterViewController* weakSelf = self; [_platformChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [weakSelf handleMethodCall:call result:result]; }]; @@ -521,10 +525,6 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase { } } -- (void)dispatchFlutterKeyEvent:(const FlutterKeyEvent&)event { - [_engine sendKeyEvent:event]; -} - - (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type { if ([type isEqual:@"keydown"]) { for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) { @@ -555,8 +555,10 @@ - (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type { - (void)handleNSKeyEvent:(NSEvent*)event ofType:(NSString*)type reply:(FlutterReply)callback { // Dispatch using new method - bool handled = [_keyboardPlugin dispatchEvent:event]; - // TODO: Handle `handled`. + [_keyboardPlugin handleEvent:event + ofType:type + callback:^(BOOL handled){ + }]; // Dispatch using legacy method NSMutableDictionary* keyMessage = [@{ @@ -571,7 +573,7 @@ - (void)handleNSKeyEvent:(NSEvent*)event ofType:(NSString*)type reply:(FlutterRe keyMessage[@"characters"] = event.characters; keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers; } - [_keyEventChannel sendMessage:keyMessage reply:replyHandler]; + [_keyEventChannel sendMessage:keyMessage reply:callback]; } - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { @@ -592,7 +594,7 @@ - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { [weakSelf propagateKeyEvent:event ofType:type]; } }; - [self handleNSKeyEvent:event ofType:type reply:replyHandler] + [self handleNSKeyEvent:event ofType:type reply:replyHandler]; } - (void)onSettingsChanged:(NSNotification*)notification { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index a3d0945b28967..a3a706b0b8298 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -4,9 +4,9 @@ #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" -#import "flutter/shell/platform/embedder/embedder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" +#import "flutter/shell/platform/embedder/embedder.h" @interface FlutterViewController () @@ -29,14 +29,9 @@ */ - (void)removeKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; -- (void)handleNSKeyEvent:(NSEvent*)event reply:(FlutterReply)callback; - -/** - * Send a FlutterKeyEvent to the framework using embedder API. - * - * Called by FlutterKeyboardPlugin. - */ -- (void)dispatchFlutterKeyEvent:(const FlutterKeyEvent&)event; +- (void)handleNSKeyEvent:(nonnull NSEvent*)event + ofType:(nonnull NSString*)type + reply:(_Nullable FlutterReply)callback; /** * Initializes this FlutterViewController with the specified `FlutterEngine`. diff --git a/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm b/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm index 49f80c6a241e1..4017b1f8642cd 100644 --- a/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm +++ b/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import +#import #include "./KeyCodeMap_internal.h" // DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT @@ -65,201 +65,201 @@ const uint64_t kHidPlane = 0x00100000000; const NSDictionary* keyCodeToPhysicalKey = @{ - @0x00000000 : @0x00070004, // keyA - @0x0000000b : @0x00070005, // keyB - @0x00000008 : @0x00070006, // keyC - @0x00000002 : @0x00070007, // keyD - @0x0000000e : @0x00070008, // keyE - @0x00000003 : @0x00070009, // keyF - @0x00000005 : @0x0007000a, // keyG - @0x00000004 : @0x0007000b, // keyH - @0x00000022 : @0x0007000c, // keyI - @0x00000026 : @0x0007000d, // keyJ - @0x00000028 : @0x0007000e, // keyK - @0x00000025 : @0x0007000f, // keyL - @0x0000002e : @0x00070010, // keyM - @0x0000002d : @0x00070011, // keyN - @0x0000001f : @0x00070012, // keyO - @0x00000023 : @0x00070013, // keyP - @0x0000000c : @0x00070014, // keyQ - @0x0000000f : @0x00070015, // keyR - @0x00000001 : @0x00070016, // keyS - @0x00000011 : @0x00070017, // keyT - @0x00000020 : @0x00070018, // keyU - @0x00000009 : @0x00070019, // keyV - @0x0000000d : @0x0007001a, // keyW - @0x00000007 : @0x0007001b, // keyX - @0x00000010 : @0x0007001c, // keyY - @0x00000006 : @0x0007001d, // keyZ - @0x00000012 : @0x0007001e, // digit1 - @0x00000013 : @0x0007001f, // digit2 - @0x00000014 : @0x00070020, // digit3 - @0x00000015 : @0x00070021, // digit4 - @0x00000017 : @0x00070022, // digit5 - @0x00000016 : @0x00070023, // digit6 - @0x0000001a : @0x00070024, // digit7 - @0x0000001c : @0x00070025, // digit8 - @0x00000019 : @0x00070026, // digit9 - @0x0000001d : @0x00070027, // digit0 - @0x00000024 : @0x00070028, // enter - @0x00000035 : @0x00070029, // escape - @0x00000033 : @0x0007002a, // backspace - @0x00000030 : @0x0007002b, // tab - @0x00000031 : @0x0007002c, // space - @0x0000001b : @0x0007002d, // minus - @0x00000018 : @0x0007002e, // equal - @0x00000021 : @0x0007002f, // bracketLeft - @0x0000001e : @0x00070030, // bracketRight - @0x0000002a : @0x00070031, // backslash - @0x00000029 : @0x00070033, // semicolon - @0x00000027 : @0x00070034, // quote - @0x00000032 : @0x00070035, // backquote - @0x0000002b : @0x00070036, // comma - @0x0000002f : @0x00070037, // period - @0x0000002c : @0x00070038, // slash - @0x00000039 : @0x00070039, // capsLock - @0x0000007a : @0x0007003a, // f1 - @0x00000078 : @0x0007003b, // f2 - @0x00000063 : @0x0007003c, // f3 - @0x00000076 : @0x0007003d, // f4 - @0x00000060 : @0x0007003e, // f5 - @0x00000061 : @0x0007003f, // f6 - @0x00000062 : @0x00070040, // f7 - @0x00000064 : @0x00070041, // f8 - @0x00000065 : @0x00070042, // f9 - @0x0000006d : @0x00070043, // f10 - @0x00000067 : @0x00070044, // f11 - @0x0000006f : @0x00070045, // f12 - @0x00000072 : @0x00070049, // insert - @0x00000073 : @0x0007004a, // home - @0x00000074 : @0x0007004b, // pageUp - @0x00000075 : @0x0007004c, // delete - @0x00000077 : @0x0007004d, // end - @0x00000079 : @0x0007004e, // pageDown - @0x0000007c : @0x0007004f, // arrowRight - @0x0000007b : @0x00070050, // arrowLeft - @0x0000007d : @0x00070051, // arrowDown - @0x0000007e : @0x00070052, // arrowUp - @0x00000047 : @0x00070053, // numLock - @0x0000004b : @0x00070054, // numpadDivide - @0x00000043 : @0x00070055, // numpadMultiply - @0x0000004e : @0x00070056, // numpadSubtract - @0x00000045 : @0x00070057, // numpadAdd - @0x0000004c : @0x00070058, // numpadEnter - @0x00000053 : @0x00070059, // numpad1 - @0x00000054 : @0x0007005a, // numpad2 - @0x00000055 : @0x0007005b, // numpad3 - @0x00000056 : @0x0007005c, // numpad4 - @0x00000057 : @0x0007005d, // numpad5 - @0x00000058 : @0x0007005e, // numpad6 - @0x00000059 : @0x0007005f, // numpad7 - @0x0000005b : @0x00070060, // numpad8 - @0x0000005c : @0x00070061, // numpad9 - @0x00000052 : @0x00070062, // numpad0 - @0x00000041 : @0x00070063, // numpadDecimal - @0x0000000a : @0x00070064, // intlBackslash - @0x0000006e : @0x00070065, // contextMenu - @0x00000051 : @0x00070067, // numpadEqual - @0x00000069 : @0x00070068, // f13 - @0x0000006b : @0x00070069, // f14 - @0x00000071 : @0x0007006a, // f15 - @0x0000006a : @0x0007006b, // f16 - @0x00000040 : @0x0007006c, // f17 - @0x0000004f : @0x0007006d, // f18 - @0x00000050 : @0x0007006e, // f19 - @0x0000005a : @0x0007006f, // f20 - @0x0000004a : @0x0007007f, // audioVolumeMute - @0x00000048 : @0x00070080, // audioVolumeUp - @0x00000049 : @0x00070081, // audioVolumeDown - @0x0000005f : @0x00070085, // numpadComma - @0x0000005e : @0x00070087, // intlRo - @0x0000005d : @0x00070089, // intlYen - @0x00000068 : @0x00070090, // lang1 - @0x00000066 : @0x00070091, // lang2 - @0x0000003b : @0x000700e0, // controlLeft - @0x00000038 : @0x000700e1, // shiftLeft - @0x0000003a : @0x000700e2, // altLeft - @0x00000037 : @0x000700e3, // metaLeft - @0x0000003e : @0x000700e4, // controlRight - @0x0000003c : @0x000700e5, // shiftRight - @0x0000003d : @0x000700e6, // altRight - @0x00000036 : @0x000700e7, // metaRight - @0x0000003f : @0x00000012, // fn + @0x00000000 : @0x00070004, // keyA + @0x0000000b : @0x00070005, // keyB + @0x00000008 : @0x00070006, // keyC + @0x00000002 : @0x00070007, // keyD + @0x0000000e : @0x00070008, // keyE + @0x00000003 : @0x00070009, // keyF + @0x00000005 : @0x0007000a, // keyG + @0x00000004 : @0x0007000b, // keyH + @0x00000022 : @0x0007000c, // keyI + @0x00000026 : @0x0007000d, // keyJ + @0x00000028 : @0x0007000e, // keyK + @0x00000025 : @0x0007000f, // keyL + @0x0000002e : @0x00070010, // keyM + @0x0000002d : @0x00070011, // keyN + @0x0000001f : @0x00070012, // keyO + @0x00000023 : @0x00070013, // keyP + @0x0000000c : @0x00070014, // keyQ + @0x0000000f : @0x00070015, // keyR + @0x00000001 : @0x00070016, // keyS + @0x00000011 : @0x00070017, // keyT + @0x00000020 : @0x00070018, // keyU + @0x00000009 : @0x00070019, // keyV + @0x0000000d : @0x0007001a, // keyW + @0x00000007 : @0x0007001b, // keyX + @0x00000010 : @0x0007001c, // keyY + @0x00000006 : @0x0007001d, // keyZ + @0x00000012 : @0x0007001e, // digit1 + @0x00000013 : @0x0007001f, // digit2 + @0x00000014 : @0x00070020, // digit3 + @0x00000015 : @0x00070021, // digit4 + @0x00000017 : @0x00070022, // digit5 + @0x00000016 : @0x00070023, // digit6 + @0x0000001a : @0x00070024, // digit7 + @0x0000001c : @0x00070025, // digit8 + @0x00000019 : @0x00070026, // digit9 + @0x0000001d : @0x00070027, // digit0 + @0x00000024 : @0x00070028, // enter + @0x00000035 : @0x00070029, // escape + @0x00000033 : @0x0007002a, // backspace + @0x00000030 : @0x0007002b, // tab + @0x00000031 : @0x0007002c, // space + @0x0000001b : @0x0007002d, // minus + @0x00000018 : @0x0007002e, // equal + @0x00000021 : @0x0007002f, // bracketLeft + @0x0000001e : @0x00070030, // bracketRight + @0x0000002a : @0x00070031, // backslash + @0x00000029 : @0x00070033, // semicolon + @0x00000027 : @0x00070034, // quote + @0x00000032 : @0x00070035, // backquote + @0x0000002b : @0x00070036, // comma + @0x0000002f : @0x00070037, // period + @0x0000002c : @0x00070038, // slash + @0x00000039 : @0x00070039, // capsLock + @0x0000007a : @0x0007003a, // f1 + @0x00000078 : @0x0007003b, // f2 + @0x00000063 : @0x0007003c, // f3 + @0x00000076 : @0x0007003d, // f4 + @0x00000060 : @0x0007003e, // f5 + @0x00000061 : @0x0007003f, // f6 + @0x00000062 : @0x00070040, // f7 + @0x00000064 : @0x00070041, // f8 + @0x00000065 : @0x00070042, // f9 + @0x0000006d : @0x00070043, // f10 + @0x00000067 : @0x00070044, // f11 + @0x0000006f : @0x00070045, // f12 + @0x00000072 : @0x00070049, // insert + @0x00000073 : @0x0007004a, // home + @0x00000074 : @0x0007004b, // pageUp + @0x00000075 : @0x0007004c, // delete + @0x00000077 : @0x0007004d, // end + @0x00000079 : @0x0007004e, // pageDown + @0x0000007c : @0x0007004f, // arrowRight + @0x0000007b : @0x00070050, // arrowLeft + @0x0000007d : @0x00070051, // arrowDown + @0x0000007e : @0x00070052, // arrowUp + @0x00000047 : @0x00070053, // numLock + @0x0000004b : @0x00070054, // numpadDivide + @0x00000043 : @0x00070055, // numpadMultiply + @0x0000004e : @0x00070056, // numpadSubtract + @0x00000045 : @0x00070057, // numpadAdd + @0x0000004c : @0x00070058, // numpadEnter + @0x00000053 : @0x00070059, // numpad1 + @0x00000054 : @0x0007005a, // numpad2 + @0x00000055 : @0x0007005b, // numpad3 + @0x00000056 : @0x0007005c, // numpad4 + @0x00000057 : @0x0007005d, // numpad5 + @0x00000058 : @0x0007005e, // numpad6 + @0x00000059 : @0x0007005f, // numpad7 + @0x0000005b : @0x00070060, // numpad8 + @0x0000005c : @0x00070061, // numpad9 + @0x00000052 : @0x00070062, // numpad0 + @0x00000041 : @0x00070063, // numpadDecimal + @0x0000000a : @0x00070064, // intlBackslash + @0x0000006e : @0x00070065, // contextMenu + @0x00000051 : @0x00070067, // numpadEqual + @0x00000069 : @0x00070068, // f13 + @0x0000006b : @0x00070069, // f14 + @0x00000071 : @0x0007006a, // f15 + @0x0000006a : @0x0007006b, // f16 + @0x00000040 : @0x0007006c, // f17 + @0x0000004f : @0x0007006d, // f18 + @0x00000050 : @0x0007006e, // f19 + @0x0000005a : @0x0007006f, // f20 + @0x0000004a : @0x0007007f, // audioVolumeMute + @0x00000048 : @0x00070080, // audioVolumeUp + @0x00000049 : @0x00070081, // audioVolumeDown + @0x0000005f : @0x00070085, // numpadComma + @0x0000005e : @0x00070087, // intlRo + @0x0000005d : @0x00070089, // intlYen + @0x00000068 : @0x00070090, // lang1 + @0x00000066 : @0x00070091, // lang2 + @0x0000003b : @0x000700e0, // controlLeft + @0x00000038 : @0x000700e1, // shiftLeft + @0x0000003a : @0x000700e2, // altLeft + @0x00000037 : @0x000700e3, // metaLeft + @0x0000003e : @0x000700e4, // controlRight + @0x0000003c : @0x000700e5, // shiftRight + @0x0000003d : @0x000700e6, // altRight + @0x00000036 : @0x000700e7, // metaRight + @0x0000003f : @0x00000012, // fn }; const NSDictionary* keyCodeToLogicalKey = @{ - @0x00000033 : @0x0000000008, // Backspace - @0x00000035 : @0x000000001b, // Escape - @0x00000039 : @0x0000000104, // CapsLock - @0x0000003f : @0x0000000106, // Fn - @0x00000047 : @0x000000010a, // NumLock - @0x0000007d : @0x0000000301, // ArrowDown - @0x0000007b : @0x0000000302, // ArrowLeft - @0x0000007c : @0x0000000303, // ArrowRight - @0x0000007e : @0x0000000304, // ArrowUp - @0x00000077 : @0x0000000305, // End - @0x00000073 : @0x0000000306, // Home - @0x00000079 : @0x0000000307, // PageDown - @0x00000074 : @0x0000000308, // PageUp - @0x00000072 : @0x0000000407, // Insert - @0x0000006e : @0x0000000505, // ContextMenu - @0x0000007a : @0x0000000801, // F1 - @0x00000078 : @0x0000000802, // F2 - @0x00000063 : @0x0000000803, // F3 - @0x00000076 : @0x0000000804, // F4 - @0x00000060 : @0x0000000805, // F5 - @0x00000061 : @0x0000000806, // F6 - @0x00000062 : @0x0000000807, // F7 - @0x00000064 : @0x0000000808, // F8 - @0x00000065 : @0x0000000809, // F9 - @0x0000006d : @0x000000080a, // F10 - @0x00000067 : @0x000000080b, // F11 - @0x0000006f : @0x000000080c, // F12 - @0x00000069 : @0x000000080d, // F13 - @0x0000006b : @0x000000080e, // F14 - @0x00000071 : @0x000000080f, // F15 - @0x0000006a : @0x0000000810, // F16 - @0x00000040 : @0x0000000811, // F17 - @0x0000004f : @0x0000000812, // F18 - @0x00000050 : @0x0000000813, // F19 - @0x0000005a : @0x0000000814, // F20 - @0x0000004c : @0x020000000d, // NumpadEnter - @0x00000043 : @0x020000002a, // NumpadMultiply - @0x00000045 : @0x020000002b, // NumpadAdd - @0x0000005f : @0x020000002c, // NumpadComma - @0x0000004e : @0x020000002d, // NumpadSubtract - @0x00000041 : @0x020000002e, // NumpadDecimal - @0x0000004b : @0x020000002f, // NumpadDivide - @0x00000052 : @0x0200000030, // Numpad0 - @0x00000053 : @0x0200000031, // Numpad1 - @0x00000054 : @0x0200000032, // Numpad2 - @0x00000055 : @0x0200000033, // Numpad3 - @0x00000056 : @0x0200000034, // Numpad4 - @0x00000057 : @0x0200000035, // Numpad5 - @0x00000058 : @0x0200000036, // Numpad6 - @0x00000059 : @0x0200000037, // Numpad7 - @0x0000005b : @0x0200000038, // Numpad8 - @0x0000005c : @0x0200000039, // Numpad9 - @0x00000051 : @0x020000003d, // NumpadEqual - @0x0000003a : @0x0300000102, // AltLeft - @0x0000003b : @0x0300000105, // ControlLeft - @0x00000037 : @0x0300000109, // MetaLeft - @0x00000038 : @0x030000010d, // ShiftLeft - @0x0000003d : @0x0400000102, // AltRight - @0x0000003e : @0x0400000105, // ControlRight - @0x00000036 : @0x0400000109, // MetaRight - @0x0000003c : @0x040000010d, // ShiftRight + @0x00000033 : @0x0000000008, // Backspace + @0x00000035 : @0x000000001b, // Escape + @0x00000039 : @0x0000000104, // CapsLock + @0x0000003f : @0x0000000106, // Fn + @0x00000047 : @0x000000010a, // NumLock + @0x0000007d : @0x0000000301, // ArrowDown + @0x0000007b : @0x0000000302, // ArrowLeft + @0x0000007c : @0x0000000303, // ArrowRight + @0x0000007e : @0x0000000304, // ArrowUp + @0x00000077 : @0x0000000305, // End + @0x00000073 : @0x0000000306, // Home + @0x00000079 : @0x0000000307, // PageDown + @0x00000074 : @0x0000000308, // PageUp + @0x00000072 : @0x0000000407, // Insert + @0x0000006e : @0x0000000505, // ContextMenu + @0x0000007a : @0x0000000801, // F1 + @0x00000078 : @0x0000000802, // F2 + @0x00000063 : @0x0000000803, // F3 + @0x00000076 : @0x0000000804, // F4 + @0x00000060 : @0x0000000805, // F5 + @0x00000061 : @0x0000000806, // F6 + @0x00000062 : @0x0000000807, // F7 + @0x00000064 : @0x0000000808, // F8 + @0x00000065 : @0x0000000809, // F9 + @0x0000006d : @0x000000080a, // F10 + @0x00000067 : @0x000000080b, // F11 + @0x0000006f : @0x000000080c, // F12 + @0x00000069 : @0x000000080d, // F13 + @0x0000006b : @0x000000080e, // F14 + @0x00000071 : @0x000000080f, // F15 + @0x0000006a : @0x0000000810, // F16 + @0x00000040 : @0x0000000811, // F17 + @0x0000004f : @0x0000000812, // F18 + @0x00000050 : @0x0000000813, // F19 + @0x0000005a : @0x0000000814, // F20 + @0x0000004c : @0x020000000d, // NumpadEnter + @0x00000043 : @0x020000002a, // NumpadMultiply + @0x00000045 : @0x020000002b, // NumpadAdd + @0x0000005f : @0x020000002c, // NumpadComma + @0x0000004e : @0x020000002d, // NumpadSubtract + @0x00000041 : @0x020000002e, // NumpadDecimal + @0x0000004b : @0x020000002f, // NumpadDivide + @0x00000052 : @0x0200000030, // Numpad0 + @0x00000053 : @0x0200000031, // Numpad1 + @0x00000054 : @0x0200000032, // Numpad2 + @0x00000055 : @0x0200000033, // Numpad3 + @0x00000056 : @0x0200000034, // Numpad4 + @0x00000057 : @0x0200000035, // Numpad5 + @0x00000058 : @0x0200000036, // Numpad6 + @0x00000059 : @0x0200000037, // Numpad7 + @0x0000005b : @0x0200000038, // Numpad8 + @0x0000005c : @0x0200000039, // Numpad9 + @0x00000051 : @0x020000003d, // NumpadEqual + @0x0000003a : @0x0300000102, // AltLeft + @0x0000003b : @0x0300000105, // ControlLeft + @0x00000037 : @0x0300000109, // MetaLeft + @0x00000038 : @0x030000010d, // ShiftLeft + @0x0000003d : @0x0400000102, // AltRight + @0x0000003e : @0x0400000105, // ControlRight + @0x00000036 : @0x0400000109, // MetaRight + @0x0000003c : @0x040000010d, // ShiftRight }; const NSDictionary* siblingKeyCodes = @{ - @0x00000038 : @0x0000003c, // shift - @0x0000003c : @0x00000038, // shift - @0x00000037 : @0x00000036, // meta - @0x00000036 : @0x00000037, // meta - @0x0000003a : @0x0000003d, // alt - @0x0000003d : @0x0000003a, // alt - @0x0000003b : @0x0000003e, // control - @0x0000003e : @0x0000003b, // control + @0x00000038 : @0x0000003c, // shift + @0x0000003c : @0x00000038, // shift + @0x00000037 : @0x00000036, // meta + @0x00000036 : @0x00000037, // meta + @0x0000003a : @0x0000003d, // alt + @0x0000003d : @0x0000003a, // alt + @0x0000003b : @0x0000003e, // control + @0x0000003e : @0x0000003b, // control }; const NSDictionary* modiferFlags = @{ From faf23c825df679a91471b3ff47ca925fa8ab1713 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 25 Feb 2021 01:02:31 -0800 Subject: [PATCH 03/46] Refactor channel to be a handler --- shell/platform/darwin/macos/BUILD.gn | 3 + .../Source/FlutterKeyChannelHandler.h | 16 ++++ .../Source/FlutterKeyChannelHandler.mm | 56 ++++++++++++++ .../FlutterKeyChannelHandlerUnittests.mm | 40 ++++++++++ .../Source/FlutterKeyEmbedderHandler.h | 1 + .../FlutterKeyEmbedderHandlerUnittests.mm | 35 +-------- .../framework/Source/FlutterKeyHandlerBase.h | 1 + .../framework/Source/FlutterViewController.mm | 77 ++++++++----------- .../Source/FlutterViewController_Internal.h | 4 - 9 files changed, 148 insertions(+), 85 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index cf0859d0ee172..c8636dee6a384 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -73,6 +73,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterIOSurfaceHolder.mm", "framework/Source/FlutterIntermediateKeyResponder.h", "framework/Source/FlutterIntermediateKeyResponder.mm", + "framework/Source/FlutterKeyChannelHandler.h", + "framework/Source/FlutterKeyChannelHandler.mm", "framework/Source/FlutterKeyEmbedderHandler.h", "framework/Source/FlutterKeyEmbedderHandler.mm", "framework/Source/FlutterKeyHandlerBase.h", @@ -161,6 +163,7 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/FlutterEmbedderExternalTextureUnittests.mm", "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterGLCompositorUnittests.mm", + "framework/Source/FlutterKeyChannelHandlerUnittests.mm", "framework/Source/FlutterKeyEmbedderHandlerUnittests.mm", "framework/Source/FlutterMetalRendererTest.mm", "framework/Source/FlutterMetalSurfaceManagerTest.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h new file mode 100644 index 0000000000000..25df41ab47899 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h @@ -0,0 +1,16 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" + +#import + +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" + + +@interface FlutterKeyChannelHandler : NSObject + +- (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm new file mode 100644 index 0000000000000..dd7424a864bcb --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm @@ -0,0 +1,56 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +#import "FlutterKeyChannelHandler.h" +#import "KeyCodeMap_internal.h" +#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" +#import "flutter/shell/platform/embedder/embedder.h" + +@interface FlutterKeyChannelHandler () + +/** + * The channel used to communicate with Flutter. + */ +@property(nonatomic) FlutterBasicMessageChannel* channel; + +@end + +@implementation FlutterKeyChannelHandler + +- (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel { + self = [super init]; + _channel = channel; + return self; +} + +- (void)handleEvent:(NSEvent*)event + ofType:(NSString*)type + callback:(FlutterKeyHandlerCallback)callback { + NSMutableDictionary* keyMessage = [@{ + @"keymap" : @"macos", + @"type" : type, + @"keyCode" : @(event.keyCode), + @"modifiers" : @(event.modifierFlags), + } mutableCopy]; + // Calling these methods on any other type of event + // (e.g NSEventTypeFlagsChanged) will raise an exception. + if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) { + keyMessage[@"characters"] = event.characters; + keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers; + } + [_channel sendMessage:keyMessage reply:^(id reply){ + if (!reply) { + return callback(true); + } + // Only propagate the event to other responders if the framework didn't handle it. + callback([[reply valueForKey:@"handled"] boolValue]); + }]; +} + +#pragma mark - Private + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm new file mode 100644 index 0000000000000..b5bf1b8b5f959 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm @@ -0,0 +1,40 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#include +#include + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h" +#import "flutter/testing/testing.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace flutter::testing { + +TEST(FlutterKeyChannelHandlerUnittests, BasicKeyEvent) { + __block std::vector calls; + + id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); + FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] + initWithChannel: keyEventChannel]; + [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + characters:@"a" + charactersIgnoringModifiers:@"a" + isARepeat:FALSE + keyCode:0x00000000] + ofType:@"keydown" + callback:^(BOOL handled){ + }]; + + EXPECT_TRUE(calls.size() == 1); +} + +} // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h index ab336ed7dfcc1..afeaef135981f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h @@ -5,6 +5,7 @@ #import #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#include "flutter/shell/platform/embedder/embedder.h" namespace { typedef void* _VoidPtr; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 801d3ffa10dad..465c202025bc8 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -9,45 +9,12 @@ #include #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" -#include "flutter/shell/platform/embedder/embedder.h" #import "flutter/testing/testing.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" namespace flutter::testing { -// @implementation FlutterViewController - -// - (void)addKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; - -// /** -// * Removes an intermediate responder for keyboard events. -// */ -// - (void)removeKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; - -// - (void)handleNSKeyEvent:(NSEvent*)event reply:(FlutterReply)callback; - -// /** -// * Send a FlutterKeyEvent to the framework using embedder API. -// * -// * Called by FlutterKeyboardPlugin. -// */ -// - (void)dispatchFlutterKeyEvent:(const FlutterKeyEvent&)event; - -// /** -// * Initializes this FlutterViewController with the specified `FlutterEngine`. -// * -// * The initialized viewcontroller will attach itself to the engine as part of this process. -// * -// * @param engine The `FlutterEngine` instance to attach to. Cannot be nil. -// * @param nibName The NIB name to initialize this controller with. -// * @param nibBundle The NIB bundle. -// */ -// - (nonnull instancetype)initWithEngine:(nonnull FlutterEngine*)engine -// nibName:(nullable NSString*)nibName -// bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER; -// @end - -TEST(FlutterKeybaordPluginUnitTests, TestTextureResolution) { +TEST(FlutterKeyEmbedderHandlerUnittests, BasicKeyEvent) { __block std::vector calls; FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h index 24246f388d69e..dab90e4a71f4a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h @@ -8,6 +8,7 @@ typedef void (^FlutterKeyHandlerCallback)(BOOL handled); @protocol FlutterKeyHandlerBase +@required - (void)handleEvent:(NSEvent*)event ofType:(NSString*)type callback:(FlutterKeyHandlerCallback)callback; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index e52c5096d03b1..d5c7874ab7514 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -9,7 +9,9 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h" @@ -115,6 +117,11 @@ @interface FlutterViewController () */ @property(nonatomic) id keyUpMonitor; +/** + * TODO + */ +@property(nonatomic) NSArray>* keyHandlers; + /** * Starts running |engine|, including any initial setup. */ @@ -206,14 +213,6 @@ @implementation FlutterViewController { // separately. FlutterTextInputPlugin* _textInputPlugin; - // The plugin used to handle key input. This is not an FlutterPlugin, so must be owned - // separately. - FlutterKeyEmbedderHandler* _keyboardPlugin; - - // A message channel for passing key events to the Flutter engine. This should be replaced with - // an embedding API; see Issue #47. - FlutterBasicMessageChannel* _keyEventChannel; - // A message channel for sending user settings to the flutter engine. FlutterBasicMessageChannel* _settingsChannel; @@ -425,16 +424,19 @@ - (void)configureTrackingArea { - (void)addInternalPlugins { __weak FlutterViewController* weakSelf = self; [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]]; - _keyboardPlugin = [[FlutterKeyEmbedderHandler alloc] - initWithSendEvent:^(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, - void* userData) { - [weakSelf.engine sendKeyEvent:event callback:callback userData:userData]; - }]; + _keyHandlers = @[ + [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, + void* userData) { + [weakSelf.engine sendKeyEvent:event callback:callback userData:userData]; + }], + [[FlutterKeyChannelHandler alloc] + initWithChannel:[FlutterBasicMessageChannel + messageChannelWithName:@"flutter/keyevent" + binaryMessenger:_engine.binaryMessenger + codec:[FlutterJSONMessageCodec sharedInstance]]] + ]; _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self]; - _keyEventChannel = - [FlutterBasicMessageChannel messageChannelWithName:@"flutter/keyevent" - binaryMessenger:_engine.binaryMessenger - codec:[FlutterJSONMessageCodec sharedInstance]]; _settingsChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/settings" binaryMessenger:_engine.binaryMessenger @@ -553,29 +555,6 @@ - (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type { } } -- (void)handleNSKeyEvent:(NSEvent*)event ofType:(NSString*)type reply:(FlutterReply)callback { - // Dispatch using new method - [_keyboardPlugin handleEvent:event - ofType:type - callback:^(BOOL handled){ - }]; - - // Dispatch using legacy method - NSMutableDictionary* keyMessage = [@{ - @"keymap" : @"macos", - @"type" : type, - @"keyCode" : @(event.keyCode), - @"modifiers" : @(event.modifierFlags), - } mutableCopy]; - // Calling these methods on any other type of event - // (e.g NSEventTypeFlagsChanged) will raise an exception. - if (event.type == NSEventTypeKeyDown || event.type == NSEventTypeKeyUp) { - keyMessage[@"characters"] = event.characters; - keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers; - } - [_keyEventChannel sendMessage:keyMessage reply:callback]; -} - - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { // Be sure to add a handler in propagateKeyEvent if you allow more event // types here. @@ -585,16 +564,20 @@ - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { } __weak __typeof__(self) weakSelf = self; - FlutterReply replyHandler = ^(id _Nullable reply) { - if (!reply) { - return; - } - // Only re-dispatch the event to other responders if the framework didn't handle it. - if (![[reply valueForKey:@"handled"] boolValue]) { + __block int unreplied = [_keyHandlers count]; + __block BOOL anyHandled = false; + FlutterKeyHandlerCallback replyCallback = ^(BOOL handled) { + unreplied -= 1; + NSAssert(unreplied >= 0, @"More key handlers replied than intended."); + anyHandled = anyHandled || handled; + if (unreplied == 0 && !anyHandled) { [weakSelf propagateKeyEvent:event ofType:type]; } }; - [self handleNSKeyEvent:event ofType:type reply:replyHandler]; + + for (id handler in _keyHandlers) { + [handler handleEvent:event ofType:type callback:replyCallback]; + } } - (void)onSettingsChanged:(NSNotification*)notification { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index a3a706b0b8298..878e6128d4a4c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -29,10 +29,6 @@ */ - (void)removeKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; -- (void)handleNSKeyEvent:(nonnull NSEvent*)event - ofType:(nonnull NSString*)type - reply:(_Nullable FlutterReply)callback; - /** * Initializes this FlutterViewController with the specified `FlutterEngine`. * From ce16bc4c779217505355aca45351f78eee20120e Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 25 Feb 2021 21:22:36 -0800 Subject: [PATCH 04/46] keyboard manager --- shell/platform/darwin/macos/BUILD.gn | 2 + .../Source/FlutterKeyChannelHandler.h | 1 - .../Source/FlutterKeyChannelHandler.mm | 16 +- .../FlutterKeyChannelHandlerUnittests.mm | 4 +- .../framework/Source/FlutterKeyboardManager.h | 26 ++ .../Source/FlutterKeyboardManager.mm | 130 +++++++ .../Source/FlutterTextInputPlugin.mm | 16 +- .../framework/Source/FlutterViewController.mm | 148 ++------ .../Source/FlutterViewControllerTest.mm | 317 +++++++++--------- .../Source/FlutterViewController_Internal.h | 13 +- 10 files changed, 365 insertions(+), 308 deletions(-) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index c8636dee6a384..311e88ed20b8c 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -78,6 +78,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterKeyEmbedderHandler.h", "framework/Source/FlutterKeyEmbedderHandler.mm", "framework/Source/FlutterKeyHandlerBase.h", + "framework/Source/FlutterKeyboardManager.h", + "framework/Source/FlutterKeyboardManager.mm", "framework/Source/FlutterMacOSExternalTexture.h", "framework/Source/FlutterMacOSExternalTexture.h", "framework/Source/FlutterMetalRenderer.h", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h index 25df41ab47899..bbbd8c713dad0 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h @@ -8,7 +8,6 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" - @interface FlutterKeyChannelHandler : NSObject - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm index dd7424a864bcb..4facf0b087f08 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm @@ -42,13 +42,15 @@ - (void)handleEvent:(NSEvent*)event keyMessage[@"characters"] = event.characters; keyMessage[@"charactersIgnoringModifiers"] = event.charactersIgnoringModifiers; } - [_channel sendMessage:keyMessage reply:^(id reply){ - if (!reply) { - return callback(true); - } - // Only propagate the event to other responders if the framework didn't handle it. - callback([[reply valueForKey:@"handled"] boolValue]); - }]; + [_channel sendMessage:keyMessage + reply:^(id reply) { + if (!reply) { + return callback(true); + } + // Only propagate the event to other responders if the framework didn't handle + // it. + callback([[reply valueForKey:@"handled"] boolValue]); + }]; } #pragma mark - Private diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm index b5bf1b8b5f959..2cc1305850745 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm @@ -18,8 +18,8 @@ __block std::vector calls; id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); - FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] - initWithChannel: keyEventChannel]; + FlutterKeyChannelHandler* handler = + [[FlutterKeyChannelHandler alloc] initWithChannel:keyEventChannel]; [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown location:NSZeroPoint modifierFlags:0 diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h new file mode 100644 index 0000000000000..f1826e635efa9 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -0,0 +1,26 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" + +#import + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" + +@interface FlutterKeyboardManager : NSObject + +- (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner; + +- (void)addHandler:(nonnull id)handler; + +- (void)addAdditionalHandler:(nonnull FlutterIntermediateKeyResponder*)handler; + +- (void)keyDown:(nonnull NSEvent*)event; + +- (void)keyUp:(nonnull NSEvent*)event; + +- (void)flagsChanged:(nonnull NSEvent*)event; + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm new file mode 100644 index 0000000000000..155272b49bc43 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -0,0 +1,130 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h" + +namespace { +} + +@interface FlutterKeyboardManager () + +/** + * TODO + */ +@property(nonatomic, weak) NSResponder* owner; + +/** + * TODO + */ +@property(nonatomic) NSMutableArray>* keyHandlers; + +/** + * A list of additional responders to keyboard events. + * + * Keyboard events received by FlutterViewController are first dispatched to + * each additional responder in order. If any of them handle the event (by + * returning true), the event is not dispatched to later additional responders + * or to the nextResponder. + */ +@property(nonatomic) NSMutableArray* additionalKeyHandlers; + +/** + * The current state of the keyboard and pressed keys. + */ +@property(nonatomic) uint64_t previouslyPressedFlags; + +- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type; + +@end + +@implementation FlutterKeyboardManager + +- (nonnull instancetype)initWithOwner:(NSResponder*)weakOwner { + self = [super init]; + _owner = weakOwner; + _keyHandlers = [[NSMutableArray alloc] init]; + _additionalKeyHandlers = [[NSMutableArray alloc] init]; + _previouslyPressedFlags = 0; + return self; +} + +- (void)addHandler:(nonnull id)handler { + [_keyHandlers addObject:handler]; +} + +- (void)addAdditionalHandler:(nonnull FlutterIntermediateKeyResponder*)handler { + [_additionalKeyHandlers addObject:handler]; +} + +- (void)dispatchToAdditionalHandlers:(NSEvent*)event ofType:(NSString*)type { + if ([type isEqual:@"keydown"]) { + for (FlutterIntermediateKeyResponder* responder in _additionalKeyHandlers) { + if ([responder handleKeyDown:event]) { + return; + } + } + if ([_owner.nextResponder respondsToSelector:@selector(keyDown:)] && + event.type == NSEventTypeKeyDown) { + [_owner.nextResponder keyDown:event]; + } + } else if ([type isEqual:@"keyup"]) { + for (FlutterIntermediateKeyResponder* responder in _additionalKeyHandlers) { + if ([responder handleKeyUp:event]) { + return; + } + } + if ([_owner.nextResponder respondsToSelector:@selector(keyUp:)] && + event.type == NSEventTypeKeyUp) { + [_owner.nextResponder keyUp:event]; + } + } + if ([_owner.nextResponder respondsToSelector:@selector(flagsChanged:)] && + event.type == NSEventTypeFlagsChanged) { + [_owner.nextResponder flagsChanged:event]; + } +} + +- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { + // Be sure to add a handler in propagateKeyEvent if you allow more event + // types here. + if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && + event.type != NSEventTypeFlagsChanged) { + return; + } + + __weak __typeof__(self) weakSelf = self; + __block int unreplied = [_keyHandlers count]; + __block BOOL anyHandled = false; + FlutterKeyHandlerCallback replyCallback = ^(BOOL handled) { + unreplied -= 1; + NSAssert(unreplied >= 0, @"More key handlers replied than intended."); + anyHandled = anyHandled || handled; + if (unreplied == 0 && !anyHandled) { + [weakSelf dispatchToAdditionalHandlers:event ofType:type]; + } + }; + + for (id handler in _keyHandlers) { + [handler handleEvent:event ofType:type callback:replyCallback]; + } +} + +- (void)keyDown:(nonnull NSEvent*)event { + [self dispatchKeyEvent:event ofType:@"keydown"]; +} + +- (void)keyUp:(nonnull NSEvent*)event { + [self dispatchKeyEvent:event ofType:@"keyup"]; +} + +- (void)flagsChanged:(NSEvent*)event { + if (event.modifierFlags < _previouslyPressedFlags) { + [self keyUp:event]; + } else { + [self keyDown:event]; + } + _previouslyPressedFlags = event.modifierFlags; +} + +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 76cd5fc02f182..0a1f3e1b0b091 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -47,6 +47,13 @@ @interface FlutterTextInputPlugin () */ @property(nonatomic, weak) FlutterViewController* flutterViewController; +/** + * Whether the text input is shown in the view. + * + * Defaults to TRUE on startup. + */ +@property(nonatomic) BOOL shown; + /** * Handles a Flutter system message on the text input channel. */ @@ -59,10 +66,10 @@ @implementation FlutterTextInputPlugin - (instancetype)initWithViewController:(FlutterViewController*)viewController { self = [super init]; if (self != nil) { - _flutterViewController = viewController; _channel = [FlutterMethodChannel methodChannelWithName:kTextInputChannel binaryMessenger:viewController.engine.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; + _shown = TRUE; __weak FlutterTextInputPlugin* weakSelf = self; [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [weakSelf handleMethodCall:call result:result]; @@ -97,10 +104,10 @@ - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { } } } else if ([method isEqualToString:kShowMethod]) { - [self.flutterViewController addKeyResponder:self]; + _shown = TRUE; [_textInputContext activate]; } else if ([method isEqualToString:kHideMethod]) { - [self.flutterViewController removeKeyResponder:self]; + _shown = FALSE; [_textInputContext deactivate]; } else if ([method isEqualToString:kClearClientMethod]) { self.activeModel = nil; @@ -142,6 +149,9 @@ - (void)updateEditState { * processing of the same keys. So for now, limit processing to just handleKeyDown. */ - (BOOL)handleKeyDown:(NSEvent*)event { + if (!_shown) { + return false; + } return [_textInputContext handleEvent:event]; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index d5c7874ab7514..f782f84655030 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -12,6 +12,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterOpenGLRenderer.h" @@ -67,17 +68,6 @@ void Reset() { } }; -/** - * State tracking for keyboard events, to adapt between the events coming from the system and the - * events that the embedding API expects. - */ -struct KeyboardState { - /** - * The last known pressed modifier flag keys. - */ - uint64_t previously_pressed_flags = 0; -}; - } // namespace #pragma mark - Private interface declaration. @@ -87,16 +77,6 @@ void Reset() { */ @interface FlutterViewController () -/** - * A list of additional responders to keyboard events. - * - * Keyboard events received by FlutterViewController are first dispatched to - * each additional responder in order. If any of them handle the event (by - * returning true), the event is not dispatched to later additional responders - * or to the nextResponder. - */ -@property(nonatomic) NSMutableOrderedSet* additionalKeyResponders; - /** * The tracking area used to generate hover events, if enabled. */ @@ -107,11 +87,6 @@ @interface FlutterViewController () */ @property(nonatomic) MouseState mouseState; -/** - * The current state of the keyboard and pressed keys. - */ -@property(nonatomic) KeyboardState keyboardState; - /** * Event monitor for keyUp events. */ @@ -120,7 +95,7 @@ @interface FlutterViewController () /** * TODO */ -@property(nonatomic) NSArray>* keyHandlers; +@property(nonatomic) FlutterKeyboardManager* keyboardManager; /** * Starts running |engine|, including any initial setup. @@ -150,19 +125,6 @@ - (void)dispatchMouseEvent:(nonnull NSEvent*)event; */ - (void)dispatchMouseEvent:(nonnull NSEvent*)event phase:(FlutterPointerPhase)phase; -/** - * Sends |event| to all responders in additionalKeyResponders and then to the - * nextResponder if none of the additional responders handled the event. - */ -- (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type; - -/** - * Converts |event| to a key event channel message, and sends it to the engine to - * hand to the framework. Once the framework responds, if the event was not handled, - * propagates the event to any additional key responders. - */ -- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type; - /** * Initializes the KVO for user settings and passes the initial user settings to the engine. */ @@ -209,10 +171,6 @@ @implementation FlutterViewController { // The project to run in this controller's engine. FlutterDartProject* _project; - // The plugin used to handle text input. This is not an FlutterPlugin, so must be owned - // separately. - FlutterTextInputPlugin* _textInputPlugin; - // A message channel for sending user settings to the flutter engine. FlutterBasicMessageChannel* _settingsChannel; @@ -231,7 +189,6 @@ static void CommonInit(FlutterViewController* controller) { project:controller->_project allowHeadlessExecution:NO]; } - controller->_additionalKeyResponders = [[NSMutableOrderedSet alloc] init]; controller->_mouseTrackingMode = FlutterMouseTrackingModeInKeyWindow; } @@ -346,14 +303,6 @@ - (FlutterView*)flutterView { return static_cast(self.view); } -- (void)addKeyResponder:(FlutterIntermediateKeyResponder*)responder { - [self.additionalKeyResponders addObject:responder]; -} - -- (void)removeKeyResponder:(FlutterIntermediateKeyResponder*)responder { - [self.additionalKeyResponders removeObject:responder]; -} - #pragma mark - Private methods - (BOOL)launchEngine { @@ -424,19 +373,22 @@ - (void)configureTrackingArea { - (void)addInternalPlugins { __weak FlutterViewController* weakSelf = self; [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]]; - _keyHandlers = @[ - [[FlutterKeyEmbedderHandler alloc] - initWithSendEvent:^(const FlutterKeyEvent& event, FlutterKeyEventCallback callback, - void* userData) { - [weakSelf.engine sendKeyEvent:event callback:callback userData:userData]; - }], - [[FlutterKeyChannelHandler alloc] - initWithChannel:[FlutterBasicMessageChannel - messageChannelWithName:@"flutter/keyevent" - binaryMessenger:_engine.binaryMessenger - codec:[FlutterJSONMessageCodec sharedInstance]]] - ]; - _textInputPlugin = [[FlutterTextInputPlugin alloc] initWithViewController:self]; + _keyboardManager = [[FlutterKeyboardManager alloc] initWithOwner:weakSelf]; + [_keyboardManager + addHandler:[[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, void* userData) { + [weakSelf.engine sendKeyEvent:event callback:callback userData:userData]; + }]]; + [_keyboardManager + addHandler:[[FlutterKeyChannelHandler alloc] + initWithChannel:[FlutterBasicMessageChannel + messageChannelWithName:@"flutter/keyevent" + binaryMessenger:_engine.binaryMessenger + codec:[FlutterJSONMessageCodec + sharedInstance]]]]; + [_keyboardManager + addAdditionalHandler:[[FlutterTextInputPlugin alloc] initWithViewController:self]]; _settingsChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/settings" binaryMessenger:_engine.binaryMessenger @@ -527,59 +479,6 @@ - (void)dispatchMouseEvent:(NSEvent*)event phase:(FlutterPointerPhase)phase { } } -- (void)propagateKeyEvent:(NSEvent*)event ofType:(NSString*)type { - if ([type isEqual:@"keydown"]) { - for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) { - if ([responder handleKeyDown:event]) { - return; - } - } - if ([self.nextResponder respondsToSelector:@selector(keyDown:)] && - event.type == NSEventTypeKeyDown) { - [self.nextResponder keyDown:event]; - } - } else if ([type isEqual:@"keyup"]) { - for (FlutterIntermediateKeyResponder* responder in self.additionalKeyResponders) { - if ([responder handleKeyUp:event]) { - return; - } - } - if ([self.nextResponder respondsToSelector:@selector(keyUp:)] && - event.type == NSEventTypeKeyUp) { - [self.nextResponder keyUp:event]; - } - } - if ([self.nextResponder respondsToSelector:@selector(flagsChanged:)] && - event.type == NSEventTypeFlagsChanged) { - [self.nextResponder flagsChanged:event]; - } -} - -- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { - // Be sure to add a handler in propagateKeyEvent if you allow more event - // types here. - if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && - event.type != NSEventTypeFlagsChanged) { - return; - } - - __weak __typeof__(self) weakSelf = self; - __block int unreplied = [_keyHandlers count]; - __block BOOL anyHandled = false; - FlutterKeyHandlerCallback replyCallback = ^(BOOL handled) { - unreplied -= 1; - NSAssert(unreplied >= 0, @"More key handlers replied than intended."); - anyHandled = anyHandled || handled; - if (unreplied == 0 && !anyHandled) { - [weakSelf propagateKeyEvent:event ofType:type]; - } - }; - - for (id handler in _keyHandlers) { - [handler handleEvent:event ofType:type callback:replyCallback]; - } -} - - (void)onSettingsChanged:(NSNotification*)notification { // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/32015. NSString* brightness = @@ -675,20 +574,15 @@ - (BOOL)acceptsFirstResponder { } - (void)keyDown:(NSEvent*)event { - [self dispatchKeyEvent:event ofType:@"keydown"]; + [_keyboardManager keyDown:event]; } - (void)keyUp:(NSEvent*)event { - [self dispatchKeyEvent:event ofType:@"keyup"]; + [_keyboardManager keyUp:event]; } - (void)flagsChanged:(NSEvent*)event { - if (event.modifierFlags < _keyboardState.previously_pressed_flags) { - [self keyUp:event]; - } else { - [self keyDown:event]; - } - _keyboardState.previously_pressed_flags = event.modifierFlags; + [_keyboardManager flagsChanged:event]; } - (void)mouseEntered:(NSEvent*)event { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm index f078c2a5a3937..f154062caf53e 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm @@ -16,9 +16,9 @@ @interface FlutterViewControllerTestObjC : NSObject - (bool)testKeyEventsAreSentToFramework; -- (bool)testKeyEventsArePropagatedIfNotHandled; -- (bool)testKeyEventsAreNotPropagatedIfHandled; -- (bool)testFlagsChangedEventsArePropagatedIfNotHandled; +// - (bool)testKeyEventsArePropagatedIfNotHandled; +// - (bool)testKeyEventsAreNotPropagatedIfHandled; +// - (bool)testFlagsChangedEventsArePropagatedIfNotHandled; @end namespace flutter::testing { @@ -91,18 +91,18 @@ id mockViewController(NSString* pasteboardString) { ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsAreSentToFramework]); } -TEST(FlutterViewControllerTest, TestKeyEventsArePropagatedIfNotHandled) { - ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsArePropagatedIfNotHandled]); -} +// TEST(FlutterViewControllerTest, TestKeyEventsArePropagatedIfNotHandled) { +// ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsArePropagatedIfNotHandled]); +// } -TEST(FlutterViewControllerTest, TestKeyEventsAreNotPropagatedIfHandled) { - ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsAreNotPropagatedIfHandled]); -} +// TEST(FlutterViewControllerTest, TestKeyEventsAreNotPropagatedIfHandled) { +// ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsAreNotPropagatedIfHandled]); +// } -TEST(FlutterViewControllerTest, TestFlagsChangedEventsArePropagatedIfNotHandled) { - ASSERT_TRUE( - [[FlutterViewControllerTestObjC alloc] testFlagsChangedEventsArePropagatedIfNotHandled]); -} +// TEST(FlutterViewControllerTest, TestFlagsChangedEventsArePropagatedIfNotHandled) { +// ASSERT_TRUE( +// [[FlutterViewControllerTestObjC alloc] testFlagsChangedEventsArePropagatedIfNotHandled]); +// } } // namespace flutter::testing @@ -141,151 +141,154 @@ - (bool)testKeyEventsAreSentToFramework { return true; } -- (bool)testKeyEventsArePropagatedIfNotHandled { - id engineMock = OCMClassMock([FlutterEngine class]); - id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); - OCMStub( // NOLINT(google-objc-avoid-throwing-exception) - [engineMock binaryMessenger]) - .andReturn(binaryMessengerMock); - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock - nibName:@"" - bundle:nil]; - id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); - [viewController addKeyResponder:responderMock]; - NSDictionary* expectedEvent = @{ - @"keymap" : @"macos", - @"type" : @"keydown", - @"keyCode" : @(65), - @"modifiers" : @(538968064), - @"characters" : @".", - @"charactersIgnoringModifiers" : @".", - }; - NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; - CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); - NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; - OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) - [binaryMessengerMock sendOnChannel:@"flutter/keyevent" - message:encodedKeyEvent - binaryReply:[OCMArg any]]) - .andDo((^(NSInvocation* invocation) { - FlutterBinaryReply handler; - [invocation getArgument:&handler atIndex:4]; - NSDictionary* reply = @{ - @"handled" : @(false), - }; - NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; - handler(encodedReply); - })); - [viewController viewWillAppear]; // Initializes the event channel. - [viewController keyDown:event]; - @try { - OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) - [responderMock handleKeyDown:[OCMArg any]]); - OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) - [binaryMessengerMock sendOnChannel:@"flutter/keyevent" - message:encodedKeyEvent - binaryReply:[OCMArg any]]); - } @catch (...) { - return false; - } - return true; -} +// - (bool)testKeyEventsArePropagatedIfNotHandled { +// id engineMock = OCMClassMock([FlutterEngine class]); +// id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); +// OCMStub( // NOLINT(google-objc-avoid-throwing-exception) +// [engineMock binaryMessenger]) +// .andReturn(binaryMessengerMock); +// FlutterViewController* viewController = [[FlutterViewController alloc] +// initWithEngine:engineMock +// nibName:@"" +// bundle:nil]; +// id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); +// [viewController addKeyResponder:responderMock]; +// NSDictionary* expectedEvent = @{ +// @"keymap" : @"macos", +// @"type" : @"keydown", +// @"keyCode" : @(65), +// @"modifiers" : @(538968064), +// @"characters" : @".", +// @"charactersIgnoringModifiers" : @".", +// }; +// NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; +// CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); +// NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; +// OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) +// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" +// message:encodedKeyEvent +// binaryReply:[OCMArg any]]) +// .andDo((^(NSInvocation* invocation) { +// FlutterBinaryReply handler; +// [invocation getArgument:&handler atIndex:4]; +// NSDictionary* reply = @{ +// @"handled" : @(false), +// }; +// NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; +// handler(encodedReply); +// })); +// [viewController viewWillAppear]; // Initializes the event channel. +// [viewController keyDown:event]; +// @try { +// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) +// [responderMock handleKeyDown:[OCMArg any]]); +// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) +// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" +// message:encodedKeyEvent +// binaryReply:[OCMArg any]]); +// } @catch (...) { +// return false; +// } +// return true; +// } -- (bool)testFlagsChangedEventsArePropagatedIfNotHandled { - id engineMock = OCMClassMock([FlutterEngine class]); - id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); - OCMStub( // NOLINT(google-objc-avoid-throwing-exception) - [engineMock binaryMessenger]) - .andReturn(binaryMessengerMock); - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock - nibName:@"" - bundle:nil]; - id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); - [viewController addKeyResponder:responderMock]; - NSDictionary* expectedEvent = @{ - @"keymap" : @"macos", - @"type" : @"keydown", - @"keyCode" : @(56), // SHIFT key - @"modifiers" : @(537001986), - }; - NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; - CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE); // SHIFT key - CGEventSetType(cgEvent, kCGEventFlagsChanged); - NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; - OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) - [binaryMessengerMock sendOnChannel:@"flutter/keyevent" - message:encodedKeyEvent - binaryReply:[OCMArg any]]) - .andDo((^(NSInvocation* invocation) { - FlutterBinaryReply handler; - [invocation getArgument:&handler atIndex:4]; - NSDictionary* reply = @{ - @"handled" : @(false), - }; - NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; - handler(encodedReply); - })); - [viewController viewWillAppear]; // Initializes the event channel. - [viewController flagsChanged:event]; - @try { - OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) - [binaryMessengerMock sendOnChannel:@"flutter/keyevent" - message:encodedKeyEvent - binaryReply:[OCMArg any]]); - } @catch (...) { - return false; - } - return true; -} +// - (bool)testFlagsChangedEventsArePropagatedIfNotHandled { +// id engineMock = OCMClassMock([FlutterEngine class]); +// id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); +// OCMStub( // NOLINT(google-objc-avoid-throwing-exception) +// [engineMock binaryMessenger]) +// .andReturn(binaryMessengerMock); +// FlutterViewController* viewController = [[FlutterViewController alloc] +// initWithEngine:engineMock +// nibName:@"" +// bundle:nil]; +// id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); +// [viewController addKeyResponder:responderMock]; +// NSDictionary* expectedEvent = @{ +// @"keymap" : @"macos", +// @"type" : @"keydown", +// @"keyCode" : @(56), // SHIFT key +// @"modifiers" : @(537001986), +// }; +// NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; +// CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE); // SHIFT key +// CGEventSetType(cgEvent, kCGEventFlagsChanged); +// NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; +// OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) +// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" +// message:encodedKeyEvent +// binaryReply:[OCMArg any]]) +// .andDo((^(NSInvocation* invocation) { +// FlutterBinaryReply handler; +// [invocation getArgument:&handler atIndex:4]; +// NSDictionary* reply = @{ +// @"handled" : @(false), +// }; +// NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; +// handler(encodedReply); +// })); +// [viewController viewWillAppear]; // Initializes the event channel. +// [viewController flagsChanged:event]; +// @try { +// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) +// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" +// message:encodedKeyEvent +// binaryReply:[OCMArg any]]); +// } @catch (...) { +// return false; +// } +// return true; +// } -- (bool)testKeyEventsAreNotPropagatedIfHandled { - id engineMock = OCMClassMock([FlutterEngine class]); - id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); - OCMStub( // NOLINT(google-objc-avoid-throwing-exception) - [engineMock binaryMessenger]) - .andReturn(binaryMessengerMock); - FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock - nibName:@"" - bundle:nil]; - id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); - [viewController addKeyResponder:responderMock]; - NSDictionary* expectedEvent = @{ - @"keymap" : @"macos", - @"type" : @"keydown", - @"keyCode" : @(65), - @"modifiers" : @(538968064), - @"characters" : @".", - @"charactersIgnoringModifiers" : @".", - }; - NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; - CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); - NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; - OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) - [binaryMessengerMock sendOnChannel:@"flutter/keyevent" - message:encodedKeyEvent - binaryReply:[OCMArg any]]) - .andDo((^(NSInvocation* invocation) { - FlutterBinaryReply handler; - [invocation getArgument:&handler atIndex:4]; - NSDictionary* reply = @{ - @"handled" : @(true), - }; - NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; - handler(encodedReply); - })); - [viewController viewWillAppear]; // Initializes the event channel. - [viewController keyDown:event]; - @try { - OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) - never(), [responderMock handleKeyDown:[OCMArg any]]); - OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) - [binaryMessengerMock sendOnChannel:@"flutter/keyevent" - message:encodedKeyEvent - binaryReply:[OCMArg any]]); - } @catch (...) { - return false; - } - return true; -} +// - (bool)testKeyEventsAreNotPropagatedIfHandled { +// id engineMock = OCMClassMock([FlutterEngine class]); +// id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); +// OCMStub( // NOLINT(google-objc-avoid-throwing-exception) +// [engineMock binaryMessenger]) +// .andReturn(binaryMessengerMock); +// FlutterViewController* viewController = [[FlutterViewController alloc] +// initWithEngine:engineMock +// nibName:@"" +// bundle:nil]; +// id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); +// [viewController addKeyResponder:responderMock]; +// NSDictionary* expectedEvent = @{ +// @"keymap" : @"macos", +// @"type" : @"keydown", +// @"keyCode" : @(65), +// @"modifiers" : @(538968064), +// @"characters" : @".", +// @"charactersIgnoringModifiers" : @".", +// }; +// NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; +// CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); +// NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; +// OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) +// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" +// message:encodedKeyEvent +// binaryReply:[OCMArg any]]) +// .andDo((^(NSInvocation* invocation) { +// FlutterBinaryReply handler; +// [invocation getArgument:&handler atIndex:4]; +// NSDictionary* reply = @{ +// @"handled" : @(true), +// }; +// NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; +// handler(encodedReply); +// })); +// [viewController viewWillAppear]; // Initializes the event channel. +// [viewController keyDown:event]; +// @try { +// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) +// never(), [responderMock handleKeyDown:[OCMArg any]]); +// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) +// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" +// message:encodedKeyEvent +// binaryReply:[OCMArg any]]); +// } @catch (...) { +// return false; +// } +// return true; +// } @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index 878e6128d4a4c..0325a195a50c6 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -18,17 +18,6 @@ */ @property(nonatomic, readonly, nonnull) NSPasteboard* pasteboard; -/** - * Adds an intermediate responder for keyboard events. Key up and key down events are forwarded to - * all added responders, and they either handle the keys or not. - */ -- (void)addKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; - -/** - * Removes an intermediate responder for keyboard events. - */ -- (void)removeKeyResponder:(nonnull FlutterIntermediateKeyResponder*)responder; - /** * Initializes this FlutterViewController with the specified `FlutterEngine`. * @@ -41,4 +30,6 @@ - (nonnull instancetype)initWithEngine:(nonnull FlutterEngine*)engine nibName:(nullable NSString*)nibName bundle:(nullable NSBundle*)nibBundle NS_DESIGNATED_INITIALIZER; + +#pragma mark - Private interface declaration. @end From 6b47a0e50306dee5f659121aaa73b0d51717c66f Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 1 Mar 2021 17:25:48 -0800 Subject: [PATCH 05/46] Basic channel test compile --- .../FlutterKeyChannelHandlerUnittests.mm | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm index 2cc1305850745..f11f9e5db0b5a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm @@ -5,9 +5,6 @@ #import #import -#include -#include - #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h" #import "flutter/testing/testing.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" @@ -15,26 +12,47 @@ namespace flutter::testing { TEST(FlutterKeyChannelHandlerUnittests, BasicKeyEvent) { - __block std::vector calls; + __block NSMutableArray* messages = [[NSMutableArray alloc] init]; + BOOL next_response = TRUE; + __block NSMutableArray* responses = [[NSMutableArray alloc] init]; + + id mockKeyEventChannel = OCMStrictClassMock([FlutterBasicMessageChannel class]); + OCMStub([mockKeyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]).andDo(( + ^(NSInvocation *invocation) { + [invocation retainArguments]; + NSDictionary* message; + [invocation getArgument:&message atIndex:2]; + [messages addObject:message]; + + FlutterReply callback; + [invocation getArgument:&callback atIndex:3]; + NSDictionary* keyMessage = @{ + @"handled" : @(next_response), + }; + callback(keyMessage); + })); - id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]); FlutterKeyChannelHandler* handler = - [[FlutterKeyChannelHandler alloc] initWithChannel:keyEventChannel]; + [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:0 context:nil - characters:@"a" + characters:@"A" charactersIgnoringModifiers:@"a" - isARepeat:FALSE - keyCode:0x00000000] + isARepeat:TRUE + keyCode:0x60] ofType:@"keydown" callback:^(BOOL handled){ + [responses addObject:@(handled)]; }]; - EXPECT_TRUE(calls.size() == 1); + EXPECT_EQ([messages count], 1u); + EXPECT_EQ([[messages firstObject][@"keyCode"] intValue], 0x60); + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses firstObject] boolValue], TRUE); } } // namespace flutter::testing From a9728fd5c9696400760a10bd54d7f9b21b432ae7 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 2 Mar 2021 15:23:10 -0800 Subject: [PATCH 06/46] More tests for channel --- .../FlutterKeyChannelHandlerUnittests.mm | 90 ++++++++++++++++++- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm index f11f9e5db0b5a..51089b3a767ed 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm @@ -13,7 +13,7 @@ TEST(FlutterKeyChannelHandlerUnittests, BasicKeyEvent) { __block NSMutableArray* messages = [[NSMutableArray alloc] init]; - BOOL next_response = TRUE; + __block BOOL next_response = TRUE; __block NSMutableArray* responses = [[NSMutableArray alloc] init]; id mockKeyEventChannel = OCMStrictClassMock([FlutterBasicMessageChannel class]); @@ -32,6 +32,7 @@ callback(keyMessage); })); + // Key down FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown @@ -50,9 +51,92 @@ }]; EXPECT_EQ([messages count], 1u); - EXPECT_EQ([[messages firstObject][@"keyCode"] intValue], 0x60); + EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); + EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keydown"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0x60); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0); + EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "A"); + EXPECT_EQ([[messages lastObject][@"charactersIgnoringModifiers"] UTF8String], "a"); + + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses lastObject] boolValue], TRUE); + + [messages removeAllObjects]; + [responses removeAllObjects]; + + // Key up + next_response = false; + [handler handleEvent:[NSEvent keyEventWithType:NSKeyUp + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + characters:@"B" + charactersIgnoringModifiers:@"b" + isARepeat:TRUE + keyCode:0x61] + ofType:@"keyup" + callback:^(BOOL handled){ + [responses addObject:@(handled)]; + }]; + + EXPECT_EQ([messages count], 1u); + EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); + EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keyup"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0x61); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0); + EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "B"); + EXPECT_EQ([[messages lastObject][@"charactersIgnoringModifiers"] UTF8String], "b"); + + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses lastObject] boolValue], FALSE); +} + +TEST(FlutterKeyChannelHandlerUnittests, EmptyResponseIsTakenAsHandled) { + __block NSMutableArray* messages = [[NSMutableArray alloc] init]; + __block NSMutableArray* responses = [[NSMutableArray alloc] init]; + + id mockKeyEventChannel = OCMStrictClassMock([FlutterBasicMessageChannel class]); + OCMStub([mockKeyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]).andDo(( + ^(NSInvocation *invocation) { + [invocation retainArguments]; + NSDictionary* message; + [invocation getArgument:&message atIndex:2]; + [messages addObject:message]; + + FlutterReply callback; + [invocation getArgument:&callback atIndex:3]; + callback(nullptr); + })); + + FlutterKeyChannelHandler* handler = + [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; + [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown + location:NSZeroPoint + modifierFlags:0 + timestamp:0 + windowNumber:0 + context:nil + characters:@"A" + charactersIgnoringModifiers:@"a" + isARepeat:TRUE + keyCode:0x60] + ofType:@"keydown" + callback:^(BOOL handled){ + [responses addObject:@(handled)]; + }]; + + EXPECT_EQ([messages count], 1u); + EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); + EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keydown"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0x60); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0); + EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "A"); + EXPECT_EQ([[messages lastObject][@"charactersIgnoringModifiers"] UTF8String], "a"); + EXPECT_EQ([responses count], 1u); - EXPECT_EQ([[responses firstObject] boolValue], TRUE); + EXPECT_EQ([[responses lastObject] boolValue], TRUE); } } // namespace flutter::testing From 52aba7d55b8e14a48ce05be2c9a789f62357d043 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Tue, 2 Mar 2021 19:03:42 -0800 Subject: [PATCH 07/46] Add embedder test --- .../FlutterKeyChannelHandlerUnittests.mm | 56 ++++----- .../Source/FlutterKeyEmbedderHandler.mm | 9 +- .../FlutterKeyEmbedderHandlerUnittests.mm | 117 +++++++++++++++--- .../framework/Source/FlutterKeyboardManager.h | 2 +- .../Source/FlutterKeyboardManager.mm | 3 - 5 files changed, 139 insertions(+), 48 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm index 51089b3a767ed..fdbb74a37c03c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm @@ -17,20 +17,20 @@ __block NSMutableArray* responses = [[NSMutableArray alloc] init]; id mockKeyEventChannel = OCMStrictClassMock([FlutterBasicMessageChannel class]); - OCMStub([mockKeyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]).andDo(( - ^(NSInvocation *invocation) { - [invocation retainArguments]; - NSDictionary* message; - [invocation getArgument:&message atIndex:2]; - [messages addObject:message]; - - FlutterReply callback; - [invocation getArgument:&callback atIndex:3]; - NSDictionary* keyMessage = @{ - @"handled" : @(next_response), - }; - callback(keyMessage); - })); + OCMStub([mockKeyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]) + .andDo((^(NSInvocation* invocation) { + [invocation retainArguments]; + NSDictionary* message; + [invocation getArgument:&message atIndex:2]; + [messages addObject:message]; + + FlutterReply callback; + [invocation getArgument:&callback atIndex:3]; + NSDictionary* keyMessage = @{ + @"handled" : @(next_response), + }; + callback(keyMessage); + })); // Key down FlutterKeyChannelHandler* handler = @@ -46,7 +46,7 @@ isARepeat:TRUE keyCode:0x60] ofType:@"keydown" - callback:^(BOOL handled){ + callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -77,7 +77,7 @@ isARepeat:TRUE keyCode:0x61] ofType:@"keyup" - callback:^(BOOL handled){ + callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -98,17 +98,17 @@ __block NSMutableArray* responses = [[NSMutableArray alloc] init]; id mockKeyEventChannel = OCMStrictClassMock([FlutterBasicMessageChannel class]); - OCMStub([mockKeyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]).andDo(( - ^(NSInvocation *invocation) { - [invocation retainArguments]; - NSDictionary* message; - [invocation getArgument:&message atIndex:2]; - [messages addObject:message]; - - FlutterReply callback; - [invocation getArgument:&callback atIndex:3]; - callback(nullptr); - })); + OCMStub([mockKeyEventChannel sendMessage:[OCMArg any] reply:[OCMArg any]]) + .andDo((^(NSInvocation* invocation) { + [invocation retainArguments]; + NSDictionary* message; + [invocation getArgument:&message atIndex:2]; + [messages addObject:message]; + + FlutterReply callback; + [invocation getArgument:&callback atIndex:3]; + callback(nullptr); + })); FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; @@ -123,7 +123,7 @@ isARepeat:TRUE keyCode:0x60] ofType:@"keydown" - callback:^(BOOL handled){ + callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index a2c31e1b0c142..eb42c4b35aff1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -199,8 +199,6 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { } - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - printf("Down event keyCode %d cIM %s c %s\n", [event keyCode], - [[event charactersIgnoringModifiers] UTF8String], [[event characters] UTF8String]); uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); @@ -371,6 +369,13 @@ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca - (void)handleEvent:(NSEvent*)event ofType:(NSString*)type callback:(FlutterKeyHandlerCallback)callback { + printf("#### Event %d keyCode %d rep %d ", (int)event.type, (int)event.keyCode, event.isARepeat); + if (event.type != NSEventTypeFlagsChanged) { + printf("cIM %s c %s\n", [[event charactersIgnoringModifiers] UTF8String], + [[event characters] UTF8String]); + } else { + printf("\n"); + } switch (event.type) { case NSEventTypeKeyDown: [self dispatchDownEvent:event callback:callback]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 465c202025bc8..2e5a10b628eb3 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -5,38 +5,127 @@ #import #import -#include -#include - #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" #import "flutter/testing/testing.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" +// A wrap to convert FlutterKeyEvent to a ObjC class. +@interface TestKeyEvent : NSObject { + @public + FlutterKeyEvent* data; +} +- (nonnull instancetype)initWithEvent:(nonnull const FlutterKeyEvent*)event; +@end + +@implementation TestKeyEvent +- (instancetype)initWithEvent:(const FlutterKeyEvent*)event { + self = [super init]; + data = new FlutterKeyEvent(*event); + if (event->character != nullptr) { + size_t len = strlen(event->character); + char* character = new char[len + 1]; + strcpy(character, event->character); + data->character = character; + } + return self; +} + +- (void)dealloc { + if (data->character != nullptr) + delete[] data->character; + delete data; +} +@end + +@interface Utils : NSObject ++ (nonnull NSEvent*)keyEventWithType:(NSEventType)type + modifierFlags:(NSEventModifierFlags)flags + characters:(NSString*)keys + charactersIgnoringModifiers:(NSString*)ukeys + isARepeat:(BOOL)flag + keyCode:(unsigned short)code; +@end + +@implementation Utils ++ (nonnull NSEvent*)keyEventWithType:(NSEventType)type + modifierFlags:(NSEventModifierFlags)flags + characters:(NSString*)keys + charactersIgnoringModifiers:(NSString*)ukeys + isARepeat:(BOOL)flag + keyCode:(unsigned short)code { + return [NSEvent keyEventWithType:type + location:NSZeroPoint + modifierFlags:flags + timestamp:0 + windowNumber:0 + context:nil + characters:keys + charactersIgnoringModifiers:ukeys + isARepeat:flag + keyCode:code]; +} +@end + namespace flutter::testing { +namespace { +constexpr uint64_t kPhysicalKeyA = 0x00070004; +// constexpr uint64_t kPhysicalControlLeft = 0x000700e0; +// constexpr uint64_t kPhysicalControlRight = 0x000700e4; +// constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; +// constexpr uint64_t kPhysicalShiftRight = 0x000700e5; +// constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; + +constexpr uint64_t kLogicalKeyA = 0x00000061; +// constexpr uint64_t kLogicalControlLeft = 0x00300000105; +// constexpr uint64_t kLogicalControlRight = 0x00400000105; +// constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; +// constexpr uint64_t kLogicalShiftRight = 0x0040000010d; +// constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; + +} + +// Test the most basic key events. +// +// Press, hold, and release key A on an US keyboard. TEST(FlutterKeyEmbedderHandlerUnittests, BasicKeyEvent) { - __block std::vector calls; + __block BOOL next_handled = TRUE; + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block NSMutableArray* responses = [[NSMutableArray alloc] init]; + FlutterKeyEvent* event; FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { - calls.push_back(event.physical); + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; + printf("callback %d handled %d\n", !!callback, next_handled); + if (callback != nullptr) { + callback(next_handled, user_data); + } }]; - [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown - location:NSZeroPoint - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil + + next_handled = TRUE; + [handler handleEvent:[Utils keyEventWithType:NSKeyDown + modifierFlags:0 characters:@"a" charactersIgnoringModifiers:@"a" isARepeat:FALSE - keyCode:0x00000000] + keyCode:0] ofType:@"keydown" - callback:^(BOOL handled){ + callback:^(BOOL handled) { + [responses addObject:@(handled)]; }]; - EXPECT_TRUE(calls.size() == 1); + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses lastObject] boolValue], TRUE); } } // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index f1826e635efa9..2cb2edfd64cc6 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -6,8 +6,8 @@ #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" @interface FlutterKeyboardManager : NSObject diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 155272b49bc43..65a0aa8ee64ce 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -4,9 +4,6 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h" -namespace { -} - @interface FlutterKeyboardManager () /** From ce9ed0b147a45aaf8edb1c88309e89420a5fffa8 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 3 Mar 2021 03:29:05 -0800 Subject: [PATCH 08/46] Implement responding --- .../Source/FlutterKeyEmbedderHandler.mm | 78 +++++++++++++++++-- .../FlutterKeyEmbedderHandlerUnittests.mm | 2 +- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index eb42c4b35aff1..6edf30641594d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -17,6 +17,8 @@ // static const NSSet* kAmbiguousCharacters = @{ // } +namespace { + // Whether a string represents a control character. static bool IsControlCharacter(NSUInteger length, NSString* label) { if (length > 1) { @@ -135,6 +137,36 @@ static double GetFlutterTimestampFrom(NSEvent* event) { return event.timestamp * 1000000.0; } +void HandleResponse(bool handled, void* user_data); +} // namespace + +/* An entry of FlutterKeyEmbedderHandler.pendingResponse. + * + * FlutterEngineSendKeyEvent only supports a global function and a pointer. + * This class is used for the global function, HandleResponse, to convert a + * pointer into a method call, FlutterKeyEmbedderHandler.handleResponse. + */ +@interface FlutterKeyPendingResponse : NSObject + +@property(nonatomic) FlutterKeyEmbedderHandler* handler; + +@property(nonatomic) uint64_t responseId; + +- (nonnull instancetype)initWithHandler:(nonnull FlutterKeyEmbedderHandler*)handler + responseId:(uint64_t)responseId; + +@end + +@implementation FlutterKeyPendingResponse +- (instancetype)initWithHandler:(FlutterKeyEmbedderHandler*)handler + responseId:(uint64_t)responseId { + self = [super init]; + _handler = handler; + _responseId = responseId; + return self; +} +@end + @interface FlutterKeyEmbedderHandler () /** @@ -148,7 +180,11 @@ @interface FlutterKeyEmbedderHandler () * The keys of the dictionary are physical keys, while the values are the logical keys * on pressing. */ -@property(nonatomic) NSMutableDictionary* pressingRecords; +@property(nonatomic) NSMutableDictionary* pressingRecords; + +@property(nonatomic) uint64_t responseId; + +@property(nonatomic) NSMutableDictionary* pendingResponses; /** * Update the pressing state. @@ -158,6 +194,9 @@ @interface FlutterKeyEmbedderHandler () */ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; +- (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event + callback:(FlutterKeyHandlerCallback)callback; + /** * Processes a down event. */ @@ -177,6 +216,8 @@ - (void)handleEvent:(NSEvent*)event ofType:(NSString*)type callback:(FlutterKeyHandlerCallback)callback; +- (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; + @end @implementation FlutterKeyEmbedderHandler @@ -186,10 +227,14 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent if (self != nil) { _sendEvent = sendEvent; _pressingRecords = [NSMutableDictionary dictionary]; + _pendingResponses = [NSMutableDictionary dictionary]; + _responseId = 1; } return self; } +#pragma mark - Private + - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { if (logicalKey == 0) { [_pressingRecords removeObjectForKey:@(physicalKey)]; @@ -198,6 +243,16 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { } } +- (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event + callback:(FlutterKeyHandlerCallback)callback { + _responseId += 1; + uint64_t responseId = _responseId; + FlutterKeyPendingResponse* pending = + [[FlutterKeyPendingResponse alloc] initWithHandler:self responseId:responseId]; + _pendingResponses[@(responseId)] = callback; + _sendEvent(event, HandleResponse, (__bridge void*)pending); +} + - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); @@ -235,7 +290,7 @@ - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca .character = event.characters.UTF8String, .synthesized = isSynthesized, }; - _sendEvent(flutterEvent, nullptr, nullptr); + [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { @@ -260,7 +315,7 @@ - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call .character = nil, .synthesized = false, }; - _sendEvent(flutterEvent, nullptr, nullptr); + [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } - (void)dispatchCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { @@ -278,7 +333,7 @@ - (void)dispatchCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallbac .character = nil, .synthesized = false, }; - _sendEvent(flutterEvent, nullptr, nullptr); + [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; // MacOS sends a Down or an Up when CapsLock is pressed, depending on whether // the lock is enabled or disabled. This event should always be converted to @@ -362,7 +417,7 @@ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca flutterEvent.logical = logicalKey; flutterEvent.synthesized = false; [self updateKey:targetKey asPressed:(targetKeyShouldDown ? logicalKey : 0)]; - _sendEvent(flutterEvent, nullptr, nullptr); + [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } } @@ -391,6 +446,17 @@ - (void)handleEvent:(NSEvent*)event } } -#pragma mark - Private +- (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { + FlutterKeyHandlerCallback callback = _pendingResponses[@(responseId)]; + [_pendingResponses removeObjectForKey:@(responseId)]; + callback(handled); +} @end + +namespace { +void HandleResponse(bool handled, void* user_data) { + FlutterKeyPendingResponse* pending = (__bridge FlutterKeyPendingResponse*)user_data; + [pending.handler handleResponse:handled forId:pending.responseId]; +} +} // namespace diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 2e5a10b628eb3..f4267d5183474 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -106,7 +106,7 @@ + (nonnull NSEvent*)keyEventWithType:(NSEventType)type next_handled = TRUE; [handler handleEvent:[Utils keyEventWithType:NSKeyDown - modifierFlags:0 + modifierFlags:0 characters:@"a" charactersIgnoringModifiers:@"a" isARepeat:FALSE From ce5dae1f5c06e6406e426482d41e14aded466eb1 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 3 Mar 2021 17:58:35 -0800 Subject: [PATCH 09/46] Resume view controller tests --- .../Source/FlutterKeyEmbedderHandler.mm | 18 +- .../FlutterKeyEmbedderHandlerUnittests.mm | 22 +- .../Source/FlutterTextInputPlugin.mm | 2 +- .../Source/FlutterViewControllerTest.mm | 357 ++++++++++-------- 4 files changed, 224 insertions(+), 175 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 6edf30641594d..6f4634c9f09a7 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -250,7 +250,8 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event FlutterKeyPendingResponse* pending = [[FlutterKeyPendingResponse alloc] initWithHandler:self responseId:responseId]; _pendingResponses[@(responseId)] = callback; - _sendEvent(event, HandleResponse, (__bridge void*)pending); + // The `__bridge_retained` here is matched by `__bridge_transfer` in HandleResponse. + _sendEvent(event, HandleResponse, (__bridge_retained void*)pending); } - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { @@ -424,10 +425,11 @@ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca - (void)handleEvent:(NSEvent*)event ofType:(NSString*)type callback:(FlutterKeyHandlerCallback)callback { - printf("#### Event %d keyCode %d rep %d ", (int)event.type, (int)event.keyCode, event.isARepeat); + printf("#### Event %d keyCode %d mod %lx", (int)event.type, (int)event.keyCode, + event.modifierFlags); if (event.type != NSEventTypeFlagsChanged) { - printf("cIM %s c %s\n", [[event charactersIgnoringModifiers] UTF8String], - [[event characters] UTF8String]); + printf("rep %d cIM %s c %s\n", event.isARepeat, + [[event charactersIgnoringModifiers] UTF8String], [[event characters] UTF8String]); } else { printf("\n"); } @@ -447,16 +449,20 @@ - (void)handleEvent:(NSEvent*)event } - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { + printf("HR %d %llu\n", handled, responseId); FlutterKeyHandlerCallback callback = _pendingResponses[@(responseId)]; - [_pendingResponses removeObjectForKey:@(responseId)]; callback(handled); + printf("after\n"); + [_pendingResponses removeObjectForKey:@(responseId)]; } @end namespace { void HandleResponse(bool handled, void* user_data) { - FlutterKeyPendingResponse* pending = (__bridge FlutterKeyPendingResponse*)user_data; + printf("Resp %d %0llx\n", handled, (uint64_t)user_data); + // The `__bridge_transfer` here is matched by `__bridge_retained` in sendPrimaryFlutterEvent. + FlutterKeyPendingResponse* pending = (__bridge_transfer FlutterKeyPendingResponse*)user_data; [pending.handler handleResponse:handled forId:pending.responseId]; } } // namespace diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index f4267d5183474..4b87a0fe06a49 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -85,26 +85,30 @@ + (nonnull NSEvent*)keyEventWithType:(NSEventType)type } +typedef void (^ResponseCallback)(bool handled); + // Test the most basic key events. // // Press, hold, and release key A on an US keyboard. TEST(FlutterKeyEmbedderHandlerUnittests, BasicKeyEvent) { - __block BOOL next_handled = TRUE; __block NSMutableArray* events = [[NSMutableArray alloc] init]; - __block NSMutableArray* responses = [[NSMutableArray alloc] init]; + __block NSMutableArray* callbacks = + [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; FlutterKeyEvent* event; FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; - printf("callback %d handled %d\n", !!callback, next_handled); if (callback != nullptr) { - callback(next_handled, user_data); + [callbacks addObject:^(bool handled) { + callback(handled, user_data); + }]; } }]; - next_handled = TRUE; + last_handled = FALSE; [handler handleEvent:[Utils keyEventWithType:NSKeyDown modifierFlags:0 characters:@"a" @@ -113,7 +117,7 @@ + (nonnull NSEvent*)keyEventWithType:(NSEventType)type keyCode:0] ofType:@"keydown" callback:^(BOOL handled) { - [responses addObject:@(handled)]; + last_handled = handled; }]; EXPECT_EQ([events count], 1u); @@ -124,8 +128,10 @@ + (nonnull NSEvent*)keyEventWithType:(NSEventType)type EXPECT_STREQ(event->character, "a"); EXPECT_EQ(event->synthesized, false); - EXPECT_EQ([responses count], 1u); - EXPECT_EQ([[responses lastObject] boolValue], TRUE); + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); } } // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 479541394a7a5..f466e1370aec5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -140,7 +140,7 @@ - (instancetype)initWithViewController:(FlutterViewController*)viewController { _channel = [FlutterMethodChannel methodChannelWithName:kTextInputChannel binaryMessenger:viewController.engine.binaryMessenger codec:[FlutterJSONMethodCodec sharedInstance]]; - _shown = TRUE; + _shown = FALSE; __weak FlutterTextInputPlugin* weakSelf = self; [_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) { [weakSelf handleMethodCall:call result:result]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm index f154062caf53e..1aa8709e75a26 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm @@ -16,9 +16,13 @@ @interface FlutterViewControllerTestObjC : NSObject - (bool)testKeyEventsAreSentToFramework; -// - (bool)testKeyEventsArePropagatedIfNotHandled; -// - (bool)testKeyEventsAreNotPropagatedIfHandled; -// - (bool)testFlagsChangedEventsArePropagatedIfNotHandled; +- (bool)testKeyEventsArePropagatedIfNotHandled; +- (bool)testKeyEventsAreNotPropagatedIfHandled; +- (bool)testFlagsChangedEventsArePropagatedIfNotHandled; + ++ (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event + callback:(nullable FlutterKeyEventCallback)callback + userData:(nullable void*)userData; @end namespace flutter::testing { @@ -48,6 +52,14 @@ id mockViewController(NSString* pasteboardString) { return viewControllerMock; } +NSResponder* mockResponder() { + NSResponder* mock = OCMClassMock([NSResponder class]); + OCMStub([mock keyDown:[OCMArg any]]).andDo(nil); + OCMStub([mock keyUp:[OCMArg any]]).andDo(nil); + OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil); + return mock; +} + TEST(FlutterViewController, HasStringsWhenPasteboardEmpty) { // Mock FlutterViewController so that it behaves like the pasteboard is empty. id viewControllerMock = CreateMockViewController(nil); @@ -91,18 +103,18 @@ id mockViewController(NSString* pasteboardString) { ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsAreSentToFramework]); } -// TEST(FlutterViewControllerTest, TestKeyEventsArePropagatedIfNotHandled) { -// ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsArePropagatedIfNotHandled]); -// } +TEST(FlutterViewControllerTest, TestKeyEventsArePropagatedIfNotHandled) { + ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsArePropagatedIfNotHandled]); +} -// TEST(FlutterViewControllerTest, TestKeyEventsAreNotPropagatedIfHandled) { -// ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsAreNotPropagatedIfHandled]); -// } +TEST(FlutterViewControllerTest, TestKeyEventsAreNotPropagatedIfHandled) { + ASSERT_TRUE([[FlutterViewControllerTestObjC alloc] testKeyEventsAreNotPropagatedIfHandled]); +} -// TEST(FlutterViewControllerTest, TestFlagsChangedEventsArePropagatedIfNotHandled) { -// ASSERT_TRUE( -// [[FlutterViewControllerTestObjC alloc] testFlagsChangedEventsArePropagatedIfNotHandled]); -// } +TEST(FlutterViewControllerTest, TestFlagsChangedEventsArePropagatedIfNotHandled) { + ASSERT_TRUE( + [[FlutterViewControllerTestObjC alloc] testFlagsChangedEventsArePropagatedIfNotHandled]); +} } // namespace flutter::testing @@ -114,6 +126,11 @@ - (bool)testKeyEventsAreSentToFramework { OCMStub( // NOLINT(google-objc-avoid-throwing-exception) [engineMock binaryMessenger]) .andReturn(binaryMessengerMock); + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + callback:nil + userData:nil]) + .andCall([FlutterViewControllerTestObjC class], + @selector(respondFalseForSendEvent:callback:userData:)); FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock nibName:@"" bundle:nil]; @@ -141,154 +158,174 @@ - (bool)testKeyEventsAreSentToFramework { return true; } -// - (bool)testKeyEventsArePropagatedIfNotHandled { -// id engineMock = OCMClassMock([FlutterEngine class]); -// id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); -// OCMStub( // NOLINT(google-objc-avoid-throwing-exception) -// [engineMock binaryMessenger]) -// .andReturn(binaryMessengerMock); -// FlutterViewController* viewController = [[FlutterViewController alloc] -// initWithEngine:engineMock -// nibName:@"" -// bundle:nil]; -// id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); -// [viewController addKeyResponder:responderMock]; -// NSDictionary* expectedEvent = @{ -// @"keymap" : @"macos", -// @"type" : @"keydown", -// @"keyCode" : @(65), -// @"modifiers" : @(538968064), -// @"characters" : @".", -// @"charactersIgnoringModifiers" : @".", -// }; -// NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; -// CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); -// NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; -// OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) -// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" -// message:encodedKeyEvent -// binaryReply:[OCMArg any]]) -// .andDo((^(NSInvocation* invocation) { -// FlutterBinaryReply handler; -// [invocation getArgument:&handler atIndex:4]; -// NSDictionary* reply = @{ -// @"handled" : @(false), -// }; -// NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; -// handler(encodedReply); -// })); -// [viewController viewWillAppear]; // Initializes the event channel. -// [viewController keyDown:event]; -// @try { -// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) -// [responderMock handleKeyDown:[OCMArg any]]); -// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) -// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" -// message:encodedKeyEvent -// binaryReply:[OCMArg any]]); -// } @catch (...) { -// return false; -// } -// return true; -// } +- (bool)testKeyEventsArePropagatedIfNotHandled { + id engineMock = OCMClassMock([FlutterEngine class]); + id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [engineMock binaryMessenger]) + .andReturn(binaryMessengerMock); + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + callback:nil + userData:nil]) + .andCall([FlutterViewControllerTestObjC class], + @selector(respondFalseForSendEvent:callback:userData:)); + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock + nibName:@"" + bundle:nil]; + id responderMock = flutter::testing::mockResponder(); + viewController.nextResponder = responderMock; + NSDictionary* expectedEvent = @{ + @"keymap" : @"macos", + @"type" : @"keydown", + @"keyCode" : @(65), + @"modifiers" : @(538968064), + @"characters" : @".", + @"charactersIgnoringModifiers" : @".", + }; + NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; + CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); + NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; + OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/keyevent" + message:encodedKeyEvent + binaryReply:[OCMArg any]]) + .andDo((^(NSInvocation* invocation) { + FlutterBinaryReply handler; + [invocation getArgument:&handler atIndex:4]; + NSDictionary* reply = @{ + @"handled" : @(false), + }; + NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; + handler(encodedReply); + })); + [viewController viewWillAppear]; // Initializes the event channel. + [viewController keyDown:event]; + @try { + OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) + [responderMock keyDown:[OCMArg any]]); + OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/keyevent" + message:encodedKeyEvent + binaryReply:[OCMArg any]]); + } @catch (...) { + return false; + } + return true; +} -// - (bool)testFlagsChangedEventsArePropagatedIfNotHandled { -// id engineMock = OCMClassMock([FlutterEngine class]); -// id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); -// OCMStub( // NOLINT(google-objc-avoid-throwing-exception) -// [engineMock binaryMessenger]) -// .andReturn(binaryMessengerMock); -// FlutterViewController* viewController = [[FlutterViewController alloc] -// initWithEngine:engineMock -// nibName:@"" -// bundle:nil]; -// id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); -// [viewController addKeyResponder:responderMock]; -// NSDictionary* expectedEvent = @{ -// @"keymap" : @"macos", -// @"type" : @"keydown", -// @"keyCode" : @(56), // SHIFT key -// @"modifiers" : @(537001986), -// }; -// NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; -// CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE); // SHIFT key -// CGEventSetType(cgEvent, kCGEventFlagsChanged); -// NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; -// OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) -// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" -// message:encodedKeyEvent -// binaryReply:[OCMArg any]]) -// .andDo((^(NSInvocation* invocation) { -// FlutterBinaryReply handler; -// [invocation getArgument:&handler atIndex:4]; -// NSDictionary* reply = @{ -// @"handled" : @(false), -// }; -// NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; -// handler(encodedReply); -// })); -// [viewController viewWillAppear]; // Initializes the event channel. -// [viewController flagsChanged:event]; -// @try { -// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) -// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" -// message:encodedKeyEvent -// binaryReply:[OCMArg any]]); -// } @catch (...) { -// return false; -// } -// return true; -// } +- (bool)testFlagsChangedEventsArePropagatedIfNotHandled { + id engineMock = OCMClassMock([FlutterEngine class]); + id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [engineMock binaryMessenger]) + .andReturn(binaryMessengerMock); + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + callback:nil + userData:nil]) + .andCall([FlutterViewControllerTestObjC class], + @selector(respondFalseForSendEvent:callback:userData:)); + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock + nibName:@"" + bundle:nil]; + id responderMock = flutter::testing::mockResponder(); + viewController.nextResponder = responderMock; + NSDictionary* expectedEvent = @{ + @"keymap" : @"macos", + @"type" : @"keydown", + @"keyCode" : @(56), // SHIFT key + @"modifiers" : @(537001986), + }; + NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; + CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE); // SHIFT key + CGEventSetType(cgEvent, kCGEventFlagsChanged); + NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; + OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/keyevent" + message:encodedKeyEvent + binaryReply:[OCMArg any]]) + .andDo((^(NSInvocation* invocation) { + FlutterBinaryReply handler; + [invocation getArgument:&handler atIndex:4]; + NSDictionary* reply = @{ + @"handled" : @(false), + }; + NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; + handler(encodedReply); + })); + [viewController viewWillAppear]; // Initializes the event channel. + [viewController flagsChanged:event]; + @try { + OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/keyevent" + message:encodedKeyEvent + binaryReply:[OCMArg any]]); + } @catch (NSException* e) { + NSLog(@"%@", e.reason); + return false; + } + return true; +} -// - (bool)testKeyEventsAreNotPropagatedIfHandled { -// id engineMock = OCMClassMock([FlutterEngine class]); -// id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); -// OCMStub( // NOLINT(google-objc-avoid-throwing-exception) -// [engineMock binaryMessenger]) -// .andReturn(binaryMessengerMock); -// FlutterViewController* viewController = [[FlutterViewController alloc] -// initWithEngine:engineMock -// nibName:@"" -// bundle:nil]; -// id responderMock = OCMClassMock([FlutterIntermediateKeyResponder class]); -// [viewController addKeyResponder:responderMock]; -// NSDictionary* expectedEvent = @{ -// @"keymap" : @"macos", -// @"type" : @"keydown", -// @"keyCode" : @(65), -// @"modifiers" : @(538968064), -// @"characters" : @".", -// @"charactersIgnoringModifiers" : @".", -// }; -// NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; -// CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); -// NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; -// OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) -// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" -// message:encodedKeyEvent -// binaryReply:[OCMArg any]]) -// .andDo((^(NSInvocation* invocation) { -// FlutterBinaryReply handler; -// [invocation getArgument:&handler atIndex:4]; -// NSDictionary* reply = @{ -// @"handled" : @(true), -// }; -// NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; -// handler(encodedReply); -// })); -// [viewController viewWillAppear]; // Initializes the event channel. -// [viewController keyDown:event]; -// @try { -// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) -// never(), [responderMock handleKeyDown:[OCMArg any]]); -// OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) -// [binaryMessengerMock sendOnChannel:@"flutter/keyevent" -// message:encodedKeyEvent -// binaryReply:[OCMArg any]]); -// } @catch (...) { -// return false; -// } -// return true; -// } +- (bool)testKeyEventsAreNotPropagatedIfHandled { + id engineMock = OCMClassMock([FlutterEngine class]); + id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [engineMock binaryMessenger]) + .andReturn(binaryMessengerMock); + OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {} + callback:nil + userData:nil]) + .andCall([FlutterViewControllerTestObjC class], + @selector(respondFalseForSendEvent:callback:userData:)); + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock + nibName:@"" + bundle:nil]; + id responderMock = flutter::testing::mockResponder(); + viewController.nextResponder = responderMock; + NSDictionary* expectedEvent = @{ + @"keymap" : @"macos", + @"type" : @"keydown", + @"keyCode" : @(65), + @"modifiers" : @(538968064), + @"characters" : @".", + @"charactersIgnoringModifiers" : @".", + }; + NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:expectedEvent]; + CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE); + NSEvent* event = [NSEvent eventWithCGEvent:cgEvent]; + OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/keyevent" + message:encodedKeyEvent + binaryReply:[OCMArg any]]) + .andDo((^(NSInvocation* invocation) { + FlutterBinaryReply handler; + [invocation getArgument:&handler atIndex:4]; + NSDictionary* reply = @{ + @"handled" : @(true), + }; + NSData* encodedReply = [[FlutterJSONMessageCodec sharedInstance] encode:reply]; + handler(encodedReply); + })); + [viewController viewWillAppear]; // Initializes the event channel. + [viewController keyDown:event]; + @try { + OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) + never(), [responderMock keyDown:[OCMArg any]]); + OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/keyevent" + message:encodedKeyEvent + binaryReply:[OCMArg any]]); + } @catch (...) { + return false; + } + return true; +} + ++ (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event + callback:(nullable FlutterKeyEventCallback)callback + userData:(nullable void*)userData { + if (callback != nullptr) + callback(false, userData); +} @end From c26c23a8afc9fd1dad2c93f9c88f1430de9daae3 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 3 Mar 2021 20:13:05 -0800 Subject: [PATCH 10/46] Change key event gen --- .../FlutterKeyChannelHandlerUnittests.mm | 156 +++++++++++++----- .../FlutterKeyEmbedderHandlerUnittests.mm | 105 ++++++++---- 2 files changed, 185 insertions(+), 76 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm index fdbb74a37c03c..a30c8f241b826 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm @@ -11,6 +11,26 @@ namespace flutter::testing { +namespace { +NSEvent* keyEvent(NSEventType type, + NSEventModifierFlags modifierFlags, + NSString* characters, + NSString* charactersIgnoringModifiers, + BOOL isARepeat, + unsigned short keyCode) { + return [NSEvent keyEventWithType:type + location:NSZeroPoint + modifierFlags:modifierFlags + timestamp:0 + windowNumber:0 + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:isARepeat + keyCode:keyCode]; +} +} // namespace + TEST(FlutterKeyChannelHandlerUnittests, BasicKeyEvent) { __block NSMutableArray* messages = [[NSMutableArray alloc] init]; __block BOOL next_response = TRUE; @@ -35,16 +55,7 @@ // Key down FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; - [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown - location:NSZeroPoint - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil - characters:@"A" - charactersIgnoringModifiers:@"a" - isARepeat:TRUE - keyCode:0x60] + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) ofType:@"keydown" callback:^(BOOL handled) { [responses addObject:@(handled)]; @@ -53,9 +64,9 @@ EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keydown"); - EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0x60); - EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0); - EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "A"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0x100); + EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "a"); EXPECT_EQ([[messages lastObject][@"charactersIgnoringModifiers"] UTF8String], "a"); EXPECT_EQ([responses count], 1u); @@ -65,17 +76,90 @@ [responses removeAllObjects]; // Key up + next_response = FALSE; + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) + ofType:@"keyup" + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; + + EXPECT_EQ([messages count], 1u); + EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); + EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keyup"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0x100); + EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "a"); + EXPECT_EQ([[messages lastObject][@"charactersIgnoringModifiers"] UTF8String], "a"); + + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses lastObject] boolValue], FALSE); + + [messages removeAllObjects]; + [responses removeAllObjects]; + + // LShift down + next_response = TRUE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, 56) + ofType:@"keydown" + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; + + EXPECT_EQ([messages count], 1u); + EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); + EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keydown"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 56); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0x20102); + + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses lastObject] boolValue], TRUE); + + [messages removeAllObjects]; + [responses removeAllObjects]; + + // RShift down + next_response = false; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, 60) + ofType:@"keydown" + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; + + EXPECT_EQ([messages count], 1u); + EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); + EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keydown"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 60); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0x20106); + + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses lastObject] boolValue], FALSE); + + [messages removeAllObjects]; + [responses removeAllObjects]; + + // LShift up + next_response = false; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, 56) + ofType:@"keyup" + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; + + EXPECT_EQ([messages count], 1u); + EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); + EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keyup"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 56); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0x20104); + + EXPECT_EQ([responses count], 1u); + EXPECT_EQ([[responses lastObject] boolValue], FALSE); + + [messages removeAllObjects]; + [responses removeAllObjects]; + + // RShift up next_response = false; - [handler handleEvent:[NSEvent keyEventWithType:NSKeyUp - location:NSZeroPoint - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil - characters:@"B" - charactersIgnoringModifiers:@"b" - isARepeat:TRUE - keyCode:0x61] + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, 60) ofType:@"keyup" callback:^(BOOL handled) { [responses addObject:@(handled)]; @@ -84,13 +168,14 @@ EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keyup"); - EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0x61); - EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0); - EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "B"); - EXPECT_EQ([[messages lastObject][@"charactersIgnoringModifiers"] UTF8String], "b"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 60); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0x100); EXPECT_EQ([responses count], 1u); EXPECT_EQ([[responses lastObject] boolValue], FALSE); + + [messages removeAllObjects]; + [responses removeAllObjects]; } TEST(FlutterKeyChannelHandlerUnittests, EmptyResponseIsTakenAsHandled) { @@ -112,16 +197,7 @@ FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; - [handler handleEvent:[NSEvent keyEventWithType:NSKeyDown - location:NSZeroPoint - modifierFlags:0 - timestamp:0 - windowNumber:0 - context:nil - characters:@"A" - charactersIgnoringModifiers:@"a" - isARepeat:TRUE - keyCode:0x60] + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) ofType:@"keydown" callback:^(BOOL handled) { [responses addObject:@(handled)]; @@ -130,9 +206,9 @@ EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); EXPECT_STREQ([[messages lastObject][@"type"] UTF8String], "keydown"); - EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0x60); - EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0); - EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "A"); + EXPECT_EQ([[messages lastObject][@"keyCode"] intValue], 0); + EXPECT_EQ([[messages lastObject][@"modifiers"] intValue], 0x100); + EXPECT_EQ([[messages lastObject][@"characters"] UTF8String], "a"); EXPECT_EQ([[messages lastObject][@"charactersIgnoringModifiers"] UTF8String], "a"); EXPECT_EQ([responses count], 1u); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 4b87a0fe06a49..140d2e11504cf 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -37,35 +37,6 @@ - (void)dealloc { } @end -@interface Utils : NSObject -+ (nonnull NSEvent*)keyEventWithType:(NSEventType)type - modifierFlags:(NSEventModifierFlags)flags - characters:(NSString*)keys - charactersIgnoringModifiers:(NSString*)ukeys - isARepeat:(BOOL)flag - keyCode:(unsigned short)code; -@end - -@implementation Utils -+ (nonnull NSEvent*)keyEventWithType:(NSEventType)type - modifierFlags:(NSEventModifierFlags)flags - characters:(NSString*)keys - charactersIgnoringModifiers:(NSString*)ukeys - isARepeat:(BOOL)flag - keyCode:(unsigned short)code { - return [NSEvent keyEventWithType:type - location:NSZeroPoint - modifierFlags:flags - timestamp:0 - windowNumber:0 - context:nil - characters:keys - charactersIgnoringModifiers:ukeys - isARepeat:flag - keyCode:code]; -} -@end - namespace flutter::testing { namespace { @@ -83,9 +54,27 @@ + (nonnull NSEvent*)keyEventWithType:(NSEventType)type // constexpr uint64_t kLogicalShiftRight = 0x0040000010d; // constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; +typedef void (^ResponseCallback)(bool handled); + +NSEvent* keyEvent(NSEventType type, + NSEventModifierFlags modifierFlags, + NSString* characters, + NSString* charactersIgnoringModifiers, + BOOL isARepeat, + unsigned short keyCode) { + return [NSEvent keyEventWithType:type + location:NSZeroPoint + modifierFlags:modifierFlags + timestamp:0 + windowNumber:0 + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:isARepeat + keyCode:keyCode]; } -typedef void (^ResponseCallback)(bool handled); +} // namespace // Test the most basic key events. // @@ -109,12 +98,7 @@ + (nonnull NSEvent*)keyEventWithType:(NSEventType)type }]; last_handled = FALSE; - [handler handleEvent:[Utils keyEventWithType:NSKeyDown - modifierFlags:0 - characters:@"a" - charactersIgnoringModifiers:@"a" - isARepeat:FALSE - keyCode:0] + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) ofType:@"keydown" callback:^(BOOL handled) { last_handled = handled; @@ -132,6 +116,55 @@ + (nonnull NSEvent*)keyEventWithType:(NSEventType)type EXPECT_EQ([callbacks count], 1u); [callbacks lastObject](TRUE); EXPECT_EQ(last_handled, TRUE); + + [callbacks removeAllObjects]; + [events removeAllObjects]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, 0) + ofType:@"keydown" + callback:^(BOOL handled) { + last_handled = handled; + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); + + [callbacks removeAllObjects]; + [events removeAllObjects]; + + last_handled = TRUE; + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) + ofType:@"keyup" + callback:^(BOOL handled) { + last_handled = handled; + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_EQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, TRUE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](FALSE); + EXPECT_EQ(last_handled, FALSE); + + [callbacks removeAllObjects]; + [events removeAllObjects]; } } // namespace flutter::testing From 09adc79afa01ddb3376a7e3f440affd566355926 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 3 Mar 2021 21:13:48 -0800 Subject: [PATCH 11/46] Refactor: remove ofType --- .../Source/FlutterIntermediateKeyResponder.h | 10 +-- .../Source/FlutterIntermediateKeyResponder.mm | 6 +- .../Source/FlutterKeyChannelHandler.mm | 29 +++++++- .../FlutterKeyChannelHandlerUnittests.mm | 9 +-- .../Source/FlutterKeyEmbedderHandler.mm | 4 -- .../FlutterKeyEmbedderHandlerUnittests.mm | 3 - .../framework/Source/FlutterKeyHandlerBase.h | 1 - .../Source/FlutterKeyboardManager.mm | 70 ++++++++----------- .../Source/FlutterTextInputPlugin.mm | 15 +++- 9 files changed, 72 insertions(+), 75 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h b/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h index 31113ec8dfef8..d1ba77f69780f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h @@ -14,15 +14,9 @@ */ @interface FlutterIntermediateKeyResponder : NSObject /* - * Informs the receiver that the user has released a key. + * Informs the receiver that the user has interacted with a key. * * Default implementation returns NO. */ -- (BOOL)handleKeyUp:(nonnull NSEvent*)event; -/* - * Informs the receiver that the user has pressed a key. - * - * Default implementation returns NO. - */ -- (BOOL)handleKeyDown:(nonnull NSEvent*)event; +- (BOOL)handleKeyEvent:(NSEvent*)event; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm index 71dd8c87cd7e4..910edf94c763d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm @@ -9,11 +9,7 @@ @implementation FlutterIntermediateKeyResponder { #pragma mark - Default key handling methods -- (BOOL)handleKeyUp:(NSEvent*)event { - return NO; -} - -- (BOOL)handleKeyDown:(NSEvent*)event { +- (BOOL)handleKeyEvent:(NSEvent*)event { return NO; } @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm index 4facf0b087f08..21894f96c425c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm @@ -17,6 +17,11 @@ @interface FlutterKeyChannelHandler () */ @property(nonatomic) FlutterBasicMessageChannel* channel; +/** + * The current state of the keyboard and pressed keys. + */ +@property(nonatomic) uint64_t previouslyPressedFlags; + @end @implementation FlutterKeyChannelHandler @@ -24,12 +29,30 @@ @implementation FlutterKeyChannelHandler - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel { self = [super init]; _channel = channel; + _previouslyPressedFlags = 0; return self; } -- (void)handleEvent:(NSEvent*)event - ofType:(NSString*)type - callback:(FlutterKeyHandlerCallback)callback { +- (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { + NSString* type; + switch (event.type) { + case NSEventTypeKeyDown: + type = @"keydown"; + break; + case NSEventTypeKeyUp: + type = @"keyup"; + break; + case NSEventTypeFlagsChanged: + if (event.modifierFlags < _previouslyPressedFlags) { + type = @"keyup"; + } else { + type = @"keydown"; + } + break; + default: + NSAssert(false, @"Unexpected key event type (got %lu).", event.type); + } + _previouslyPressedFlags = event.modifierFlags; NSMutableDictionary* keyMessage = [@{ @"keymap" : @"macos", @"type" : type, diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm index a30c8f241b826..7ef4041f07ac7 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm @@ -56,7 +56,6 @@ FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) - ofType:@"keydown" callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -78,7 +77,6 @@ // Key up next_response = FALSE; [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) - ofType:@"keyup" callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -100,7 +98,6 @@ // LShift down next_response = TRUE; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, 56) - ofType:@"keydown" callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -120,7 +117,6 @@ // RShift down next_response = false; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, 60) - ofType:@"keydown" callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -140,7 +136,6 @@ // LShift up next_response = false; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, 56) - ofType:@"keyup" callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -160,7 +155,6 @@ // RShift up next_response = false; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, 60) - ofType:@"keyup" callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; @@ -197,8 +191,7 @@ FlutterKeyChannelHandler* handler = [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) - ofType:@"keydown" + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) callback:^(BOOL handled) { [responses addObject:@(handled)]; }]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 6f4634c9f09a7..8ab16b1b1c48d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -212,10 +212,6 @@ - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call */ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; -- (void)handleEvent:(NSEvent*)event - ofType:(NSString*)type - callback:(FlutterKeyHandlerCallback)callback; - - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 140d2e11504cf..749dd29f102ba 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -99,7 +99,6 @@ - (void)dealloc { last_handled = FALSE; [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) - ofType:@"keydown" callback:^(BOOL handled) { last_handled = handled; }]; @@ -122,7 +121,6 @@ - (void)dealloc { last_handled = FALSE; [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, 0) - ofType:@"keydown" callback:^(BOOL handled) { last_handled = handled; }]; @@ -145,7 +143,6 @@ - (void)dealloc { last_handled = TRUE; [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) - ofType:@"keyup" callback:^(BOOL handled) { last_handled = handled; }]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h index dab90e4a71f4a..31ec25b85c1b4 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h @@ -10,7 +10,6 @@ typedef void (^FlutterKeyHandlerCallback)(BOOL handled); @required - (void)handleEvent:(NSEvent*)event - ofType:(NSString*)type callback:(FlutterKeyHandlerCallback)callback; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 65a0aa8ee64ce..93691f0a0aa1b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -26,12 +26,7 @@ @interface FlutterKeyboardManager () */ @property(nonatomic) NSMutableArray* additionalKeyHandlers; -/** - * The current state of the keyboard and pressed keys. - */ -@property(nonatomic) uint64_t previouslyPressedFlags; - -- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type; +- (void)dispatchKeyEvent:(NSEvent*)event; @end @@ -42,7 +37,6 @@ - (nonnull instancetype)initWithOwner:(NSResponder*)weakOwner { _owner = weakOwner; _keyHandlers = [[NSMutableArray alloc] init]; _additionalKeyHandlers = [[NSMutableArray alloc] init]; - _previouslyPressedFlags = 0; return self; } @@ -54,35 +48,34 @@ - (void)addAdditionalHandler:(nonnull FlutterIntermediateKeyResponder*)handler { [_additionalKeyHandlers addObject:handler]; } -- (void)dispatchToAdditionalHandlers:(NSEvent*)event ofType:(NSString*)type { - if ([type isEqual:@"keydown"]) { - for (FlutterIntermediateKeyResponder* responder in _additionalKeyHandlers) { - if ([responder handleKeyDown:event]) { - return; - } - } - if ([_owner.nextResponder respondsToSelector:@selector(keyDown:)] && - event.type == NSEventTypeKeyDown) { - [_owner.nextResponder keyDown:event]; - } - } else if ([type isEqual:@"keyup"]) { - for (FlutterIntermediateKeyResponder* responder in _additionalKeyHandlers) { - if ([responder handleKeyUp:event]) { - return; - } - } - if ([_owner.nextResponder respondsToSelector:@selector(keyUp:)] && - event.type == NSEventTypeKeyUp) { - [_owner.nextResponder keyUp:event]; +- (void)dispatchToAdditionalHandlers:(NSEvent*)event { + for (FlutterIntermediateKeyResponder* responder in _additionalKeyHandlers) { + if ([responder handleKeyEvent:event]) { + return; } } - if ([_owner.nextResponder respondsToSelector:@selector(flagsChanged:)] && - event.type == NSEventTypeFlagsChanged) { - [_owner.nextResponder flagsChanged:event]; + switch (event.type) { + case NSEventTypeKeyDown: + if ([_owner.nextResponder respondsToSelector:@selector(keyDown:)]) { + [_owner.nextResponder keyDown:event]; + } + break; + case NSEventTypeKeyUp: + if ([_owner.nextResponder respondsToSelector:@selector(keyUp:)]) { + [_owner.nextResponder keyUp:event]; + } + break; + case NSEventTypeFlagsChanged: + if ([_owner.nextResponder respondsToSelector:@selector(flagsChanged:)]) { + [_owner.nextResponder flagsChanged:event]; + } + break; + default: + NSAssert(false, @"Unexpected key event type (got %lu).", event.type); } } -- (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { +- (void)dispatchKeyEvent:(NSEvent*)event { // Be sure to add a handler in propagateKeyEvent if you allow more event // types here. if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && @@ -98,30 +91,25 @@ - (void)dispatchKeyEvent:(NSEvent*)event ofType:(NSString*)type { NSAssert(unreplied >= 0, @"More key handlers replied than intended."); anyHandled = anyHandled || handled; if (unreplied == 0 && !anyHandled) { - [weakSelf dispatchToAdditionalHandlers:event ofType:type]; + [weakSelf dispatchToAdditionalHandlers:event]; } }; for (id handler in _keyHandlers) { - [handler handleEvent:event ofType:type callback:replyCallback]; + [handler handleEvent:event callback:replyCallback]; } } - (void)keyDown:(nonnull NSEvent*)event { - [self dispatchKeyEvent:event ofType:@"keydown"]; + [self dispatchKeyEvent:event]; } - (void)keyUp:(nonnull NSEvent*)event { - [self dispatchKeyEvent:event ofType:@"keyup"]; + [self dispatchKeyEvent:event]; } - (void)flagsChanged:(NSEvent*)event { - if (event.modifierFlags < _previouslyPressedFlags) { - [self keyUp:event]; - } else { - [self keyDown:event]; - } - _previouslyPressedFlags = event.modifierFlags; + [self dispatchKeyEvent:event]; } @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index f466e1370aec5..cb0cb0a205cf5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -92,6 +92,11 @@ @interface FlutterTextInputPlugin () */ @property(nonatomic) BOOL shown; +/** + * The current state of the keyboard and pressed keys. + */ +@property(nonatomic) uint64_t previouslyPressedFlags; + /** * The affinity for the current cursor position. */ @@ -146,6 +151,7 @@ - (instancetype)initWithViewController:(FlutterViewController*)viewController { [weakSelf handleMethodCall:call result:result]; }]; _textInputContext = [[NSTextInputContext alloc] initWithClient:self]; + _previouslyPressedFlags = 0; } return self; } @@ -260,9 +266,14 @@ - (void)updateEditState { * mouse events. Additionally, processing both keyUp and keyDown results in duplicate * processing of the same keys. So for now, limit processing to just handleKeyDown. */ -- (BOOL)handleKeyDown:(NSEvent*)event { +- (BOOL)handleKeyEvent:(NSEvent*)event { + if (event.type == NSEventTypeKeyUp || + (event.type == NSEventTypeFlagsChanged && event.modifierFlags < _previouslyPressedFlags)) { + return NO; + } + _previouslyPressedFlags = event.modifierFlags; if (!_shown) { - return false; + return NO; } return [_textInputContext handleEvent:event]; } From 1f6f920e6797f60cdd9134015e3698b29f2142b4 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 3 Mar 2021 21:45:10 -0800 Subject: [PATCH 12/46] Shift and A unit test --- .../FlutterKeyEmbedderHandlerUnittests.mm | 101 +++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 749dd29f102ba..c52c62cf5c51b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -44,14 +44,14 @@ - (void)dealloc { // constexpr uint64_t kPhysicalControlLeft = 0x000700e0; // constexpr uint64_t kPhysicalControlRight = 0x000700e4; // constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; -// constexpr uint64_t kPhysicalShiftRight = 0x000700e5; +constexpr uint64_t kPhysicalShiftRight = 0x000700e5; // constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; constexpr uint64_t kLogicalKeyA = 0x00000061; // constexpr uint64_t kLogicalControlLeft = 0x00300000105; // constexpr uint64_t kLogicalControlRight = 0x00400000105; // constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; -// constexpr uint64_t kLogicalShiftRight = 0x0040000010d; +constexpr uint64_t kLogicalShiftRight = 0x0040000010d; // constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; typedef void (^ResponseCallback)(bool handled); @@ -164,4 +164,101 @@ - (void)dealloc { [events removeAllObjects]; } +// Press L shift, A, then release L shift then A, on an US keyboard. +// +// This is special because the characters for the A key will change in this +// process. +TEST(FlutterKeyEmbedderHandlerUnittests, ToggleModifiersDuringKeyTap) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; + if (callback != nullptr) { + callback(true, user_data); + } + }]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, 60) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", FALSE, 0) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "A"); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", TRUE, 0) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "A"); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, 60) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, 0) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x20104, @"a", @"a", FALSE, 0) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; +} + + } // namespace flutter::testing From 963fca59f706533e7d39f60ff3a7315ffac24bfc Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 4 Mar 2021 01:17:25 -0800 Subject: [PATCH 13/46] IdentifyLeftAndRightModifiers --- .../Source/FlutterKeyEmbedderHandler.mm | 2 +- .../FlutterKeyEmbedderHandlerUnittests.mm | 90 ++++++++++++++++--- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 8ab16b1b1c48d..53e045b0f5397 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -345,7 +345,7 @@ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca // flag, but not whether the change is a down or up. For keys such as // CapsLock, the change type can be inferred from the key and the flag. // - // But some other modifier keys come in paris, such as shift or control, and + // But some other modifier keys come in pairs, such as shift or control, and // both of the pair share one flag, which indicates either key is pressed. // If the pressing states of paired keys are desynchronized due to loss of // focus, the change might have to be guessed and synchronized. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index c52c62cf5c51b..c2a3445652790 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -40,17 +40,21 @@ - (void)dealloc { namespace flutter::testing { namespace { +constexpr uint64_t kKeyCodeKeyA = 0; +constexpr uint64_t kKeyCodeShiftLeft = 56; +constexpr uint64_t kKeyCodeShiftRight = 60; + constexpr uint64_t kPhysicalKeyA = 0x00070004; // constexpr uint64_t kPhysicalControlLeft = 0x000700e0; // constexpr uint64_t kPhysicalControlRight = 0x000700e4; -// constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; +constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; constexpr uint64_t kPhysicalShiftRight = 0x000700e5; // constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; constexpr uint64_t kLogicalKeyA = 0x00000061; // constexpr uint64_t kLogicalControlLeft = 0x00300000105; // constexpr uint64_t kLogicalControlRight = 0x00400000105; -// constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; +constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; constexpr uint64_t kLogicalShiftRight = 0x0040000010d; // constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; @@ -120,7 +124,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, 0) + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -142,7 +146,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = TRUE; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -181,7 +185,7 @@ - (void)dealloc { } }]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, 60) + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftRight) callback:^(BOOL handled) {}]; EXPECT_EQ([events count], 1u); @@ -194,7 +198,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", FALSE, 0) + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) {}]; EXPECT_EQ([events count], 1u); @@ -207,7 +211,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", TRUE, 0) + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", TRUE, kKeyCodeKeyA) callback:^(BOOL handled) {}]; EXPECT_EQ([events count], 1u); @@ -220,7 +224,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, 60) + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) callback:^(BOOL handled) {}]; EXPECT_EQ([events count], 1u); @@ -233,7 +237,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, 0) + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) callback:^(BOOL handled) {}]; EXPECT_EQ([events count], 1u); @@ -246,7 +250,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x20104, @"a", @"a", FALSE, 0) + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x20104, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) {}]; EXPECT_EQ([events count], 1u); @@ -260,5 +264,71 @@ - (void)dealloc { [events removeAllObjects]; } +TEST(FlutterKeyEmbedderHandlerUnittests, IdentifyLeftAndRightModifiers) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; + if (callback != nullptr) { + callback(true, user_data); + } + }]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + callback:^(BOOL handled) {}]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; +} + } // namespace flutter::testing From de0fa2e8ae42d026c3957ce4cb187ea9f24e29eb Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 4 Mar 2021 02:09:56 -0800 Subject: [PATCH 14/46] Modifier synthesizing test --- .../Source/FlutterKeyEmbedderHandler.mm | 9 +- .../FlutterKeyEmbedderHandlerUnittests.mm | 204 ++++++++++++++++++ 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 53e045b0f5397..1a41fab03b9c8 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -268,6 +268,7 @@ - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca // a currently pressed one, usually indicating multiple keyboards are // pressing keys with the same physical key, or the up event was lost // during a loss of focus. The down event is ignored. + callback(TRUE); return; } } else { @@ -299,6 +300,7 @@ - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call if (!pressedLogicalKey) { // The physical key has been released before. It indicates multiple // keyboards pressed keys with the same physical key. Ignore the up event. + callback(TRUE); return; } [self updateKey:physicalKey asPressed:0]; @@ -317,8 +319,10 @@ - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call - (void)dispatchCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { NSNumber* logicalKey = [keyCodeToLogicalKey objectForKey:@(event.keyCode)]; - if (logicalKey == nil) + if (logicalKey == nil) { + callback(TRUE); return; + } uint64_t logical = logicalKey.unsignedLongLongValue; FlutterKeyEvent flutterEvent = { @@ -376,6 +380,7 @@ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca uint64_t modifierFlag = GetModifierFlagForKey(targetKey); if (targetKey == 0 || modifierFlag == 0) { // Unrecognized modifier. + callback(TRUE); return; } // The `siblingKeyCode` may be 0, which means it doesn't have a sibling key. @@ -415,6 +420,8 @@ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca flutterEvent.synthesized = false; [self updateKey:targetKey asPressed:(targetKeyShouldDown ? logicalKey : 0)]; [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; + } else { + callback(TRUE); } } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index c2a3445652790..de2c49b0e4b9f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -330,5 +330,209 @@ - (void)dealloc { [events removeAllObjects]; } +// Process various cases where pair modifier key events are missed, and the +// handler has to "guess" how to synchronize states. +// +// In the following comments, parentheses indicate missed events, while +// asterisks indicate synthesized events. +TEST(FlutterKeyEmbedderHandlerUnittests, SynthesizeMissedModifierEvents) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block NSMutableArray* callbacks = + [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; + id keyEventCallback = ^(BOOL handled) { + last_handled = handled; + }; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; + if (callback != nullptr) { + [callbacks addObject:^(bool handled) { + callback(handled, user_data); + }]; + } + }]; + + // Case 1: + // In: L down, (L up), L down, L up + // Out: L down, L up + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + [callbacks removeAllObjects]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 0u); + EXPECT_EQ([callbacks count], 0u); + EXPECT_EQ(last_handled, TRUE); + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + [callbacks removeAllObjects]; + + // Case 2: + // In: (L down), L up + // Out: + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 0u); + EXPECT_EQ([callbacks count], 0u); + EXPECT_EQ(last_handled, TRUE); + + // Case 3: + // In: L down, (L up), (R down), R up + // Out: L down, *L up + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + [callbacks removeAllObjects]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + + EXPECT_EQ([callbacks count], 0u); + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + [callbacks removeAllObjects]; + + // Case 4: + // In: L down, R down, (L up), R up + // Out: L down, R down *L up & R up + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + [callbacks removeAllObjects]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + [callbacks removeAllObjects]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 2u); + event = [events firstObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + + event = [events lastObject]->data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftRight); + EXPECT_EQ(event->logical, kLogicalShiftRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + EXPECT_EQ(last_handled, FALSE); + EXPECT_EQ([callbacks count], 1u); + [callbacks lastObject](TRUE); + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + [callbacks removeAllObjects]; +} + + } // namespace flutter::testing From 0ec1292310871f05ad448fea8ca670dbb453d871 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 4 Mar 2021 03:31:03 -0800 Subject: [PATCH 15/46] Refactor tests --- .../FlutterKeyEmbedderHandlerUnittests.mm | 240 +++++++++--------- .../framework/Source/FlutterKeyHandlerBase.h | 3 +- .../Source/FlutterTextInputPlugin.mm | 2 +- 3 files changed, 129 insertions(+), 116 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index de2c49b0e4b9f..b5fb46b9f67d7 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -10,30 +10,48 @@ #include "third_party/googletest/googletest/include/gtest/gtest.h" // A wrap to convert FlutterKeyEvent to a ObjC class. -@interface TestKeyEvent : NSObject { - @public - FlutterKeyEvent* data; -} -- (nonnull instancetype)initWithEvent:(nonnull const FlutterKeyEvent*)event; +@interface TestKeyEvent : NSObject +@property(nonatomic) FlutterKeyEvent* data; +@property(nonatomic) FlutterKeyEventCallback callback; +@property(nonatomic) _VoidPtr userData; +- (nonnull instancetype)initWithEvent:(const FlutterKeyEvent*)event + callback:(nullable FlutterKeyEventCallback)callback + userData:(nullable _VoidPtr)userData; +- (BOOL)hasCallback; +- (void)respond:(BOOL)handled; @end @implementation TestKeyEvent -- (instancetype)initWithEvent:(const FlutterKeyEvent*)event { +- (instancetype)initWithEvent:(const FlutterKeyEvent*)event + callback:(nullable FlutterKeyEventCallback)callback + userData:(nullable _VoidPtr)userData { self = [super init]; - data = new FlutterKeyEvent(*event); + _data = new FlutterKeyEvent(*event); if (event->character != nullptr) { size_t len = strlen(event->character); char* character = new char[len + 1]; strcpy(character, event->character); - data->character = character; + _data->character = character; } + _callback = callback; + _userData = userData; return self; } +- (BOOL)hasCallback { + return _callback != nil; +} + +- (void)respond:(BOOL)handled { + NSAssert(_callback != nil, + @"Only call `respond` after checking `hasCallback`."); // Caller's responsibility + _callback(handled, _userData); +} + - (void)dealloc { - if (data->character != nullptr) - delete[] data->character; - delete data; + if (_data->character != nullptr) + delete[] _data->character; + delete _data; } @end @@ -85,20 +103,15 @@ - (void)dealloc { // Press, hold, and release key A on an US keyboard. TEST(FlutterKeyEmbedderHandlerUnittests, BasicKeyEvent) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; - __block NSMutableArray* callbacks = - [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; FlutterKeyEvent* event; FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { - [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; - if (callback != nullptr) { - [callbacks addObject:^(bool handled) { - callback(handled, user_data); - }]; - } + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; }]; last_handled = FALSE; @@ -108,7 +121,7 @@ - (void)dealloc { }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); @@ -116,11 +129,10 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); - [callbacks removeAllObjects]; [events removeAllObjects]; last_handled = FALSE; @@ -130,7 +142,7 @@ - (void)dealloc { }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); @@ -138,11 +150,10 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); - [callbacks removeAllObjects]; [events removeAllObjects]; last_handled = TRUE; @@ -152,7 +163,7 @@ - (void)dealloc { }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); @@ -160,11 +171,10 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, TRUE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](FALSE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:FALSE]; // Check if responding FALSE works EXPECT_EQ(last_handled, FALSE); - [callbacks removeAllObjects]; [events removeAllObjects]; } @@ -179,17 +189,18 @@ - (void)dealloc { FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { - [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; - if (callback != nullptr) { - callback(true, user_data); - } + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; }]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftRight) - callback:^(BOOL handled) {}]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftRight) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalShiftRight); EXPECT_EQ(event->logical, kLogicalShiftRight); @@ -199,10 +210,11 @@ - (void)dealloc { [events removeAllObjects]; [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled) {}]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); @@ -212,10 +224,11 @@ - (void)dealloc { [events removeAllObjects]; [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", TRUE, kKeyCodeKeyA) - callback:^(BOOL handled) {}]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); @@ -225,10 +238,11 @@ - (void)dealloc { [events removeAllObjects]; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) - callback:^(BOOL handled) {}]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalShiftRight); EXPECT_EQ(event->logical, kLogicalShiftRight); @@ -238,10 +252,11 @@ - (void)dealloc { [events removeAllObjects]; [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) - callback:^(BOOL handled) {}]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeRepeat); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); @@ -251,10 +266,11 @@ - (void)dealloc { [events removeAllObjects]; [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x20104, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled) {}]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); @@ -271,17 +287,18 @@ - (void)dealloc { FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { - [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; - if (callback != nullptr) { - callback(true, user_data); - } + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; }]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:^(BOOL handled) {}]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); @@ -290,11 +307,13 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) - callback:^(BOOL handled) {}]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalShiftRight); EXPECT_EQ(event->logical, kLogicalShiftRight); @@ -303,11 +322,13 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:^(BOOL handled) {}]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); @@ -317,10 +338,11 @@ - (void)dealloc { [events removeAllObjects]; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) - callback:^(BOOL handled) {}]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalShiftRight); EXPECT_EQ(event->logical, kLogicalShiftRight); @@ -337,8 +359,6 @@ - (void)dealloc { // asterisks indicate synthesized events. TEST(FlutterKeyEmbedderHandlerUnittests, SynthesizeMissedModifierEvents) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; - __block NSMutableArray* callbacks = - [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; id keyEventCallback = ^(BOOL handled) { last_handled = handled; @@ -348,23 +368,21 @@ - (void)dealloc { FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { - [events addObject:[[TestKeyEvent alloc] initWithEvent:&event]]; - if (callback != nullptr) { - [callbacks addObject:^(bool handled) { - callback(handled, user_data); - }]; - } + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; }]; // Case 1: // In: L down, (L up), L down, L up // Out: L down, L up last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:keyEventCallback]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); @@ -372,19 +390,18 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); [events removeAllObjects]; - [callbacks removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:keyEventCallback]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; EXPECT_EQ([events count], 0u); - EXPECT_EQ([callbacks count], 0u); EXPECT_EQ(last_handled, TRUE); last_handled = FALSE; @@ -392,7 +409,7 @@ - (void)dealloc { callback:keyEventCallback]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); @@ -400,12 +417,11 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); [events removeAllObjects]; - [callbacks removeAllObjects]; // Case 2: // In: (L down), L up @@ -416,7 +432,6 @@ - (void)dealloc { callback:keyEventCallback]; EXPECT_EQ([events count], 0u); - EXPECT_EQ([callbacks count], 0u); EXPECT_EQ(last_handled, TRUE); // Case 3: @@ -424,11 +439,12 @@ - (void)dealloc { // Out: L down, *L up last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:keyEventCallback]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); @@ -436,41 +452,41 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); [events removeAllObjects]; - [callbacks removeAllObjects]; last_handled = FALSE; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) callback:keyEventCallback]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, true); - EXPECT_EQ([callbacks count], 0u); + // The primary event is automatically replied with TRUE, unrelated to the received event. EXPECT_EQ(last_handled, TRUE); + EXPECT_FALSE([[events lastObject] hasCallback]); [events removeAllObjects]; - [callbacks removeAllObjects]; // Case 4: // In: L down, R down, (L up), R up // Out: L down, R down *L up & R up last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:keyEventCallback]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); @@ -478,19 +494,19 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); [events removeAllObjects]; - [callbacks removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) - callback:keyEventCallback]; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) + callback:keyEventCallback]; EXPECT_EQ([events count], 1u); - event = [events lastObject]->data; + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalShiftRight); EXPECT_EQ(event->logical, kLogicalShiftRight); @@ -498,26 +514,27 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); [events removeAllObjects]; - [callbacks removeAllObjects]; last_handled = FALSE; [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) callback:keyEventCallback]; EXPECT_EQ([events count], 2u); - event = [events firstObject]->data; + event = [events firstObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalShiftLeft); EXPECT_EQ(event->logical, kLogicalShiftLeft); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, true); - event = [events lastObject]->data; + EXPECT_FALSE([[events firstObject] hasCallback]); + + event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); EXPECT_EQ(event->physical, kPhysicalShiftRight); EXPECT_EQ(event->logical, kLogicalShiftRight); @@ -525,14 +542,11 @@ - (void)dealloc { EXPECT_EQ(event->synthesized, false); EXPECT_EQ(last_handled, FALSE); - EXPECT_EQ([callbacks count], 1u); - [callbacks lastObject](TRUE); + EXPECT_TRUE([[events lastObject] hasCallback]); + [[events lastObject] respond:TRUE]; EXPECT_EQ(last_handled, TRUE); [events removeAllObjects]; - [callbacks removeAllObjects]; } - - } // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h index 31ec25b85c1b4..c77c1a59e5227 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h @@ -9,7 +9,6 @@ typedef void (^FlutterKeyHandlerCallback)(BOOL handled); @protocol FlutterKeyHandlerBase @required -- (void)handleEvent:(NSEvent*)event - callback:(FlutterKeyHandlerCallback)callback; +- (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index cb0cb0a205cf5..73fbae8365e60 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -268,7 +268,7 @@ - (void)updateEditState { */ - (BOOL)handleKeyEvent:(NSEvent*)event { if (event.type == NSEventTypeKeyUp || - (event.type == NSEventTypeFlagsChanged && event.modifierFlags < _previouslyPressedFlags)) { + (event.type == NSEventTypeFlagsChanged && event.modifierFlags < _previouslyPressedFlags)) { return NO; } _previouslyPressedFlags = event.modifierFlags; From 4d62a9da17574aac5c3bdf2049c906a4ebdfd1bf Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 4 Mar 2021 17:34:48 -0800 Subject: [PATCH 16/46] Add capslock test --- .../FlutterKeyEmbedderHandlerUnittests.mm | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index b5fb46b9f67d7..a43b43052743a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -61,12 +61,14 @@ - (void)dealloc { constexpr uint64_t kKeyCodeKeyA = 0; constexpr uint64_t kKeyCodeShiftLeft = 56; constexpr uint64_t kKeyCodeShiftRight = 60; +constexpr uint64_t kKeyCodeCapsLock = 57; constexpr uint64_t kPhysicalKeyA = 0x00070004; // constexpr uint64_t kPhysicalControlLeft = 0x000700e0; // constexpr uint64_t kPhysicalControlRight = 0x000700e4; constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; constexpr uint64_t kPhysicalShiftRight = 0x000700e5; +constexpr uint64_t kPhysicalCapsLock = 0x00070039; // constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; constexpr uint64_t kLogicalKeyA = 0x00000061; @@ -74,6 +76,7 @@ - (void)dealloc { // constexpr uint64_t kLogicalControlRight = 0x00400000105; constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; constexpr uint64_t kLogicalShiftRight = 0x0040000010d; +constexpr uint64_t kLogicalCapsLock = 0x00000000104; // constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; typedef void (^ResponseCallback)(bool handled); @@ -549,4 +552,83 @@ - (void)dealloc { [events removeAllObjects]; } +TEST(FlutterKeyEmbedderHandlerUnittests, ConvertCapsLockEvents) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; + id keyEventCallback = ^(BOOL handled) { + last_handled = handled; + }; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + // In: CapsLock down + // Out: CapsLock down & *CapsLock Up + last_handled = FALSE; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x10100, @"", @"", FALSE, kKeyCodeCapsLock) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 2u); + + event = [events firstObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalCapsLock); + EXPECT_EQ(event->logical, kLogicalCapsLock); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + EXPECT_TRUE([[events firstObject] hasCallback]); + + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalCapsLock); + EXPECT_EQ(event->logical, kLogicalCapsLock); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + EXPECT_FALSE([[events lastObject] hasCallback]); + + EXPECT_EQ(last_handled, FALSE); + [[events firstObject] respond:TRUE]; + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + + // In: CapsLock up + // Out: CapsLock down & *CapsLock Up + last_handled = FALSE; + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 2u); + + event = [events firstObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalCapsLock); + EXPECT_EQ(event->logical, kLogicalCapsLock); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + EXPECT_TRUE([[events firstObject] hasCallback]); + + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalCapsLock); + EXPECT_EQ(event->logical, kLogicalCapsLock); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + EXPECT_FALSE([[events lastObject] hasCallback]); + + EXPECT_EQ(last_handled, FALSE); + [[events firstObject] respond:TRUE]; + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; +} + } // namespace flutter::testing From 9aec95230f8ae172d8e45037f4ec591d159ba834 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 4 Mar 2021 21:02:21 -0800 Subject: [PATCH 17/46] Numpad and F1 keys --- .../FlutterKeyEmbedderHandlerUnittests.mm | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index a43b43052743a..c3384ab6c6e6a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -62,6 +62,8 @@ - (void)dealloc { constexpr uint64_t kKeyCodeShiftLeft = 56; constexpr uint64_t kKeyCodeShiftRight = 60; constexpr uint64_t kKeyCodeCapsLock = 57; +constexpr uint64_t kKeyCodeNumpad1 = 83; +constexpr uint64_t kKeyCodeF1 = 122; constexpr uint64_t kPhysicalKeyA = 0x00070004; // constexpr uint64_t kPhysicalControlLeft = 0x000700e0; @@ -69,6 +71,8 @@ - (void)dealloc { constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; constexpr uint64_t kPhysicalShiftRight = 0x000700e5; constexpr uint64_t kPhysicalCapsLock = 0x00070039; +constexpr uint64_t kPhysicalNumpad1 = 0x00070059; +constexpr uint64_t kPhysicalF1 = 0x0007003a; // constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; constexpr uint64_t kLogicalKeyA = 0x00000061; @@ -77,6 +81,8 @@ - (void)dealloc { constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; constexpr uint64_t kLogicalShiftRight = 0x0040000010d; constexpr uint64_t kLogicalCapsLock = 0x00000000104; +constexpr uint64_t kLogicalNumpad1 = 0x00200000031; +constexpr uint64_t kLogicalF1 = 0x00000000801; // constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; typedef void (^ResponseCallback)(bool handled); @@ -283,6 +289,156 @@ - (void)dealloc { [events removeAllObjects]; } +// Special modifier flags. +// +// Some keys in modifierFlags are not to indicate modifier state, but to mark +// the key area that the key belongs to, such as numpad keys or function keys. +// Ensure these flags do not obstruct other keys. +TEST(FlutterKeyEmbedderHandlerUnittests, SpecialModiferFlags) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + // Keydown: Numpad1, F1, KeyA, ShiftLeft + // Then KeyUp: Numpad1, F1, KeyA, ShiftLeft + + // Numpad 1 + [handler + handleEvent:keyEvent(NSEventTypeKeyDown, 0x200100, @"1", @"1", FALSE, kKeyCodeNumpad1) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalNumpad1); + EXPECT_EQ(event->logical, kLogicalNumpad1); + EXPECT_STREQ(event->character, "1"); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + // F1 + [handler + handleEvent:keyEvent(NSEventTypeKeyDown, 0x800100, @"1", @"1", FALSE, kKeyCodeF1) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalF1); + EXPECT_EQ(event->logical, kLogicalF1); + EXPECT_STREQ(event->character, "1"); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + // KeyA + [handler + handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + // ShiftLeft + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + // Numpad 1 + [handler + handleEvent:keyEvent(NSEventTypeKeyUp, 0x220102, @"1", @"1", FALSE, kKeyCodeNumpad1) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalNumpad1); + EXPECT_EQ(event->logical, kLogicalNumpad1); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + // F1 + [handler + handleEvent:keyEvent(NSEventTypeKeyUp, 0x820102, @"1", @"1", FALSE, kKeyCodeF1) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalF1); + EXPECT_EQ(event->logical, kLogicalF1); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + // KeyA + [handler + handleEvent:keyEvent(NSEventTypeKeyUp, 0x20102, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + // ShiftLeft + [handler + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; +} + + TEST(FlutterKeyEmbedderHandlerUnittests, IdentifyLeftAndRightModifiers) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; From 3793b0a39490778a9d0eea795a67de42d498e659 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 4 Mar 2021 22:10:52 -0800 Subject: [PATCH 18/46] Add character filtering --- .../Source/FlutterKeyEmbedderHandler.mm | 23 +++++++- .../FlutterKeyEmbedderHandlerUnittests.mm | 57 ++++++++----------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 1a41fab03b9c8..5fd00d107d357 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -138,6 +138,27 @@ static double GetFlutterTimestampFrom(NSEvent* event) { } void HandleResponse(bool handled, void* user_data); + +// Converts NSEvent.characters to a C-string for FlutterKeyEvent. +const char* getEventString(NSString* characters) { + if ([characters length] == 0) { + return nullptr; + } + unichar utf16Code = [characters characterAtIndex:0]; + if (utf16Code >= 0xf700 && utf16Code <= 0xf7ff) { + // Some function keys are assigned characters with codepoints from the + // private use area (see + // https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc). + // These characters are filtered out since they're unprintable. + // + // Although the documentation claims to reserve 0xF700-0xF8FF, only up to 0xF747 + // is actually used. Here we choose to filter out 0xF700-0xF7FF section. + // The reason for keeping the 0xF800-0xF8FF section is because 0xF8FF is + // used for the "Apple logo" character (Option-Shift-K on US keyboard.) + return nullptr; + } + return [characters UTF8String]; +} } // namespace /* An entry of FlutterKeyEmbedderHandler.pendingResponse. @@ -285,7 +306,7 @@ - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca .type = isARepeat ? kFlutterKeyEventTypeRepeat : kFlutterKeyEventTypeDown, .physical = physicalKey, .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue], - .character = event.characters.UTF8String, + .character = getEventString(event.characters), .synthesized = isSynthesized, }; [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index c3384ab6c6e6a..6bfd8b7c4b153 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -310,10 +310,9 @@ - (void)dealloc { // Then KeyUp: Numpad1, F1, KeyA, ShiftLeft // Numpad 1 - [handler - handleEvent:keyEvent(NSEventTypeKeyDown, 0x200100, @"1", @"1", FALSE, kKeyCodeNumpad1) - callback:^(BOOL handled){ - }]; + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x200100, @"1", @"1", FALSE, kKeyCodeNumpad1) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -327,7 +326,7 @@ - (void)dealloc { // F1 [handler - handleEvent:keyEvent(NSEventTypeKeyDown, 0x800100, @"1", @"1", FALSE, kKeyCodeF1) + handleEvent:keyEvent(NSEventTypeKeyDown, 0x800100, @"\uf704", @"\uf704", FALSE, kKeyCodeF1) callback:^(BOOL handled){ }]; @@ -336,16 +335,15 @@ - (void)dealloc { EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); EXPECT_EQ(event->physical, kPhysicalF1); EXPECT_EQ(event->logical, kLogicalF1); - EXPECT_STREQ(event->character, "1"); + EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); [events removeAllObjects]; // KeyA - [handler - handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -374,10 +372,9 @@ - (void)dealloc { [events removeAllObjects]; // Numpad 1 - [handler - handleEvent:keyEvent(NSEventTypeKeyUp, 0x220102, @"1", @"1", FALSE, kKeyCodeNumpad1) - callback:^(BOOL handled){ - }]; + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x220102, @"1", @"1", FALSE, kKeyCodeNumpad1) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -390,10 +387,9 @@ - (void)dealloc { [events removeAllObjects]; // F1 - [handler - handleEvent:keyEvent(NSEventTypeKeyUp, 0x820102, @"1", @"1", FALSE, kKeyCodeF1) - callback:^(BOOL handled){ - }]; + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x820102, @"\uF704", @"\uF704", FALSE, kKeyCodeF1) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -406,10 +402,9 @@ - (void)dealloc { [events removeAllObjects]; // KeyA - [handler - handleEvent:keyEvent(NSEventTypeKeyUp, 0x20102, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x20102, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -422,10 +417,9 @@ - (void)dealloc { [events removeAllObjects]; // ShiftLeft - [handler - handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:^(BOOL handled){ - }]; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -438,7 +432,6 @@ - (void)dealloc { [events removeAllObjects]; } - TEST(FlutterKeyEmbedderHandlerUnittests, IdentifyLeftAndRightModifiers) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; @@ -727,9 +720,8 @@ - (void)dealloc { // In: CapsLock down // Out: CapsLock down & *CapsLock Up last_handled = FALSE; - [handler - handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x10100, @"", @"", FALSE, kKeyCodeCapsLock) - callback:keyEventCallback]; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x10100, @"", @"", FALSE, kKeyCodeCapsLock) + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -758,9 +750,8 @@ - (void)dealloc { // In: CapsLock up // Out: CapsLock down & *CapsLock Up last_handled = FALSE; - [handler - handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) - callback:keyEventCallback]; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); From 99b4abb2f0d771811310792c14c9fa1562ff9122 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 4 Mar 2021 23:09:30 -0800 Subject: [PATCH 19/46] Simplify logic and more assertion --- .../Source/FlutterKeyEmbedderHandler.mm | 38 +++++++------------ 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 5fd00d107d357..adb641663b20c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -10,7 +10,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/embedder/embedder.h" -// Values of `characterIgnoringModifiers` that must not be directly converted to +// Values of `charactersIgnoringModifiers` that must not be directly converted to // a logical key value, but should look up `keyCodeToLogical` by `keyCode`. // This is because each of these of character codes is mapped from multiple // logical keys, usually because of numpads. @@ -276,26 +276,9 @@ - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; - bool isARepeat = false; + bool isARepeat = (pressedLogicalKey != nil) || event.isARepeat; bool isSynthesized = false; - if (pressedLogicalKey) { - // This physical key is being pressed according to the record. - if (event.isARepeat) { - // A normal repeated key. - isARepeat = true; - } else { - // A non-repeated key has been pressed that has the exact physical key as - // a currently pressed one, usually indicating multiple keyboards are - // pressing keys with the same physical key, or the up event was lost - // during a loss of focus. The down event is ignored. - callback(TRUE); - return; - } - } else { - // This physical key is not being pressed according to the record. It's a - // normal down event, whether the system event is a repeat or not. - } if (pressedLogicalKey == nil) { [self updateKey:physicalKey asPressed:logicalKey]; } @@ -313,14 +296,17 @@ - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca } - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - NSAssert(!event.isARepeat, @"Unexpected repeated Up event. Please report this to Flutter.", - event.characters); + NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@", + event.keyCode, event.characters, event.charactersIgnoringModifiers); uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; - if (!pressedLogicalKey) { - // The physical key has been released before. It indicates multiple - // keyboards pressed keys with the same physical key. Ignore the up event. + if (pressedLogicalKey == nil) { + NSAssert(FALSE, + @"Received key up event that has not been pressed: " + @"keyCode %d, char %@, charIM %@, previously pressed logical 0x%llx", + event.keyCode, event.characters, event.charactersIgnoringModifiers, + [pressedLogicalKey unsignedLongLongValue]); callback(TRUE); return; } @@ -341,6 +327,8 @@ - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call - (void)dispatchCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { NSNumber* logicalKey = [keyCodeToLogicalKey objectForKey:@(event.keyCode)]; if (logicalKey == nil) { + NSAssert(FALSE, + @"Invalid keyCode is considered CapsLock event: keyCode %d", event.keyCode); callback(TRUE); return; } @@ -400,7 +388,7 @@ - (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca } uint64_t modifierFlag = GetModifierFlagForKey(targetKey); if (targetKey == 0 || modifierFlag == 0) { - // Unrecognized modifier. + NSLog(@"Unrecognized modifier key: keyCode %d", event.keyCode); callback(TRUE); return; } From c508b6fde3cd6c3fbf12242e4413ef112be8a5a8 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 15:13:04 -0800 Subject: [PATCH 20/46] Rewrite modifier algorithm --- .../Source/FlutterKeyEmbedderHandler.mm | 324 ++++++++++-------- .../FlutterKeyEmbedderHandlerUnittests.mm | 12 +- .../macos/framework/Source/KeyCodeMap.mm | 45 ++- .../framework/Source/KeyCodeMap_internal.h | 35 +- 4 files changed, 244 insertions(+), 172 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index adb641663b20c..77cf57e9ca746 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -19,6 +19,26 @@ namespace { +// The portion of NSEvent.modifierFlags that Flutter tries to keep synchronized +// on. +// +// This must be equal to the sum of all keys in keyCodeToModifierFlag plus +// keyCodeToModifierFlag. +static const uint64_t kModifierFlagOfInterestMask = + kModifierFlagControlLeft | kModifierFlagShiftLeft | kModifierFlagShiftRight | + kModifierFlagMetaLeft | kModifierFlagMetaRight | kModifierFlagAltLeft | kModifierFlagAltRight | + kModifierFlagControlRight | NSEventModifierFlagCapsLock; + +// Isolate the least significant 1-bit. +// +// For example, +// +// * lowestSetBit(0x1010) returns 0x10. +// * lowestSetBit(0) returns 0. +static NSUInteger lowestSetBit(NSUInteger bitmask) { + return bitmask & -bitmask; +} + // Whether a string represents a control character. static bool IsControlCharacter(NSUInteger length, NSString* label) { if (length > 1) { @@ -42,28 +62,18 @@ static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) { return plane | (baseKey & kValueMask); } -// Find the sibling key for a physical key by looking up `siblingKeyCodes`. -// -// Returns 0 if not found. -static uint64_t GetSiblingKeyCodeForKey(uint64_t physicalKey) { - NSNumber* siblingKey = [siblingKeyCodes objectForKey:@(physicalKey)]; - if (siblingKey == nil) - return 0; - return siblingKey.unsignedLongLongValue; -} - -// Find the modifer flag for a physical key by looking up `modiferFlags`. +// Find the modifier flag for a physical key by looking up `modifierFlags`. // // Returns 0 if not found. -static uint64_t GetModifierFlagForKey(uint64_t physicalKey) { - NSNumber* modifierFlag = [modiferFlags objectForKey:@(physicalKey)]; - if (modifierFlag == nil) - return 0; - return modifierFlag.unsignedLongLongValue; -} +// static uint64_t GetModifierFlagForKey(uint64_t physicalKey) { +// NSNumber* modifierFlag = [keyCodeToModifierFlag objectForKey:@(physicalKey)]; +// if (modifierFlag == nil) +// return 0; +// return modifierFlag.unsignedLongLongValue; +// } // Returns the physical key for a key code. -static uint64_t GetPhysicalKeyForKeyCode(uint64_t keyCode) { +static uint64_t GetPhysicalKeyForKeyCode(unsigned short keyCode) { NSNumber* physicalKeyKey = [keyCodeToPhysicalKey objectForKey:@(keyCode)]; if (physicalKeyKey == nil) return 0; @@ -71,7 +81,7 @@ static uint64_t GetPhysicalKeyForKeyCode(uint64_t keyCode) { } // Returns the logical key for a modifier physical key. -static uint64_t GetLogicalKeyForModifier(uint64_t keyCode, uint64_t hidCode) { +static uint64_t GetLogicalKeyForModifier(unsigned short keyCode, uint64_t hidCode) { NSNumber* fromKeyCode = [keyCodeToLogicalKey objectForKey:@(keyCode)]; if (fromKeyCode != nil) return fromKeyCode.unsignedLongLongValue; @@ -203,10 +213,14 @@ @interface FlutterKeyEmbedderHandler () */ @property(nonatomic) NSMutableDictionary* pressingRecords; +@property(nonatomic) NSUInteger lastModifierFlags; + @property(nonatomic) uint64_t responseId; @property(nonatomic) NSMutableDictionary* pendingResponses; +- (void)synchronizeModifiers:(uint64_t)currentFlags ignoringFlags:(uint64_t)ignoringFlags; + /** * Update the pressing state. * @@ -216,22 +230,40 @@ @interface FlutterKeyEmbedderHandler () - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event - callback:(FlutterKeyHandlerCallback)callback; + callback:(nonnull FlutterKeyHandlerCallback)callback; + +/** + * Send a CapsLock down event, then a CapsLock up event. + * + * If downCallback is nil, then both events will be synthesized. Otherwise, the + * downCallback will be used as the callback for the down event, which is not + * synthesized. + */ +- (void)sendCapsLockTapWithCallback:(nullable FlutterKeyHandlerCallback)downCallback; + +- (void)sendModifierEventForDown:(BOOL)shouldDown + keyCode:(unsigned short)keyCode + callback:(nullable FlutterKeyHandlerCallback)downCallback; /** * Processes a down event. */ -- (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; +- (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; + +/** + * Processes an up event. + */ +- (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; /** * Processes an up event. */ -- (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; +- (void)handleCapsLockEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; /** * Processes a flags changed event, where modifier keys are pressed or released. */ -- (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; +- (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; @@ -243,6 +275,7 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent self = [super init]; if (self != nil) { _sendEvent = sendEvent; + _lastModifierFlags = 0; _pressingRecords = [NSMutableDictionary dictionary]; _pendingResponses = [NSMutableDictionary dictionary]; _responseId = 1; @@ -252,6 +285,30 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent #pragma mark - Private +- (void)synchronizeModifiers:(uint64_t)currentFlags ignoringFlags:(uint64_t)ignoringFlags { + NSUInteger currentInterestedFlags = currentFlags & kModifierFlagOfInterestMask; + NSUInteger lastInterestedFlags = _lastModifierFlags & kModifierFlagOfInterestMask; + NSUInteger flagDifference = (currentInterestedFlags ^ lastInterestedFlags) & ~ignoringFlags; + if (flagDifference & NSEventModifierFlagCapsLock) { + [self sendCapsLockTapWithCallback:nil]; + flagDifference = flagDifference & ~NSEventModifierFlagCapsLock; + } + while (true) { + NSUInteger currentFlag = lowestSetBit(flagDifference); + if (currentFlag == 0) { + break; + } + flagDifference = flagDifference & ~currentFlag; + NSNumber* keyCode = [modifierFlagToKeyCode objectForKey:@(currentFlag)]; + NSAssert(keyCode != nil, @"Invalid modifier flag 0x%lx", currentFlag); + if (keyCode == nil) { + continue; + } + BOOL shouldDown = (currentInterestedFlags & currentFlag) != 0; + [self sendModifierEventForDown:shouldDown keyCode:[keyCode unsignedShortValue] callback:nil]; + } +} + - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { if (logicalKey == 0) { [_pressingRecords removeObjectForKey:@(physicalKey)]; @@ -271,9 +328,62 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event _sendEvent(event, HandleResponse, (__bridge_retained void*)pending); } -- (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)sendCapsLockTapWithCallback:(FlutterKeyHandlerCallback)downCallback { + // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on + // even taps and odd taps. A CapsLock down or CapsLock up should always be + // converted to a down *and* an up, and the up should always be a synthesized + // event, since we will never know when the button is released. + FlutterKeyEvent flutterEvent = { + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = 0, // TODO + .type = kFlutterKeyEventTypeDown, + .physical = kCapsLockPhysicalKey, + .logical = kCapsLockLogicalKey, + .character = nil, + .synthesized = downCallback == nil, + }; + if (downCallback != nil) { + [self sendPrimaryFlutterEvent:flutterEvent callback:downCallback]; + } else { + _sendEvent(flutterEvent, nullptr, nullptr); + } + + flutterEvent.type = kFlutterKeyEventTypeUp; + flutterEvent.synthesized = true; + _sendEvent(flutterEvent, nullptr, nullptr); +} + +- (void)sendModifierEventForDown:(BOOL)shouldDown + keyCode:(unsigned short)keyCode + callback:(FlutterKeyHandlerCallback)callback { + uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode); + uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey); + if (physicalKey == 0 || logicalKey == 0) { + NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey); + callback(TRUE); + return; + } + FlutterKeyEvent flutterEvent = { + .struct_size = sizeof(FlutterKeyEvent), + .timestamp = 0, // TODO + .type = shouldDown ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp, + .physical = physicalKey, + .logical = logicalKey, + .character = nil, + .synthesized = callback == nil, + }; + [self updateKey:physicalKey asPressed:shouldDown ? logicalKey : 0]; + if (callback != nil) { + [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; + } else { + _sendEvent(flutterEvent, nullptr, nullptr); + } +} + +- (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); + [self synchronizeModifiers:event.modifierFlags ignoringFlags:0]; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; bool isARepeat = (pressedLogicalKey != nil) || event.isARepeat; @@ -295,18 +405,19 @@ - (void)dispatchDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)ca [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } -- (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@", event.keyCode, event.characters, event.charactersIgnoringModifiers); + [self synchronizeModifiers:event.modifierFlags ignoringFlags:0]; uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; if (pressedLogicalKey == nil) { NSAssert(FALSE, - @"Received key up event that has not been pressed: " - @"keyCode %d, char %@, charIM %@, previously pressed logical 0x%llx", - event.keyCode, event.characters, event.charactersIgnoringModifiers, - [pressedLogicalKey unsignedLongLongValue]); + @"Received key up event that has not been pressed: " + @"keyCode %d, char %@, charIM %@, previously pressed logical 0x%llx", + event.keyCode, event.characters, event.charactersIgnoringModifiers, + [pressedLogicalKey unsignedLongLongValue]); callback(TRUE); return; } @@ -324,136 +435,77 @@ - (void)dispatchUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } -- (void)dispatchCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - NSNumber* logicalKey = [keyCodeToLogicalKey objectForKey:@(event.keyCode)]; - if (logicalKey == nil) { - NSAssert(FALSE, - @"Invalid keyCode is considered CapsLock event: keyCode %d", event.keyCode); - callback(TRUE); - return; - } - uint64_t logical = logicalKey.unsignedLongLongValue; - - FlutterKeyEvent flutterEvent = { - .struct_size = sizeof(FlutterKeyEvent), - .timestamp = GetFlutterTimestampFrom(event), - .type = kFlutterKeyEventTypeDown, - .physical = kCapsLockPhysicalKey, - .logical = logical, - .character = nil, - .synthesized = false, - }; - [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; - - // MacOS sends a Down or an Up when CapsLock is pressed, depending on whether - // the lock is enabled or disabled. This event should always be converted to - // a Down and a cancel, since we don't know how long it will be pressed. - flutterEvent.type = kFlutterKeyEventTypeUp; - flutterEvent.synthesized = true; - _sendEvent(flutterEvent, nullptr, nullptr); +- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { + [self sendCapsLockTapWithCallback:callback]; } -- (void)dispatchFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - // NSEvent only tells us the key that triggered the event and the resulting - // flag, but not whether the change is a down or up. For keys such as - // CapsLock, the change type can be inferred from the key and the flag. - // - // But some other modifier keys come in pairs, such as shift or control, and - // both of the pair share one flag, which indicates either key is pressed. - // If the pressing states of paired keys are desynchronized due to loss of - // focus, the change might have to be guessed and synchronized. - // - // For convenience, the key corresponding to `event.keycode` is called - // `targetKey`. If the key is among a pair, the other key is called - // `siblingKey`. - // - // The logic of guessing is shown as follows, based on whether the key pairs - // were recorded as pressed and whether the incoming flag is set: ("*" - // indicates synthesized event.) - // - // LastPressed \ NowEither | | - // (Tgt,Sbl) \ Pressed | 1 | 0 - // -----------------------|-------------------|------------------- - // (1, 0) | - | TgtUp - // (0, 0) | TgtDown | - - // (0, 1) | TgtDown | SblUp* - // (1, 1) | TgtUp | SblUp*,TgtUp - // - // For non-pair keys, lastSiblingPressed is always set to 0, resulting in the - // top half of the table. - +- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { + NSNumber* targetModifierFlagObj = keyCodeToModifierFlag[@(event.keyCode)]; + NSUInteger targetModifierFlag = + targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue]; uint64_t targetKey = GetPhysicalKeyForKeyCode(event.keyCode); if (targetKey == kCapsLockPhysicalKey) { - return [self dispatchCapsLockEvent:event callback:callback]; + return [self handleCapsLockEvent:event callback:callback]; } - uint64_t modifierFlag = GetModifierFlagForKey(targetKey); - if (targetKey == 0 || modifierFlag == 0) { - NSLog(@"Unrecognized modifier key: keyCode %d", event.keyCode); + + [self synchronizeModifiers:event.modifierFlags ignoringFlags:targetModifierFlag]; + + NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)]; + BOOL lastTargetPressed = pressedLogicalKey != nil; + NSAssert(targetModifierFlagObj == nil || + (_lastModifierFlags & targetModifierFlag) != 0 == lastTargetPressed, + @"Desynchronized state between lastModifierFlags (0x%lx) on bit 0x%lx " + @"for keyCode 0x%hx, whose pressing state is %@.", + _lastModifierFlags, targetModifierFlag, event.keyCode, + lastTargetPressed + ? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]] + : @"empty"); + BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0; + printf("Key %hx lastP %d shouldP %d\n", event.keyCode, lastTargetPressed, shouldBePressed); + + _lastModifierFlags = event.modifierFlags; + if (lastTargetPressed == shouldBePressed) { callback(TRUE); return; } - // The `siblingKeyCode` may be 0, which means it doesn't have a sibling key. - uint64_t siblingKeyCode = GetSiblingKeyCodeForKey(event.keyCode); - uint64_t siblingKeyPhysical = siblingKeyCode == 0 ? 0 : GetPhysicalKeyForKeyCode(siblingKeyCode); - uint64_t siblingKeyLogical = - siblingKeyCode == 0 ? 0 : GetLogicalKeyForModifier(siblingKeyCode, siblingKeyPhysical); - - bool lastTargetPressed = [_pressingRecords objectForKey:@(targetKey)] != nil; - bool lastSiblingPressed = - siblingKeyCode == 0 ? false : [_pressingRecords objectForKey:@(siblingKeyPhysical)] != nil; - bool nowEitherPressed = (event.modifierFlags & modifierFlag) != 0; - - bool targetKeyShouldDown = !lastTargetPressed && nowEitherPressed; - bool targetKeyShouldUp = lastTargetPressed && (lastSiblingPressed || !nowEitherPressed); - bool siblingKeyShouldUp = lastSiblingPressed && !nowEitherPressed; - FlutterKeyEvent flutterEvent = { - .struct_size = sizeof(FlutterKeyEvent), - .timestamp = GetFlutterTimestampFrom(event), - .character = nil, - }; - if (siblingKeyShouldUp) { - flutterEvent.type = kFlutterKeyEventTypeUp; - flutterEvent.physical = siblingKeyPhysical; - flutterEvent.logical = siblingKeyLogical; - flutterEvent.synthesized = true; - [self updateKey:siblingKeyPhysical asPressed:0]; - _sendEvent(flutterEvent, nullptr, nullptr); - } + [self sendModifierEventForDown:shouldBePressed keyCode:event.keyCode callback:callback]; +} - if (targetKeyShouldDown || targetKeyShouldUp) { - uint64_t logicalKey = GetLogicalKeyForModifier(event.keyCode, targetKey); - flutterEvent.type = targetKeyShouldDown ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp; - flutterEvent.physical = targetKey; - flutterEvent.logical = logicalKey; - flutterEvent.synthesized = false; - [self updateKey:targetKey asPressed:(targetKeyShouldDown ? logicalKey : 0)]; - [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; - } else { - callback(TRUE); +void ps(const char* s) { + int i = 0; + while (s[i] != 0) { + printf("%02hhx", s[i]); + i++; } + if (i == 0) + printf("00"); } -- (void)handleEvent:(NSEvent*)event - ofType:(NSString*)type - callback:(FlutterKeyHandlerCallback)callback { - printf("#### Event %d keyCode %d mod %lx", (int)event.type, (int)event.keyCode, - event.modifierFlags); +- (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { + printf("#### Event %d keyCode %hx mod %lx ", (int)event.type, event.keyCode, event.modifierFlags); if (event.type != NSEventTypeFlagsChanged) { - printf("rep %d cIM %s c %s\n", event.isARepeat, - [[event charactersIgnoringModifiers] UTF8String], [[event characters] UTF8String]); + printf("rep %d cIM %s(", event.isARepeat, [[event charactersIgnoringModifiers] UTF8String]); + ps([[event charactersIgnoringModifiers] UTF8String]); + printf(") c %s(", [[event characters] UTF8String]); + ps([[event characters] UTF8String]); + printf(")\n"); } else { printf("\n"); } + // The conversion algorithm relies on a non-nil callback to properly compute + // `synthesized`. If someday callback is allowed to be nil, make a dummy empty + // callback instead. + NSAssert(callback != nil, @"The callback must not be nil."); switch (event.type) { case NSEventTypeKeyDown: - [self dispatchDownEvent:event callback:callback]; + [self handleDownEvent:event callback:callback]; break; case NSEventTypeKeyUp: - [self dispatchUpEvent:event callback:callback]; + [self handleUpEvent:event callback:callback]; break; case NSEventTypeFlagsChanged: - [self dispatchFlagEvent:event callback:callback]; + [self handleFlagEvent:event callback:callback]; break; default: NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 6bfd8b7c4b153..81faa3c7a98a1 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -59,11 +59,11 @@ - (void)dealloc { namespace { constexpr uint64_t kKeyCodeKeyA = 0; -constexpr uint64_t kKeyCodeShiftLeft = 56; -constexpr uint64_t kKeyCodeShiftRight = 60; -constexpr uint64_t kKeyCodeCapsLock = 57; -constexpr uint64_t kKeyCodeNumpad1 = 83; -constexpr uint64_t kKeyCodeF1 = 122; +constexpr uint64_t kKeyCodeShiftLeft = 0x38; +constexpr uint64_t kKeyCodeShiftRight = 0x3c; +constexpr uint64_t kKeyCodeCapsLock = 0x39; +constexpr uint64_t kKeyCodeNumpad1 = 0x53; +constexpr uint64_t kKeyCodeF1 = 0x7a; constexpr uint64_t kPhysicalKeyA = 0x00070004; // constexpr uint64_t kPhysicalControlLeft = 0x000700e0; @@ -274,7 +274,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x20104, @"a", @"a", FALSE, kKeyCodeKeyA) + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled){ }]; diff --git a/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm b/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm index 4017b1f8642cd..4c8a0ac4fc3e7 100644 --- a/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm +++ b/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm @@ -190,6 +190,7 @@ const NSDictionary* keyCodeToLogicalKey = @{ @0x00000033 : @0x0000000008, // Backspace @0x00000035 : @0x000000001b, // Escape + @0x00000075 : @0x000000007f, // Delete @0x00000039 : @0x0000000104, // CapsLock @0x0000003f : @0x0000000106, // Fn @0x00000047 : @0x000000010a, // NumLock @@ -223,6 +224,13 @@ @0x0000004f : @0x0000000812, // F18 @0x00000050 : @0x0000000813, // F19 @0x0000005a : @0x0000000814, // F20 + @0x00000049 : @0x0000000a0f, // AudioVolumeDown + @0x00000048 : @0x0000000a10, // AudioVolumeUp + @0x0000004a : @0x0000000a11, // AudioVolumeMute + @0x0000005e : @0x0100070087, // IntlRo + @0x0000005d : @0x0100070089, // IntlYen + @0x00000068 : @0x0100070090, // Lang1 + @0x00000066 : @0x0100070091, // Lang2 @0x0000004c : @0x020000000d, // NumpadEnter @0x00000043 : @0x020000002a, // NumpadMultiply @0x00000045 : @0x020000002b, // NumpadAdd @@ -251,26 +259,27 @@ @0x0000003c : @0x040000010d, // ShiftRight }; -const NSDictionary* siblingKeyCodes = @{ - @0x00000038 : @0x0000003c, // shift - @0x0000003c : @0x00000038, // shift - @0x00000037 : @0x00000036, // meta - @0x00000036 : @0x00000037, // meta - @0x0000003a : @0x0000003d, // alt - @0x0000003d : @0x0000003a, // alt - @0x0000003b : @0x0000003e, // control - @0x0000003e : @0x0000003b, // control +const NSDictionary* keyCodeToModifierFlag = @{ + @0x00000038 : @(kModifierFlagShiftLeft), + @0x0000003c : @(kModifierFlagShiftRight), + @0x0000003b : @(kModifierFlagControlLeft), + @0x0000003e : @(kModifierFlagControlRight), + @0x0000003a : @(kModifierFlagAltLeft), + @0x0000003d : @(kModifierFlagAltRight), + @0x00000037 : @(kModifierFlagMetaLeft), + @0x00000036 : @(kModifierFlagMetaRight), }; -const NSDictionary* modiferFlags = @{ - @0x000700e1 : @(NSEventModifierFlagShift), - @0x000700e5 : @(NSEventModifierFlagShift), - @0x000700e0 : @(NSEventModifierFlagControl), - @0x000700e4 : @(NSEventModifierFlagControl), - @0x000700e2 : @(NSEventModifierFlagOption), - @0x000700e6 : @(NSEventModifierFlagOption), - @0x000700e3 : @(NSEventModifierFlagCommand), - @0x000700e7 : @(NSEventModifierFlagCommand), +const NSDictionary* modifierFlagToKeyCode = @{ + @(kModifierFlagShiftLeft) : @0x00000038, + @(kModifierFlagShiftRight) : @0x0000003c, + @(kModifierFlagControlLeft) : @0x0000003b, + @(kModifierFlagControlRight) : @0x0000003e, + @(kModifierFlagAltLeft) : @0x0000003a, + @(kModifierFlagAltRight) : @0x0000003d, + @(kModifierFlagMetaLeft) : @0x00000037, + @(kModifierFlagMetaRight) : @0x00000036, }; const uint64_t kCapsLockPhysicalKey = 0x00070039; +const uint64_t kCapsLockLogicalKey = 0x00000104; diff --git a/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h b/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h index ee5ed503d8033..ad83b42f1470e 100644 --- a/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h +++ b/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h @@ -33,25 +33,36 @@ extern const uint64_t kSynonymMask; static const uint64_t kMacosPlane = 0x00500000000; /** - * Map the key code of a key to that of its sibling key. + * Map the physical key code of a key to its corresponding bitmask of + * NSEventModifierFlags. * - * A sibling key is the other key of a pair of keys that share the same modifier - * flag, such as left and right shift keys. + * This does not include CapsLock, for it is handled specially. */ -extern const NSDictionary* siblingKeyCodes; +extern const NSDictionary* keyCodeToModifierFlag; /** - * Map the physical key code of a key to its corresponding bitmask of - * NSEventModifierFlags. - * - * This does not include CapsLock, for it is handled specially. Other modifier - * keys do not seem to be needed either. */ -extern const NSDictionary* modiferFlags; +extern const NSDictionary* modifierFlagToKeyCode; /** * The physical key for CapsLock, which needs special handling. - * - * This should be kept up to date with KeyCodeMap.mm */ extern const uint64_t kCapsLockPhysicalKey; + +/** + * The logical key for CapsLock, which needs special handling. + */ +extern const uint64_t kCapsLockLogicalKey; + +// The following constants, excluded from +// NSEventModifierFlagDeviceIndependentFlagsMask, are derived from experiments. +typedef enum { + kModifierFlagControlLeft = 0x1, + kModifierFlagShiftLeft = 0x2, + kModifierFlagShiftRight = 0x4, + kModifierFlagMetaLeft = 0x8, + kModifierFlagMetaRight = 0x10, + kModifierFlagAltLeft = 0x20, + kModifierFlagAltRight = 0x40, + kModifierFlagControlRight = 0x200, +} ModifierFlag; From 8b051f4fd1ca2f1e2e18e4d6dadb60f5ed308ff6 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 15:27:54 -0800 Subject: [PATCH 21/46] Compute mask --- .../Source/FlutterKeyEmbedderHandler.mm | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 77cf57e9ca746..cbd672bbb0a4c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -19,16 +19,6 @@ namespace { -// The portion of NSEvent.modifierFlags that Flutter tries to keep synchronized -// on. -// -// This must be equal to the sum of all keys in keyCodeToModifierFlag plus -// keyCodeToModifierFlag. -static const uint64_t kModifierFlagOfInterestMask = - kModifierFlagControlLeft | kModifierFlagShiftLeft | kModifierFlagShiftRight | - kModifierFlagMetaLeft | kModifierFlagMetaRight | kModifierFlagAltLeft | kModifierFlagAltRight | - kModifierFlagControlRight | NSEventModifierFlagCapsLock; - // Isolate the least significant 1-bit. // // For example, @@ -147,6 +137,14 @@ static double GetFlutterTimestampFrom(NSEvent* event) { return event.timestamp * 1000000.0; } +static uint64_t computeModifierFlagOfInterestMask() { + __block uint64_t modifierFlagOfInterestMask = NSEventModifierFlagCapsLock; + [keyCodeToModifierFlag enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL *stop) { + modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue]; + }]; + return modifierFlagOfInterestMask; +} + void HandleResponse(bool handled, void* user_data); // Converts NSEvent.characters to a C-string for FlutterKeyEvent. @@ -215,6 +213,15 @@ @interface FlutterKeyEmbedderHandler () @property(nonatomic) NSUInteger lastModifierFlags; +/** + * A constant mask for NSEvent.modifierFlags that Flutter tries to keep + * synchronized on. + * + * It equals to the sum of all values of keyCodeToModifierFlag as well as + * NSEventModifierFlagCapsLock. + */ +@property(nonatomic) NSUInteger modifierFlagOfInterestMask; + @property(nonatomic) uint64_t responseId; @property(nonatomic) NSMutableDictionary* pendingResponses; @@ -279,6 +286,7 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent _pressingRecords = [NSMutableDictionary dictionary]; _pendingResponses = [NSMutableDictionary dictionary]; _responseId = 1; + _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask(); } return self; } @@ -286,8 +294,8 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent #pragma mark - Private - (void)synchronizeModifiers:(uint64_t)currentFlags ignoringFlags:(uint64_t)ignoringFlags { - NSUInteger currentInterestedFlags = currentFlags & kModifierFlagOfInterestMask; - NSUInteger lastInterestedFlags = _lastModifierFlags & kModifierFlagOfInterestMask; + NSUInteger currentInterestedFlags = currentFlags & _modifierFlagOfInterestMask; + NSUInteger lastInterestedFlags = _lastModifierFlags & _modifierFlagOfInterestMask; NSUInteger flagDifference = (currentInterestedFlags ^ lastInterestedFlags) & ~ignoringFlags; if (flagDifference & NSEventModifierFlagCapsLock) { [self sendCapsLockTapWithCallback:nil]; From 5eb0f287fe7b6eef0795f1ba4929a9647ae2f812 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 18:34:14 -0800 Subject: [PATCH 22/46] Add modifier sync test and assertion --- .../Source/FlutterKeyEmbedderHandler.mm | 38 ++++++--- .../FlutterKeyEmbedderHandlerUnittests.mm | 77 +++++++++++++++++++ 2 files changed, 104 insertions(+), 11 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index cbd672bbb0a4c..4fde36c472a6a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -137,8 +137,8 @@ static double GetFlutterTimestampFrom(NSEvent* event) { return event.timestamp * 1000000.0; } -static uint64_t computeModifierFlagOfInterestMask() { - __block uint64_t modifierFlagOfInterestMask = NSEventModifierFlagCapsLock; +static NSUInteger computeModifierFlagOfInterestMask() { + __block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock; [keyCodeToModifierFlag enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL *stop) { modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue]; }]; @@ -226,7 +226,7 @@ @interface FlutterKeyEmbedderHandler () @property(nonatomic) NSMutableDictionary* pendingResponses; -- (void)synchronizeModifiers:(uint64_t)currentFlags ignoringFlags:(uint64_t)ignoringFlags; +- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags; /** * Update the pressing state. @@ -282,27 +282,32 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent self = [super init]; if (self != nil) { _sendEvent = sendEvent; - _lastModifierFlags = 0; _pressingRecords = [NSMutableDictionary dictionary]; _pendingResponses = [NSMutableDictionary dictionary]; _responseId = 1; - _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask(); + _lastModifierFlags = 0; + _modifierFlagOfInterestMask = 0; // Being 0 means _lastModifierFlags hasn't been updated at all } return self; } #pragma mark - Private -- (void)synchronizeModifiers:(uint64_t)currentFlags ignoringFlags:(uint64_t)ignoringFlags { - NSUInteger currentInterestedFlags = currentFlags & _modifierFlagOfInterestMask; - NSUInteger lastInterestedFlags = _lastModifierFlags & _modifierFlagOfInterestMask; - NSUInteger flagDifference = (currentInterestedFlags ^ lastInterestedFlags) & ~ignoringFlags; +- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags { + BOOL firstTime = _modifierFlagOfInterestMask == 0; + if (firstTime) { + _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask(); + } + const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; + const NSUInteger currentInterestedFlags = currentFlags & updatingMask; + const NSUInteger lastInterestedFlags = _lastModifierFlags & updatingMask; + NSUInteger flagDifference = currentInterestedFlags ^ lastInterestedFlags; if (flagDifference & NSEventModifierFlagCapsLock) { [self sendCapsLockTapWithCallback:nil]; flagDifference = flagDifference & ~NSEventModifierFlagCapsLock; } while (true) { - NSUInteger currentFlag = lowestSetBit(flagDifference); + const NSUInteger currentFlag = lowestSetBit(flagDifference); if (currentFlag == 0) { break; } @@ -315,6 +320,12 @@ - (void)synchronizeModifiers:(uint64_t)currentFlags ignoringFlags:(uint64_t)igno BOOL shouldDown = (currentInterestedFlags & currentFlag) != 0; [self sendModifierEventForDown:shouldDown keyCode:[keyCode unsignedShortValue] callback:nil]; } + printf("last %lx updating %lx current %lx\n", _lastModifierFlags, updatingMask, currentFlags); + // if (firstTime) { + // _lastModifierFlags = currentFlags & updatingMask; + // } else { + _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentInterestedFlags; + // } } - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { @@ -444,7 +455,9 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callba } - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { + [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock]; [self sendCapsLockTapWithCallback:callback]; + _lastModifierFlags = event.modifierFlags & _modifierFlagOfInterestMask; } - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { @@ -471,7 +484,7 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0; printf("Key %hx lastP %d shouldP %d\n", event.keyCode, lastTargetPressed, shouldBePressed); - _lastModifierFlags = event.modifierFlags; + _lastModifierFlags = event.modifierFlags & _modifierFlagOfInterestMask; if (lastTargetPressed == shouldBePressed) { callback(TRUE); return; @@ -518,6 +531,9 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback default: NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); } + NSAssert(_lastModifierFlags == (event.modifierFlags & _modifierFlagOfInterestMask), + @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", + _lastModifierFlags, (event.modifierFlags & _modifierFlagOfInterestMask)); } - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 81faa3c7a98a1..ea048fe4b39d5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -701,6 +701,83 @@ - (void)dealloc { [events removeAllObjects]; } +TEST(FlutterKeyEmbedderHandlerUnittests, SynthesizeMissedModifierEventsInNormalEvents) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; + id keyEventCallback = ^(BOOL handled) { + last_handled = handled; + }; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + // In: (LShift down), A down, (LShift up), A up + // Out: *LS down & A down, *LS up & A up + + last_handled = FALSE; + [handler + handleEvent:keyEvent(NSEventTypeKeyDown, 0x20102, @"A", @"A", FALSE, kKeyCodeKeyA) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 2u); + event = [events firstObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + EXPECT_FALSE([[events firstObject] hasCallback]); + + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "A"); + EXPECT_EQ(event->synthesized, false); + EXPECT_TRUE([[events lastObject] hasCallback]); + + EXPECT_EQ(last_handled, FALSE); + [[events lastObject] respond:TRUE]; + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + + last_handled = FALSE; + [handler + handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 2u); + event = [events firstObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalShiftLeft); + EXPECT_EQ(event->logical, kLogicalShiftLeft); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + EXPECT_FALSE([[events firstObject] hasCallback]); + + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + EXPECT_TRUE([[events lastObject] hasCallback]); + + EXPECT_EQ(last_handled, FALSE); + [[events lastObject] respond:TRUE]; + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + +} + TEST(FlutterKeyEmbedderHandlerUnittests, ConvertCapsLockEvents) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; From 43a16cbffd4ba958952b19d768a0542c2f56247c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 18:39:31 -0800 Subject: [PATCH 23/46] Stricter state update --- .../Source/FlutterKeyEmbedderHandler.mm | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 4fde36c472a6a..1a9f7f8692460 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -286,7 +286,7 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent _pendingResponses = [NSMutableDictionary dictionary]; _responseId = 1; _lastModifierFlags = 0; - _modifierFlagOfInterestMask = 0; // Being 0 means _lastModifierFlags hasn't been updated at all + _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask(); } return self; } @@ -294,10 +294,6 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent #pragma mark - Private - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags { - BOOL firstTime = _modifierFlagOfInterestMask == 0; - if (firstTime) { - _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask(); - } const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; const NSUInteger currentInterestedFlags = currentFlags & updatingMask; const NSUInteger lastInterestedFlags = _lastModifierFlags & updatingMask; @@ -320,12 +316,7 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger) BOOL shouldDown = (currentInterestedFlags & currentFlag) != 0; [self sendModifierEventForDown:shouldDown keyCode:[keyCode unsignedShortValue] callback:nil]; } - printf("last %lx updating %lx current %lx\n", _lastModifierFlags, updatingMask, currentFlags); - // if (firstTime) { - // _lastModifierFlags = currentFlags & updatingMask; - // } else { _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentInterestedFlags; - // } } - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { @@ -457,7 +448,7 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callba - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock]; [self sendCapsLockTapWithCallback:callback]; - _lastModifierFlags = event.modifierFlags & _modifierFlagOfInterestMask; + _lastModifierFlags = _lastModifierFlags ^ NSEventModifierFlagCapsLock; } - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { @@ -484,12 +475,11 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0; printf("Key %hx lastP %d shouldP %d\n", event.keyCode, lastTargetPressed, shouldBePressed); - _lastModifierFlags = event.modifierFlags & _modifierFlagOfInterestMask; if (lastTargetPressed == shouldBePressed) { callback(TRUE); return; } - + _lastModifierFlags = _lastModifierFlags ^ targetModifierFlag; [self sendModifierEventForDown:shouldBePressed keyCode:event.keyCode callback:callback]; } From 7f93f5d748f34dde071a2032a875ede27ab0d930 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 18:57:07 -0800 Subject: [PATCH 24/46] Test CapsLock sync --- .../Source/FlutterKeyEmbedderHandler.mm | 29 ++++-- .../FlutterKeyEmbedderHandlerUnittests.mm | 91 +++++++++++++++++-- 2 files changed, 103 insertions(+), 17 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 1a9f7f8692460..44aa97a178ba8 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -139,9 +139,10 @@ static double GetFlutterTimestampFrom(NSEvent* event) { static NSUInteger computeModifierFlagOfInterestMask() { __block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock; - [keyCodeToModifierFlag enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL *stop) { - modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue]; - }]; + [keyCodeToModifierFlag + enumerateKeysAndObjectsUsingBlock:^(NSNumber* keyCode, NSNumber* flag, BOOL* stop) { + modifierFlagOfInterestMask = modifierFlagOfInterestMask | [flag unsignedLongValue]; + }]; return modifierFlagOfInterestMask; } @@ -255,7 +256,8 @@ - (void)sendModifierEventForDown:(BOOL)shouldDown /** * Processes a down event. */ -- (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; +- (void)handleDownEvent:(nonnull NSEvent*)event + callback:(nonnull FlutterKeyHandlerCallback)callback; /** * Processes an up event. @@ -265,12 +267,14 @@ - (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandle /** * Processes an up event. */ -- (void)handleCapsLockEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; +- (void)handleCapsLockEvent:(nonnull NSEvent*)event + callback:(nonnull FlutterKeyHandlerCallback)callback; /** * Processes a flags changed event, where modifier keys are pressed or released. */ -- (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; +- (void)handleFlagEvent:(nonnull NSEvent*)event + callback:(nonnull FlutterKeyHandlerCallback)callback; - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; @@ -447,8 +451,13 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callba - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock]; - [self sendCapsLockTapWithCallback:callback]; - _lastModifierFlags = _lastModifierFlags ^ NSEventModifierFlagCapsLock; + if ((_lastModifierFlags & NSEventModifierFlagCapsLock) != + (event.modifierFlags & NSEventModifierFlagCapsLock)) { + [self sendCapsLockTapWithCallback:callback]; + _lastModifierFlags = _lastModifierFlags ^ NSEventModifierFlagCapsLock; + } else { + callback(TRUE); + } } - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { @@ -522,8 +531,8 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); } NSAssert(_lastModifierFlags == (event.modifierFlags & _modifierFlagOfInterestMask), - @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", - _lastModifierFlags, (event.modifierFlags & _modifierFlagOfInterestMask)); + @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", + _lastModifierFlags, (event.modifierFlags & _modifierFlagOfInterestMask)); } - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index ea048fe4b39d5..116c8c41b5788 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -721,9 +721,8 @@ - (void)dealloc { // Out: *LS down & A down, *LS up & A up last_handled = FALSE; - [handler - handleEvent:keyEvent(NSEventTypeKeyDown, 0x20102, @"A", @"A", FALSE, kKeyCodeKeyA) - callback:keyEventCallback]; + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20102, @"A", @"A", FALSE, kKeyCodeKeyA) + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); event = [events firstObject].data; @@ -749,9 +748,8 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler - handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:keyEventCallback]; + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); event = [events firstObject].data; @@ -775,7 +773,6 @@ - (void)dealloc { EXPECT_EQ(last_handled, TRUE); [events removeAllObjects]; - } TEST(FlutterKeyEmbedderHandlerUnittests, ConvertCapsLockEvents) { @@ -855,4 +852,84 @@ - (void)dealloc { [events removeAllObjects]; } +// Press the CapsLock key when CapsLock state is desynchronized +TEST(FlutterKeyEmbedderHandlerUnittests, SynchronizeCapsLockStateOnCapsLock) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; + id keyEventCallback = ^(BOOL handled) { + last_handled = handled; + }; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + // In: CapsLock down + // Out: + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 0u); + EXPECT_EQ(last_handled, TRUE); +} + +// Press the CapsLock key when CapsLock state is desynchronized +TEST(FlutterKeyEmbedderHandlerUnittests, SynchronizeCapsLockStateOnNormalKey) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; + id keyEventCallback = ^(BOOL handled) { + last_handled = handled; + }; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x10100, @"A", @"a", FALSE, kKeyCodeKeyA) + callback:keyEventCallback]; + + EXPECT_EQ([events count], 3u); + + event = events[0].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalCapsLock); + EXPECT_EQ(event->logical, kLogicalCapsLock); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + EXPECT_FALSE([events[0] hasCallback]); + + event = events[1].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalCapsLock); + EXPECT_EQ(event->logical, kLogicalCapsLock); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, true); + EXPECT_FALSE([events[1] hasCallback]); + + event = events[2].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "A"); + EXPECT_EQ(event->synthesized, false); + EXPECT_TRUE([events[2] hasCallback]); + + EXPECT_EQ(last_handled, FALSE); + [[events lastObject] respond:TRUE]; + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; +} + } // namespace flutter::testing From a634fed8e6442dfd3f30beb69acbffb51092cdf7 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 19:25:17 -0800 Subject: [PATCH 25/46] Add special key test --- .../Source/FlutterKeyEmbedderHandler.mm | 3 +- .../FlutterKeyEmbedderHandlerUnittests.mm | 94 ++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 44aa97a178ba8..e736b3ef4057d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -503,7 +503,8 @@ void ps(const char* s) { } - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - printf("#### Event %d keyCode %hx mod %lx ", (int)event.type, event.keyCode, event.modifierFlags); + printf("#### Event %d keyCode 0x%hx mod 0x%lx ", (int)event.type, event.keyCode, + event.modifierFlags); if (event.type != NSEventTypeFlagsChanged) { printf("rep %d cIM %s(", event.isARepeat, [[event charactersIgnoringModifiers] UTF8String]); ps([[event charactersIgnoringModifiers] UTF8String]); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 116c8c41b5788..1f0880a5b643a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -58,14 +58,17 @@ - (void)dealloc { namespace flutter::testing { namespace { -constexpr uint64_t kKeyCodeKeyA = 0; +constexpr uint64_t kKeyCodeKeyA = 0x00; +constexpr uint64_t kKeyCodeKeyW = 0x0d; constexpr uint64_t kKeyCodeShiftLeft = 0x38; constexpr uint64_t kKeyCodeShiftRight = 0x3c; constexpr uint64_t kKeyCodeCapsLock = 0x39; constexpr uint64_t kKeyCodeNumpad1 = 0x53; constexpr uint64_t kKeyCodeF1 = 0x7a; +constexpr uint64_t kKeyCodeAltRight = 0x3d; constexpr uint64_t kPhysicalKeyA = 0x00070004; +constexpr uint64_t kPhysicalKeyW = 0x0007001a; // constexpr uint64_t kPhysicalControlLeft = 0x000700e0; // constexpr uint64_t kPhysicalControlRight = 0x000700e4; constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; @@ -73,9 +76,11 @@ - (void)dealloc { constexpr uint64_t kPhysicalCapsLock = 0x00070039; constexpr uint64_t kPhysicalNumpad1 = 0x00070059; constexpr uint64_t kPhysicalF1 = 0x0007003a; +constexpr uint64_t kPhysicalAltRight = 0x000700e6; // constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; constexpr uint64_t kLogicalKeyA = 0x00000061; +constexpr uint64_t kLogicalKeyW = 0x00000077; // constexpr uint64_t kLogicalControlLeft = 0x00300000105; // constexpr uint64_t kLogicalControlRight = 0x00400000105; constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; @@ -84,6 +89,7 @@ - (void)dealloc { constexpr uint64_t kLogicalNumpad1 = 0x00200000031; constexpr uint64_t kLogicalF1 = 0x00000000801; // constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; +constexpr uint64_t kLogicalAltRight = 0x00400000102; typedef void (^ResponseCallback)(bool handled); @@ -187,6 +193,75 @@ - (void)dealloc { [events removeAllObjects]; } +TEST(FlutterKeyEmbedderHandlerUnittests, NonAsciiCharacters) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x80140, @"", @"", FALSE, kKeyCodeAltRight) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalAltRight); + EXPECT_EQ(event->logical, kLogicalAltRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x80140, @"∑", @"w", FALSE, kKeyCodeKeyW) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyW); + EXPECT_EQ(event->logical, kLogicalKeyW); + EXPECT_STREQ(event->character, "∑"); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeAltRight) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalAltRight); + EXPECT_EQ(event->logical, kLogicalAltRight); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; + + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"w", @"w", FALSE, kKeyCodeKeyW) + callback:^(BOOL handled){ + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyW); + EXPECT_EQ(event->logical, kLogicalKeyW); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + + [events removeAllObjects]; +} + // Press L shift, A, then release L shift then A, on an US keyboard. // // This is special because the characters for the A key will change in this @@ -215,6 +290,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftRight); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -229,6 +305,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalKeyA); EXPECT_STREQ(event->character, "A"); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -243,6 +320,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalKeyA); EXPECT_STREQ(event->character, "A"); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -257,6 +335,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftRight); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -285,6 +364,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalKeyA); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; } @@ -321,6 +401,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalNumpad1); EXPECT_STREQ(event->character, "1"); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -337,6 +418,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalF1); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -352,6 +434,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalKeyA); EXPECT_STREQ(event->character, "a"); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -368,6 +451,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftLeft); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -383,6 +467,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalNumpad1); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -398,6 +483,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalF1); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -413,6 +499,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalKeyA); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -428,6 +515,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftLeft); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; } @@ -456,6 +544,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftLeft); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -471,6 +560,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftRight); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -486,6 +576,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftLeft); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; @@ -500,6 +591,7 @@ - (void)dealloc { EXPECT_EQ(event->logical, kLogicalShiftRight); EXPECT_STREQ(event->character, nullptr); EXPECT_EQ(event->synthesized, false); + [[events lastObject] respond:TRUE]; [events removeAllObjects]; } From 809ff9eb9452d07a442ad3f5e77ee4357065a5ac Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 19:57:05 -0800 Subject: [PATCH 26/46] Timestamp --- .../Source/FlutterKeyEmbedderHandler.mm | 40 ++++++++++--------- .../FlutterKeyEmbedderHandlerUnittests.mm | 28 +++++++++++-- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index e736b3ef4057d..814802ffd9cd2 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -132,9 +132,9 @@ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { } // Returns the timestamp for an event. -static double GetFlutterTimestampFrom(NSEvent* event) { +static double GetFlutterTimestampFrom(NSTimeInterval timestamp) { // Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision. - return event.timestamp * 1000000.0; + return timestamp * 1000000.0; } static NSUInteger computeModifierFlagOfInterestMask() { @@ -227,7 +227,7 @@ @interface FlutterKeyEmbedderHandler () @property(nonatomic) NSMutableDictionary* pendingResponses; -- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags; +- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags timestamp:(NSTimeInterval)timestamp; /** * Update the pressing state. @@ -247,9 +247,11 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event * downCallback will be used as the callback for the down event, which is not * synthesized. */ -- (void)sendCapsLockTapWithCallback:(nullable FlutterKeyHandlerCallback)downCallback; +- (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp + callback:(FlutterKeyHandlerCallback)downCallback; - (void)sendModifierEventForDown:(BOOL)shouldDown + timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode callback:(nullable FlutterKeyHandlerCallback)downCallback; @@ -297,13 +299,13 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent #pragma mark - Private -- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags { +- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags timestamp:(NSTimeInterval)timestamp { const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; const NSUInteger currentInterestedFlags = currentFlags & updatingMask; const NSUInteger lastInterestedFlags = _lastModifierFlags & updatingMask; NSUInteger flagDifference = currentInterestedFlags ^ lastInterestedFlags; if (flagDifference & NSEventModifierFlagCapsLock) { - [self sendCapsLockTapWithCallback:nil]; + [self sendCapsLockTapWithTimestamp:timestamp callback:nil]; flagDifference = flagDifference & ~NSEventModifierFlagCapsLock; } while (true) { @@ -318,7 +320,7 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger) continue; } BOOL shouldDown = (currentInterestedFlags & currentFlag) != 0; - [self sendModifierEventForDown:shouldDown keyCode:[keyCode unsignedShortValue] callback:nil]; + [self sendModifierEventForDown:shouldDown timestamp:timestamp keyCode:[keyCode unsignedShortValue] callback:nil]; } _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentInterestedFlags; } @@ -342,14 +344,15 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event _sendEvent(event, HandleResponse, (__bridge_retained void*)pending); } -- (void)sendCapsLockTapWithCallback:(FlutterKeyHandlerCallback)downCallback { +- (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp + callback:(FlutterKeyHandlerCallback)downCallback { // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on // even taps and odd taps. A CapsLock down or CapsLock up should always be // converted to a down *and* an up, and the up should always be a synthesized // event, since we will never know when the button is released. FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), - .timestamp = 0, // TODO + .timestamp = GetFlutterTimestampFrom(timestamp), .type = kFlutterKeyEventTypeDown, .physical = kCapsLockPhysicalKey, .logical = kCapsLockLogicalKey, @@ -368,6 +371,7 @@ - (void)sendCapsLockTapWithCallback:(FlutterKeyHandlerCallback)downCallback { } - (void)sendModifierEventForDown:(BOOL)shouldDown + timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode callback:(FlutterKeyHandlerCallback)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode); @@ -379,7 +383,7 @@ - (void)sendModifierEventForDown:(BOOL)shouldDown } FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), - .timestamp = 0, // TODO + .timestamp = GetFlutterTimestampFrom(timestamp), .type = shouldDown ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp, .physical = physicalKey, .logical = logicalKey, @@ -397,7 +401,7 @@ - (void)sendModifierEventForDown:(BOOL)shouldDown - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); - [self synchronizeModifiers:event.modifierFlags ignoringFlags:0]; + [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; bool isARepeat = (pressedLogicalKey != nil) || event.isARepeat; @@ -409,7 +413,7 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), - .timestamp = GetFlutterTimestampFrom(event), + .timestamp = GetFlutterTimestampFrom(event.timestamp), .type = isARepeat ? kFlutterKeyEventTypeRepeat : kFlutterKeyEventTypeDown, .physical = physicalKey, .logical = pressedLogicalKey == nil ? logicalKey : [pressedLogicalKey unsignedLongLongValue], @@ -422,7 +426,7 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@", event.keyCode, event.characters, event.charactersIgnoringModifiers); - [self synchronizeModifiers:event.modifierFlags ignoringFlags:0]; + [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; @@ -439,7 +443,7 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callba FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), - .timestamp = GetFlutterTimestampFrom(event), + .timestamp = GetFlutterTimestampFrom(event.timestamp), .type = kFlutterKeyEventTypeUp, .physical = physicalKey, .logical = [pressedLogicalKey unsignedLongLongValue], @@ -450,10 +454,10 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callba } - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock]; + [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock timestamp:event.timestamp]; if ((_lastModifierFlags & NSEventModifierFlagCapsLock) != (event.modifierFlags & NSEventModifierFlagCapsLock)) { - [self sendCapsLockTapWithCallback:callback]; + [self sendCapsLockTapWithTimestamp:event.timestamp callback:callback]; _lastModifierFlags = _lastModifierFlags ^ NSEventModifierFlagCapsLock; } else { callback(TRUE); @@ -469,7 +473,7 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call return [self handleCapsLockEvent:event callback:callback]; } - [self synchronizeModifiers:event.modifierFlags ignoringFlags:targetModifierFlag]; + [self synchronizeModifiers:event.modifierFlags ignoringFlags:targetModifierFlag timestamp:event.timestamp]; NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)]; BOOL lastTargetPressed = pressedLogicalKey != nil; @@ -489,7 +493,7 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call return; } _lastModifierFlags = _lastModifierFlags ^ targetModifierFlag; - [self sendModifierEventForDown:shouldBePressed keyCode:event.keyCode callback:callback]; + [self sendModifierEventForDown:shouldBePressed timestamp:event.timestamp keyCode:event.keyCode callback:callback]; } void ps(const char* s) { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 1f0880a5b643a..a4b1ba07ef423 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -111,6 +111,25 @@ - (void)dealloc { keyCode:keyCode]; } +NSEvent* keyEvent(NSEventType type, + NSTimeInterval timestamp, + NSEventModifierFlags modifierFlags, + NSString* characters, + NSString* charactersIgnoringModifiers, + BOOL isARepeat, + unsigned short keyCode) { + return [NSEvent keyEventWithType:type + location:NSZeroPoint + modifierFlags:modifierFlags + timestamp:timestamp + windowNumber:0 + context:nil + characters:characters + charactersIgnoringModifiers:charactersIgnoringModifiers + isARepeat:isARepeat + keyCode:keyCode]; +} + } // namespace // Test the most basic key events. @@ -130,7 +149,7 @@ - (void)dealloc { }]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 123.0f, 0x100, @"a", @"a", FALSE, 0) callback:^(BOOL handled) { last_handled = handled; }]; @@ -138,6 +157,7 @@ - (void)dealloc { EXPECT_EQ([events count], 1u); event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->timestamp, 123000000.0f); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); EXPECT_STREQ(event->character, "a"); @@ -172,7 +192,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = TRUE; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 124.0f, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -180,6 +200,7 @@ - (void)dealloc { EXPECT_EQ([events count], 1u); event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->timestamp, 124000000.0f); EXPECT_EQ(event->physical, kPhysicalKeyA); EXPECT_EQ(event->logical, kLogicalKeyA); EXPECT_EQ(event->character, nullptr); @@ -279,13 +300,14 @@ - (void)dealloc { }]; [handler - handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftRight) + handleEvent:keyEvent(NSEventTypeFlagsChanged, 123.0f, 0x20104, @"", @"", FALSE, kKeyCodeShiftRight) callback:^(BOOL handled){ }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->timestamp, 123000000.0f); EXPECT_EQ(event->physical, kPhysicalShiftRight); EXPECT_EQ(event->logical, kLogicalShiftRight); EXPECT_STREQ(event->character, nullptr); From 30ded045a14ec08252d385a3ad72f4c75b73dad6 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Fri, 5 Mar 2021 20:08:47 -0800 Subject: [PATCH 27/46] Duplicate down keys --- .../Source/FlutterKeyEmbedderHandler.mm | 32 ++++++-- .../FlutterKeyEmbedderHandlerUnittests.mm | 75 ++++++++++++++++++- 2 files changed, 96 insertions(+), 11 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 814802ffd9cd2..032fa7e804ff2 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -227,7 +227,9 @@ @interface FlutterKeyEmbedderHandler () @property(nonatomic) NSMutableDictionary* pendingResponses; -- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags timestamp:(NSTimeInterval)timestamp; +- (void)synchronizeModifiers:(NSUInteger)currentFlags + ignoringFlags:(NSUInteger)ignoringFlags + timestamp:(NSTimeInterval)timestamp; /** * Update the pressing state. @@ -299,7 +301,9 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent #pragma mark - Private -- (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags timestamp:(NSTimeInterval)timestamp { +- (void)synchronizeModifiers:(NSUInteger)currentFlags + ignoringFlags:(NSUInteger)ignoringFlags + timestamp:(NSTimeInterval)timestamp { const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; const NSUInteger currentInterestedFlags = currentFlags & updatingMask; const NSUInteger lastInterestedFlags = _lastModifierFlags & updatingMask; @@ -320,7 +324,10 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger) continue; } BOOL shouldDown = (currentInterestedFlags & currentFlag) != 0; - [self sendModifierEventForDown:shouldDown timestamp:timestamp keyCode:[keyCode unsignedShortValue] callback:nil]; + [self sendModifierEventForDown:shouldDown + timestamp:timestamp + keyCode:[keyCode unsignedShortValue] + callback:nil]; } _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentInterestedFlags; } @@ -403,8 +410,12 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; + bool isARepeat = event.isARepeat; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; - bool isARepeat = (pressedLogicalKey != nil) || event.isARepeat; + if (pressedLogicalKey != nil && !isARepeat) { + callback(TRUE); + return; + } bool isSynthesized = false; if (pressedLogicalKey == nil) { @@ -454,7 +465,9 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callba } - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock timestamp:event.timestamp]; + [self synchronizeModifiers:event.modifierFlags + ignoringFlags:NSEventModifierFlagCapsLock + timestamp:event.timestamp]; if ((_lastModifierFlags & NSEventModifierFlagCapsLock) != (event.modifierFlags & NSEventModifierFlagCapsLock)) { [self sendCapsLockTapWithTimestamp:event.timestamp callback:callback]; @@ -473,7 +486,9 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call return [self handleCapsLockEvent:event callback:callback]; } - [self synchronizeModifiers:event.modifierFlags ignoringFlags:targetModifierFlag timestamp:event.timestamp]; + [self synchronizeModifiers:event.modifierFlags + ignoringFlags:targetModifierFlag + timestamp:event.timestamp]; NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)]; BOOL lastTargetPressed = pressedLogicalKey != nil; @@ -493,7 +508,10 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call return; } _lastModifierFlags = _lastModifierFlags ^ targetModifierFlag; - [self sendModifierEventForDown:shouldBePressed timestamp:event.timestamp keyCode:event.keyCode callback:callback]; + [self sendModifierEventForDown:shouldBePressed + timestamp:event.timestamp + keyCode:event.keyCode + callback:callback]; } void ps(const char* s) { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index a4b1ba07ef423..3263fcb3a25dc 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -283,6 +283,73 @@ - (void)dealloc { [events removeAllObjects]; } +// In very rare occasions, the up event can be missed. Test that duplicate down events +// in these situations are ignored. +// +// MacOS usually matches down and up events perfectly since it tracks key taps to a window. +// Unmatched events can occur when you hold a key, then Ctrl-clicks desktop to trigger a +// menu, and release key. +TEST(FlutterKeyEmbedderHandlerUnittests, IgnoreDuplicateDownEvent) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; + FlutterKeyEvent* event; + + FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled) { + last_handled = handled; + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, "a"); + EXPECT_EQ(event->synthesized, false); + EXPECT_EQ(last_handled, FALSE); + [[events lastObject] respond:TRUE]; + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled) { + last_handled = handled; + }]; + + EXPECT_EQ([events count], 0u); + EXPECT_EQ(last_handled, TRUE); + + last_handled = FALSE; + [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled) { + last_handled = handled; + }]; + + EXPECT_EQ([events count], 1u); + event = [events lastObject].data; + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, kPhysicalKeyA); + EXPECT_EQ(event->logical, kLogicalKeyA); + EXPECT_STREQ(event->character, nullptr); + EXPECT_EQ(event->synthesized, false); + EXPECT_EQ(last_handled, FALSE); + [[events lastObject] respond:TRUE]; + EXPECT_EQ(last_handled, TRUE); + + [events removeAllObjects]; +} + // Press L shift, A, then release L shift then A, on an US keyboard. // // This is special because the characters for the A key will change in this @@ -299,10 +366,10 @@ - (void)dealloc { userData:user_data]]; }]; - [handler - handleEvent:keyEvent(NSEventTypeFlagsChanged, 123.0f, 0x20104, @"", @"", FALSE, kKeyCodeShiftRight) - callback:^(BOOL handled){ - }]; + [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 123.0f, 0x20104, @"", @"", FALSE, + kKeyCodeShiftRight) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; From 8efcb6e6039fefde51881ad5ca765732fc87de07 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Sun, 7 Mar 2021 22:33:46 -0800 Subject: [PATCH 28/46] Rename and key manager test --- shell/platform/darwin/macos/BUILD.gn | 6 +- .../Source/FlutterIntermediateKeyResponder.mm | 15 ---- .../Source/FlutterKeyChannelHandler.h | 4 +- .../Source/FlutterKeyEmbedderHandler.h | 4 +- .../FlutterKeyEmbedderHandlerUnittests.mm | 11 +-- ...Responder.h => FlutterKeyFinalResponder.h} | 9 +-- ...erKeyHandlerBase.h => FlutterKeyHandler.h} | 2 +- .../framework/Source/FlutterKeyboardManager.h | 16 ++-- .../Source/FlutterKeyboardManager.mm | 28 ++----- .../Source/FlutterKeyboardManagerUnittests.mm | 74 +++++++++++++++++++ .../framework/Source/FlutterTextInputPlugin.h | 4 +- .../Source/FlutterTextInputPlugin.mm | 2 +- .../framework/Source/FlutterViewController.mm | 8 +- .../Source/FlutterViewControllerTest.mm | 28 +------ .../Source/FlutterViewController_Internal.h | 2 +- 15 files changed, 113 insertions(+), 100 deletions(-) delete mode 100644 shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm rename shell/platform/darwin/macos/framework/Source/{FlutterIntermediateKeyResponder.h => FlutterKeyFinalResponder.h} (69%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyHandlerBase.h => FlutterKeyHandler.h} (91%) create mode 100644 shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index b8efa400b8bcc..9404df29d721b 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -71,13 +71,12 @@ source_set("flutter_framework_source") { "framework/Source/FlutterGLCompositor.mm", "framework/Source/FlutterIOSurfaceHolder.h", "framework/Source/FlutterIOSurfaceHolder.mm", - "framework/Source/FlutterIntermediateKeyResponder.h", - "framework/Source/FlutterIntermediateKeyResponder.mm", "framework/Source/FlutterKeyChannelHandler.h", "framework/Source/FlutterKeyChannelHandler.mm", "framework/Source/FlutterKeyEmbedderHandler.h", "framework/Source/FlutterKeyEmbedderHandler.mm", - "framework/Source/FlutterKeyHandlerBase.h", + "framework/Source/FlutterKeyFinalResponder.h", + "framework/Source/FlutterKeyHandler.h", "framework/Source/FlutterKeyboardManager.h", "framework/Source/FlutterKeyboardManager.mm", "framework/Source/FlutterMacOSExternalTexture.h", @@ -166,6 +165,7 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/FlutterGLCompositorUnittests.mm", "framework/Source/FlutterKeyChannelHandlerUnittests.mm", "framework/Source/FlutterKeyEmbedderHandlerUnittests.mm", + "framework/Source/FlutterKeyboardManagerUnittests.mm", "framework/Source/FlutterMetalRendererTest.mm", "framework/Source/FlutterMetalSurfaceManagerTest.mm", "framework/Source/FlutterOpenGLRendererTest.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm deleted file mode 100644 index 910edf94c763d..0000000000000 --- a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" - -@implementation FlutterIntermediateKeyResponder { -} - -#pragma mark - Default key handling methods - -- (BOOL)handleKeyEvent:(NSEvent*)event { - return NO; -} -@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h index bbbd8c713dad0..814e51f12baa4 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" #import #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" -@interface FlutterKeyChannelHandler : NSObject +@interface FlutterKeyChannelHandler : NSObject - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h index afeaef135981f..b161050623c4c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h @@ -4,7 +4,7 @@ #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" #include "flutter/shell/platform/embedder/embedder.h" namespace { @@ -15,7 +15,7 @@ typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */, _Nullable FlutterKeyEventCallback /* callback */, _Nullable _VoidPtr /* user_data */); -@interface FlutterKeyEmbedderHandler : NSObject +@interface FlutterKeyEmbedderHandler : NSObject - (nonnull instancetype)initWithSendEvent:(_Nonnull FlutterSendEmbedderKeyEvent)sendEvent; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm index 3263fcb3a25dc..234203db04991 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm @@ -43,8 +43,9 @@ - (BOOL)hasCallback { } - (void)respond:(BOOL)handled { - NSAssert(_callback != nil, - @"Only call `respond` after checking `hasCallback`."); // Caller's responsibility + NSAssert( + _callback != nil, + @"Improper call to `respond` that does not have a callback."); // Caller's responsibility _callback(handled, _userData); } @@ -69,26 +70,20 @@ - (void)dealloc { constexpr uint64_t kPhysicalKeyA = 0x00070004; constexpr uint64_t kPhysicalKeyW = 0x0007001a; -// constexpr uint64_t kPhysicalControlLeft = 0x000700e0; -// constexpr uint64_t kPhysicalControlRight = 0x000700e4; constexpr uint64_t kPhysicalShiftLeft = 0x000700e1; constexpr uint64_t kPhysicalShiftRight = 0x000700e5; constexpr uint64_t kPhysicalCapsLock = 0x00070039; constexpr uint64_t kPhysicalNumpad1 = 0x00070059; constexpr uint64_t kPhysicalF1 = 0x0007003a; constexpr uint64_t kPhysicalAltRight = 0x000700e6; -// constexpr uint64_t kPhysicalKeyNumLock = 0x00070053; constexpr uint64_t kLogicalKeyA = 0x00000061; constexpr uint64_t kLogicalKeyW = 0x00000077; -// constexpr uint64_t kLogicalControlLeft = 0x00300000105; -// constexpr uint64_t kLogicalControlRight = 0x00400000105; constexpr uint64_t kLogicalShiftLeft = 0x0030000010d; constexpr uint64_t kLogicalShiftRight = 0x0040000010d; constexpr uint64_t kLogicalCapsLock = 0x00000000104; constexpr uint64_t kLogicalNumpad1 = 0x00200000031; constexpr uint64_t kLogicalF1 = 0x00000000801; -// constexpr uint64_t kLogicalKeyNumLock = 0x0000000010a; constexpr uint64_t kLogicalAltRight = 0x00400000102; typedef void (^ResponseCallback)(bool handled); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h similarity index 69% rename from shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h rename to shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h index d1ba77f69780f..9100751cd281c 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h @@ -7,16 +7,15 @@ /* * An interface for a key responder that can declare itself as the final * responder of the event, terminating the event propagation. - * - * It differs from an NSResponder in that it returns a boolean from the - * handleKeyUp and handleKeyDown calls, where true means it has handled the - * given event. */ -@interface FlutterIntermediateKeyResponder : NSObject +@protocol FlutterKeyFinalResponder /* * Informs the receiver that the user has interacted with a key. * + * The return value indicates whether it has handled the given event. + * * Default implementation returns NO. */ +@required - (BOOL)handleKeyEvent:(NSEvent*)event; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h similarity index 91% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h rename to shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h index c77c1a59e5227..8e7ae9cfef693 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h @@ -6,7 +6,7 @@ typedef void (^FlutterKeyHandlerCallback)(BOOL handled); -@protocol FlutterKeyHandlerBase +@protocol FlutterKeyHandler @required - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index 2cb2edfd64cc6..3f1a2f30b6816 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -2,25 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" @interface FlutterKeyboardManager : NSObject - (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner; -- (void)addHandler:(nonnull id)handler; +- (void)addHandler:(nonnull id)handler; -- (void)addAdditionalHandler:(nonnull FlutterIntermediateKeyResponder*)handler; +- (void)addAdditionalHandler:(nonnull id)handler; -- (void)keyDown:(nonnull NSEvent*)event; - -- (void)keyUp:(nonnull NSEvent*)event; - -- (void)flagsChanged:(nonnull NSEvent*)event; +- (void)handleEvent:(nonnull NSEvent*)event; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 93691f0a0aa1b..e4c7646db8719 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -14,7 +14,7 @@ @interface FlutterKeyboardManager () /** * TODO */ -@property(nonatomic) NSMutableArray>* keyHandlers; +@property(nonatomic) NSMutableArray>* keyHandlers; /** * A list of additional responders to keyboard events. @@ -24,9 +24,7 @@ @interface FlutterKeyboardManager () * returning true), the event is not dispatched to later additional responders * or to the nextResponder. */ -@property(nonatomic) NSMutableArray* additionalKeyHandlers; - -- (void)dispatchKeyEvent:(NSEvent*)event; +@property(nonatomic) NSMutableArray>* additionalKeyHandlers; @end @@ -40,16 +38,16 @@ - (nonnull instancetype)initWithOwner:(NSResponder*)weakOwner { return self; } -- (void)addHandler:(nonnull id)handler { +- (void)addHandler:(nonnull id)handler { [_keyHandlers addObject:handler]; } -- (void)addAdditionalHandler:(nonnull FlutterIntermediateKeyResponder*)handler { +- (void)addAdditionalHandler:(nonnull id)handler { [_additionalKeyHandlers addObject:handler]; } - (void)dispatchToAdditionalHandlers:(NSEvent*)event { - for (FlutterIntermediateKeyResponder* responder in _additionalKeyHandlers) { + for (id responder in _additionalKeyHandlers) { if ([responder handleKeyEvent:event]) { return; } @@ -75,7 +73,7 @@ - (void)dispatchToAdditionalHandlers:(NSEvent*)event { } } -- (void)dispatchKeyEvent:(NSEvent*)event { +- (void)handleEvent:(nonnull NSEvent*)event { // Be sure to add a handler in propagateKeyEvent if you allow more event // types here. if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && @@ -95,21 +93,9 @@ - (void)dispatchKeyEvent:(NSEvent*)event { } }; - for (id handler in _keyHandlers) { + for (id handler in _keyHandlers) { [handler handleEvent:event callback:replyCallback]; } } -- (void)keyDown:(nonnull NSEvent*)event { - [self dispatchKeyEvent:event]; -} - -- (void)keyUp:(nonnull NSEvent*)event { - [self dispatchKeyEvent:event]; -} - -- (void)flagsChanged:(NSEvent*)event { - [self dispatchKeyEvent:event]; -} - @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm new file mode 100644 index 0000000000000..1a46ca5ca3094 --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm @@ -0,0 +1,74 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import + +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h" +#import "flutter/testing/testing.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace flutter::testing { + +namespace { + +NSResponder* mockResponder() { + NSResponder* mock = OCMStrictClassMock([NSResponder class]); + OCMStub([mock keyDown:[OCMArg any]]).andDo(nil); + OCMStub([mock keyUp:[OCMArg any]]).andDo(nil); + OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil); + return mock; +} + +typedef void (^KeyCallbackHandler)(FlutterKeyHandlerCallback callback); + +id mockAsyncKeyHandler(KeyCallbackHandler handler) { + id mock = OCMStrictProtocolMock(@protocol(FlutterKeyHandler)); + OCMStub([mock handleEvent:[OCMArg any] callback:[OCMArg any]]) + .andDo((^(NSInvocation* invocation) { + FlutterKeyHandlerCallback callback; + [invocation getArgument:&callback atIndex:3]; + handler(callback); + })); + return mock; +} + +NSEvent* keyDownEvent(unsigned short keyCode) { + return [NSEvent keyEventWithType:NSEventTypeKeyDown + location:NSZeroPoint + modifierFlags:0x100 + timestamp:0 + windowNumber:0 + context:nil + characters:@"" + charactersIgnoringModifiers:@"" + isARepeat:NO + keyCode:keyCode]; +} + +// NSResponder* mockKeyHandler() { +// NSResponder* mock = OCMStrictClassMock([NSResponder class]); +// OCMStub([mock keyDown:[OCMArg any]]).andDo(nil); +// OCMStub([mock keyUp:[OCMArg any]]).andDo(nil); +// OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil); +// return mock; +// } + +} // namespace + +TEST(FlutterKeyboardManagerUnittests, BasicKeyEvent) { + NSResponder* owner = mockResponder(); + FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; + + __block NSMutableArray* callbacks = [NSMutableArray array]; + [manager addHandler:mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { + [callbacks addObject:callback]; + })]; + + [manager handleEvent:keyDownEvent(0x50)]; + + EXPECT_EQ([callbacks count], 1u); +} + +} // namespace flutter::testing diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h index 2f4a5436e74d7..5a9b4b27ce013 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h @@ -6,7 +6,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" /** * A plugin to handle text input. @@ -17,7 +17,7 @@ * This is not an FlutterPlugin since it needs access to FlutterViewController internals, so needs * to be managed differently. */ -@interface FlutterTextInputPlugin : FlutterIntermediateKeyResponder +@interface FlutterTextInputPlugin : NSObject /** * Initializes a text input plugin that coordinates key event handling with |viewController|. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 73fbae8365e60..71cf1768e66f7 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -254,7 +254,7 @@ - (void)updateEditState { } #pragma mark - -#pragma mark FlutterIntermediateKeyResponder +#pragma mark FlutterKeyFinalResponder /** * Handles key down events received from the view controller, responding TRUE if diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index f782f84655030..486e79cc79920 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -11,7 +11,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandlerBase.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h" @@ -574,15 +574,15 @@ - (BOOL)acceptsFirstResponder { } - (void)keyDown:(NSEvent*)event { - [_keyboardManager keyDown:event]; + [_keyboardManager handleEvent:event]; } - (void)keyUp:(NSEvent*)event { - [_keyboardManager keyUp:event]; + [_keyboardManager handleEvent:event]; } - (void)flagsChanged:(NSEvent*)event { - [_keyboardManager flagsChanged:event]; + [_keyboardManager handleEvent:event]; } - (void)mouseEntered:(NSEvent*)event { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm index 1aa8709e75a26..fbf01cfbe7dd5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTest.mm @@ -27,38 +27,16 @@ + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event namespace flutter::testing { -// Returns a mock FlutterViewController that is able to work in environments -// without a real pasteboard. -id mockViewController(NSString* pasteboardString) { - NSString* fixtures = @(testing::GetFixturesPath()); - FlutterDartProject* project = [[FlutterDartProject alloc] - initWithAssetsPath:fixtures - ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]]; - FlutterViewController* viewController = [[FlutterViewController alloc] initWithProject:project]; - - // Mock pasteboard so that this test will work in environments without a - // real pasteboard. - id pasteboardMock = OCMClassMock([NSPasteboard class]); - OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) - [pasteboardMock stringForType:[OCMArg any]]) - .andDo(^(NSInvocation* invocation) { - NSString* returnValue = pasteboardString.length > 0 ? pasteboardString : nil; - [invocation setReturnValue:&returnValue]; - }); - id viewControllerMock = OCMPartialMock(viewController); - OCMStub( // NOLINT(google-objc-avoid-throwing-exception) - [viewControllerMock pasteboard]) - .andReturn(pasteboardMock); - return viewControllerMock; -} +namespace { NSResponder* mockResponder() { - NSResponder* mock = OCMClassMock([NSResponder class]); + NSResponder* mock = OCMStrictClassMock([NSResponder class]); OCMStub([mock keyDown:[OCMArg any]]).andDo(nil); OCMStub([mock keyUp:[OCMArg any]]).andDo(nil); OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil); return mock; } +} // namespace TEST(FlutterViewController, HasStringsWhenPasteboardEmpty) { // Mock FlutterViewController so that it behaves like the pasteboard is empty. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index 0325a195a50c6..f71d89fa928ec 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -4,7 +4,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" #import "flutter/shell/platform/embedder/embedder.h" From 5c6e8fd97cc84f7670dd42d613ed78b998bfa5ac Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 8 Mar 2021 00:20:07 -0800 Subject: [PATCH 29/46] Many manager tests --- .../Source/FlutterKeyboardManagerUnittests.mm | 234 +++++++++++++++--- 1 file changed, 201 insertions(+), 33 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm index 1a46ca5ca3094..6e2d352a8df59 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm @@ -9,19 +9,65 @@ #import "flutter/testing/testing.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" +@interface FlutterKeyboardManagerUnittestsObjC : NSObject +- (bool)nextResponderShouldThrowOnKeyUp; +- (bool)singleAsyncHandler; +- (bool)doubleAsyncHandlers; +@end + namespace flutter::testing { namespace { -NSResponder* mockResponder() { - NSResponder* mock = OCMStrictClassMock([NSResponder class]); - OCMStub([mock keyDown:[OCMArg any]]).andDo(nil); - OCMStub([mock keyUp:[OCMArg any]]).andDo(nil); - OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil); - return mock; +NSEvent* keyDownEvent(unsigned short keyCode) { + return [NSEvent keyEventWithType:NSEventTypeKeyDown + location:NSZeroPoint + modifierFlags:0x100 + timestamp:0 + windowNumber:0 + context:nil + characters:@"" + charactersIgnoringModifiers:@"" + isARepeat:NO + keyCode:keyCode]; +} + +NSEvent* keyUpEvent(unsigned short keyCode) { + return [NSEvent keyEventWithType:NSEventTypeKeyUp + location:NSZeroPoint + modifierFlags:0x100 + timestamp:0 + windowNumber:0 + context:nil + characters:@"" + charactersIgnoringModifiers:@"" + isARepeat:NO + keyCode:keyCode]; +} + +id checkKeyDownEvent(unsigned short keyCode) { + return [OCMArg checkWithBlock:^BOOL(id value) { + if (![value isKindOfClass:[NSEvent class]]) { + return NO; + } + NSEvent* event = value; + return event.keyCode == keyCode; + }]; +} + +NSResponder* mockOwnerWithDownOnlyNext() { + NSResponder* nextResponder = OCMStrictClassMock([NSResponder class]); + OCMStub([nextResponder keyDown:[OCMArg any]]).andDo(nil); + // The nextResponder is a strict mock and hasn't stubbed keyUp. + // An error will be thrown on keyUp. + + NSResponder* owner = OCMStrictClassMock([NSResponder class]); + OCMStub([owner nextResponder]).andReturn(nextResponder); + return owner; } typedef void (^KeyCallbackHandler)(FlutterKeyHandlerCallback callback); +typedef BOOL (^BoolGetter)(); id mockAsyncKeyHandler(KeyCallbackHandler handler) { id mock = OCMStrictProtocolMock(@protocol(FlutterKeyHandler)); @@ -34,41 +80,163 @@ return mock; } -NSEvent* keyDownEvent(unsigned short keyCode) { - return [NSEvent keyEventWithType:NSEventTypeKeyDown - location:NSZeroPoint - modifierFlags:0x100 - timestamp:0 - windowNumber:0 - context:nil - characters:@"" - charactersIgnoringModifiers:@"" - isARepeat:NO - keyCode:keyCode]; +id mockFinalResponder(BoolGetter resultGetter) { + id mock = OCMStrictProtocolMock(@protocol(FlutterKeyFinalResponder)); + OCMStub([mock handleKeyEvent:[OCMArg any]]) + .andDo((^(NSInvocation* invocation) { + BOOL result = resultGetter(); + [invocation setReturnValue:&result]; + })); + return mock; } -// NSResponder* mockKeyHandler() { -// NSResponder* mock = OCMStrictClassMock([NSResponder class]); -// OCMStub([mock keyDown:[OCMArg any]]).andDo(nil); -// OCMStub([mock keyUp:[OCMArg any]]).andDo(nil); -// OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil); -// return mock; -// } +} // namespace + +TEST(FlutterKeyboardManagerUnittests, SingleAsyncHandler) { + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] nextResponderShouldThrowOnKeyUp]); + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singleAsyncHandler]); + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] doubleAsyncHandlers]); +} + +} // namespace flutter::testing -} // namespace +@implementation FlutterKeyboardManagerUnittestsObjC -TEST(FlutterKeyboardManagerUnittests, BasicKeyEvent) { - NSResponder* owner = mockResponder(); +// Verify that the nextResponder returned from mockOwnerWithDownOnlyNext() +// throws exception when keyUp is called. +- (bool)nextResponderShouldThrowOnKeyUp { + NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext(); + @try { + [owner.nextResponder keyUp:flutter::testing::keyUpEvent(0x50)]; + return false; + } @catch (...) { + return true; + } +} + +- (bool)singleAsyncHandler { + NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext(); FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; - __block NSMutableArray* callbacks = [NSMutableArray array]; - [manager addHandler:mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { - [callbacks addObject:callback]; - })]; + __block NSMutableArray* callbacks = + [NSMutableArray array]; + [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { + [callbacks addObject:callback]; + })]; - [manager handleEvent:keyDownEvent(0x50)]; + // Case: The handler reports FALSE + [manager handleEvent:flutter::testing::keyDownEvent(0x50)]; + EXPECT_EQ([callbacks count], 1u); + callbacks[0](FALSE); + OCMVerify([owner.nextResponder keyDown:flutter::testing::checkKeyDownEvent(0x50)]); + [callbacks removeAllObjects]; + // Case: The handler reports TRUE + [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; EXPECT_EQ([callbacks count], 1u); + callbacks[0](TRUE); + // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown. + + return true; } -} // namespace flutter::testing +- (bool)doubleAsyncHandlers { + NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext(); + FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; + + __block NSMutableArray* callbacks1 = + [NSMutableArray array]; + [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { + [callbacks1 addObject:callback]; + })]; + + __block NSMutableArray* callbacks2 = + [NSMutableArray array]; + [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { + [callbacks2 addObject:callback]; + })]; + + // Case: Both handler report TRUE. + [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; + EXPECT_EQ([callbacks1 count], 1u); + EXPECT_EQ([callbacks2 count], 1u); + callbacks1[0](TRUE); + callbacks2[0](TRUE); + EXPECT_EQ([callbacks1 count], 1u); + EXPECT_EQ([callbacks2 count], 1u); + // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown. + [callbacks1 removeAllObjects]; + [callbacks2 removeAllObjects]; + + // Case: One handler reports TRUE. + [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; + EXPECT_EQ([callbacks1 count], 1u); + EXPECT_EQ([callbacks2 count], 1u); + callbacks1[0](FALSE); + callbacks2[0](TRUE); + EXPECT_EQ([callbacks1 count], 1u); + EXPECT_EQ([callbacks2 count], 1u); + // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown. + [callbacks1 removeAllObjects]; + [callbacks2 removeAllObjects]; + + // Case: Both handlers report FALSE. + [manager handleEvent:flutter::testing::keyDownEvent(0x50)]; + EXPECT_EQ([callbacks1 count], 1u); + EXPECT_EQ([callbacks2 count], 1u); + callbacks1[0](FALSE); + callbacks2[0](FALSE); + EXPECT_EQ([callbacks1 count], 1u); + EXPECT_EQ([callbacks2 count], 1u); + OCMVerify([owner.nextResponder keyDown:flutter::testing::checkKeyDownEvent(0x50)]); + [callbacks1 removeAllObjects]; + [callbacks2 removeAllObjects]; + + return true; +} + +- (bool)singleFinalResponder { + NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext(); + FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; + + __block NSMutableArray* callbacks = + [NSMutableArray array]; + [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { + [callbacks addObject:callback]; + })]; + + __block BOOL nextResponse; + [manager addAdditionalHandler:flutter::testing::mockFinalResponder(^() { + return nextResponse; + })]; + + // Case: Handler responds TRUE. The event shouldn't be handled by the final + // responder. + nextResponse = FALSE; + [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; + EXPECT_EQ([callbacks count], 1u); + callbacks[0](TRUE); + // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown. + [callbacks removeAllObjects]; + + // Case: Handler responds FALSE. The final responder returns TRUE. + nextResponse = TRUE; + [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; + EXPECT_EQ([callbacks count], 1u); + callbacks[0](FALSE); + // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown. + [callbacks removeAllObjects]; + + // Case: Handler responds FALSE. The final responder returns FALSE. + nextResponse = FALSE; + [manager handleEvent:flutter::testing::keyDownEvent(0x50)]; + EXPECT_EQ([callbacks count], 1u); + callbacks[0](FALSE); + OCMVerify([owner.nextResponder keyDown:flutter::testing::checkKeyDownEvent(0x50)]); + [callbacks removeAllObjects]; + + return true; +} + + +@end From ef452e02921a70a62e01b0d185c7b1ba3f8f7276 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 8 Mar 2021 00:31:06 -0800 Subject: [PATCH 30/46] Single final responder and empty next responder --- .../Source/FlutterKeyboardManager.mm | 5 ++- .../Source/FlutterKeyboardManagerUnittests.mm | 31 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index e4c7646db8719..a0512b07918be 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -80,13 +80,16 @@ - (void)handleEvent:(nonnull NSEvent*)event { event.type != NSEventTypeFlagsChanged) { return; } + // Having no key handlers require extra logic, but since Flutter adds all + // handlers in hard-code, this is a situation that Flutter will never meet. + NSAssert([_keyHandlers count] >= 0, @"At least one key handler must be added."); __weak __typeof__(self) weakSelf = self; __block int unreplied = [_keyHandlers count]; __block BOOL anyHandled = false; FlutterKeyHandlerCallback replyCallback = ^(BOOL handled) { unreplied -= 1; - NSAssert(unreplied >= 0, @"More key handlers replied than intended."); + NSAssert(unreplied >= 0, @"More key handlers replied than possible."); anyHandled = anyHandled || handled; if (unreplied == 0 && !anyHandled) { [weakSelf dispatchToAdditionalHandlers:event]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm index 6e2d352a8df59..aabfa693dcc99 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm @@ -13,6 +13,8 @@ @interface FlutterKeyboardManagerUnittestsObjC : NSObject - (bool)nextResponderShouldThrowOnKeyUp; - (bool)singleAsyncHandler; - (bool)doubleAsyncHandlers; +- (bool)singleFinalResponder; +- (bool)emptyNextResponder; @end namespace flutter::testing { @@ -92,12 +94,26 @@ id checkKeyDownEvent(unsigned short keyCode) { } // namespace -TEST(FlutterKeyboardManagerUnittests, SingleAsyncHandler) { +TEST(FlutterKeyboardManagerUnittests, NextResponderShouldThrowOnKeyUp) { ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] nextResponderShouldThrowOnKeyUp]); +} + +TEST(FlutterKeyboardManagerUnittests, SingleAsyncHandler) { ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singleAsyncHandler]); +} + +TEST(FlutterKeyboardManagerUnittests, DoubleAsyncHandlers) { ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] doubleAsyncHandlers]); } +TEST(FlutterKeyboardManagerUnittests, SingleFinalResponder) { + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singleFinalResponder]); +} + +TEST(FlutterKeyboardManagerUnittests, EmptyNextResponder) { + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] emptyNextResponder]); +} + } // namespace flutter::testing @implementation FlutterKeyboardManagerUnittestsObjC @@ -238,5 +254,18 @@ - (bool)singleFinalResponder { return true; } +- (bool)emptyNextResponder { + NSResponder* owner = OCMStrictClassMock([NSResponder class]); + OCMStub([owner nextResponder]).andReturn(nil); + + FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; + + [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { + callback(FALSE); + })]; + // Passes if no error is thrown. + return true; +} + @end From d3feb5151c1e66ed7779daa73334f3dd6c310071 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 8 Mar 2021 01:34:33 -0800 Subject: [PATCH 31/46] Docs --- .../Source/FlutterKeyChannelHandler.h | 9 + .../Source/FlutterKeyChannelHandler.mm | 5 +- .../Source/FlutterKeyEmbedderHandler.h | 11 ++ .../Source/FlutterKeyEmbedderHandler.mm | 177 ++++++++++-------- .../Source/FlutterKeyFinalResponder.h | 13 +- .../framework/Source/FlutterKeyHandler.h | 15 +- .../framework/Source/FlutterKeyboardManager.h | 46 +++++ .../Source/FlutterKeyboardManager.mm | 11 +- .../framework/Source/KeyCodeMap_internal.h | 21 ++- 9 files changed, 212 insertions(+), 96 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h index 814e51f12baa4..055a37e20dc17 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h @@ -8,8 +8,17 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" +/** + * A handler of |FlutterKeyboardManager| that handles events by sending the + * raw information through the method channel. + * + * This class corresponds to the RawKeyboard API in the framework. + */ @interface FlutterKeyChannelHandler : NSObject +/** + * Create a handler by specifying the method channel to use. + */ - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm index 21894f96c425c..4aefb18f2308a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm @@ -18,7 +18,10 @@ @interface FlutterKeyChannelHandler () @property(nonatomic) FlutterBasicMessageChannel* channel; /** - * The current state of the keyboard and pressed keys. + * The |NSEvent.modifierFlags| of the last event received. + * + * Used to determine whether a FlagsChanged event should count as a keydown or + * a keyup event. */ @property(nonatomic) uint64_t previouslyPressedFlags; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h index b161050623c4c..77492286d3d14 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h @@ -15,8 +15,19 @@ typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */, _Nullable FlutterKeyEventCallback /* callback */, _Nullable _VoidPtr /* user_data */); +/** + * A handler of |FlutterKeyboardManager| that handles events by sending the + * converted events through the embedder API. + * + * This class corresponds to the HardwareKeyboard API in the framework. + */ @interface FlutterKeyEmbedderHandler : NSObject +/** + * Create a handler by specifying the function to send converted events to. + * + * The |sendEvent| is typically engine's sendKeyEvent. + */ - (nonnull instancetype)initWithSendEvent:(_Nonnull FlutterSendEmbedderKeyEvent)sendEvent; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index 032fa7e804ff2..ff8dcc850fed2 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -10,26 +10,23 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/embedder/embedder.h" -// Values of `charactersIgnoringModifiers` that must not be directly converted to -// a logical key value, but should look up `keyCodeToLogical` by `keyCode`. -// This is because each of these of character codes is mapped from multiple -// logical keys, usually because of numpads. -// static const NSSet* kAmbiguousCharacters = @{ -// } - namespace { -// Isolate the least significant 1-bit. -// -// For example, -// -// * lowestSetBit(0x1010) returns 0x10. -// * lowestSetBit(0) returns 0. +/** + * Isolate the least significant 1-bit. + * + * For example, + * + * * lowestSetBit(0x1010) returns 0x10. + * * lowestSetBit(0) returns 0. + */ static NSUInteger lowestSetBit(NSUInteger bitmask) { return bitmask & -bitmask; } -// Whether a string represents a control character. +/** + * Whether a string represents a control character. + */ static bool IsControlCharacter(NSUInteger length, NSString* label) { if (length > 1) { return false; @@ -38,7 +35,9 @@ static bool IsControlCharacter(NSUInteger length, NSString* label) { return (codeUnit <= 0x1f && codeUnit >= 0x00) || (codeUnit >= 0x7f && codeUnit <= 0x9f); } -// Whether a string represents an unprintable key. +/** + * Whether a string represents an unprintable key. + */ static bool IsUnprintableKey(NSUInteger length, NSString* label) { if (length > 1) { return false; @@ -47,22 +46,16 @@ static bool IsUnprintableKey(NSUInteger length, NSString* label) { return codeUnit >= 0xF700 && codeUnit <= 0xF8FF; } -// Returns a key code composited by a base key and a plane. +/** + * Returns a key code composited by a base key and a plane. + */ static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) { return plane | (baseKey & kValueMask); } -// Find the modifier flag for a physical key by looking up `modifierFlags`. -// -// Returns 0 if not found. -// static uint64_t GetModifierFlagForKey(uint64_t physicalKey) { -// NSNumber* modifierFlag = [keyCodeToModifierFlag objectForKey:@(physicalKey)]; -// if (modifierFlag == nil) -// return 0; -// return modifierFlag.unsignedLongLongValue; -// } - -// Returns the physical key for a key code. +/** + * Returns the physical key for a key code. + */ static uint64_t GetPhysicalKeyForKeyCode(unsigned short keyCode) { NSNumber* physicalKeyKey = [keyCodeToPhysicalKey objectForKey:@(keyCode)]; if (physicalKeyKey == nil) @@ -70,7 +63,9 @@ static uint64_t GetPhysicalKeyForKeyCode(unsigned short keyCode) { return physicalKeyKey.unsignedLongLongValue; } -// Returns the logical key for a modifier physical key. +/** + * Returns the logical key for a modifier physical key. + */ static uint64_t GetLogicalKeyForModifier(unsigned short keyCode, uint64_t hidCode) { NSNumber* fromKeyCode = [keyCodeToLogicalKey objectForKey:@(keyCode)]; if (fromKeyCode != nil) @@ -78,9 +73,11 @@ static uint64_t GetLogicalKeyForModifier(unsigned short keyCode, uint64_t hidCod return KeyOfPlane(hidCode, kHidPlane); } -// Returns the logical key of a KeyUp or KeyDown event. -// -// For FlagsChanged event, use GetLogicalKeyForModifier. +/** + * Returns the logical key of a KeyUp or KeyDown event. + * + * For FlagsChanged event, use GetLogicalKeyForModifier. + */ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { // Look to see if the keyCode can be mapped from keycode. NSNumber* fromKeyCode = [keyCodeToLogicalKey objectForKey:@(event.keyCode)]; @@ -131,12 +128,20 @@ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { return KeyOfPlane(event.keyCode, kMacosPlane | kAutogeneratedMask); } -// Returns the timestamp for an event. +/** + * Converts NSEvent.timestamp to the timestamp for Flutter. + */ static double GetFlutterTimestampFrom(NSTimeInterval timestamp) { // Timestamp in microseconds. The event.timestamp is in seconds with sub-ms precision. return timestamp * 1000000.0; } +/** + * Compute |modifierFlagOfInterestMask| out of |keyCodeToModifierFlag|. + * + * This equals to the bitwise-or of all values of |keyCodeToModifierFlag| as well as + * NSEventModifierFlagCapsLock. + */ static NSUInteger computeModifierFlagOfInterestMask() { __block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock; [keyCodeToModifierFlag @@ -146,9 +151,18 @@ static NSUInteger computeModifierFlagOfInterestMask() { return modifierFlagOfInterestMask; } +/** + * The handler sent to the embedder's SendKeyEvent. + * + * The |user_data| should actually be a |FlutterKeyPendingResponse| that has + * been retained once with |__bridge_retained|. This function calls + * |FlutterKeyPendingResponse.handler|'s |handleResponse| using |handled|. + */ void HandleResponse(bool handled, void* user_data); -// Converts NSEvent.characters to a C-string for FlutterKeyEvent. +/* + * Converts NSEvent.characters to a C-string for FlutterKeyEvent. + */ const char* getEventString(NSString* characters) { if ([characters length] == 0) { return nullptr; @@ -174,7 +188,9 @@ static NSUInteger computeModifierFlagOfInterestMask() { * * FlutterEngineSendKeyEvent only supports a global function and a pointer. * This class is used for the global function, HandleResponse, to convert a - * pointer into a method call, FlutterKeyEmbedderHandler.handleResponse. + * pointer into a method call, FlutterKeyEmbedderHandler.handleResponse, + * essentially binding |FlutterKeyPendingResponse.handleResponse| with a + * |FlutterKeyPendingResponse| instance. */ @interface FlutterKeyPendingResponse : NSObject @@ -200,7 +216,9 @@ - (instancetype)initWithHandler:(FlutterKeyEmbedderHandler*)handler @interface FlutterKeyEmbedderHandler () /** + * The function to send converted events to. * + * Set by the initializer. */ @property(nonatomic, copy) FlutterSendEmbedderKeyEvent sendEvent; @@ -208,25 +226,48 @@ @interface FlutterKeyEmbedderHandler () * A map of presessd keys. * * The keys of the dictionary are physical keys, while the values are the logical keys - * on pressing. + * of the key down event. */ @property(nonatomic) NSMutableDictionary* pressingRecords; -@property(nonatomic) NSUInteger lastModifierFlags; - /** * A constant mask for NSEvent.modifierFlags that Flutter tries to keep * synchronized on. * - * It equals to the sum of all values of keyCodeToModifierFlag as well as - * NSEventModifierFlagCapsLock. + * It is computed by computeModifierFlagOfInterestMask. */ @property(nonatomic) NSUInteger modifierFlagOfInterestMask; +/** + * The |NSEvent.modifierFlags| of the last received key event after masking + * with |modifierFlagOfInterestMask|. + * + * This should be kept synchronized with the corresponding keys of + * |pressingRecords|. This is used by |synchronizeModifiers| to quickly find + * out modifier keys that are desynchronized. + */ +@property(nonatomic) NSUInteger lastModifierFlags; + +/** + * A self-incrementing ID used to label key events sent to the framework. + */ @property(nonatomic) uint64_t responseId; +/** + * 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. + */ @property(nonatomic) NSMutableDictionary* pendingResponses; +/** + * 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|. + */ - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags timestamp:(NSTimeInterval)timestamp; @@ -239,6 +280,9 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags */ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; +/** + * Send an event to the framework, expecting its response. + */ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event callback:(nonnull FlutterKeyHandlerCallback)callback; @@ -252,10 +296,15 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp callback:(FlutterKeyHandlerCallback)downCallback; -- (void)sendModifierEventForDown:(BOOL)shouldDown +/** + * Send a key event for a modifier key. + * + * If callback is nil, then the event is synthesized. + */ +- (void)sendModifierEventOfType:(BOOL)shouldDown timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode - callback:(nullable FlutterKeyHandlerCallback)downCallback; + callback:(nullable FlutterKeyHandlerCallback)cCallback; /** * Processes a down event. @@ -280,6 +329,9 @@ - (void)handleCapsLockEvent:(nonnull NSEvent*)event - (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; +/** + * Processes the response from a framework. + */ - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; @end @@ -305,9 +357,9 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags ignoringFlags:(NSUInteger)ignoringFlags timestamp:(NSTimeInterval)timestamp { const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; - const NSUInteger currentInterestedFlags = currentFlags & updatingMask; - const NSUInteger lastInterestedFlags = _lastModifierFlags & updatingMask; - NSUInteger flagDifference = currentInterestedFlags ^ lastInterestedFlags; + const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask; + const NSUInteger lastFlagsOfInterest = _lastModifierFlags & updatingMask; + NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest; if (flagDifference & NSEventModifierFlagCapsLock) { [self sendCapsLockTapWithTimestamp:timestamp callback:nil]; flagDifference = flagDifference & ~NSEventModifierFlagCapsLock; @@ -323,13 +375,13 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags if (keyCode == nil) { continue; } - BOOL shouldDown = (currentInterestedFlags & currentFlag) != 0; - [self sendModifierEventForDown:shouldDown + BOOL shouldDown = (currentFlagsOfInterest & currentFlag) != 0; + [self sendModifierEventOfType:shouldDown timestamp:timestamp keyCode:[keyCode unsignedShortValue] callback:nil]; } - _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentInterestedFlags; + _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentFlagsOfInterest; } - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { @@ -377,7 +429,7 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp _sendEvent(flutterEvent, nullptr, nullptr); } -- (void)sendModifierEventForDown:(BOOL)shouldDown +- (void)sendModifierEventOfType:(BOOL)shouldDown timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode callback:(FlutterKeyHandlerCallback)callback { @@ -500,42 +552,20 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call lastTargetPressed ? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]] : @"empty"); - BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0; - printf("Key %hx lastP %d shouldP %d\n", event.keyCode, lastTargetPressed, shouldBePressed); + BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0; if (lastTargetPressed == shouldBePressed) { callback(TRUE); return; } _lastModifierFlags = _lastModifierFlags ^ targetModifierFlag; - [self sendModifierEventForDown:shouldBePressed + [self sendModifierEventOfType:shouldBePressed timestamp:event.timestamp keyCode:event.keyCode callback:callback]; } -void ps(const char* s) { - int i = 0; - while (s[i] != 0) { - printf("%02hhx", s[i]); - i++; - } - if (i == 0) - printf("00"); -} - - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { - printf("#### Event %d keyCode 0x%hx mod 0x%lx ", (int)event.type, event.keyCode, - event.modifierFlags); - if (event.type != NSEventTypeFlagsChanged) { - printf("rep %d cIM %s(", event.isARepeat, [[event charactersIgnoringModifiers] UTF8String]); - ps([[event charactersIgnoringModifiers] UTF8String]); - printf(") c %s(", [[event characters] UTF8String]); - ps([[event characters] UTF8String]); - printf(")\n"); - } else { - printf("\n"); - } // The conversion algorithm relies on a non-nil callback to properly compute // `synthesized`. If someday callback is allowed to be nil, make a dummy empty // callback instead. @@ -559,10 +589,8 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback } - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { - printf("HR %d %llu\n", handled, responseId); FlutterKeyHandlerCallback callback = _pendingResponses[@(responseId)]; callback(handled); - printf("after\n"); [_pendingResponses removeObjectForKey:@(responseId)]; } @@ -570,7 +598,6 @@ - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { namespace { void HandleResponse(bool handled, void* user_data) { - printf("Resp %d %0llx\n", handled, (uint64_t)user_data); // The `__bridge_transfer` here is matched by `__bridge_retained` in sendPrimaryFlutterEvent. FlutterKeyPendingResponse* pending = (__bridge_transfer FlutterKeyPendingResponse*)user_data; [pending.handler handleResponse:handled forId:pending.responseId]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h index 9100751cd281c..7265ce776643f 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h @@ -4,12 +4,15 @@ #import -/* - * An interface for a key responder that can declare itself as the final - * responder of the event, terminating the event propagation. +/** + * An interface for a responder that can process a key event and decides whether + * to handle an event synchronously. + * + * To use this class, add it to a |FlutterKeyboardManager| with + * |addAdditionalHandler|. */ @protocol FlutterKeyFinalResponder -/* +/** * Informs the receiver that the user has interacted with a key. * * The return value indicates whether it has handled the given event. @@ -17,5 +20,5 @@ * Default implementation returns NO. */ @required -- (BOOL)handleKeyEvent:(NSEvent*)event; +- (BOOL)handleKeyEvent:(nonnull NSEvent*)event; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h index 8e7ae9cfef693..cdc818e239e94 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h @@ -6,9 +6,22 @@ typedef void (^FlutterKeyHandlerCallback)(BOOL handled); +/** + * An interface for a responder that can process a key event and decides whether + * to handle an event asynchronously. + * + * To use this class, add it to a |FlutterKeyboardManager| with |addHandler|. + */ @protocol FlutterKeyHandler +/** + * Process the event. + * + * The |callback| should be called with a value that indicates whether the + * handler has handled the given event. The |callback| must be called exactly + * once, and can be called before the return of this method, or after. + */ @required -- (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback; +- (void)handleEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index 3f1a2f30b6816..e5fa685daebe6 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -9,14 +9,60 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" +/** + * A hub that manages how key events are dispatched to various handlers, and + * whether the event is propagated to the next responder. + * + * This class can be added with one or more handlers, as well as zero or more + * additional handlers. + * + * An event that is received by |handleEvent| is first dispatched to *all* + * handlers. Each handler responds ascynchronously with a boolean, indicating + * whether it handles the event. + * + * An event that is not handled by any handlers, is then passed to to the first + * added additional handlers, which responds synchronously with a boolean, + * indicating whether it handles the event. If not, the event is passed to the + * next additional responder, and so on. + * + * If all handlers and additional handlers respond with not to handle the + * event, the event is then passed to the owner's |nextResponder| if not nil, + * on method |keyDown|, |keyUp|, or |flagsChanged|, depending on the event's + * type. If the |nextResponder| is nil, then the event will be propagated no + * further. + * + * It's not supported to prevent any handlers from receiving the events, because + * in reality this class will only support 2 hardcoded delegates (channel and + * embedder), with the only purpose to support the legacy API (channel) during + * the deprecation window, after which the channel handler should be removed. + */ @interface FlutterKeyboardManager : NSObject +/** + * Create a manager by specifying the owner. + * + * The owner should be an object that handles the lifecycle of this instance. + * The |owner.nextResponder| can be nil, but if it isn't, it will be where the + * key events are propagated to, if no handlers or additional handlers handles + * the event. The owner is typically a |FlutterViewController|. + */ - (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner; +/** + * Add a handler, which asynchronously decides whether to handle an event. + */ - (void)addHandler:(nonnull id)handler; +/** + * Add an additional handler, which synchronously decides whether to handle an + * event. + */ - (void)addAdditionalHandler:(nonnull id)handler; +/** + * Dispatch a key event to all handlers and additional handlers, and possibly + * the next responder afterwards. + */ - (void)handleEvent:(nonnull NSEvent*)event; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index a0512b07918be..14879b7864f60 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -7,22 +7,17 @@ @interface FlutterKeyboardManager () /** - * TODO + * The owner set by initWithOwner. */ @property(nonatomic, weak) NSResponder* owner; /** - * TODO + * The handlers added by addHandler. */ @property(nonatomic) NSMutableArray>* keyHandlers; /** - * A list of additional responders to keyboard events. - * - * Keyboard events received by FlutterViewController are first dispatched to - * each additional responder in order. If any of them handle the event (by - * returning true), the event is not dispatched to later additional responders - * or to the nextResponder. + * The additional handlers added by addAdditionalHandler. */ @property(nonatomic) NSMutableArray>* additionalKeyHandlers; diff --git a/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h b/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h index ad83b42f1470e..0e523155559b0 100644 --- a/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h +++ b/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h @@ -3,7 +3,7 @@ // found in the LICENSE file. /** - * Maps macOS-specific key code values representing [PhysicalKeyboardKey]. + * Maps macOS-specific key code values representing |PhysicalKeyboardKey|. * * MacOS doesn't provide a scan code, but a virtual keycode to represent a physical key. */ @@ -13,7 +13,7 @@ extern const NSDictionary* keyCodeToPhysicalKey; * A map from macOS key codes to Flutter's logical key values. * * This is used to derive logical keys that can't or shouldn't be derived from - * `charactersIgnoringModifiers`. + * |charactersIgnoringModifiers|. */ extern const NSDictionary* keyCodeToLogicalKey; @@ -33,14 +33,17 @@ extern const uint64_t kSynonymMask; static const uint64_t kMacosPlane = 0x00500000000; /** - * Map the physical key code of a key to its corresponding bitmask of - * NSEventModifierFlags. + * Map |NSEvent.keyCode| to its corresponding bitmask of NSEventModifierFlags. * * This does not include CapsLock, for it is handled specially. */ extern const NSDictionary* keyCodeToModifierFlag; /** + * Map a bit of bitmask of NSEventModifierFlags to its corresponding + * |NSEvent.keyCode|. + * + * This does not include CapsLock, for it is handled specially. */ extern const NSDictionary* modifierFlagToKeyCode; @@ -54,8 +57,14 @@ extern const uint64_t kCapsLockPhysicalKey; */ extern const uint64_t kCapsLockLogicalKey; -// The following constants, excluded from -// NSEventModifierFlagDeviceIndependentFlagsMask, are derived from experiments. +/** + * Bits in |NSEvent.modifierFlags| indicating whether a modifier key is pressed. + * + * These constants are not written in the official documentation, but derived + * from experiments. This is currently the only way to know whether a one-side + * modifier key (such as ShiftLeft) is pressed, instead of the general combined + * modifier state (such as Shift). + */ typedef enum { kModifierFlagControlLeft = 0x1, kModifierFlagShiftLeft = 0x2, From 30b1e76698acb19685a9c7490b29cdbf99c7f679 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 8 Mar 2021 01:40:02 -0800 Subject: [PATCH 32/46] Fix format and remove redundant file --- .../darwin/ios/keycodes/keyboard_map_ios.h | 202 ------------------ .../Source/FlutterKeyEmbedderHandler.mm | 24 +-- .../Source/FlutterKeyboardManagerUnittests.mm | 14 +- 3 files changed, 18 insertions(+), 222 deletions(-) delete mode 100644 shell/platform/darwin/ios/keycodes/keyboard_map_ios.h diff --git a/shell/platform/darwin/ios/keycodes/keyboard_map_ios.h b/shell/platform/darwin/ios/keycodes/keyboard_map_ios.h deleted file mode 100644 index 37e993ebee88e..0000000000000 --- a/shell/platform/darwin/ios/keycodes/keyboard_map_ios.h +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include - -// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT -// This file is generated by flutter/flutter@dev/tools/gen_keycodes/bin/gen_keycodes.dart and -// should not be edited directly. -// -// Edit the template dev/tools/gen_keycodes/data/keyboard_map_ios_cc.tmpl instead. -// See dev/tools/gen_keycodes/README.md for more information. - -// Maps macOS-specific key code values representing [PhysicalKeyboardKey]. -// -// iOS doesn't provide a scan code, but a virtual keycode to represent a physical key. -const std::map g_ios_to_physical_key = { - { 0x00000000, 0x00070000 }, // usbReserved - { 0x00000001, 0x00070001 }, // usbErrorRollOver - { 0x00000002, 0x00070002 }, // usbPostFail - { 0x00000003, 0x00070003 }, // usbErrorUndefined - { 0x00000004, 0x00070004 }, // keyA - { 0x00000005, 0x00070005 }, // keyB - { 0x00000006, 0x00070006 }, // keyC - { 0x00000007, 0x00070007 }, // keyD - { 0x00000008, 0x00070008 }, // keyE - { 0x00000009, 0x00070009 }, // keyF - { 0x0000000a, 0x0007000a }, // keyG - { 0x0000000b, 0x0007000b }, // keyH - { 0x0000000c, 0x0007000c }, // keyI - { 0x0000000d, 0x0007000d }, // keyJ - { 0x0000000e, 0x0007000e }, // keyK - { 0x0000000f, 0x0007000f }, // keyL - { 0x00000010, 0x00070010 }, // keyM - { 0x00000011, 0x00070011 }, // keyN - { 0x00000012, 0x00070012 }, // keyO - { 0x00000013, 0x00070013 }, // keyP - { 0x00000014, 0x00070014 }, // keyQ - { 0x00000015, 0x00070015 }, // keyR - { 0x00000016, 0x00070016 }, // keyS - { 0x00000017, 0x00070017 }, // keyT - { 0x00000018, 0x00070018 }, // keyU - { 0x00000019, 0x00070019 }, // keyV - { 0x0000001a, 0x0007001a }, // keyW - { 0x0000001b, 0x0007001b }, // keyX - { 0x0000001c, 0x0007001c }, // keyY - { 0x0000001d, 0x0007001d }, // keyZ - { 0x0000001e, 0x0007001e }, // digit1 - { 0x0000001f, 0x0007001f }, // digit2 - { 0x00000020, 0x00070020 }, // digit3 - { 0x00000021, 0x00070021 }, // digit4 - { 0x00000022, 0x00070022 }, // digit5 - { 0x00000023, 0x00070023 }, // digit6 - { 0x00000024, 0x00070024 }, // digit7 - { 0x00000025, 0x00070025 }, // digit8 - { 0x00000026, 0x00070026 }, // digit9 - { 0x00000027, 0x00070027 }, // digit0 - { 0x00000028, 0x00070028 }, // enter - { 0x00000029, 0x00070029 }, // escape - { 0x0000002a, 0x0007002a }, // backspace - { 0x0000002b, 0x0007002b }, // tab - { 0x0000002c, 0x0007002c }, // space - { 0x0000002d, 0x0007002d }, // minus - { 0x0000002e, 0x0007002e }, // equal - { 0x0000002f, 0x0007002f }, // bracketLeft - { 0x00000030, 0x00070030 }, // bracketRight - { 0x00000031, 0x00070031 }, // backslash - { 0x00000033, 0x00070033 }, // semicolon - { 0x00000034, 0x00070034 }, // quote - { 0x00000035, 0x00070035 }, // backquote - { 0x00000036, 0x00070036 }, // comma - { 0x00000037, 0x00070037 }, // period - { 0x00000038, 0x00070038 }, // slash - { 0x00000039, 0x00070039 }, // capsLock - { 0x0000003a, 0x0007003a }, // f1 - { 0x0000003b, 0x0007003b }, // f2 - { 0x0000003c, 0x0007003c }, // f3 - { 0x0000003d, 0x0007003d }, // f4 - { 0x0000003e, 0x0007003e }, // f5 - { 0x0000003f, 0x0007003f }, // f6 - { 0x00000040, 0x00070040 }, // f7 - { 0x00000041, 0x00070041 }, // f8 - { 0x00000042, 0x00070042 }, // f9 - { 0x00000043, 0x00070043 }, // f10 - { 0x00000044, 0x00070044 }, // f11 - { 0x00000045, 0x00070045 }, // f12 - { 0x00000046, 0x00070046 }, // printScreen - { 0x00000047, 0x00070047 }, // scrollLock - { 0x00000048, 0x00070048 }, // pause - { 0x00000049, 0x00070049 }, // insert - { 0x0000004a, 0x0007004a }, // home - { 0x0000004b, 0x0007004b }, // pageUp - { 0x0000004c, 0x0007004c }, // delete - { 0x0000004d, 0x0007004d }, // end - { 0x0000004e, 0x0007004e }, // pageDown - { 0x0000004f, 0x0007004f }, // arrowRight - { 0x00000050, 0x00070050 }, // arrowLeft - { 0x00000051, 0x00070051 }, // arrowDown - { 0x00000052, 0x00070052 }, // arrowUp - { 0x00000053, 0x00070053 }, // numLock - { 0x00000054, 0x00070054 }, // numpadDivide - { 0x00000055, 0x00070055 }, // numpadMultiply - { 0x00000056, 0x00070056 }, // numpadSubtract - { 0x00000057, 0x00070057 }, // numpadAdd - { 0x00000058, 0x00070058 }, // numpadEnter - { 0x00000059, 0x00070059 }, // numpad1 - { 0x0000005a, 0x0007005a }, // numpad2 - { 0x0000005b, 0x0007005b }, // numpad3 - { 0x0000005c, 0x0007005c }, // numpad4 - { 0x0000005d, 0x0007005d }, // numpad5 - { 0x0000005e, 0x0007005e }, // numpad6 - { 0x0000005f, 0x0007005f }, // numpad7 - { 0x00000060, 0x00070060 }, // numpad8 - { 0x00000061, 0x00070061 }, // numpad9 - { 0x00000062, 0x00070062 }, // numpad0 - { 0x00000063, 0x00070063 }, // numpadDecimal - { 0x00000064, 0x00070064 }, // intlBackslash - { 0x00000065, 0x00070065 }, // contextMenu - { 0x00000066, 0x00070066 }, // power - { 0x00000067, 0x00070067 }, // numpadEqual - { 0x00000068, 0x00070068 }, // f13 - { 0x00000069, 0x00070069 }, // f14 - { 0x0000006a, 0x0007006a }, // f15 - { 0x0000006b, 0x0007006b }, // f16 - { 0x0000006c, 0x0007006c }, // f17 - { 0x0000006d, 0x0007006d }, // f18 - { 0x0000006e, 0x0007006e }, // f19 - { 0x0000006f, 0x0007006f }, // f20 - { 0x00000070, 0x00070070 }, // f21 - { 0x00000071, 0x00070071 }, // f22 - { 0x00000072, 0x00070072 }, // f23 - { 0x00000073, 0x00070073 }, // f24 - { 0x00000074, 0x00070074 }, // open - { 0x00000075, 0x00070075 }, // help - { 0x00000077, 0x00070077 }, // select - { 0x00000079, 0x00070079 }, // again - { 0x0000007a, 0x0007007a }, // undo - { 0x0000007b, 0x0007007b }, // cut - { 0x0000007c, 0x0007007c }, // copy - { 0x0000007d, 0x0007007d }, // paste - { 0x0000007e, 0x0007007e }, // find - { 0x0000007f, 0x0007007f }, // audioVolumeMute - { 0x00000080, 0x00070080 }, // audioVolumeUp - { 0x00000081, 0x00070081 }, // audioVolumeDown - { 0x00000085, 0x00070085 }, // numpadComma - { 0x00000087, 0x00070087 }, // intlRo - { 0x00000088, 0x00070088 }, // kanaMode - { 0x00000089, 0x00070089 }, // intlYen - { 0x0000008a, 0x0007008a }, // convert - { 0x0000008b, 0x0007008b }, // nonConvert - { 0x00000090, 0x00070090 }, // lang1 - { 0x00000091, 0x00070091 }, // lang2 - { 0x00000092, 0x00070092 }, // lang3 - { 0x00000093, 0x00070093 }, // lang4 - { 0x00000094, 0x00070094 }, // lang5 - { 0x0000009b, 0x0007009b }, // abort - { 0x000000a3, 0x000700a3 }, // props - { 0x000000b6, 0x000700b6 }, // numpadParenLeft - { 0x000000b7, 0x000700b7 }, // numpadParenRight - { 0x000000bb, 0x000700bb }, // numpadBackspace - { 0x000000d0, 0x000700d0 }, // numpadMemoryStore - { 0x000000d1, 0x000700d1 }, // numpadMemoryRecall - { 0x000000d2, 0x000700d2 }, // numpadMemoryClear - { 0x000000d3, 0x000700d3 }, // numpadMemoryAdd - { 0x000000d4, 0x000700d4 }, // numpadMemorySubtract - { 0x000000d7, 0x000700d7 }, // numpadSignChange - { 0x000000d8, 0x000700d8 }, // numpadClear - { 0x000000d9, 0x000700d9 }, // numpadClearEntry - { 0x000000e0, 0x000700e0 }, // controlLeft - { 0x000000e1, 0x000700e1 }, // shiftLeft - { 0x000000e2, 0x000700e2 }, // altLeft - { 0x000000e3, 0x000700e3 }, // metaLeft - { 0x000000e4, 0x000700e4 }, // controlRight - { 0x000000e5, 0x000700e5 }, // shiftRight - { 0x000000e6, 0x000700e6 }, // altRight - { 0x000000e7, 0x000700e7 }, // metaRight -}; - -// A map of iOS key codes which have printable representations, but appear -// on the number pad. Used to provide different key objects for keys like -// KEY_EQUALS and NUMPAD_EQUALS. -const std::map g_ios_numpad_map = { - { 0x00000054, 0x0100070054 }, // numpadDivide - { 0x00000055, 0x0100070055 }, // numpadMultiply - { 0x00000056, 0x0100070056 }, // numpadSubtract - { 0x00000057, 0x0100070057 }, // numpadAdd - { 0x00000059, 0x0100070059 }, // numpad1 - { 0x0000005a, 0x010007005a }, // numpad2 - { 0x0000005b, 0x010007005b }, // numpad3 - { 0x0000005c, 0x010007005c }, // numpad4 - { 0x0000005d, 0x010007005d }, // numpad5 - { 0x0000005e, 0x010007005e }, // numpad6 - { 0x0000005f, 0x010007005f }, // numpad7 - { 0x00000060, 0x0100070060 }, // numpad8 - { 0x00000061, 0x0100070061 }, // numpad9 - { 0x00000062, 0x0100070062 }, // numpad0 - { 0x00000063, 0x0100070063 }, // numpadDecimal - { 0x00000067, 0x0100070067 }, // numpadEqual - { 0x00000085, 0x0100070085 }, // numpadComma - { 0x000000b6, 0x01000700b6 }, // numpadParenLeft - { 0x000000b7, 0x01000700b7 }, // numpadParenRight -}; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm index ff8dcc850fed2..c0a79b4127a6b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm @@ -302,9 +302,9 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp * If callback is nil, then the event is synthesized. */ - (void)sendModifierEventOfType:(BOOL)shouldDown - timestamp:(NSTimeInterval)timestamp - keyCode:(unsigned short)keyCode - callback:(nullable FlutterKeyHandlerCallback)cCallback; + timestamp:(NSTimeInterval)timestamp + keyCode:(unsigned short)keyCode + callback:(nullable FlutterKeyHandlerCallback)cCallback; /** * Processes a down event. @@ -377,9 +377,9 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags } BOOL shouldDown = (currentFlagsOfInterest & currentFlag) != 0; [self sendModifierEventOfType:shouldDown - timestamp:timestamp - keyCode:[keyCode unsignedShortValue] - callback:nil]; + timestamp:timestamp + keyCode:[keyCode unsignedShortValue] + callback:nil]; } _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentFlagsOfInterest; } @@ -430,9 +430,9 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp } - (void)sendModifierEventOfType:(BOOL)shouldDown - timestamp:(NSTimeInterval)timestamp - keyCode:(unsigned short)keyCode - callback:(FlutterKeyHandlerCallback)callback { + timestamp:(NSTimeInterval)timestamp + keyCode:(unsigned short)keyCode + callback:(FlutterKeyHandlerCallback)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode); uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey); if (physicalKey == 0 || logicalKey == 0) { @@ -560,9 +560,9 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call } _lastModifierFlags = _lastModifierFlags ^ targetModifierFlag; [self sendModifierEventOfType:shouldBePressed - timestamp:event.timestamp - keyCode:event.keyCode - callback:callback]; + timestamp:event.timestamp + keyCode:event.keyCode + callback:callback]; } - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm index aabfa693dcc99..a160b7d8a3e15 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm @@ -84,11 +84,10 @@ id checkKeyDownEvent(unsigned short keyCode) { id mockFinalResponder(BoolGetter resultGetter) { id mock = OCMStrictProtocolMock(@protocol(FlutterKeyFinalResponder)); - OCMStub([mock handleKeyEvent:[OCMArg any]]) - .andDo((^(NSInvocation* invocation) { - BOOL result = resultGetter(); - [invocation setReturnValue:&result]; - })); + OCMStub([mock handleKeyEvent:[OCMArg any]]).andDo((^(NSInvocation* invocation) { + BOOL result = resultGetter(); + [invocation setReturnValue:&result]; + })); return mock; } @@ -261,11 +260,10 @@ - (bool)emptyNextResponder { FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { - callback(FALSE); - })]; + callback(FALSE); + })]; // Passes if no error is thrown. return true; } - @end From 3fd66feebd6ff5ac9b97a77cb3957f53022a9fd4 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 8 Mar 2021 01:53:35 -0800 Subject: [PATCH 33/46] License --- ci/licenses_golden/licenses_flutter | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 042890e6b772f..e7957725031c7 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1120,8 +1120,17 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCom FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.h -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIntermediateKeyResponder.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMacOSExternalTexture.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.mm @@ -1152,6 +1161,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewC FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/KeyCodeMap_internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSGLContextSwitch.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/MacOSGLContextSwitch.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/fixtures/flutter_desktop_test.dart From f5e2f83875d03a151eb27c8805726413c6ef5af5 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 8 Mar 2021 02:31:10 -0800 Subject: [PATCH 34/46] Remove redundant header --- .../macos/framework/Source/FlutterViewController_Internal.h | 1 - 1 file changed, 1 deletion(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index f71d89fa928ec..cd10a3f8901e8 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -6,7 +6,6 @@ #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" -#import "flutter/shell/platform/embedder/embedder.h" @interface FlutterViewController () From 15a9abd8f651d0e9eaac30673874d9a1f0b64429 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 8 Mar 2021 16:34:01 -0800 Subject: [PATCH 35/46] Apply suggestions from code review Co-authored-by: Greg Spencer --- .../darwin/macos/framework/Source/FlutterKeyboardManager.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index e5fa685daebe6..80b18b0d4b997 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -31,9 +31,9 @@ * type. If the |nextResponder| is nil, then the event will be propagated no * further. * - * It's not supported to prevent any handlers from receiving the events, because + * Preventing handlers from receiving events is not supported, because * in reality this class will only support 2 hardcoded delegates (channel and - * embedder), with the only purpose to support the legacy API (channel) during + * embedder), where the only purpose of supporting two is to support the legacy API (channel) during * the deprecation window, after which the channel handler should be removed. */ @interface FlutterKeyboardManager : NSObject @@ -43,7 +43,7 @@ * * The owner should be an object that handles the lifecycle of this instance. * The |owner.nextResponder| can be nil, but if it isn't, it will be where the - * key events are propagated to, if no handlers or additional handlers handles + * key events are propagated to, if no handlers or additional handlers handle * the event. The owner is typically a |FlutterViewController|. */ - (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner; From e950e237aca544584d5ebac9d6fe64087fe8800c Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 11 Mar 2021 17:23:15 -0800 Subject: [PATCH 36/46] Renames --- ci/licenses_golden/licenses_flutter | 16 +-- shell/platform/darwin/macos/BUILD.gn | 16 +-- ...Handler.h => FlutterChannelKeyResponder.h} | 10 +- ...ndler.mm => FlutterChannelKeyResponder.mm} | 8 +- ...=> FlutterChannelKeyResponderUnittests.mm} | 70 ++++++------ ...andler.h => FlutterEmbedderKeyResponder.h} | 12 +- ...dler.mm => FlutterEmbedderKeyResponder.mm} | 46 ++++---- ...> FlutterEmbedderKeyResponderUnittests.mm} | 46 ++++---- ...Handler.h => FlutterKeyPrimaryResponder.h} | 10 +- ...onder.h => FlutterKeySecondaryResponder.h} | 4 +- .../framework/Source/FlutterKeyboardManager.h | 60 +++++----- .../Source/FlutterKeyboardManager.mm | 31 ++--- .../Source/FlutterKeyboardManagerUnittests.mm | 108 ++++++++++-------- .../framework/Source/FlutterTextInputPlugin.h | 4 +- .../Source/FlutterTextInputPlugin.mm | 2 +- .../framework/Source/FlutterViewController.mm | 34 +++--- .../Source/FlutterViewController_Internal.h | 2 +- 17 files changed, 249 insertions(+), 230 deletions(-) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyChannelHandler.h => FlutterChannelKeyResponder.h} (63%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyChannelHandler.mm => FlutterChannelKeyResponder.mm} (92%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyChannelHandlerUnittests.mm => FlutterChannelKeyResponderUnittests.mm} (78%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyEmbedderHandler.h => FlutterEmbedderKeyResponder.h} (67%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyEmbedderHandler.mm => FlutterEmbedderKeyResponder.mm} (93%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyEmbedderHandlerUnittests.mm => FlutterEmbedderKeyResponderUnittests.mm} (95%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyHandler.h => FlutterKeyPrimaryResponder.h} (73%) rename shell/platform/darwin/macos/framework/Source/{FlutterKeyFinalResponder.h => FlutterKeySecondaryResponder.h} (90%) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 540a4ada3a153..ce51d52b9aeb0 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1122,14 +1122,14 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCom FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponderUnittests.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index cbbcfa9b0ef28..dfb5d736faec9 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -59,8 +59,12 @@ source_set("flutter_framework_source") { "framework/Source/FlutterBackingStore.mm", "framework/Source/FlutterBackingStoreData.h", "framework/Source/FlutterBackingStoreData.mm", + "framework/Source/FlutterChannelKeyResponder.h", + "framework/Source/FlutterChannelKeyResponder.mm", "framework/Source/FlutterDartProject.mm", "framework/Source/FlutterDartProject_Internal.h", + "framework/Source/FlutterEmbedderKeyResponder.h", + "framework/Source/FlutterEmbedderKeyResponder.mm", "framework/Source/FlutterEngine.mm", "framework/Source/FlutterEngine_Internal.h", "framework/Source/FlutterExternalTextureGL.h", @@ -73,12 +77,8 @@ source_set("flutter_framework_source") { "framework/Source/FlutterGLCompositor.mm", "framework/Source/FlutterIOSurfaceHolder.h", "framework/Source/FlutterIOSurfaceHolder.mm", - "framework/Source/FlutterKeyChannelHandler.h", - "framework/Source/FlutterKeyChannelHandler.mm", - "framework/Source/FlutterKeyEmbedderHandler.h", - "framework/Source/FlutterKeyEmbedderHandler.mm", - "framework/Source/FlutterKeyFinalResponder.h", - "framework/Source/FlutterKeyHandler.h", + "framework/Source/FlutterKeyPrimaryResponder.h", + "framework/Source/FlutterKeySecondaryResponder.h", "framework/Source/FlutterKeyboardManager.h", "framework/Source/FlutterKeyboardManager.mm", "framework/Source/FlutterMacOSExternalTexture.h", @@ -165,11 +165,11 @@ executable("flutter_desktop_darwin_unittests") { testonly = true sources = [ + "framework/Source/FlutterChannelKeyResponderUnittests.mm", "framework/Source/FlutterEmbedderExternalTextureUnittests.mm", + "framework/Source/FlutterEmbedderKeyResponderUnittests.mm", "framework/Source/FlutterEngineTest.mm", "framework/Source/FlutterGLCompositorUnittests.mm", - "framework/Source/FlutterKeyChannelHandlerUnittests.mm", - "framework/Source/FlutterKeyEmbedderHandlerUnittests.mm", "framework/Source/FlutterKeyboardManagerUnittests.mm", "framework/Source/FlutterMetalRendererTest.mm", "framework/Source/FlutterMetalSurfaceManagerTest.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h similarity index 63% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h rename to shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h index 055a37e20dc17..b40492a3550fb 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h @@ -2,22 +2,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h" #import #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" /** - * A handler of |FlutterKeyboardManager| that handles events by sending the - * raw information through the method channel. + * A primary responder of |FlutterKeyboardManager| that handles events by + * sending the raw information through the method channel. * * This class corresponds to the RawKeyboard API in the framework. */ -@interface FlutterKeyChannelHandler : NSObject +@interface FlutterChannelKeyResponder : NSObject /** - * Create a handler by specifying the method channel to use. + * Create an instance by specifying the method channel to use. */ - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm similarity index 92% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm rename to shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm index 4aefb18f2308a..f0483a129e2f5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm @@ -4,13 +4,13 @@ #import -#import "FlutterKeyChannelHandler.h" +#import "FlutterChannelKeyResponder.h" #import "KeyCodeMap_internal.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/embedder/embedder.h" -@interface FlutterKeyChannelHandler () +@interface FlutterChannelKeyResponder () /** * The channel used to communicate with Flutter. @@ -27,7 +27,7 @@ @interface FlutterKeyChannelHandler () @end -@implementation FlutterKeyChannelHandler +@implementation FlutterChannelKeyResponder - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel { self = [super init]; @@ -36,7 +36,7 @@ - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)cha return self; } -- (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { NSString* type; switch (event.type) { case NSEventTypeKeyDown: diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponderUnittests.mm similarity index 78% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm rename to shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponderUnittests.mm index 7ef4041f07ac7..0ee7445efecc3 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponderUnittests.mm @@ -5,7 +5,7 @@ #import #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h" #import "flutter/testing/testing.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" @@ -31,7 +31,7 @@ } } // namespace -TEST(FlutterKeyChannelHandlerUnittests, BasicKeyEvent) { +TEST(FlutterChannelKeyResponderUnittests, BasicKeyEvent) { __block NSMutableArray* messages = [[NSMutableArray alloc] init]; __block BOOL next_response = TRUE; __block NSMutableArray* responses = [[NSMutableArray alloc] init]; @@ -53,12 +53,12 @@ })); // Key down - FlutterKeyChannelHandler* handler = - [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) - callback:^(BOOL handled) { - [responses addObject:@(handled)]; - }]; + FlutterChannelKeyResponder* responder = + [[FlutterChannelKeyResponder alloc] initWithChannel:mockKeyEventChannel]; + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); @@ -76,10 +76,10 @@ // Key up next_response = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) - callback:^(BOOL handled) { - [responses addObject:@(handled)]; - }]; + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, 0) + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); @@ -97,10 +97,10 @@ // LShift down next_response = TRUE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, 56) - callback:^(BOOL handled) { - [responses addObject:@(handled)]; - }]; + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, 56) + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); @@ -116,10 +116,10 @@ // RShift down next_response = false; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, 60) - callback:^(BOOL handled) { - [responses addObject:@(handled)]; - }]; + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, 60) + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); @@ -135,10 +135,10 @@ // LShift up next_response = false; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, 56) - callback:^(BOOL handled) { - [responses addObject:@(handled)]; - }]; + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, 56) + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); @@ -154,10 +154,10 @@ // RShift up next_response = false; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, 60) - callback:^(BOOL handled) { - [responses addObject:@(handled)]; - }]; + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, 60) + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); @@ -172,7 +172,7 @@ [responses removeAllObjects]; } -TEST(FlutterKeyChannelHandlerUnittests, EmptyResponseIsTakenAsHandled) { +TEST(FlutterChannelKeyResponderUnittests, EmptyResponseIsTakenAsHandled) { __block NSMutableArray* messages = [[NSMutableArray alloc] init]; __block NSMutableArray* responses = [[NSMutableArray alloc] init]; @@ -189,12 +189,12 @@ callback(nullptr); })); - FlutterKeyChannelHandler* handler = - [[FlutterKeyChannelHandler alloc] initWithChannel:mockKeyEventChannel]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) - callback:^(BOOL handled) { - [responses addObject:@(handled)]; - }]; + FlutterChannelKeyResponder* responder = + [[FlutterChannelKeyResponder alloc] initWithChannel:mockKeyEventChannel]; + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, 0) + callback:^(BOOL handled) { + [responses addObject:@(handled)]; + }]; EXPECT_EQ([messages count], 1u); EXPECT_STREQ([[messages lastObject][@"keymap"] UTF8String], "macos"); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h similarity index 67% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h rename to shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h index 77492286d3d14..5cd68954d0df9 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h @@ -4,7 +4,7 @@ #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h" #include "flutter/shell/platform/embedder/embedder.h" namespace { @@ -16,17 +16,17 @@ typedef void (^FlutterSendEmbedderKeyEvent)(const FlutterKeyEvent& /* event */, _Nullable _VoidPtr /* user_data */); /** - * A handler of |FlutterKeyboardManager| that handles events by sending the - * converted events through the embedder API. + * A primary responder of |FlutterKeyboardManager| that handles events by + * sending the converted events through the embedder API. * * This class corresponds to the HardwareKeyboard API in the framework. */ -@interface FlutterKeyEmbedderHandler : NSObject +@interface FlutterEmbedderKeyResponder : NSObject /** - * Create a handler by specifying the function to send converted events to. + * Create an instance by specifying the function to send converted events to. * - * The |sendEvent| is typically engine's sendKeyEvent. + * The |sendEvent| is typically |FlutterEngine|'s |sendKeyEvent|. */ - (nonnull instancetype)initWithSendEvent:(_Nonnull FlutterSendEmbedderKeyEvent)sendEvent; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm similarity index 93% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm rename to shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index c0a79b4127a6b..8a863f6e8b180 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -4,7 +4,7 @@ #import -#import "FlutterKeyEmbedderHandler.h" +#import "FlutterEmbedderKeyResponder.h" #import "KeyCodeMap_internal.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" @@ -152,11 +152,10 @@ static NSUInteger computeModifierFlagOfInterestMask() { } /** - * The handler sent to the embedder's SendKeyEvent. + * The C-function sent to the embedder's |SendKeyEvent|, wrapping + * |FlutterEmbedderKeyResponder.handleResponse|. * - * The |user_data| should actually be a |FlutterKeyPendingResponse| that has - * been retained once with |__bridge_retained|. This function calls - * |FlutterKeyPendingResponse.handler|'s |handleResponse| using |handled|. + * For the reason of this wrap, see |FlutterKeyPendingResponse|. */ void HandleResponse(bool handled, void* user_data); @@ -174,37 +173,44 @@ static NSUInteger computeModifierFlagOfInterestMask() { // https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc). // These characters are filtered out since they're unprintable. // - // Although the documentation claims to reserve 0xF700-0xF8FF, only up to 0xF747 - // is actually used. Here we choose to filter out 0xF700-0xF7FF section. - // The reason for keeping the 0xF800-0xF8FF section is because 0xF8FF is - // used for the "Apple logo" character (Option-Shift-K on US keyboard.) + // Although the documentation claims to reserve 0xF700-0xF8FF, only up to + // 0xF747 is actually used. Here we choose to filter out 0xF700-0xF7FF + // section. The reason for keeping the 0xF800-0xF8FF section is because + // 0xF8FF is used for the "Apple logo" character (Option-Shift-K on US + // keyboard.) return nullptr; } return [characters UTF8String]; } } // namespace -/* An entry of FlutterKeyEmbedderHandler.pendingResponse. +/* The invocation context for |HandleResponse|, wrapping + * |FlutterEmbedderKeyResponder.handleResponse|. * - * FlutterEngineSendKeyEvent only supports a global function and a pointer. - * This class is used for the global function, HandleResponse, to convert a - * pointer into a method call, FlutterKeyEmbedderHandler.handleResponse, - * essentially binding |FlutterKeyPendingResponse.handleResponse| with a - * |FlutterKeyPendingResponse| instance. + * The embedder functions only accept C-functions as callbacks, as well as an + * arbitray user_data. In order to send an instance method of + * |FlutterEmbedderKeyResponder.handleResponse| to the engine's |SendKeyEvent|, + * we wrap the invocation into a C-function |HandleResponse| and invocation + * context |FlutterKeyPendingResponse|. + * + * When this object is sent to the engine's |SendKeyEvent| as |user_data|, it + * must be attached with |__bridge_retained|. When this object is parsed + * in |HandleResponse| from |user_data|, it will be attached with + * |__bridge_transfer|. */ @interface FlutterKeyPendingResponse : NSObject -@property(nonatomic) FlutterKeyEmbedderHandler* handler; +@property(nonatomic) FlutterEmbedderKeyResponder* handler; @property(nonatomic) uint64_t responseId; -- (nonnull instancetype)initWithHandler:(nonnull FlutterKeyEmbedderHandler*)handler +- (nonnull instancetype)initWithHandler:(nonnull FlutterEmbedderKeyResponder*)handler responseId:(uint64_t)responseId; @end @implementation FlutterKeyPendingResponse -- (instancetype)initWithHandler:(FlutterKeyEmbedderHandler*)handler +- (instancetype)initWithHandler:(FlutterEmbedderKeyResponder*)handler responseId:(uint64_t)responseId { self = [super init]; _handler = handler; @@ -213,7 +219,7 @@ - (instancetype)initWithHandler:(FlutterKeyEmbedderHandler*)handler } @end -@interface FlutterKeyEmbedderHandler () +@interface FlutterEmbedderKeyResponder () /** * The function to send converted events to. @@ -336,7 +342,7 @@ - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; @end -@implementation FlutterKeyEmbedderHandler +@implementation FlutterEmbedderKeyResponder - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent { self = [super init]; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm similarity index 95% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm rename to shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm index 234203db04991..49e901d92e413 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandlerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm @@ -5,7 +5,7 @@ #import #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h" #import "flutter/testing/testing.h" #include "third_party/googletest/googletest/include/gtest/gtest.h" @@ -130,12 +130,12 @@ - (void)dealloc { // Test the most basic key events. // // Press, hold, and release key A on an US keyboard. -TEST(FlutterKeyEmbedderHandlerUnittests, BasicKeyEvent) { +TEST(FlutterEmbedderKeyResponderUnittests, BasicKeyEvent) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -209,11 +209,11 @@ - (void)dealloc { [events removeAllObjects]; } -TEST(FlutterKeyEmbedderHandlerUnittests, NonAsciiCharacters) { +TEST(FlutterEmbedderKeyResponderUnittests, NonAsciiCharacters) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -284,12 +284,12 @@ - (void)dealloc { // MacOS usually matches down and up events perfectly since it tracks key taps to a window. // Unmatched events can occur when you hold a key, then Ctrl-clicks desktop to trigger a // menu, and release key. -TEST(FlutterKeyEmbedderHandlerUnittests, IgnoreDuplicateDownEvent) { +TEST(FlutterEmbedderKeyResponderUnittests, IgnoreDuplicateDownEvent) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -349,11 +349,11 @@ - (void)dealloc { // // This is special because the characters for the A key will change in this // process. -TEST(FlutterKeyEmbedderHandlerUnittests, ToggleModifiersDuringKeyTap) { +TEST(FlutterEmbedderKeyResponderUnittests, ToggleModifiersDuringKeyTap) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -458,11 +458,11 @@ - (void)dealloc { // Some keys in modifierFlags are not to indicate modifier state, but to mark // the key area that the key belongs to, such as numpad keys or function keys. // Ensure these flags do not obstruct other keys. -TEST(FlutterKeyEmbedderHandlerUnittests, SpecialModiferFlags) { +TEST(FlutterEmbedderKeyResponderUnittests, SpecialModiferFlags) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -604,11 +604,11 @@ - (void)dealloc { [events removeAllObjects]; } -TEST(FlutterKeyEmbedderHandlerUnittests, IdentifyLeftAndRightModifiers) { +TEST(FlutterEmbedderKeyResponderUnittests, IdentifyLeftAndRightModifiers) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -685,7 +685,7 @@ - (void)dealloc { // // In the following comments, parentheses indicate missed events, while // asterisks indicate synthesized events. -TEST(FlutterKeyEmbedderHandlerUnittests, SynthesizeMissedModifierEvents) { +TEST(FlutterEmbedderKeyResponderUnittests, SynthesizeMissedModifierEvents) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; id keyEventCallback = ^(BOOL handled) { @@ -693,7 +693,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -877,7 +877,7 @@ - (void)dealloc { [events removeAllObjects]; } -TEST(FlutterKeyEmbedderHandlerUnittests, SynthesizeMissedModifierEventsInNormalEvents) { +TEST(FlutterEmbedderKeyResponderUnittests, SynthesizeMissedModifierEventsInNormalEvents) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; id keyEventCallback = ^(BOOL handled) { @@ -885,7 +885,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -951,7 +951,7 @@ - (void)dealloc { [events removeAllObjects]; } -TEST(FlutterKeyEmbedderHandlerUnittests, ConvertCapsLockEvents) { +TEST(FlutterEmbedderKeyResponderUnittests, ConvertCapsLockEvents) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; id keyEventCallback = ^(BOOL handled) { @@ -959,7 +959,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -1029,14 +1029,14 @@ - (void)dealloc { } // Press the CapsLock key when CapsLock state is desynchronized -TEST(FlutterKeyEmbedderHandlerUnittests, SynchronizeCapsLockStateOnCapsLock) { +TEST(FlutterEmbedderKeyResponderUnittests, SynchronizeCapsLockStateOnCapsLock) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; id keyEventCallback = ^(BOOL handled) { last_handled = handled; }; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -1055,7 +1055,7 @@ - (void)dealloc { } // Press the CapsLock key when CapsLock state is desynchronized -TEST(FlutterKeyEmbedderHandlerUnittests, SynchronizeCapsLockStateOnNormalKey) { +TEST(FlutterEmbedderKeyResponderUnittests, SynchronizeCapsLockStateOnNormalKey) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; id keyEventCallback = ^(BOOL handled) { @@ -1063,7 +1063,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterKeyEmbedderHandler* handler = [[FlutterKeyEmbedderHandler alloc] + FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h similarity index 73% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h rename to shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h index cdc818e239e94..28cd4ddd72cd5 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h @@ -4,24 +4,24 @@ #import -typedef void (^FlutterKeyHandlerCallback)(BOOL handled); +typedef void (^FlutterAsyncKeyCallback)(BOOL handled); /** * An interface for a responder that can process a key event and decides whether * to handle an event asynchronously. * - * To use this class, add it to a |FlutterKeyboardManager| with |addHandler|. + * To use this class, add it to a |FlutterKeyboardManager| with |addPrimaryResponder|. */ -@protocol FlutterKeyHandler +@protocol FlutterKeyPrimaryResponder /** * Process the event. * * The |callback| should be called with a value that indicates whether the - * handler has handled the given event. The |callback| must be called exactly + * responder has handled the given event. The |callback| must be called exactly * once, and can be called before the return of this method, or after. */ @required -- (void)handleEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; +- (void)handleEvent:(nonnull NSEvent*)event callback:(nonnull FlutterAsyncKeyCallback)callback; @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h b/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h similarity index 90% rename from shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h rename to shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h index 7265ce776643f..e0d33522d2342 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h @@ -9,9 +9,9 @@ * to handle an event synchronously. * * To use this class, add it to a |FlutterKeyboardManager| with - * |addAdditionalHandler|. + * |addSecondaryResponder|. */ -@protocol FlutterKeyFinalResponder +@protocol FlutterKeySecondaryResponder /** * Informs the receiver that the user has interacted with a key. * diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index 80b18b0d4b997..649ca9a5ddc94 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -2,39 +2,40 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h" #import -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h" /** - * A hub that manages how key events are dispatched to various handlers, and - * whether the event is propagated to the next responder. + * A hub that manages how key events are dispatched to various Flutter key + * responders, and whether the event is propagated to the next NSResponder. * - * This class can be added with one or more handlers, as well as zero or more - * additional handlers. + * This class can be added with one or more primary responders, as well as zero + * or more secondary responders. * * An event that is received by |handleEvent| is first dispatched to *all* - * handlers. Each handler responds ascynchronously with a boolean, indicating - * whether it handles the event. + * primary resopnders. Each primary responder responds *ascynchronously* with a + * boolean, indicating whether it handles the event. * - * An event that is not handled by any handlers, is then passed to to the first - * added additional handlers, which responds synchronously with a boolean, - * indicating whether it handles the event. If not, the event is passed to the - * next additional responder, and so on. + * An event that is not handled by any primary responders is then passed to to + * the first secondary responder (in the chronological order of addition), + * which responds *synchronously* with a boolean, indicating whether it handles + * the event. If not, the event is passed to the next secondary responder, and + * so on. * - * If all handlers and additional handlers respond with not to handle the - * event, the event is then passed to the owner's |nextResponder| if not nil, - * on method |keyDown|, |keyUp|, or |flagsChanged|, depending on the event's - * type. If the |nextResponder| is nil, then the event will be propagated no - * further. + * If no responders handle the event, the event is then handed over to the + * owner's |nextResponder| if not nil, dispatching to method |keyDown|, + * |keyUp|, or |flagsChanged| depending on the event's type. If the + * |nextResponder| is nil, then the event will be propagated no further. * - * Preventing handlers from receiving events is not supported, because - * in reality this class will only support 2 hardcoded delegates (channel and - * embedder), where the only purpose of supporting two is to support the legacy API (channel) during - * the deprecation window, after which the channel handler should be removed. + * Preventing primary responders from receiving events is not supported, + * because in reality this class will only support 2 hardcoded ones (channel + * and embedder), where the only purpose of supporting two is to support the + * legacy API (channel) during the deprecation window, after which the channel + * resopnder should be removed. */ @interface FlutterKeyboardManager : NSObject @@ -49,19 +50,20 @@ - (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner; /** - * Add a handler, which asynchronously decides whether to handle an event. + * Add a primary resopnder, which asynchronously decides whether to handle an + * event. */ -- (void)addHandler:(nonnull id)handler; +- (void)addPrimaryResponder:(nonnull id)handler; /** - * Add an additional handler, which synchronously decides whether to handle an - * event. + * Add a secondary handler, which synchronously decides whether to handle an + * event in order if no earlier responders handle. */ -- (void)addAdditionalHandler:(nonnull id)handler; +- (void)addSecondaryResponder:(nonnull id)handler; /** - * Dispatch a key event to all handlers and additional handlers, and possibly - * the next responder afterwards. + * Dispatch a key event to all responders, and possibly the next |NSResponder| + * afterwards. */ - (void)handleEvent:(nonnull NSEvent*)event; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 14879b7864f60..3cae90edf6b28 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -12,14 +12,14 @@ @interface FlutterKeyboardManager () @property(nonatomic, weak) NSResponder* owner; /** - * The handlers added by addHandler. + * The primary responders added by addPrimaryResponder. */ -@property(nonatomic) NSMutableArray>* keyHandlers; +@property(nonatomic) NSMutableArray>* keyHandlers; /** - * The additional handlers added by addAdditionalHandler. + * The secondary responders added by addSecondaryResponder. */ -@property(nonatomic) NSMutableArray>* additionalKeyHandlers; +@property(nonatomic) NSMutableArray>* additionalKeyHandlers; @end @@ -33,16 +33,16 @@ - (nonnull instancetype)initWithOwner:(NSResponder*)weakOwner { return self; } -- (void)addHandler:(nonnull id)handler { +- (void)addPrimaryResponder:(nonnull id)handler { [_keyHandlers addObject:handler]; } -- (void)addAdditionalHandler:(nonnull id)handler { +- (void)addSecondaryResponder:(nonnull id)handler { [_additionalKeyHandlers addObject:handler]; } - (void)dispatchToAdditionalHandlers:(NSEvent*)event { - for (id responder in _additionalKeyHandlers) { + for (id responder in _additionalKeyHandlers) { if ([responder handleKeyEvent:event]) { return; } @@ -69,29 +69,30 @@ - (void)dispatchToAdditionalHandlers:(NSEvent*)event { } - (void)handleEvent:(nonnull NSEvent*)event { - // Be sure to add a handler in propagateKeyEvent if you allow more event - // types here. + // Be sure to add a handling method in propagateKeyEvent if you allow more + // event types here. if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && event.type != NSEventTypeFlagsChanged) { return; } - // Having no key handlers require extra logic, but since Flutter adds all - // handlers in hard-code, this is a situation that Flutter will never meet. - NSAssert([_keyHandlers count] >= 0, @"At least one key handler must be added."); + // Having no primary responders require extra logic, but since Flutter adds + // all primary responders in hard-code, this is a situation that Flutter will + // never meet. + NSAssert([_keyHandlers count] >= 0, @"At least one primary responder must be added."); __weak __typeof__(self) weakSelf = self; __block int unreplied = [_keyHandlers count]; __block BOOL anyHandled = false; - FlutterKeyHandlerCallback replyCallback = ^(BOOL handled) { + FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) { unreplied -= 1; - NSAssert(unreplied >= 0, @"More key handlers replied than possible."); + NSAssert(unreplied >= 0, @"More primary responders replied than possible."); anyHandled = anyHandled || handled; if (unreplied == 0 && !anyHandled) { [weakSelf dispatchToAdditionalHandlers:event]; } }; - for (id handler in _keyHandlers) { + for (id handler in _keyHandlers) { [handler handleEvent:event callback:replyCallback]; } } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm index a160b7d8a3e15..bd4bc6bca1682 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm @@ -11,9 +11,9 @@ @interface FlutterKeyboardManagerUnittestsObjC : NSObject - (bool)nextResponderShouldThrowOnKeyUp; -- (bool)singleAsyncHandler; -- (bool)doubleAsyncHandlers; -- (bool)singleFinalResponder; +- (bool)singlePrimaryResponder; +- (bool)doublePrimaryResponder; +- (bool)singleSecondaryResponder; - (bool)emptyNextResponder; @end @@ -68,22 +68,24 @@ id checkKeyDownEvent(unsigned short keyCode) { return owner; } -typedef void (^KeyCallbackHandler)(FlutterKeyHandlerCallback callback); +typedef void (^KeyCallbackSetter)(FlutterAsyncKeyCallback callback); typedef BOOL (^BoolGetter)(); -id mockAsyncKeyHandler(KeyCallbackHandler handler) { - id mock = OCMStrictProtocolMock(@protocol(FlutterKeyHandler)); +id mockPrimaryResponder(KeyCallbackSetter callbackSetter) { + id mock = + OCMStrictProtocolMock(@protocol(FlutterKeyPrimaryResponder)); OCMStub([mock handleEvent:[OCMArg any] callback:[OCMArg any]]) .andDo((^(NSInvocation* invocation) { - FlutterKeyHandlerCallback callback; + FlutterAsyncKeyCallback callback; [invocation getArgument:&callback atIndex:3]; - handler(callback); + callbackSetter(callback); })); return mock; } -id mockFinalResponder(BoolGetter resultGetter) { - id mock = OCMStrictProtocolMock(@protocol(FlutterKeyFinalResponder)); +id mockSecondaryResponder(BoolGetter resultGetter) { + id mock = + OCMStrictProtocolMock(@protocol(FlutterKeySecondaryResponder)); OCMStub([mock handleKeyEvent:[OCMArg any]]).andDo((^(NSInvocation* invocation) { BOOL result = resultGetter(); [invocation setReturnValue:&result]; @@ -98,15 +100,15 @@ id checkKeyDownEvent(unsigned short keyCode) { } TEST(FlutterKeyboardManagerUnittests, SingleAsyncHandler) { - ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singleAsyncHandler]); + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singlePrimaryResponder]); } TEST(FlutterKeyboardManagerUnittests, DoubleAsyncHandlers) { - ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] doubleAsyncHandlers]); + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] doublePrimaryResponder]); } TEST(FlutterKeyboardManagerUnittests, SingleFinalResponder) { - ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singleFinalResponder]); + ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singleSecondaryResponder]); } TEST(FlutterKeyboardManagerUnittests, EmptyNextResponder) { @@ -129,24 +131,25 @@ - (bool)nextResponderShouldThrowOnKeyUp { } } -- (bool)singleAsyncHandler { +- (bool)singlePrimaryResponder { NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext(); FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; - __block NSMutableArray* callbacks = - [NSMutableArray array]; - [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { - [callbacks addObject:callback]; - })]; + __block NSMutableArray* callbacks = + [NSMutableArray array]; + [manager addPrimaryResponder:flutter::testing::mockPrimaryResponder( + ^(FlutterAsyncKeyCallback callback) { + [callbacks addObject:callback]; + })]; - // Case: The handler reports FALSE + // Case: The responder reports FALSE [manager handleEvent:flutter::testing::keyDownEvent(0x50)]; EXPECT_EQ([callbacks count], 1u); callbacks[0](FALSE); OCMVerify([owner.nextResponder keyDown:flutter::testing::checkKeyDownEvent(0x50)]); [callbacks removeAllObjects]; - // Case: The handler reports TRUE + // Case: The responder reports TRUE [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; EXPECT_EQ([callbacks count], 1u); callbacks[0](TRUE); @@ -155,23 +158,25 @@ - (bool)singleAsyncHandler { return true; } -- (bool)doubleAsyncHandlers { +- (bool)doublePrimaryResponder { NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext(); FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; - __block NSMutableArray* callbacks1 = - [NSMutableArray array]; - [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { - [callbacks1 addObject:callback]; - })]; - - __block NSMutableArray* callbacks2 = - [NSMutableArray array]; - [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { - [callbacks2 addObject:callback]; - })]; - - // Case: Both handler report TRUE. + __block NSMutableArray* callbacks1 = + [NSMutableArray array]; + [manager addPrimaryResponder:flutter::testing::mockPrimaryResponder( + ^(FlutterAsyncKeyCallback callback) { + [callbacks1 addObject:callback]; + })]; + + __block NSMutableArray* callbacks2 = + [NSMutableArray array]; + [manager addPrimaryResponder:flutter::testing::mockPrimaryResponder( + ^(FlutterAsyncKeyCallback callback) { + [callbacks2 addObject:callback]; + })]; + + // Case: Both responder report TRUE. [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; EXPECT_EQ([callbacks1 count], 1u); EXPECT_EQ([callbacks2 count], 1u); @@ -183,7 +188,7 @@ - (bool)doubleAsyncHandlers { [callbacks1 removeAllObjects]; [callbacks2 removeAllObjects]; - // Case: One handler reports TRUE. + // Case: One responder reports TRUE. [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; EXPECT_EQ([callbacks1 count], 1u); EXPECT_EQ([callbacks2 count], 1u); @@ -210,23 +215,24 @@ - (bool)doubleAsyncHandlers { return true; } -- (bool)singleFinalResponder { +- (bool)singleSecondaryResponder { NSResponder* owner = flutter::testing::mockOwnerWithDownOnlyNext(); FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; - __block NSMutableArray* callbacks = - [NSMutableArray array]; - [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { - [callbacks addObject:callback]; - })]; + __block NSMutableArray* callbacks = + [NSMutableArray array]; + [manager addPrimaryResponder:flutter::testing::mockPrimaryResponder( + ^(FlutterAsyncKeyCallback callback) { + [callbacks addObject:callback]; + })]; __block BOOL nextResponse; - [manager addAdditionalHandler:flutter::testing::mockFinalResponder(^() { + [manager addSecondaryResponder:flutter::testing::mockSecondaryResponder(^() { return nextResponse; })]; - // Case: Handler responds TRUE. The event shouldn't be handled by the final - // responder. + // Case: Primary responder responds TRUE. The event shouldn't be handled by + // the secondary responder. nextResponse = FALSE; [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; EXPECT_EQ([callbacks count], 1u); @@ -234,7 +240,8 @@ - (bool)singleFinalResponder { // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown. [callbacks removeAllObjects]; - // Case: Handler responds FALSE. The final responder returns TRUE. + // Case: Primary responder responds FALSE. The secondary responder returns + // TRUE. nextResponse = TRUE; [manager handleEvent:flutter::testing::keyUpEvent(0x50)]; EXPECT_EQ([callbacks count], 1u); @@ -242,7 +249,7 @@ - (bool)singleFinalResponder { // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown. [callbacks removeAllObjects]; - // Case: Handler responds FALSE. The final responder returns FALSE. + // Case: Primary responder responds FALSE. The secondary responder returns FALSE. nextResponse = FALSE; [manager handleEvent:flutter::testing::keyDownEvent(0x50)]; EXPECT_EQ([callbacks count], 1u); @@ -259,9 +266,10 @@ - (bool)emptyNextResponder { FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] initWithOwner:owner]; - [manager addHandler:flutter::testing::mockAsyncKeyHandler(^(FlutterKeyHandlerCallback callback) { - callback(FALSE); - })]; + [manager addPrimaryResponder:flutter::testing::mockPrimaryResponder( + ^(FlutterAsyncKeyCallback callback) { + callback(FALSE); + })]; // Passes if no error is thrown. return true; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h index 3bdc04c9e9705..7ce919d22e0b8 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h @@ -6,7 +6,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h" /** * A plugin to handle text input. @@ -17,7 +17,7 @@ * This is not an FlutterPlugin since it needs access to FlutterViewController internals, so needs * to be managed differently. */ -@interface FlutterTextInputPlugin : NSObject +@interface FlutterTextInputPlugin : NSObject /** * Initializes a text input plugin that coordinates key event handling with |viewController|. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 982bb2fdafba8..169269112ec37 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -258,7 +258,7 @@ - (void)updateEditState { } #pragma mark - -#pragma mark FlutterKeyFinalResponder +#pragma mark FlutterKeySecondaryResponder /** * Handles key down events received from the view controller, responding TRUE if diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm index 673bfbcfbfdb8..64f86fee2aab8 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController.mm @@ -8,10 +8,10 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterCodecs.h" #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyChannelHandler.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyEmbedderHandler.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyHandler.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMetalRenderer.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMouseCursorPlugin.h" @@ -391,21 +391,23 @@ - (void)addInternalPlugins { __weak FlutterViewController* weakSelf = self; [FlutterMouseCursorPlugin registerWithRegistrar:[self registrarForPlugin:@"mousecursor"]]; _keyboardManager = [[FlutterKeyboardManager alloc] initWithOwner:weakSelf]; + [_keyboardManager addPrimaryResponder:[[FlutterEmbedderKeyResponder alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, + FlutterKeyEventCallback callback, + void* userData) { + [weakSelf.engine sendKeyEvent:event + callback:callback + userData:userData]; + }]]; [_keyboardManager - addHandler:[[FlutterKeyEmbedderHandler alloc] - initWithSendEvent:^(const FlutterKeyEvent& event, - FlutterKeyEventCallback callback, void* userData) { - [weakSelf.engine sendKeyEvent:event callback:callback userData:userData]; - }]]; + addPrimaryResponder:[[FlutterChannelKeyResponder alloc] + initWithChannel:[FlutterBasicMessageChannel + messageChannelWithName:@"flutter/keyevent" + binaryMessenger:_engine.binaryMessenger + codec:[FlutterJSONMessageCodec + sharedInstance]]]]; [_keyboardManager - addHandler:[[FlutterKeyChannelHandler alloc] - initWithChannel:[FlutterBasicMessageChannel - messageChannelWithName:@"flutter/keyevent" - binaryMessenger:_engine.binaryMessenger - codec:[FlutterJSONMessageCodec - sharedInstance]]]]; - [_keyboardManager - addAdditionalHandler:[[FlutterTextInputPlugin alloc] initWithViewController:self]]; + addSecondaryResponder:[[FlutterTextInputPlugin alloc] initWithViewController:self]]; _settingsChannel = [FlutterBasicMessageChannel messageChannelWithName:@"flutter/settings" binaryMessenger:_engine.binaryMessenger diff --git a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index cd10a3f8901e8..a32f2f4f96764 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -4,7 +4,7 @@ #import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" -#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyFinalResponder.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h" #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h" @interface FlutterViewController () From 6143ed98480de3facd9be74428df8b8d4614c90a Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 11 Mar 2021 17:32:01 -0800 Subject: [PATCH 37/46] More rename fixes --- .../Source/FlutterEmbedderKeyResponder.mm | 12 +- .../FlutterEmbedderKeyResponderUnittests.mm | 110 +++++++++--------- .../framework/Source/FlutterKeyboardManager.h | 10 +- .../Source/FlutterKeyboardManager.mm | 82 ++++++------- .../Source/FlutterKeyboardManagerUnittests.mm | 6 +- 5 files changed, 112 insertions(+), 108 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 8a863f6e8b180..112befedd844d 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -200,20 +200,20 @@ static NSUInteger computeModifierFlagOfInterestMask() { */ @interface FlutterKeyPendingResponse : NSObject -@property(nonatomic) FlutterEmbedderKeyResponder* handler; +@property(nonatomic) FlutterEmbedderKeyResponder* responder; @property(nonatomic) uint64_t responseId; -- (nonnull instancetype)initWithHandler:(nonnull FlutterEmbedderKeyResponder*)handler +- (nonnull instancetype)initWithHandler:(nonnull FlutterEmbedderKeyResponder*)responder responseId:(uint64_t)responseId; @end @implementation FlutterKeyPendingResponse -- (instancetype)initWithHandler:(FlutterEmbedderKeyResponder*)handler +- (instancetype)initWithHandler:(FlutterEmbedderKeyResponder*)responder responseId:(uint64_t)responseId { self = [super init]; - _handler = handler; + _responder = responder; _responseId = responseId; return self; } @@ -310,7 +310,7 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - (void)sendModifierEventOfType:(BOOL)shouldDown timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode - callback:(nullable FlutterKeyHandlerCallback)cCallback; + callback:(nullable FlutterKeyHandlerCallback)callback; /** * Processes a down event. @@ -606,6 +606,6 @@ - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { void HandleResponse(bool handled, void* user_data) { // The `__bridge_transfer` here is matched by `__bridge_retained` in sendPrimaryFlutterEvent. FlutterKeyPendingResponse* pending = (__bridge_transfer FlutterKeyPendingResponse*)user_data; - [pending.handler handleResponse:handled forId:pending.responseId]; + [pending.responder handleResponse:handled forId:pending.responseId]; } } // namespace diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm index 49e901d92e413..ddd51f5df40ca 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm @@ -135,7 +135,7 @@ - (void)dealloc { __block BOOL last_handled = TRUE; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -144,7 +144,7 @@ - (void)dealloc { }]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 123.0f, 0x100, @"a", @"a", FALSE, 0) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 123.0f, 0x100, @"a", @"a", FALSE, 0) callback:^(BOOL handled) { last_handled = handled; }]; @@ -166,7 +166,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -187,7 +187,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = TRUE; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 124.0f, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 124.0f, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -213,7 +213,7 @@ - (void)dealloc { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -221,7 +221,7 @@ - (void)dealloc { userData:user_data]]; }]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x80140, @"", @"", FALSE, kKeyCodeAltRight) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x80140, @"", @"", FALSE, kKeyCodeAltRight) callback:^(BOOL handled){ }]; @@ -235,7 +235,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x80140, @"∑", @"w", FALSE, kKeyCodeKeyW) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x80140, @"∑", @"w", FALSE, kKeyCodeKeyW) callback:^(BOOL handled){ }]; @@ -249,7 +249,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeAltRight) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeAltRight) callback:^(BOOL handled){ }]; @@ -263,7 +263,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"w", @"w", FALSE, kKeyCodeKeyW) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"w", @"w", FALSE, kKeyCodeKeyW) callback:^(BOOL handled){ }]; @@ -289,7 +289,7 @@ - (void)dealloc { __block BOOL last_handled = TRUE; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -298,7 +298,7 @@ - (void)dealloc { }]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -317,7 +317,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -326,7 +326,7 @@ - (void)dealloc { EXPECT_EQ(last_handled, TRUE); last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled) { last_handled = handled; }]; @@ -353,7 +353,7 @@ - (void)dealloc { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -361,7 +361,7 @@ - (void)dealloc { userData:user_data]]; }]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 123.0f, 0x20104, @"", @"", FALSE, + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 123.0f, 0x20104, @"", @"", FALSE, kKeyCodeShiftRight) callback:^(BOOL handled){ }]; @@ -378,7 +378,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", FALSE, kKeyCodeKeyA) callback:^(BOOL handled){ }]; @@ -393,7 +393,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", TRUE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", TRUE, kKeyCodeKeyA) callback:^(BOOL handled){ }]; @@ -408,7 +408,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) callback:^(BOOL handled){ }]; @@ -423,7 +423,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) callback:^(BOOL handled){ }]; @@ -437,7 +437,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled){ }]; @@ -462,7 +462,7 @@ - (void)dealloc { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -474,7 +474,7 @@ - (void)dealloc { // Then KeyUp: Numpad1, F1, KeyA, ShiftLeft // Numpad 1 - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x200100, @"1", @"1", FALSE, kKeyCodeNumpad1) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x200100, @"1", @"1", FALSE, kKeyCodeNumpad1) callback:^(BOOL handled){ }]; @@ -490,7 +490,7 @@ - (void)dealloc { [events removeAllObjects]; // F1 - [handler + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x800100, @"\uf704", @"\uf704", FALSE, kKeyCodeF1) callback:^(BOOL handled){ }]; @@ -507,7 +507,7 @@ - (void)dealloc { [events removeAllObjects]; // KeyA - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled){ }]; @@ -523,7 +523,7 @@ - (void)dealloc { [events removeAllObjects]; // ShiftLeft - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) callback:^(BOOL handled){ }]; @@ -540,7 +540,7 @@ - (void)dealloc { [events removeAllObjects]; // Numpad 1 - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x220102, @"1", @"1", FALSE, kKeyCodeNumpad1) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x220102, @"1", @"1", FALSE, kKeyCodeNumpad1) callback:^(BOOL handled){ }]; @@ -556,7 +556,7 @@ - (void)dealloc { [events removeAllObjects]; // F1 - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x820102, @"\uF704", @"\uF704", FALSE, kKeyCodeF1) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x820102, @"\uF704", @"\uF704", FALSE, kKeyCodeF1) callback:^(BOOL handled){ }]; @@ -572,7 +572,7 @@ - (void)dealloc { [events removeAllObjects]; // KeyA - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x20102, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x20102, @"a", @"a", FALSE, kKeyCodeKeyA) callback:^(BOOL handled){ }]; @@ -588,7 +588,7 @@ - (void)dealloc { [events removeAllObjects]; // ShiftLeft - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) callback:^(BOOL handled){ }]; @@ -608,7 +608,7 @@ - (void)dealloc { __block NSMutableArray* events = [[NSMutableArray alloc] init]; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -616,7 +616,7 @@ - (void)dealloc { userData:user_data]]; }]; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) callback:^(BOOL handled){ }]; @@ -632,7 +632,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) callback:^(BOOL handled){ }]; @@ -648,7 +648,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20104, @"", @"", FALSE, kKeyCodeShiftLeft) callback:^(BOOL handled){ }]; @@ -664,7 +664,7 @@ - (void)dealloc { [events removeAllObjects]; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) callback:^(BOOL handled){ }]; @@ -681,7 +681,7 @@ - (void)dealloc { } // Process various cases where pair modifier key events are missed, and the -// handler has to "guess" how to synchronize states. +// responder has to "guess" how to synchronize states. // // In the following comments, parentheses indicate missed events, while // asterisks indicate synthesized events. @@ -693,7 +693,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -705,7 +705,7 @@ - (void)dealloc { // In: L down, (L up), L down, L up // Out: L down, L up last_handled = FALSE; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; @@ -725,7 +725,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; @@ -733,7 +733,7 @@ - (void)dealloc { EXPECT_EQ(last_handled, TRUE); last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; EXPECT_EQ([events count], 1u); @@ -756,7 +756,7 @@ - (void)dealloc { // Out: last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; EXPECT_EQ([events count], 0u); @@ -767,7 +767,7 @@ - (void)dealloc { // Out: L down, *L up last_handled = FALSE; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; @@ -787,7 +787,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) callback:keyEventCallback]; EXPECT_EQ([events count], 1u); @@ -809,7 +809,7 @@ - (void)dealloc { // Out: L down, R down *L up & R up last_handled = FALSE; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20102, @"", @"", FALSE, kKeyCodeShiftLeft) callback:keyEventCallback]; @@ -829,7 +829,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x20106, @"", @"", FALSE, kKeyCodeShiftRight) callback:keyEventCallback]; @@ -849,7 +849,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -885,7 +885,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -897,7 +897,7 @@ - (void)dealloc { // Out: *LS down & A down, *LS up & A up last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x20102, @"A", @"A", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x20102, @"A", @"A", FALSE, kKeyCodeKeyA) callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -924,7 +924,7 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -959,7 +959,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -970,7 +970,7 @@ - (void)dealloc { // In: CapsLock down // Out: CapsLock down & *CapsLock Up last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x10100, @"", @"", FALSE, kKeyCodeCapsLock) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x10100, @"", @"", FALSE, kKeyCodeCapsLock) callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -1000,7 +1000,7 @@ - (void)dealloc { // In: CapsLock up // Out: CapsLock down & *CapsLock Up last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -1036,7 +1036,7 @@ - (void)dealloc { last_handled = handled; }; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -1047,7 +1047,7 @@ - (void)dealloc { // In: CapsLock down // Out: last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) + [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) callback:keyEventCallback]; EXPECT_EQ([events count], 0u); @@ -1063,7 +1063,7 @@ - (void)dealloc { }; FlutterKeyEvent* event; - FlutterEmbedderKeyResponder* handler = [[FlutterEmbedderKeyResponder alloc] + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, _Nullable _VoidPtr user_data) { [events addObject:[[TestKeyEvent alloc] initWithEvent:&event @@ -1072,7 +1072,7 @@ - (void)dealloc { }]; last_handled = FALSE; - [handler handleEvent:keyEvent(NSEventTypeKeyDown, 0x10100, @"A", @"a", FALSE, kKeyCodeKeyA) + [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x10100, @"A", @"a", FALSE, kKeyCodeKeyA) callback:keyEventCallback]; EXPECT_EQ([events count], 3u); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index 649ca9a5ddc94..fae8da76ba473 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -44,8 +44,8 @@ * * The owner should be an object that handles the lifecycle of this instance. * The |owner.nextResponder| can be nil, but if it isn't, it will be where the - * key events are propagated to, if no handlers or additional handlers handle - * the event. The owner is typically a |FlutterViewController|. + * key events are propagated to if no responders handle the event. The owner + * is typically a |FlutterViewController|. */ - (nonnull instancetype)initWithOwner:(nonnull NSResponder*)weakOwner; @@ -53,13 +53,13 @@ * Add a primary resopnder, which asynchronously decides whether to handle an * event. */ -- (void)addPrimaryResponder:(nonnull id)handler; +- (void)addPrimaryResponder:(nonnull id)responder; /** - * Add a secondary handler, which synchronously decides whether to handle an + * Add a secondary responder, which synchronously decides whether to handle an * event in order if no earlier responders handle. */ -- (void)addSecondaryResponder:(nonnull id)handler; +- (void)addSecondaryResponder:(nonnull id)responder; /** * Dispatch a key event to all responders, and possibly the next |NSResponder| diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 3cae90edf6b28..4a2008c55ac59 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -14,12 +14,14 @@ @interface FlutterKeyboardManager () /** * The primary responders added by addPrimaryResponder. */ -@property(nonatomic) NSMutableArray>* keyHandlers; +@property(nonatomic) NSMutableArray>* primaryResponders; /** * The secondary responders added by addSecondaryResponder. */ -@property(nonatomic) NSMutableArray>* additionalKeyHandlers; +@property(nonatomic) NSMutableArray>* secondaryResponders; + +- (void)dispatchToSecondaryResponders:(NSEvent*)event; @end @@ -28,21 +30,52 @@ @implementation FlutterKeyboardManager - (nonnull instancetype)initWithOwner:(NSResponder*)weakOwner { self = [super init]; _owner = weakOwner; - _keyHandlers = [[NSMutableArray alloc] init]; - _additionalKeyHandlers = [[NSMutableArray alloc] init]; + _primaryResponders = [[NSMutableArray alloc] init]; + _secondaryResponders = [[NSMutableArray alloc] init]; return self; } -- (void)addPrimaryResponder:(nonnull id)handler { - [_keyHandlers addObject:handler]; +- (void)addPrimaryResponder:(nonnull id)responder { + [_primaryResponders addObject:responder]; +} + +- (void)addSecondaryResponder:(nonnull id)responder { + [_secondaryResponders addObject:responder]; } -- (void)addSecondaryResponder:(nonnull id)handler { - [_additionalKeyHandlers addObject:handler]; +- (void)handleEvent:(nonnull NSEvent*)event { + // Be sure to add a handling method in propagateKeyEvent if you allow more + // event types here. + if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && + event.type != NSEventTypeFlagsChanged) { + return; + } + // Having no primary responders require extra logic, but since Flutter adds + // all primary responders in hard-code, this is a situation that Flutter will + // never meet. + NSAssert([_primaryResponders count] >= 0, @"At least one primary responder must be added."); + + __weak __typeof__(self) weakSelf = self; + __block int unreplied = [_primaryResponders count]; + __block BOOL anyHandled = false; + FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) { + unreplied -= 1; + NSAssert(unreplied >= 0, @"More primary responders replied than possible."); + anyHandled = anyHandled || handled; + if (unreplied == 0 && !anyHandled) { + [weakSelf dispatchToSecondaryResponders:event]; + } + }; + + for (id responder in _primaryResponders) { + [responder handleEvent:event callback:replyCallback]; + } } -- (void)dispatchToAdditionalHandlers:(NSEvent*)event { - for (id responder in _additionalKeyHandlers) { +#pragma mark - Private + +- (void)dispatchToSecondaryResponders:(NSEvent*)event { + for (id responder in _secondaryResponders) { if ([responder handleKeyEvent:event]) { return; } @@ -68,33 +101,4 @@ - (void)dispatchToAdditionalHandlers:(NSEvent*)event { } } -- (void)handleEvent:(nonnull NSEvent*)event { - // Be sure to add a handling method in propagateKeyEvent if you allow more - // event types here. - if (event.type != NSEventTypeKeyDown && event.type != NSEventTypeKeyUp && - event.type != NSEventTypeFlagsChanged) { - return; - } - // Having no primary responders require extra logic, but since Flutter adds - // all primary responders in hard-code, this is a situation that Flutter will - // never meet. - NSAssert([_keyHandlers count] >= 0, @"At least one primary responder must be added."); - - __weak __typeof__(self) weakSelf = self; - __block int unreplied = [_keyHandlers count]; - __block BOOL anyHandled = false; - FlutterAsyncKeyCallback replyCallback = ^(BOOL handled) { - unreplied -= 1; - NSAssert(unreplied >= 0, @"More primary responders replied than possible."); - anyHandled = anyHandled || handled; - if (unreplied == 0 && !anyHandled) { - [weakSelf dispatchToAdditionalHandlers:event]; - } - }; - - for (id handler in _keyHandlers) { - [handler handleEvent:event callback:replyCallback]; - } -} - @end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm index bd4bc6bca1682..3d0589dd1c4ff 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm @@ -99,11 +99,11 @@ id checkKeyDownEvent(unsigned short keyCode) { ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] nextResponderShouldThrowOnKeyUp]); } -TEST(FlutterKeyboardManagerUnittests, SingleAsyncHandler) { +TEST(FlutterKeyboardManagerUnittests, SinglePrimaryResponder) { ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singlePrimaryResponder]); } -TEST(FlutterKeyboardManagerUnittests, DoubleAsyncHandlers) { +TEST(FlutterKeyboardManagerUnittests, DoublePrimaryResponder) { ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] doublePrimaryResponder]); } @@ -200,7 +200,7 @@ - (bool)doublePrimaryResponder { [callbacks1 removeAllObjects]; [callbacks2 removeAllObjects]; - // Case: Both handlers report FALSE. + // Case: Both responders report FALSE. [manager handleEvent:flutter::testing::keyDownEvent(0x50)]; EXPECT_EQ([callbacks1 count], 1u); EXPECT_EQ([callbacks2 count], 1u); From 88957a7019820bcb84692222b980c30ffee72d04 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 11 Mar 2021 17:34:56 -0800 Subject: [PATCH 38/46] Format --- .../FlutterEmbedderKeyResponderUnittests.mm | 156 ++++++++++-------- 1 file changed, 83 insertions(+), 73 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm index ddd51f5df40ca..e17ba27463c8a 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm @@ -145,9 +145,9 @@ - (void)dealloc { last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 123.0f, 0x100, @"a", @"a", FALSE, 0) - callback:^(BOOL handled) { - last_handled = handled; - }]; + callback:^(BOOL handled) { + last_handled = handled; + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -167,9 +167,9 @@ - (void)dealloc { last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) - callback:^(BOOL handled) { - last_handled = handled; - }]; + callback:^(BOOL handled) { + last_handled = handled; + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -188,9 +188,9 @@ - (void)dealloc { last_handled = TRUE; [responder handleEvent:keyEvent(NSEventTypeKeyUp, 124.0f, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled) { - last_handled = handled; - }]; + callback:^(BOOL handled) { + last_handled = handled; + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -221,9 +221,10 @@ - (void)dealloc { userData:user_data]]; }]; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x80140, @"", @"", FALSE, kKeyCodeAltRight) - callback:^(BOOL handled){ - }]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x80140, @"", @"", FALSE, kKeyCodeAltRight) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -236,8 +237,8 @@ - (void)dealloc { [events removeAllObjects]; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x80140, @"∑", @"w", FALSE, kKeyCodeKeyW) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -250,8 +251,8 @@ - (void)dealloc { [events removeAllObjects]; [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeAltRight) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -264,8 +265,8 @@ - (void)dealloc { [events removeAllObjects]; [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"w", @"w", FALSE, kKeyCodeKeyW) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -299,9 +300,9 @@ - (void)dealloc { last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled) { - last_handled = handled; - }]; + callback:^(BOOL handled) { + last_handled = handled; + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -318,18 +319,18 @@ - (void)dealloc { last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled) { - last_handled = handled; - }]; + callback:^(BOOL handled) { + last_handled = handled; + }]; EXPECT_EQ([events count], 0u); EXPECT_EQ(last_handled, TRUE); last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled) { - last_handled = handled; - }]; + callback:^(BOOL handled) { + last_handled = handled; + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -362,9 +363,9 @@ - (void)dealloc { }]; [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 123.0f, 0x20104, @"", @"", FALSE, - kKeyCodeShiftRight) - callback:^(BOOL handled){ - }]; + kKeyCodeShiftRight) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -379,8 +380,8 @@ - (void)dealloc { [events removeAllObjects]; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -394,8 +395,8 @@ - (void)dealloc { [events removeAllObjects]; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x20104, @"A", @"A", TRUE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -408,9 +409,10 @@ - (void)dealloc { [events removeAllObjects]; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) - callback:^(BOOL handled){ - }]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -424,8 +426,8 @@ - (void)dealloc { [events removeAllObjects]; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", TRUE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -438,8 +440,8 @@ - (void)dealloc { [events removeAllObjects]; [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -475,8 +477,8 @@ - (void)dealloc { // Numpad 1 [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x200100, @"1", @"1", FALSE, kKeyCodeNumpad1) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -508,8 +510,8 @@ - (void)dealloc { // KeyA [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -541,8 +543,8 @@ - (void)dealloc { // Numpad 1 [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x220102, @"1", @"1", FALSE, kKeyCodeNumpad1) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -556,9 +558,10 @@ - (void)dealloc { [events removeAllObjects]; // F1 - [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x820102, @"\uF704", @"\uF704", FALSE, kKeyCodeF1) - callback:^(BOOL handled){ - }]; + [responder + handleEvent:keyEvent(NSEventTypeKeyUp, 0x820102, @"\uF704", @"\uF704", FALSE, kKeyCodeF1) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -573,8 +576,8 @@ - (void)dealloc { // KeyA [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x20102, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:^(BOOL handled){ - }]; + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -588,9 +591,10 @@ - (void)dealloc { [events removeAllObjects]; // ShiftLeft - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:^(BOOL handled){ - }]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -664,9 +668,10 @@ - (void)dealloc { [events removeAllObjects]; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) - callback:^(BOOL handled){ - }]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + callback:^(BOOL handled){ + }]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -733,8 +738,9 @@ - (void)dealloc { EXPECT_EQ(last_handled, TRUE); last_handled = FALSE; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:keyEventCallback]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -756,8 +762,9 @@ - (void)dealloc { // Out: last_handled = FALSE; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) - callback:keyEventCallback]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftLeft) + callback:keyEventCallback]; EXPECT_EQ([events count], 0u); EXPECT_EQ(last_handled, TRUE); @@ -787,8 +794,9 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) - callback:keyEventCallback]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + callback:keyEventCallback]; EXPECT_EQ([events count], 1u); event = [events lastObject].data; @@ -849,8 +857,9 @@ - (void)dealloc { [events removeAllObjects]; last_handled = FALSE; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) - callback:keyEventCallback]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeShiftRight) + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); event = [events firstObject].data; @@ -898,7 +907,7 @@ - (void)dealloc { last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x20102, @"A", @"A", FALSE, kKeyCodeKeyA) - callback:keyEventCallback]; + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); event = [events firstObject].data; @@ -925,7 +934,7 @@ - (void)dealloc { last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) - callback:keyEventCallback]; + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); event = [events firstObject].data; @@ -970,8 +979,9 @@ - (void)dealloc { // In: CapsLock down // Out: CapsLock down & *CapsLock Up last_handled = FALSE; - [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x10100, @"", @"", FALSE, kKeyCodeCapsLock) - callback:keyEventCallback]; + [responder + handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x10100, @"", @"", FALSE, kKeyCodeCapsLock) + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -1001,7 +1011,7 @@ - (void)dealloc { // Out: CapsLock down & *CapsLock Up last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) - callback:keyEventCallback]; + callback:keyEventCallback]; EXPECT_EQ([events count], 2u); @@ -1048,7 +1058,7 @@ - (void)dealloc { // Out: last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeFlagsChanged, 0x100, @"", @"", FALSE, kKeyCodeCapsLock) - callback:keyEventCallback]; + callback:keyEventCallback]; EXPECT_EQ([events count], 0u); EXPECT_EQ(last_handled, TRUE); @@ -1073,7 +1083,7 @@ - (void)dealloc { last_handled = FALSE; [responder handleEvent:keyEvent(NSEventTypeKeyDown, 0x10100, @"A", @"a", FALSE, kKeyCodeKeyA) - callback:keyEventCallback]; + callback:keyEventCallback]; EXPECT_EQ([events count], 3u); From 4618dcdcde6a43a9f9422377e3fa6f1a82c43a65 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 11 Mar 2021 17:57:10 -0800 Subject: [PATCH 39/46] Compile --- .../Source/FlutterEmbedderKeyResponder.mm | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 112befedd844d..c90badca91e23 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -265,7 +265,7 @@ @interface FlutterEmbedderKeyResponder () * Its values are |responseId|s, and keys are the callback that was received * along with the event. */ -@property(nonatomic) NSMutableDictionary* pendingResponses; +@property(nonatomic) NSMutableDictionary* pendingResponses; /** * Compare the last modifier flags and the current, and dispatch synthesized @@ -290,7 +290,7 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; * Send an event to the framework, expecting its response. */ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event - callback:(nonnull FlutterKeyHandlerCallback)callback; + callback:(nonnull FlutterAsyncKeyCallback)callback; /** * Send a CapsLock down event, then a CapsLock up event. @@ -300,7 +300,7 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event * synthesized. */ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - callback:(FlutterKeyHandlerCallback)downCallback; + callback:(FlutterAsyncKeyCallback)downCallback; /** * Send a key event for a modifier key. @@ -310,30 +310,30 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - (void)sendModifierEventOfType:(BOOL)shouldDown timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode - callback:(nullable FlutterKeyHandlerCallback)callback; + callback:(nullable FlutterAsyncKeyCallback)callback; /** * Processes a down event. */ - (void)handleDownEvent:(nonnull NSEvent*)event - callback:(nonnull FlutterKeyHandlerCallback)callback; + callback:(nonnull FlutterAsyncKeyCallback)callback; /** * Processes an up event. */ -- (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyHandlerCallback)callback; +- (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterAsyncKeyCallback)callback; /** * Processes an up event. */ - (void)handleCapsLockEvent:(nonnull NSEvent*)event - callback:(nonnull FlutterKeyHandlerCallback)callback; + callback:(nonnull FlutterAsyncKeyCallback)callback; /** * Processes a flags changed event, where modifier keys are pressed or released. */ - (void)handleFlagEvent:(nonnull NSEvent*)event - callback:(nonnull FlutterKeyHandlerCallback)callback; + callback:(nonnull FlutterAsyncKeyCallback)callback; /** * Processes the response from a framework. @@ -399,7 +399,7 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { } - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event - callback:(FlutterKeyHandlerCallback)callback { + callback:(FlutterAsyncKeyCallback)callback { _responseId += 1; uint64_t responseId = _responseId; FlutterKeyPendingResponse* pending = @@ -410,7 +410,7 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event } - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - callback:(FlutterKeyHandlerCallback)downCallback { + callback:(FlutterAsyncKeyCallback)downCallback { // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on // even taps and odd taps. A CapsLock down or CapsLock up should always be // converted to a down *and* an up, and the up should always be a synthesized @@ -438,7 +438,7 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - (void)sendModifierEventOfType:(BOOL)shouldDown timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode - callback:(FlutterKeyHandlerCallback)callback { + callback:(FlutterAsyncKeyCallback)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode); uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey); if (physicalKey == 0 || logicalKey == 0) { @@ -463,7 +463,7 @@ - (void)sendModifierEventOfType:(BOOL)shouldDown } } -- (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)handleDownEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; @@ -492,7 +492,7 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } -- (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)handleUpEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@", event.keyCode, event.characters, event.charactersIgnoringModifiers); [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; @@ -522,7 +522,7 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callba [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } -- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock timestamp:event.timestamp]; @@ -535,7 +535,7 @@ - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback) } } -- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { NSNumber* targetModifierFlagObj = keyCodeToModifierFlag[@(event.keyCode)]; NSUInteger targetModifierFlag = targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue]; @@ -571,7 +571,7 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)call callback:callback]; } -- (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback { +- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { // The conversion algorithm relies on a non-nil callback to properly compute // `synthesized`. If someday callback is allowed to be nil, make a dummy empty // callback instead. @@ -595,7 +595,7 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterKeyHandlerCallback)callback } - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { - FlutterKeyHandlerCallback callback = _pendingResponses[@(responseId)]; + FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)]; callback(handled); [_pendingResponses removeObjectForKey:@(responseId)]; } From fb51a67026806332d41423b6e590d9cb3938fb3b Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 11 Mar 2021 18:29:44 -0800 Subject: [PATCH 40/46] FlutterKeyCallbackGuard --- .../Source/FlutterEmbedderKeyResponder.mm | 127 ++++++++++++++---- 1 file changed, 100 insertions(+), 27 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index c90badca91e23..d6407c5c08a69 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -159,7 +159,7 @@ static NSUInteger computeModifierFlagOfInterestMask() { */ void HandleResponse(bool handled, void* user_data); -/* +/** * Converts NSEvent.characters to a C-string for FlutterKeyEvent. */ const char* getEventString(NSString* characters) { @@ -184,7 +184,8 @@ static NSUInteger computeModifierFlagOfInterestMask() { } } // namespace -/* The invocation context for |HandleResponse|, wrapping +/** + * The invocation context for |HandleResponse|, wrapping * |FlutterEmbedderKeyResponder.handleResponse|. * * The embedder functions only accept C-functions as callbacks, as well as an @@ -219,6 +220,77 @@ - (instancetype)initWithHandler:(FlutterEmbedderKeyResponder*)responder } @end +/** + * Guards a |FlutterAsyncKeyCallback| to make sure it's handled exactly once + * throughout |FlutterEmbedderKeyResponder.handleEvent|. + * + * A callback can either be handled with |pendTo:withId:|, or with |resolveTo:|. + * Either way, the callback can not be handled again, or an assertion will be + * thrown. + */ +@interface FlutterKeyCallbackGuard : NSObject +- (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback; + +/** + * Handle the callback by storing it to pending responses. + */ +- (void)pendTo:(nonnull NSMutableDictionary*)pendingResponses + withId:(uint64_t)responseId; + +/** + * Handle the callback by calling it with a result. + */ +- (void)resolveTo:(BOOL)handled; + +@property(nonatomic) BOOL handled; +/** + * A string indicating how the callback is handled. + * + * Only set in debug mode. Nil in release mode, or if the callback has not been + * handled. + */ +@property(nonatomic) NSString* debugHandleSource; +@end + +@implementation FlutterKeyCallbackGuard { + // The callback is declared in the implemnetation block to avoid being + // accessed directly. + FlutterAsyncKeyCallback _callback; +} +- (nonnull instancetype)initWithCallback:(FlutterAsyncKeyCallback)callback { + self = [super init]; + if (self != nil) { + _callback = callback; + _handled = FALSE; + } + return self; +} + +- (void)pendTo:(nonnull NSMutableDictionary*)pendingResponses + withId:(uint64_t)responseId { + NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource); + if (_handled) { + return; + } + pendingResponses[@(responseId)] = _callback; + _handled = TRUE; + NSAssert( + ((_debugHandleSource = [NSString stringWithFormat:@"pending event %llu", responseId]), TRUE), + @""); +} + +- (void)resolveTo:(BOOL)handled { + NSAssert(!_handled, @"This callback has been handled by %@.", _debugHandleSource); + if (_handled) { + return; + } + _callback(handled); + _handled = TRUE; + NSAssert(((_debugHandleSource = [NSString stringWithFormat:@"resolved with %d", _handled]), TRUE), + @""); +} +@end + @interface FlutterEmbedderKeyResponder () /** @@ -290,7 +362,7 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey; * Send an event to the framework, expecting its response. */ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event - callback:(nonnull FlutterAsyncKeyCallback)callback; + callback:(nonnull FlutterKeyCallbackGuard*)callback; /** * Send a CapsLock down event, then a CapsLock up event. @@ -300,7 +372,7 @@ - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event * synthesized. */ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - callback:(FlutterAsyncKeyCallback)downCallback; + callback:(nullable FlutterKeyCallbackGuard*)downCallback; /** * Send a key event for a modifier key. @@ -310,30 +382,28 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - (void)sendModifierEventOfType:(BOOL)shouldDown timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode - callback:(nullable FlutterAsyncKeyCallback)callback; + callback:(nullable FlutterKeyCallbackGuard*)callback; /** * Processes a down event. */ -- (void)handleDownEvent:(nonnull NSEvent*)event - callback:(nonnull FlutterAsyncKeyCallback)callback; +- (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; /** * Processes an up event. */ -- (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterAsyncKeyCallback)callback; +- (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; /** * Processes an up event. */ - (void)handleCapsLockEvent:(nonnull NSEvent*)event - callback:(nonnull FlutterAsyncKeyCallback)callback; + callback:(nonnull FlutterKeyCallbackGuard*)callback; /** * Processes a flags changed event, where modifier keys are pressed or released. */ -- (void)handleFlagEvent:(nonnull NSEvent*)event - callback:(nonnull FlutterAsyncKeyCallback)callback; +- (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; /** * Processes the response from a framework. @@ -399,18 +469,18 @@ - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { } - (void)sendPrimaryFlutterEvent:(const FlutterKeyEvent&)event - callback:(FlutterAsyncKeyCallback)callback { + callback:(FlutterKeyCallbackGuard*)callback { _responseId += 1; uint64_t responseId = _responseId; FlutterKeyPendingResponse* pending = [[FlutterKeyPendingResponse alloc] initWithHandler:self responseId:responseId]; - _pendingResponses[@(responseId)] = callback; + [callback pendTo:_pendingResponses withId:responseId]; // The `__bridge_retained` here is matched by `__bridge_transfer` in HandleResponse. _sendEvent(event, HandleResponse, (__bridge_retained void*)pending); } - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - callback:(FlutterAsyncKeyCallback)downCallback { + callback:(FlutterKeyCallbackGuard*)downCallback { // MacOS sends a down *or* an up when CapsLock is tapped, alternatively on // even taps and odd taps. A CapsLock down or CapsLock up should always be // converted to a down *and* an up, and the up should always be a synthesized @@ -438,12 +508,12 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp - (void)sendModifierEventOfType:(BOOL)shouldDown timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode - callback:(FlutterAsyncKeyCallback)callback { + callback:(FlutterKeyCallbackGuard*)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(keyCode); uint64_t logicalKey = GetLogicalKeyForModifier(keyCode, physicalKey); if (physicalKey == 0 || logicalKey == 0) { NSLog(@"Unrecognized modifier key: keyCode 0x%hx, physical key 0x%llx", keyCode, physicalKey); - callback(TRUE); + [callback resolveTo:TRUE]; return; } FlutterKeyEvent flutterEvent = { @@ -463,7 +533,7 @@ - (void)sendModifierEventOfType:(BOOL)shouldDown } } -- (void)handleDownEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { +- (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); uint64_t logicalKey = GetLogicalKeyForEvent(event, physicalKey); [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; @@ -471,7 +541,7 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callba bool isARepeat = event.isARepeat; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; if (pressedLogicalKey != nil && !isARepeat) { - callback(TRUE); + [callback resolveTo:TRUE]; return; } bool isSynthesized = false; @@ -492,7 +562,7 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callba [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } -- (void)handleUpEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { +- (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { NSAssert(!event.isARepeat, @"Unexpected repeated Up event: keyCode %d, char %@, charIM %@", event.keyCode, event.characters, event.charactersIgnoringModifiers); [self synchronizeModifiers:event.modifierFlags ignoringFlags:0 timestamp:event.timestamp]; @@ -505,7 +575,7 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback @"keyCode %d, char %@, charIM %@, previously pressed logical 0x%llx", event.keyCode, event.characters, event.charactersIgnoringModifiers, [pressedLogicalKey unsignedLongLongValue]); - callback(TRUE); + [callback resolveTo:TRUE]; return; } [self updateKey:physicalKey asPressed:0]; @@ -522,7 +592,7 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } -- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { +- (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock timestamp:event.timestamp]; @@ -531,11 +601,11 @@ - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)ca [self sendCapsLockTapWithTimestamp:event.timestamp callback:callback]; _lastModifierFlags = _lastModifierFlags ^ NSEventModifierFlagCapsLock; } else { - callback(TRUE); + [callback resolveTo:TRUE]; } } -- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { +- (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callback { NSNumber* targetModifierFlagObj = keyCodeToModifierFlag[@(event.keyCode)]; NSUInteger targetModifierFlag = targetModifierFlagObj == nil ? 0 : [targetModifierFlagObj unsignedLongValue]; @@ -561,7 +631,7 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callba BOOL shouldBePressed = (event.modifierFlags & targetModifierFlag) != 0; if (lastTargetPressed == shouldBePressed) { - callback(TRUE); + [callback resolveTo:TRUE]; return; } _lastModifierFlags = _lastModifierFlags ^ targetModifierFlag; @@ -576,19 +646,22 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { // `synthesized`. If someday callback is allowed to be nil, make a dummy empty // callback instead. NSAssert(callback != nil, @"The callback must not be nil."); + FlutterKeyCallbackGuard* guardedCallback = + [[FlutterKeyCallbackGuard alloc] initWithCallback:callback]; switch (event.type) { case NSEventTypeKeyDown: - [self handleDownEvent:event callback:callback]; + [self handleDownEvent:event callback:guardedCallback]; break; case NSEventTypeKeyUp: - [self handleUpEvent:event callback:callback]; + [self handleUpEvent:event callback:guardedCallback]; break; case NSEventTypeFlagsChanged: - [self handleFlagEvent:event callback:callback]; + [self handleFlagEvent:event callback:guardedCallback]; break; default: NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); } + NSAssert(guardedCallback.handled, @"The callback is returned without being handled."); NSAssert(_lastModifierFlags == (event.modifierFlags & _modifierFlagOfInterestMask), @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", _lastModifierFlags, (event.modifierFlags & _modifierFlagOfInterestMask)); From 8dd318ed50b2415d4b0c1cc0cdd9b5a8d7438a08 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 11 Mar 2021 18:34:48 -0800 Subject: [PATCH 41/46] init check --- .../Source/FlutterChannelKeyResponder.mm | 6 +- .../Source/FlutterEmbedderKeyResponder.mm | 58 ++++++++++--------- .../Source/FlutterKeyboardManager.mm | 8 ++- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm index f0483a129e2f5..392986e0d01af 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm @@ -31,8 +31,10 @@ @implementation FlutterChannelKeyResponder - (nonnull instancetype)initWithChannel:(nonnull FlutterBasicMessageChannel*)channel { self = [super init]; - _channel = channel; - _previouslyPressedFlags = 0; + if (self != nil) { + _channel = channel; + _previouslyPressedFlags = 0; + } return self; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index d6407c5c08a69..74339e6b499d0 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -214,8 +214,10 @@ @implementation FlutterKeyPendingResponse - (instancetype)initWithHandler:(FlutterEmbedderKeyResponder*)responder responseId:(uint64_t)responseId { self = [super init]; - _responder = responder; - _responseId = responseId; + if (self != nil) { + _responder = responder; + _responseId = responseId; + } return self; } @end @@ -427,6 +429,32 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent return self; } +- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { + // The conversion algorithm relies on a non-nil callback to properly compute + // `synthesized`. If someday callback is allowed to be nil, make a dummy empty + // callback instead. + NSAssert(callback != nil, @"The callback must not be nil."); + FlutterKeyCallbackGuard* guardedCallback = + [[FlutterKeyCallbackGuard alloc] initWithCallback:callback]; + switch (event.type) { + case NSEventTypeKeyDown: + [self handleDownEvent:event callback:guardedCallback]; + break; + case NSEventTypeKeyUp: + [self handleUpEvent:event callback:guardedCallback]; + break; + case NSEventTypeFlagsChanged: + [self handleFlagEvent:event callback:guardedCallback]; + break; + default: + NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); + } + NSAssert(guardedCallback.handled, @"The callback is returned without being handled."); + NSAssert(_lastModifierFlags == (event.modifierFlags & _modifierFlagOfInterestMask), + @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", + _lastModifierFlags, (event.modifierFlags & _modifierFlagOfInterestMask)); +} + #pragma mark - Private - (void)synchronizeModifiers:(NSUInteger)currentFlags @@ -641,32 +669,6 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb callback:callback]; } -- (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { - // The conversion algorithm relies on a non-nil callback to properly compute - // `synthesized`. If someday callback is allowed to be nil, make a dummy empty - // callback instead. - NSAssert(callback != nil, @"The callback must not be nil."); - FlutterKeyCallbackGuard* guardedCallback = - [[FlutterKeyCallbackGuard alloc] initWithCallback:callback]; - switch (event.type) { - case NSEventTypeKeyDown: - [self handleDownEvent:event callback:guardedCallback]; - break; - case NSEventTypeKeyUp: - [self handleUpEvent:event callback:guardedCallback]; - break; - case NSEventTypeFlagsChanged: - [self handleFlagEvent:event callback:guardedCallback]; - break; - default: - NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); - } - NSAssert(guardedCallback.handled, @"The callback is returned without being handled."); - NSAssert(_lastModifierFlags == (event.modifierFlags & _modifierFlagOfInterestMask), - @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", - _lastModifierFlags, (event.modifierFlags & _modifierFlagOfInterestMask)); -} - - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId { FlutterAsyncKeyCallback callback = _pendingResponses[@(responseId)]; callback(handled); diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 4a2008c55ac59..cdb9704f96b0b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -29,9 +29,11 @@ @implementation FlutterKeyboardManager - (nonnull instancetype)initWithOwner:(NSResponder*)weakOwner { self = [super init]; - _owner = weakOwner; - _primaryResponders = [[NSMutableArray alloc] init]; - _secondaryResponders = [[NSMutableArray alloc] init]; + if (self != nil) { + _owner = weakOwner; + _primaryResponders = [[NSMutableArray alloc] init]; + _secondaryResponders = [[NSMutableArray alloc] init]; + } return self; } From 6c6bb8bff13252d0ac9ce64e7f7f0b3f73eac582 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 11 Mar 2021 19:20:12 -0800 Subject: [PATCH 42/46] Fix review --- ci/licenses_golden/licenses_flutter | 14 ++-- .../Source/FlutterEmbedderKeyResponder.mm | 83 +++++++++++++------ .../FlutterEmbedderKeyResponderUnittests.mm | 28 +++++-- .../framework/Source/FlutterKeyboardManager.h | 4 +- 4 files changed, 87 insertions(+), 42 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ce51d52b9aeb0..8c8658b626594 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1105,9 +1105,15 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBacki FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStore.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterBackingStoreData.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponderUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderExternalTextureUnittests.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngineTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h @@ -1122,14 +1128,8 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCom FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterGLCompositorUnittests.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterIOSurfaceHolder.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.h -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponder.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterChannelKeyResponderUnittests.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.h -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm -FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyPrimaryResponder.h +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeySecondaryResponder.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManagerUnittests.mm diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 74339e6b499d0..65b17036cde37 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -21,6 +21,8 @@ * * lowestSetBit(0) returns 0. */ static NSUInteger lowestSetBit(NSUInteger bitmask) { + // This utilizes property of two's complement (negation), which propagates a + // carry bit from LSB to the lowest set bit. return bitmask & -bitmask; } @@ -47,7 +49,14 @@ static bool IsUnprintableKey(NSUInteger length, NSString* label) { } /** - * Returns a key code composited by a base key and a plane. + * Returns a key code composed with a base key and a plane. + * + * Examples of unprintable keys are "NSUpArrowFunctionKey = 0xF700" or + * "NSHomeFunctionKey = 0xF729". + * + * See + * https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc + * for more information. */ static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) { return plane | (baseKey & kValueMask); @@ -57,10 +66,10 @@ static uint64_t KeyOfPlane(uint64_t baseKey, uint64_t plane) { * Returns the physical key for a key code. */ static uint64_t GetPhysicalKeyForKeyCode(unsigned short keyCode) { - NSNumber* physicalKeyKey = [keyCodeToPhysicalKey objectForKey:@(keyCode)]; - if (physicalKeyKey == nil) + NSNumber* physicalKey = [keyCodeToPhysicalKey objectForKey:@(keyCode)]; + if (physicalKey == nil) return 0; - return physicalKeyKey.unsignedLongLongValue; + return physicalKey.unsignedLongLongValue; } /** @@ -73,6 +82,21 @@ static uint64_t GetLogicalKeyForModifier(unsigned short keyCode, uint64_t hidCod return KeyOfPlane(hidCode, kHidPlane); } +/** + * Converts upper letters to lower letters in ASCII, and returns as-is + * otherwise. + * + * Independent of locale. + */ +static uint64_t toLower(uint64_t ch) { + const uint64_t lowerA = 0x61; + const uint64_t upperA = 0x41; + const uint64_t upperZ = 0x5a; + if (ch >= upperA && ch <= upperZ) + return ch + (lowerA - upperA); + return ch; +} + /** * Returns the logical key of a KeyUp or KeyDown event. * @@ -87,7 +111,7 @@ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { NSString* keyLabel = event.charactersIgnoringModifiers; NSUInteger keyLabelLength = [keyLabel length]; // If this key is printable, generate the logical key from its Unicode - // value. Control keys such as ESC, CRTL, and SHIFT are not printable. HOME, + // value. Control keys such as ESC, CTRL, and SHIFT are not printable. HOME, // DEL, arrow keys, and function keys are considered modifier function keys, // which generate invalid Unicode scalar values. if (keyLabelLength != 0 && !IsControlCharacter(keyLabelLength, keyLabel) && @@ -96,8 +120,7 @@ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { // length, limit to a maximum of two Unicode scalar values. It is unlikely // that a keyboard would produce a code point bigger than 32 bits, but it is // still worth defending against this case. - NSCAssert((keyLabelLength < 2), - @"Unexpected long key label: |%@|. Please report this to Flutter.", keyLabel); + NSCAssert((keyLabelLength < 2), @"Unexpected long key label: |%@|.", keyLabel); uint64_t codeUnit = (uint64_t)[keyLabel characterAtIndex:0]; if (keyLabelLength == 2) { @@ -105,10 +128,7 @@ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { codeUnit = (codeUnit << 16) | secondCode; } if (codeUnit < 256) { - if (isupper(codeUnit)) { - return tolower(codeUnit); - } - return codeUnit; + return toLower(codeUnit); } return KeyOfPlane(codeUnit, kUnicodePlane); } @@ -139,8 +159,8 @@ static double GetFlutterTimestampFrom(NSTimeInterval timestamp) { /** * Compute |modifierFlagOfInterestMask| out of |keyCodeToModifierFlag|. * - * This equals to the bitwise-or of all values of |keyCodeToModifierFlag| as well as - * NSEventModifierFlagCapsLock. + * This is equal to the bitwise-or of all values of |keyCodeToModifierFlag| as + * well as NSEventModifierFlagCapsLock. */ static NSUInteger computeModifierFlagOfInterestMask() { __block NSUInteger modifierFlagOfInterestMask = NSEventModifierFlagCapsLock; @@ -311,8 +331,14 @@ @interface FlutterEmbedderKeyResponder () @property(nonatomic) NSMutableDictionary* pressingRecords; /** - * A constant mask for NSEvent.modifierFlags that Flutter tries to keep - * synchronized on. + * A constant mask for NSEvent.modifierFlags that Flutter keep synchronized + * with. + * + * Flutter keeps track of the last |modifierFlags| and compare 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. */ @@ -381,7 +407,7 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp * * If callback is nil, then the event is synthesized. */ -- (void)sendModifierEventOfType:(BOOL)shouldDown +- (void)sendModifierEventOfType:(BOOL)isDownEvent timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode callback:(nullable FlutterKeyCallbackGuard*)callback; @@ -408,7 +434,7 @@ - (void)handleCapsLockEvent:(nonnull NSEvent*)event - (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; /** - * Processes the response from a framework. + * Processes the response from the framework. */ - (void)handleResponse:(BOOL)handled forId:(uint64_t)responseId; @@ -479,8 +505,8 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags if (keyCode == nil) { continue; } - BOOL shouldDown = (currentFlagsOfInterest & currentFlag) != 0; - [self sendModifierEventOfType:shouldDown + BOOL isDownEvent = (currentFlagsOfInterest & currentFlag) != 0; + [self sendModifierEventOfType:isDownEvent timestamp:timestamp keyCode:[keyCode unsignedShortValue] callback:nil]; @@ -533,7 +559,7 @@ - (void)sendCapsLockTapWithTimestamp:(NSTimeInterval)timestamp _sendEvent(flutterEvent, nullptr, nullptr); } -- (void)sendModifierEventOfType:(BOOL)shouldDown +- (void)sendModifierEventOfType:(BOOL)isDownEvent timestamp:(NSTimeInterval)timestamp keyCode:(unsigned short)keyCode callback:(FlutterKeyCallbackGuard*)callback { @@ -547,13 +573,13 @@ - (void)sendModifierEventOfType:(BOOL)shouldDown FlutterKeyEvent flutterEvent = { .struct_size = sizeof(FlutterKeyEvent), .timestamp = GetFlutterTimestampFrom(timestamp), - .type = shouldDown ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp, + .type = isDownEvent ? kFlutterKeyEventTypeDown : kFlutterKeyEventTypeUp, .physical = physicalKey, .logical = logicalKey, .character = nil, .synthesized = callback == nil, }; - [self updateKey:physicalKey asPressed:shouldDown ? logicalKey : 0]; + [self updateKey:physicalKey asPressed:isDownEvent ? logicalKey : 0]; if (callback != nil) { [self sendPrimaryFlutterEvent:flutterEvent callback:callback]; } else { @@ -569,6 +595,10 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb bool isARepeat = event.isARepeat; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; if (pressedLogicalKey != nil && !isARepeat) { + // Normally the key up events won't be missed since macOS always send the + // key up event to the window where the corresponding key down occurred. + // However this might happen in add-to-app scenarios if the focus is changed + // from the native view to the Flutter view amid the key tap. [callback resolveTo:TRUE]; return; } @@ -598,11 +628,10 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callbac uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; if (pressedLogicalKey == nil) { - NSAssert(FALSE, - @"Received key up event that has not been pressed: " - @"keyCode %d, char %@, charIM %@, previously pressed logical 0x%llx", - event.keyCode, event.characters, event.charactersIgnoringModifiers, - [pressedLogicalKey unsignedLongLongValue]); + // Normally the key up events won't be missed since macOS always send the + // key up event to the window where the corresponding key down occurred. + // However this might happen in add-to-app scenarios if the focus is changed + // from the native view to the Flutter view amid the key tap. [callback resolveTo:TRUE]; return; } diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm index e17ba27463c8a..67974abcb6952 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponderUnittests.mm @@ -279,12 +279,6 @@ - (void)dealloc { [events removeAllObjects]; } -// In very rare occasions, the up event can be missed. Test that duplicate down events -// in these situations are ignored. -// -// MacOS usually matches down and up events perfectly since it tracks key taps to a window. -// Unmatched events can occur when you hold a key, then Ctrl-clicks desktop to trigger a -// menu, and release key. TEST(FlutterEmbedderKeyResponderUnittests, IgnoreDuplicateDownEvent) { __block NSMutableArray* events = [[NSMutableArray alloc] init]; __block BOOL last_handled = TRUE; @@ -346,6 +340,28 @@ - (void)dealloc { [events removeAllObjects]; } +TEST(FlutterEmbedderKeyResponderUnittests, IgnoreDuplicateUpEvent) { + __block NSMutableArray* events = [[NSMutableArray alloc] init]; + __block BOOL last_handled = TRUE; + + FlutterEmbedderKeyResponder* responder = [[FlutterEmbedderKeyResponder alloc] + initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback, + _Nullable _VoidPtr user_data) { + [events addObject:[[TestKeyEvent alloc] initWithEvent:&event + callback:callback + userData:user_data]]; + }]; + + last_handled = FALSE; + [responder handleEvent:keyEvent(NSEventTypeKeyUp, 0x100, @"a", @"a", FALSE, kKeyCodeKeyA) + callback:^(BOOL handled) { + last_handled = handled; + }]; + + EXPECT_EQ([events count], 0u); + EXPECT_EQ(last_handled, TRUE); +} + // Press L shift, A, then release L shift then A, on an US keyboard. // // This is special because the characters for the A key will change in this diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h index fae8da76ba473..922f1ad0721f4 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.h @@ -13,8 +13,8 @@ * A hub that manages how key events are dispatched to various Flutter key * responders, and whether the event is propagated to the next NSResponder. * - * This class can be added with one or more primary responders, as well as zero - * or more secondary responders. + * This class manage one or more primary responders, as well as zero or more + * secondary responders. * * An event that is received by |handleEvent| is first dispatched to *all* * primary resopnders. Each primary responder responds *ascynchronously* with a From eabdde49020b2467796d1a4d58705b68a85b7024 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 15 Mar 2021 17:27:53 -0700 Subject: [PATCH 43/46] Fix docs --- .../Source/FlutterEmbedderKeyResponder.mm | 46 ++++++++++--------- .../Source/FlutterKeyboardManager.mm | 3 ++ 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 65b17036cde37..d871f3659b199 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -127,10 +127,7 @@ static uint64_t GetLogicalKeyForEvent(NSEvent* event, uint64_t physicalKey) { uint64_t secondCode = (uint64_t)[keyLabel characterAtIndex:1]; codeUnit = (codeUnit << 16) | secondCode; } - if (codeUnit < 256) { - return toLower(codeUnit); - } - return KeyOfPlane(codeUnit, kUnicodePlane); + return KeyOfPlane(toLower(codeUnit), kUnicodePlane); } // Control keys like "backspace" and movement keys like arrow keys don't have @@ -189,15 +186,22 @@ static NSUInteger computeModifierFlagOfInterestMask() { unichar utf16Code = [characters characterAtIndex:0]; if (utf16Code >= 0xf700 && utf16Code <= 0xf7ff) { // Some function keys are assigned characters with codepoints from the - // private use area (see - // https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc). - // These characters are filtered out since they're unprintable. + // private use area. These characters are filtered out since they're + // unprintable. // - // Although the documentation claims to reserve 0xF700-0xF8FF, only up to - // 0xF747 is actually used. Here we choose to filter out 0xF700-0xF7FF - // section. The reason for keeping the 0xF800-0xF8FF section is because - // 0xF8FF is used for the "Apple logo" character (Option-Shift-K on US + // The official documentation reserves 0xF700-0xF8FF as private use area + // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc). + // But macOS seems to only use a reduced range of it. The official doc + // defines a few constants, all of which are within 0xF700-0xF747. + // (https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?language=objc). + // This mostly aligns with the experimentation result, except for 0xF8FF, + // which is used for the "Apple logo" character (Option-Shift-K on a US // keyboard.) + // + // We hereby assume that non-printable function keys are defined from + // 0xF700 upwards, and printable private keys are defined from 0xF8FF + // downwards. We want to keep the printable private keys, therefore we only + // filter out 0xF700-0xF7FF. return nullptr; } return [characters UTF8String]; @@ -331,10 +335,9 @@ @interface FlutterEmbedderKeyResponder () @property(nonatomic) NSMutableDictionary* pressingRecords; /** - * A constant mask for NSEvent.modifierFlags that Flutter keep synchronized - * with. + * A constant mask for NSEvent.modifierFlags that Flutter synchronizes with. * - * Flutter keeps track of the last |modifierFlags| and compare it with the + * 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 @@ -413,23 +416,23 @@ - (void)sendModifierEventOfType:(BOOL)isDownEvent callback:(nullable FlutterKeyCallbackGuard*)callback; /** - * Processes a down event. + * Processes a down event from the system. */ - (void)handleDownEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; /** - * Processes an up event. + * Processes an up event from the system. */ - (void)handleUpEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; /** - * Processes an up event. + * Processes an event from the system for the CapsLock key. */ - (void)handleCapsLockEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; /** - * Processes a flags changed event, where modifier keys are pressed or released. + * Processes a flags changed event from the system, where modifier keys are pressed or released. */ - (void)handleFlagEvent:(nonnull NSEvent*)event callback:(nonnull FlutterKeyCallbackGuard*)callback; @@ -457,8 +460,7 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { // The conversion algorithm relies on a non-nil callback to properly compute - // `synthesized`. If someday callback is allowed to be nil, make a dummy empty - // callback instead. + // `synthesized`. NSAssert(callback != nil, @"The callback must not be nil."); FlutterKeyCallbackGuard* guardedCallback = [[FlutterKeyCallbackGuard alloc] initWithCallback:callback]; @@ -595,7 +597,7 @@ - (void)handleDownEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb bool isARepeat = event.isARepeat; NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; if (pressedLogicalKey != nil && !isARepeat) { - // Normally the key up events won't be missed since macOS always send the + // Normally the key up events won't be missed since macOS always sends the // key up event to the window where the corresponding key down occurred. // However this might happen in add-to-app scenarios if the focus is changed // from the native view to the Flutter view amid the key tap. @@ -628,7 +630,7 @@ - (void)handleUpEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callbac uint64_t physicalKey = GetPhysicalKeyForKeyCode(event.keyCode); NSNumber* pressedLogicalKey = _pressingRecords[@(physicalKey)]; if (pressedLogicalKey == nil) { - // Normally the key up events won't be missed since macOS always send the + // Normally the key up events won't be missed since macOS always sends the // key up event to the window where the corresponding key down occurred. // However this might happen in add-to-app scenarios if the focus is changed // from the native view to the Flutter view amid the key tap. diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index cdb9704f96b0b..48ee53aff68b0 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -52,6 +52,9 @@ - (void)handleEvent:(nonnull NSEvent*)event { event.type != NSEventTypeFlagsChanged) { return; } + NSLog(@"Type %lu moF %lu winN %ld c %@ cIM %@ rep %d keyC %x", + (unsigned long)event.type, (unsigned long)event.modifierFlags, (long)event.windowNumber, event.characters, event.charactersIgnoringModifiers, + event.isARepeat, event.keyCode); // Having no primary responders require extra logic, but since Flutter adds // all primary responders in hard-code, this is a situation that Flutter will // never meet. From ad270a8bbf1d200774e042630369437e6521df24 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 15 Mar 2021 17:40:17 -0700 Subject: [PATCH 44/46] Doc for lastModifierFlags --- .../Source/FlutterEmbedderKeyResponder.mm | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index d871f3659b199..407dd9a46d491 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -348,11 +348,14 @@ @interface FlutterEmbedderKeyResponder () @property(nonatomic) NSUInteger modifierFlagOfInterestMask; /** - * The |NSEvent.modifierFlags| of the last received key event after masking - * with |modifierFlagOfInterestMask|. + * The modifier flags of the last received key event, excluding uninterested + * bits. * - * This should be kept synchronized with the corresponding keys of - * |pressingRecords|. This is used by |synchronizeModifiers| to quickly find + * 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. */ @property(nonatomic) NSUInteger lastModifierFlags; @@ -478,9 +481,9 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); } NSAssert(guardedCallback.handled, @"The callback is returned without being handled."); - NSAssert(_lastModifierFlags == (event.modifierFlags & _modifierFlagOfInterestMask), + NSAssert(_lastModifierFlags == event.modifierFlags, @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", - _lastModifierFlags, (event.modifierFlags & _modifierFlagOfInterestMask)); + _lastModifierFlags, event.modifierFlags); } #pragma mark - Private @@ -490,7 +493,7 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags timestamp:(NSTimeInterval)timestamp { const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask; - const NSUInteger lastFlagsOfInterest = _lastModifierFlags & updatingMask; + const NSUInteger lastFlagsOfInterest = _lastModifierFlags; NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest; if (flagDifference & NSEventModifierFlagCapsLock) { [self sendCapsLockTapWithTimestamp:timestamp callback:nil]; From 875606722d3f42a6a1bd34caf9bc5f5ff90e10c3 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 15 Mar 2021 17:43:15 -0700 Subject: [PATCH 45/46] Rename --- .../darwin/macos/framework/Source/FlutterKeyboardManager.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 48ee53aff68b0..8c0babffcc910 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -52,9 +52,9 @@ - (void)handleEvent:(nonnull NSEvent*)event { event.type != NSEventTypeFlagsChanged) { return; } - NSLog(@"Type %lu moF %lu winN %ld c %@ cIM %@ rep %d keyC %x", - (unsigned long)event.type, (unsigned long)event.modifierFlags, (long)event.windowNumber, event.characters, event.charactersIgnoringModifiers, - event.isARepeat, event.keyCode); + NSLog(@"Type %lu moF %lu winN %ld c %@ cIM %@ rep %d keyC %x", (unsigned long)event.type, + (unsigned long)event.modifierFlags, (long)event.windowNumber, event.characters, + event.charactersIgnoringModifiers, event.isARepeat, event.keyCode); // Having no primary responders require extra logic, but since Flutter adds // all primary responders in hard-code, this is a situation that Flutter will // never meet. From 5959edc6aeaa43f469fe48c4e759fc8bf0898df8 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Mon, 15 Mar 2021 19:43:24 -0700 Subject: [PATCH 46/46] Fix test --- .../Source/FlutterEmbedderKeyResponder.mm | 27 ++++++++++--------- .../Source/FlutterKeyboardManager.mm | 3 --- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm index 407dd9a46d491..5ae7e6fc5e853 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterEmbedderKeyResponder.mm @@ -358,7 +358,7 @@ @interface FlutterEmbedderKeyResponder () * This is used by |synchronizeModifiers| to quickly find * out modifier keys that are desynchronized. */ -@property(nonatomic) NSUInteger lastModifierFlags; +@property(nonatomic) NSUInteger lastModifierFlagsOfInterest; /** * A self-incrementing ID used to label key events sent to the framework. @@ -455,7 +455,7 @@ - (nonnull instancetype)initWithSendEvent:(FlutterSendEmbedderKeyEvent)sendEvent _pressingRecords = [NSMutableDictionary dictionary]; _pendingResponses = [NSMutableDictionary dictionary]; _responseId = 1; - _lastModifierFlags = 0; + _lastModifierFlagsOfInterest = 0; _modifierFlagOfInterestMask = computeModifierFlagOfInterestMask(); } return self; @@ -481,9 +481,9 @@ - (void)handleEvent:(NSEvent*)event callback:(FlutterAsyncKeyCallback)callback { NSAssert(false, @"Unexpected key event type: |%@|.", @(event.type)); } NSAssert(guardedCallback.handled, @"The callback is returned without being handled."); - NSAssert(_lastModifierFlags == event.modifierFlags, - @"The modifier flags are not properly updated: recorded 0x%lx, event 0x%lx", - _lastModifierFlags, event.modifierFlags); + NSAssert(_lastModifierFlagsOfInterest == (event.modifierFlags & _modifierFlagOfInterestMask), + @"The modifier flags are not properly updated: recorded 0x%lx, event with mask 0x%lx", + _lastModifierFlagsOfInterest, event.modifierFlags & _modifierFlagOfInterestMask); } #pragma mark - Private @@ -493,7 +493,7 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags timestamp:(NSTimeInterval)timestamp { const NSUInteger updatingMask = _modifierFlagOfInterestMask & ~ignoringFlags; const NSUInteger currentFlagsOfInterest = currentFlags & updatingMask; - const NSUInteger lastFlagsOfInterest = _lastModifierFlags; + const NSUInteger lastFlagsOfInterest = _lastModifierFlagsOfInterest & updatingMask; NSUInteger flagDifference = currentFlagsOfInterest ^ lastFlagsOfInterest; if (flagDifference & NSEventModifierFlagCapsLock) { [self sendCapsLockTapWithTimestamp:timestamp callback:nil]; @@ -516,7 +516,8 @@ - (void)synchronizeModifiers:(NSUInteger)currentFlags keyCode:[keyCode unsignedShortValue] callback:nil]; } - _lastModifierFlags = (_lastModifierFlags & ~updatingMask) | currentFlagsOfInterest; + _lastModifierFlagsOfInterest = + (_lastModifierFlagsOfInterest & ~updatingMask) | currentFlagsOfInterest; } - (void)updateKey:(uint64_t)physicalKey asPressed:(uint64_t)logicalKey { @@ -658,10 +659,10 @@ - (void)handleCapsLockEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)c [self synchronizeModifiers:event.modifierFlags ignoringFlags:NSEventModifierFlagCapsLock timestamp:event.timestamp]; - if ((_lastModifierFlags & NSEventModifierFlagCapsLock) != + if ((_lastModifierFlagsOfInterest & NSEventModifierFlagCapsLock) != (event.modifierFlags & NSEventModifierFlagCapsLock)) { [self sendCapsLockTapWithTimestamp:event.timestamp callback:callback]; - _lastModifierFlags = _lastModifierFlags ^ NSEventModifierFlagCapsLock; + _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ NSEventModifierFlagCapsLock; } else { [callback resolveTo:TRUE]; } @@ -683,10 +684,10 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb NSNumber* pressedLogicalKey = [_pressingRecords objectForKey:@(targetKey)]; BOOL lastTargetPressed = pressedLogicalKey != nil; NSAssert(targetModifierFlagObj == nil || - (_lastModifierFlags & targetModifierFlag) != 0 == lastTargetPressed, - @"Desynchronized state between lastModifierFlags (0x%lx) on bit 0x%lx " + (_lastModifierFlagsOfInterest & targetModifierFlag) != 0 == lastTargetPressed, + @"Desynchronized state between lastModifierFlagsOfInterest (0x%lx) on bit 0x%lx " @"for keyCode 0x%hx, whose pressing state is %@.", - _lastModifierFlags, targetModifierFlag, event.keyCode, + _lastModifierFlagsOfInterest, targetModifierFlag, event.keyCode, lastTargetPressed ? [NSString stringWithFormat:@"0x%llx", [pressedLogicalKey unsignedLongLongValue]] : @"empty"); @@ -696,7 +697,7 @@ - (void)handleFlagEvent:(NSEvent*)event callback:(FlutterKeyCallbackGuard*)callb [callback resolveTo:TRUE]; return; } - _lastModifierFlags = _lastModifierFlags ^ targetModifierFlag; + _lastModifierFlagsOfInterest = _lastModifierFlagsOfInterest ^ targetModifierFlag; [self sendModifierEventOfType:shouldBePressed timestamp:event.timestamp keyCode:event.keyCode diff --git a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 8c0babffcc910..cdb9704f96b0b 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -52,9 +52,6 @@ - (void)handleEvent:(nonnull NSEvent*)event { event.type != NSEventTypeFlagsChanged) { return; } - NSLog(@"Type %lu moF %lu winN %ld c %@ cIM %@ rep %d keyC %x", (unsigned long)event.type, - (unsigned long)event.modifierFlags, (long)event.windowNumber, event.characters, - event.charactersIgnoringModifiers, event.isARepeat, event.keyCode); // Having no primary responders require extra logic, but since Flutter adds // all primary responders in hard-code, this is a situation that Flutter will // never meet.