From 426c808924ec191f9e89d74827da595195de4abf Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Mon, 20 Feb 2023 20:53:07 +0100 Subject: [PATCH 1/5] feat: implement a press key action --- .../fluttium/lib/src/actions/press_key.dart | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 packages/fluttium/lib/src/actions/press_key.dart diff --git a/packages/fluttium/lib/src/actions/press_key.dart b/packages/fluttium/lib/src/actions/press_key.dart new file mode 100644 index 00000000..f0c69df3 --- /dev/null +++ b/packages/fluttium/lib/src/actions/press_key.dart @@ -0,0 +1,61 @@ +import 'dart:ui'; + +import 'package:flutter/services.dart'; +import 'package:fluttium/fluttium.dart'; + +/// {@template press_key} +/// TODO: docs +/// {@endtemplate} +class PressKey extends Action { + /// {@macro press_key} + const PressKey({ + required this.key, + this.downFor = 1, + }); + + /// The key that is being pressed. + final String key; + + /// For how long the key will be down. + final int downFor; + + @override + Future execute(Tester tester) async { + final logicalKey = LogicalKeyboardKey.knownLogicalKeys.firstWhere((key) { + return key.debugName == this.key; + }); + + final physicalKey = PhysicalKeyboardKey.knownPhysicalKeys.firstWhere((key) { + return logicalKey.debugName == key.debugName; + }); + + tester.keyEventManager.handleKeyData( + KeyData( + type: KeyEventType.down, + physical: physicalKey.usbHidUsage, + logical: logicalKey.keyId, + timeStamp: Duration.zero, + character: null, + synthesized: false, + ), + ); + + await tester.pump(duration: Duration(milliseconds: downFor)); + + tester.keyEventManager.handleKeyData( + KeyData( + type: KeyEventType.up, + physical: physicalKey.usbHidUsage, + logical: logicalKey.keyId, + timeStamp: Duration.zero, + character: null, + synthesized: false, + ), + ); + + return true; + } + + @override + String description() => 'Press key: $key'; +} From 435081d24443556387d1fb3347d0bc9e86c7782f Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Mon, 20 Feb 2023 23:10:35 +0100 Subject: [PATCH 2/5] feat: implement a press key action --- example/flows/text_flow.yaml | 2 ++ packages/fluttium/lib/src/actions/press_key.dart | 11 ++++++++--- packages/fluttium/lib/src/registry.dart | 2 ++ packages/fluttium/lib/src/tester.dart | 2 ++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/example/flows/text_flow.yaml b/example/flows/text_flow.yaml index a29043a6..9d2b4b58 100644 --- a/example/flows/text_flow.yaml +++ b/example/flows/text_flow.yaml @@ -10,6 +10,8 @@ description: Testing the text page - pressOn: "Enter text" - inputText: "Testing the text page" - expectVisible: "Result: Testing the text page" +- pressKey: "backspace" +- expectVisible: "FAILING ON PURPOSE TO TEST BACKSPACE KEY" # Return to the app page - pressOn: "Back" - expectEnvironmentText: diff --git a/packages/fluttium/lib/src/actions/press_key.dart b/packages/fluttium/lib/src/actions/press_key.dart index f0c69df3..9c60eda2 100644 --- a/packages/fluttium/lib/src/actions/press_key.dart +++ b/packages/fluttium/lib/src/actions/press_key.dart @@ -22,18 +22,23 @@ class PressKey extends Action { @override Future execute(Tester tester) async { final logicalKey = LogicalKeyboardKey.knownLogicalKeys.firstWhere((key) { - return key.debugName == this.key; + return key.debugName?.toLowerCase() == this.key.toLowerCase(); }); final physicalKey = PhysicalKeyboardKey.knownPhysicalKeys.firstWhere((key) { return logicalKey.debugName == key.debugName; }); + HardwareKeyboard.instance.addHandler((event) { + print(event); + return false; + }); + tester.keyEventManager.handleKeyData( KeyData( type: KeyEventType.down, - physical: physicalKey.usbHidUsage, - logical: logicalKey.keyId, + physical: PhysicalKeyboardKey.keyA.usbHidUsage, + logical: LogicalKeyboardKey.keyA.keyId, timeStamp: Duration.zero, character: null, synthesized: false, diff --git a/packages/fluttium/lib/src/registry.dart b/packages/fluttium/lib/src/registry.dart index 0edeeb73..98cd551f 100644 --- a/packages/fluttium/lib/src/registry.dart +++ b/packages/fluttium/lib/src/registry.dart @@ -1,6 +1,7 @@ import 'dart:collection'; import 'package:fluttium/fluttium.dart'; +import 'package:fluttium/src/actions/press_key.dart'; /// {@template registry} /// The registry of all the actions a [Tester] can perform. @@ -15,6 +16,7 @@ class Registry { 'expectVisible': ActionRegistration(ExpectVisible.new, #text), 'expectNotVisible': ActionRegistration(ExpectNotVisible.new, #text), 'takeScreenshot': ActionRegistration(TakeScreenshot.new, #fileName), + 'pressKey': ActionRegistration(PressKey.new, #key), }; /// Map of all the action that are registered. diff --git a/packages/fluttium/lib/src/tester.dart b/packages/fluttium/lib/src/tester.dart index a6d45e3f..36b02781 100644 --- a/packages/fluttium/lib/src/tester.dart +++ b/packages/fluttium/lib/src/tester.dart @@ -20,6 +20,8 @@ class Tester { final Registry _registry; + KeyEventManager get keyEventManager => _binding.keyEventManager; + SemanticsOwner get _semanticsOwner => _binding.pipelineOwner.semanticsOwner!; /// Converts the [steps] into a list of executable actions. From 3dca4e5ddb939c2d6c45e15ae463fcfbd10379f5 Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Mon, 20 Feb 2023 23:38:08 +0100 Subject: [PATCH 3/5] feat: implement a press key action --- .../fluttium/lib/src/actions/press_key.dart | 29 +++++-------------- packages/fluttium/lib/src/tester.dart | 7 +++-- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/fluttium/lib/src/actions/press_key.dart b/packages/fluttium/lib/src/actions/press_key.dart index 9c60eda2..51616e4e 100644 --- a/packages/fluttium/lib/src/actions/press_key.dart +++ b/packages/fluttium/lib/src/actions/press_key.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:flutter/services.dart'; import 'package:fluttium/fluttium.dart'; @@ -29,32 +27,21 @@ class PressKey extends Action { return logicalKey.debugName == key.debugName; }); - HardwareKeyboard.instance.addHandler((event) { - print(event); - return false; - }); - - tester.keyEventManager.handleKeyData( - KeyData( - type: KeyEventType.down, - physical: PhysicalKeyboardKey.keyA.usbHidUsage, - logical: LogicalKeyboardKey.keyA.keyId, + tester.emitKeyEvent( + KeyDownEvent( + physicalKey: physicalKey, + logicalKey: logicalKey, timeStamp: Duration.zero, - character: null, - synthesized: false, ), ); await tester.pump(duration: Duration(milliseconds: downFor)); - tester.keyEventManager.handleKeyData( - KeyData( - type: KeyEventType.up, - physical: physicalKey.usbHidUsage, - logical: logicalKey.keyId, + tester.emitKeyEvent( + KeyUpEvent( + physicalKey: physicalKey, + logicalKey: logicalKey, timeStamp: Duration.zero, - character: null, - synthesized: false, ), ); diff --git a/packages/fluttium/lib/src/tester.dart b/packages/fluttium/lib/src/tester.dart index 36b02781..3f8e6385 100644 --- a/packages/fluttium/lib/src/tester.dart +++ b/packages/fluttium/lib/src/tester.dart @@ -20,8 +20,6 @@ class Tester { final Registry _registry; - KeyEventManager get keyEventManager => _binding.keyEventManager; - SemanticsOwner get _semanticsOwner => _binding.pipelineOwner.semanticsOwner!; /// Converts the [steps] into a list of executable actions. @@ -64,6 +62,11 @@ class Tester { return _binding.handlePointerEvent(event); } + /// Dispatch an event on the keyboard. + void emitKeyEvent(KeyEvent event) { + _binding.keyboard.handleKeyEvent(event); + } + /// Dispatch a message to the platform. Future emitPlatformMessage(String channel, ByteData? data) async { await _binding.defaultBinaryMessenger.handlePlatformMessage( From e6aa80fcce9e04ede620ff425ca737a999b32160 Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Tue, 21 Feb 2023 18:58:58 +0100 Subject: [PATCH 4/5] feat: implement a press key action --- example/flows/text_flow.yaml | 4 ++-- example/lib/text/view/text_page.dart | 8 ++++++++ packages/fluttium/lib/src/actions/press_key.dart | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/example/flows/text_flow.yaml b/example/flows/text_flow.yaml index 9d2b4b58..bb579b32 100644 --- a/example/flows/text_flow.yaml +++ b/example/flows/text_flow.yaml @@ -8,8 +8,8 @@ description: Testing the text page - expectVisible: "Text" - expectVisible: "Result: " - pressOn: "Enter text" -- inputText: "Testing the text page" -- expectVisible: "Result: Testing the text page" +# - inputText: "Testing the text page" +# - expectVisible: "Result: Testing the text page" - pressKey: "backspace" - expectVisible: "FAILING ON PURPOSE TO TEST BACKSPACE KEY" # Return to the app page diff --git a/example/lib/text/view/text_page.dart b/example/lib/text/view/text_page.dart index 86dfe80f..20c810df 100644 --- a/example/lib/text/view/text_page.dart +++ b/example/lib/text/view/text_page.dart @@ -29,11 +29,18 @@ class TextView extends StatefulWidget { class _TextViewState extends State { late TextEditingController _controller; + late FocusNode focusNode; @override void initState() { super.initState(); _controller = TextEditingController(); + focusNode = FocusNode( + onKey: (node, event) { + print(event); + return KeyEventResult.handled; + }, + ); _controller.addListener(_onChange); } @@ -59,6 +66,7 @@ class _TextViewState extends State { child: Column( children: [ TextField( + focusNode: focusNode, controller: _controller, decoration: const InputDecoration(labelText: 'Enter text'), ), diff --git a/packages/fluttium/lib/src/actions/press_key.dart b/packages/fluttium/lib/src/actions/press_key.dart index 51616e4e..4a90fcee 100644 --- a/packages/fluttium/lib/src/actions/press_key.dart +++ b/packages/fluttium/lib/src/actions/press_key.dart @@ -27,7 +27,7 @@ class PressKey extends Action { return logicalKey.debugName == key.debugName; }); - tester.emitKeyEvent( + await tester.emitKeyEvent( KeyDownEvent( physicalKey: physicalKey, logicalKey: logicalKey, @@ -37,7 +37,7 @@ class PressKey extends Action { await tester.pump(duration: Duration(milliseconds: downFor)); - tester.emitKeyEvent( + await tester.emitKeyEvent( KeyUpEvent( physicalKey: physicalKey, logicalKey: logicalKey, From b2d39177b4bedec7762f26651e0c42cdf011c917 Mon Sep 17 00:00:00 2001 From: Jochum van der Ploeg Date: Tue, 21 Feb 2023 21:19:20 +0100 Subject: [PATCH 5/5] feat: implement a press key action --- packages/fluttium/lib/src/tester.dart | 717 +++++++++++++++++++++++++- 1 file changed, 713 insertions(+), 4 deletions(-) diff --git a/packages/fluttium/lib/src/tester.dart b/packages/fluttium/lib/src/tester.dart index 3f8e6385..d87809e2 100644 --- a/packages/fluttium/lib/src/tester.dart +++ b/packages/fluttium/lib/src/tester.dart @@ -1,4 +1,9 @@ +import 'dart:async'; +import 'dart:io' if (kIsWeb) ''; +import 'dart:ui'; + import 'package:clock/clock.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart' hide Action; @@ -63,17 +68,711 @@ class Tester { } /// Dispatch an event on the keyboard. - void emitKeyEvent(KeyEvent event) { - _binding.keyboard.handleKeyEvent(event); + Future emitKeyEvent(KeyEvent event) async { + final result = _binding.keyEventManager.handleKeyData( + KeyData( + timeStamp: event.timeStamp, + type: event is KeyDownEvent ? KeyEventType.down : KeyEventType.up, + physical: event.physicalKey.usbHidUsage, + logical: event.logicalKey.keyId, + character: event.character, + synthesized: event.synthesized, + ), + ); + + final data = await emitPlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage( + getKeyData( + event.logicalKey, + isDown: event is KeyDownEvent, + platform: kIsWeb ? 'web' : Platform.operatingSystem, + physicalKey: event.physicalKey, + character: event.character, + ), + ), + ); + + if (data == null) { + return false; + } + final Map decoded = SystemChannels.keyEvent.codec + .decodeMessage(data)! as Map; + return decoded['handled']! as bool || result; + } + + // Look up a synonym key, and just return the left version of it. + static LogicalKeyboardKey _getKeySynonym(LogicalKeyboardKey origKey) { + if (origKey == LogicalKeyboardKey.shift) { + return LogicalKeyboardKey.shiftLeft; + } + if (origKey == LogicalKeyboardKey.alt) { + return LogicalKeyboardKey.altLeft; + } + if (origKey == LogicalKeyboardKey.meta) { + return LogicalKeyboardKey.metaLeft; + } + if (origKey == LogicalKeyboardKey.control) { + return LogicalKeyboardKey.controlLeft; + } + return origKey; + } + + static PhysicalKeyboardKey _findPhysicalKeyByPlatform( + LogicalKeyboardKey key, + String platform, + ) { + late Map map; + if (kIsWeb) { + // This check is used to treeshake keymap code. + map = kWebToPhysicalKey; + } else { + switch (platform) { + case 'android': + map = kAndroidToPhysicalKey; + break; + case 'fuchsia': + map = kFuchsiaToPhysicalKey; + break; + case 'macos': + map = kMacOsToPhysicalKey; + break; + case 'ios': + map = kIosToPhysicalKey; + break; + case 'linux': + map = kLinuxToPhysicalKey; + break; + case 'web': + map = kWebToPhysicalKey; + break; + case 'windows': + map = kWindowsToPhysicalKey; + break; + } + } + PhysicalKeyboardKey? result; + for (final physicalKey in map.values) { + if (key.debugName == physicalKey.debugName) { + result = physicalKey; + break; + } + } + assert( + result != null, + 'Physical key for $key not found in $platform physical key map', + ); + return result!; + } + + static String? _keyLabel(LogicalKeyboardKey key) { + final keyLabel = key.keyLabel; + if (keyLabel.length == 1) { + return keyLabel.toLowerCase(); + } + return null; + } + + static int _getKeyCode(LogicalKeyboardKey key, String platform) { + if (kIsWeb) { + // web doesn't have int type code. This check is used to treeshake + // keyboard map code. + return -1; + } else { + late Map map; + switch (platform) { + case 'android': + map = kAndroidToLogicalKey; + break; + case 'fuchsia': + map = kFuchsiaToLogicalKey; + break; + case 'macos': + // macOS doesn't do key codes, just scan codes. + return -1; + case 'ios': + // iOS doesn't do key codes, just scan codes. + return -1; + case 'web': + // web doesn't have int type code. + return -1; + case 'linux': + map = kGlfwToLogicalKey; + break; + case 'windows': + map = kWindowsToLogicalKey; + break; + } + int? keyCode; + for (final code in map.keys) { + if (key.keyId == map[code]!.keyId) { + keyCode = code; + break; + } + } + assert(keyCode != null, 'Key $key not found in $platform keyCode map'); + return keyCode!; + } + } + + static int _getScanCode(PhysicalKeyboardKey key, String platform) { + late Map map; + switch (platform) { + case 'android': + map = kAndroidToPhysicalKey; + break; + case 'fuchsia': + map = kFuchsiaToPhysicalKey; + break; + case 'macos': + map = kMacOsToPhysicalKey; + break; + case 'ios': + map = kIosToPhysicalKey; + break; + case 'linux': + map = kLinuxToPhysicalKey; + break; + case 'windows': + map = kWindowsToPhysicalKey; + break; + case 'web': + // web doesn't have int type code + return -1; + } + int? scanCode; + for (final code in map.keys) { + if (key.usbHidUsage == map[code]!.usbHidUsage) { + scanCode = code; + break; + } + } + assert( + scanCode != null, + 'Physical key for $key not found in $platform scanCode map', + ); + return scanCode!; + } + + static _WebKeyLocationPair _getWebKeyLocation( + LogicalKeyboardKey key, + String keyLabel, + ) { + String? result; + for (final entry in kWebLocationMap.entries) { + final foundIndex = entry.value.lastIndexOf(key); + // If foundIndex is -1, then the key is not defined in kWebLocationMap. + // If foundIndex is 0, then the key is in the standard part of the keyboard, + // but we have to check `keyLabel` to see if it's remapped or modified. + if (foundIndex != -1 && foundIndex != 0) { + return _WebKeyLocationPair(entry.key, foundIndex); + } + } + if (keyLabel.isNotEmpty) { + return _WebKeyLocationPair(keyLabel, 0); + } + for (final code in kWebToLogicalKey.keys) { + if (key.keyId == kWebToLogicalKey[code]!.keyId) { + result = code; + break; + } + } + assert(result != null, 'Key $key not found in web keyCode map'); + return _WebKeyLocationPair(result!, 0); + } + + static PhysicalKeyboardKey _inferPhysicalKey(LogicalKeyboardKey key) { + PhysicalKeyboardKey? result; + for (final physicalKey in PhysicalKeyboardKey.knownPhysicalKeys) { + if (physicalKey.debugName == key.debugName) { + result = physicalKey; + break; + } + } + assert(result != null, 'Unable to infer physical key for $key'); + return result!; + } + + static String _getWebCode(PhysicalKeyboardKey key) { + String? result; + for (final entry in kWebToPhysicalKey.entries) { + if (entry.value.usbHidUsage == key.usbHidUsage) { + result = entry.key; + break; + } + } + assert(result != null, 'Key $key not found in web code map'); + return result!; + } + + /// Get a raw key data map given a [LogicalKeyboardKey] and a platform. + static Map getKeyData( + LogicalKeyboardKey key, { + required String platform, + bool isDown = true, + PhysicalKeyboardKey? physicalKey, + String? character, + }) { + key = _getKeySynonym(key); + + // Find a suitable physical key if none was supplied. + physicalKey ??= _findPhysicalKeyByPlatform(key, platform); + + final result = { + 'type': isDown ? 'keydown' : 'keyup', + 'keymap': platform, + }; + + final resultCharacter = character ?? _keyLabel(key) ?? ''; + void assignWeb() { + final keyLocation = _getWebKeyLocation(key, resultCharacter); + final actualPhysicalKey = physicalKey ?? _inferPhysicalKey(key); + result['code'] = _getWebCode(actualPhysicalKey); + result['key'] = keyLocation.key; + result['location'] = keyLocation.location; + result['metaState'] = _getWebModifierFlags(key, isDown); + } + + if (kIsWeb) { + assignWeb(); + return result; + } + final keyCode = _getKeyCode(key, platform); + final scanCode = _getScanCode(physicalKey, platform); + + switch (platform) { + case 'android': + result['keyCode'] = keyCode; + if (resultCharacter.isNotEmpty) { + result['codePoint'] = resultCharacter.codeUnitAt(0); + result['character'] = resultCharacter; + } + result['scanCode'] = scanCode; + result['metaState'] = _getAndroidModifierFlags(key, isDown); + break; + case 'fuchsia': + result['hidUsage'] = physicalKey.usbHidUsage; + if (resultCharacter.isNotEmpty) { + result['codePoint'] = resultCharacter.codeUnitAt(0); + } + result['modifiers'] = _getFuchsiaModifierFlags(key, isDown); + break; + case 'linux': + result['toolkit'] = 'glfw'; + result['keyCode'] = keyCode; + result['scanCode'] = scanCode; + result['modifiers'] = _getGlfwModifierFlags(key, isDown); + result['unicodeScalarValues'] = + resultCharacter.isNotEmpty ? resultCharacter.codeUnitAt(0) : 0; + break; + case 'macos': + result['keyCode'] = scanCode; + if (resultCharacter.isNotEmpty) { + result['characters'] = resultCharacter; + result['charactersIgnoringModifiers'] = resultCharacter; + } + result['modifiers'] = _getMacOsModifierFlags(key, isDown); + break; + case 'ios': + result['keyCode'] = scanCode; + result['characters'] = resultCharacter; + result['charactersIgnoringModifiers'] = resultCharacter; + result['modifiers'] = _getIOSModifierFlags(key, isDown); + break; + case 'windows': + result['keyCode'] = keyCode; + result['scanCode'] = scanCode; + if (resultCharacter.isNotEmpty) { + result['characterCodePoint'] = resultCharacter.codeUnitAt(0); + } + result['modifiers'] = _getWindowsModifierFlags(key, isDown); + break; + case 'web': + assignWeb(); + break; + } + return result; + } + + static int _getAndroidModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftShift | + RawKeyEventDataAndroid.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataAndroid.modifierRightShift | + RawKeyEventDataAndroid.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftMeta | + RawKeyEventDataAndroid.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataAndroid.modifierRightMeta | + RawKeyEventDataAndroid.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftControl | + RawKeyEventDataAndroid.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataAndroid.modifierRightControl | + RawKeyEventDataAndroid.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftAlt | + RawKeyEventDataAndroid.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataAndroid.modifierRightAlt | + RawKeyEventDataAndroid.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.fn)) { + result |= RawKeyEventDataAndroid.modifierFunction; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataAndroid.modifierScrollLock; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataAndroid.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataAndroid.modifierCapsLock; + } + return result; + } + + static int _getGlfwModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft) || + pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= GLFWKeyHelper.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft) || + pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= GLFWKeyHelper.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft) || + pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= GLFWKeyHelper.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft) || + pressed.contains(LogicalKeyboardKey.altRight)) { + result |= GLFWKeyHelper.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= GLFWKeyHelper.modifierCapsLock; + } + return result; + } + + static int _getWindowsModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shift)) { + result |= RawKeyEventDataWindows.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataWindows.modifierLeftShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataWindows.modifierRightShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataWindows.modifierLeftMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataWindows.modifierRightMeta; + } + if (pressed.contains(LogicalKeyboardKey.control)) { + result |= RawKeyEventDataWindows.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataWindows.modifierLeftControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataWindows.modifierRightControl; + } + if (pressed.contains(LogicalKeyboardKey.alt)) { + result |= RawKeyEventDataWindows.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataWindows.modifierLeftAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataWindows.modifierRightAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataWindows.modifierCaps; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataWindows.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataWindows.modifierScrollLock; + } + return result; + } + + static int _getFuchsiaModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataFuchsia.modifierRightShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataFuchsia.modifierRightMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataFuchsia.modifierRightControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataFuchsia.modifierRightAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataFuchsia.modifierCapsLock; + } + return result; + } + + static int _getWebModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataWeb.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataWeb.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataWeb.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataWeb.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataWeb.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataWeb.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataWeb.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataWeb.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataWeb.modifierCapsLock; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataWeb.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataWeb.modifierScrollLock; + } + return result; + } + + static int _getMacOsModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftShift | + RawKeyEventDataMacOs.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataMacOs.modifierRightShift | + RawKeyEventDataMacOs.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftCommand | + RawKeyEventDataMacOs.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataMacOs.modifierRightCommand | + RawKeyEventDataMacOs.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftControl | + RawKeyEventDataMacOs.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataMacOs.modifierRightControl | + RawKeyEventDataMacOs.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftOption | + RawKeyEventDataMacOs.modifierOption; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataMacOs.modifierRightOption | + RawKeyEventDataMacOs.modifierOption; + } + final functionKeys = { + LogicalKeyboardKey.f1, + LogicalKeyboardKey.f2, + LogicalKeyboardKey.f3, + LogicalKeyboardKey.f4, + LogicalKeyboardKey.f5, + LogicalKeyboardKey.f6, + LogicalKeyboardKey.f7, + LogicalKeyboardKey.f8, + LogicalKeyboardKey.f9, + LogicalKeyboardKey.f10, + LogicalKeyboardKey.f11, + LogicalKeyboardKey.f12, + LogicalKeyboardKey.f13, + LogicalKeyboardKey.f14, + LogicalKeyboardKey.f15, + LogicalKeyboardKey.f16, + LogicalKeyboardKey.f17, + LogicalKeyboardKey.f18, + LogicalKeyboardKey.f19, + LogicalKeyboardKey.f20, + LogicalKeyboardKey.f21, + }; + if (pressed.intersection(functionKeys).isNotEmpty) { + result |= RawKeyEventDataMacOs.modifierFunction; + } + if (pressed.intersection(kMacOsNumPadMap.values.toSet()).isNotEmpty) { + result |= RawKeyEventDataMacOs.modifierNumericPad; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataMacOs.modifierCapsLock; + } + return result; + } + + static int _getIOSModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + var result = 0; + final pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataIos.modifierLeftShift | + RawKeyEventDataIos.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataIos.modifierRightShift | + RawKeyEventDataIos.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataIos.modifierLeftCommand | + RawKeyEventDataIos.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataIos.modifierRightCommand | + RawKeyEventDataIos.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataIos.modifierLeftControl | + RawKeyEventDataIos.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataIos.modifierRightControl | + RawKeyEventDataIos.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataIos.modifierLeftOption | + RawKeyEventDataIos.modifierOption; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataIos.modifierRightOption | + RawKeyEventDataIos.modifierOption; + } + final functionKeys = { + LogicalKeyboardKey.f1, + LogicalKeyboardKey.f2, + LogicalKeyboardKey.f3, + LogicalKeyboardKey.f4, + LogicalKeyboardKey.f5, + LogicalKeyboardKey.f6, + LogicalKeyboardKey.f7, + LogicalKeyboardKey.f8, + LogicalKeyboardKey.f9, + LogicalKeyboardKey.f10, + LogicalKeyboardKey.f11, + LogicalKeyboardKey.f12, + LogicalKeyboardKey.f13, + LogicalKeyboardKey.f14, + LogicalKeyboardKey.f15, + LogicalKeyboardKey.f16, + LogicalKeyboardKey.f17, + LogicalKeyboardKey.f18, + LogicalKeyboardKey.f19, + LogicalKeyboardKey.f20, + LogicalKeyboardKey.f21, + }; + if (pressed.intersection(functionKeys).isNotEmpty) { + result |= RawKeyEventDataIos.modifierFunction; + } + if (pressed.intersection(kMacOsNumPadMap.values.toSet()).isNotEmpty) { + result |= RawKeyEventDataIos.modifierNumericPad; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataIos.modifierCapsLock; + } + return result; } /// Dispatch a message to the platform. - Future emitPlatformMessage(String channel, ByteData? data) async { + Future emitPlatformMessage(String channel, ByteData? data) async { + final completer = Completer(); await _binding.defaultBinaryMessenger.handlePlatformMessage( channel, data, - (ByteData? data) {}, + completer.complete, ); + return completer.future; } /// Pump the widget tree for the given [duration]. @@ -169,3 +868,13 @@ class Tester { return boundary; } } + +// A tuple of `key` and `location` from Web's `KeyboardEvent` class. +// +// See [RawKeyEventDataWeb]'s `key` and `location` fields for details. +@immutable +class _WebKeyLocationPair { + const _WebKeyLocationPair(this.key, this.location); + final String key; + final int location; +}