Unverified Commit c5dd3ec4 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Fix stuck keys when shift is released before the letter. (#51095)

This fixes a problem where if you press "Shift" and then "A", then release "Shift" and then "a", then the "A" key will be "stuck" on because the logical key for the key down message is different (capital "A") from the logical key for the key up message (lowercase "a").

This PR changes the pressed keys logic so that it uses the physical key to add/remove keys from the list of pressed keys, but keeps the associated logical key.

This does mean that after the "Shift" key goes up, the pressed keys contains a capital "A" and it doesn't switch to be a lowercase "a", but there isn't currently any mechanism we can use to do that remapping. This is far less surprising than the current behavior, but is still not quite correct.

I fixed the event simulation code to take a physicalKey so that it could be matched with the logical key, but the event simulation code isn't up to the task, since it can only simulate keys that appear in the key maps. The new platform key event design should fix that (added TODOs).
parent 479e27c9
......@@ -136,6 +136,11 @@ class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
}
}
}
final List<String> pressed = <String>['Pressed:'];
for (final LogicalKeyboardKey key in RawKeyboard.instance.keysPressed) {
pressed.add(key.debugName);
}
dataText.add(Text(pressed.join(' ')));
return DefaultTextStyle(
style: textTheme.subtitle1,
child: Column(
......
......@@ -512,10 +512,13 @@ class RawKeyboard {
return;
}
if (event is RawKeyDownEvent) {
_keysPressed.add(event.logicalKey);
_keysPressed[event.physicalKey] = event.logicalKey;
}
if (event is RawKeyUpEvent) {
_keysPressed.remove(event.logicalKey);
// Use the physical key in the key up event to find the physical key from
// the corresponding key down event and remove it, even if the logical
// keys don't match.
_keysPressed.remove(event.physicalKey);
}
// Make sure that the modifiers reflect reality, in case a modifier key was
// pressed/released while the app didn't have focus.
......@@ -530,85 +533,94 @@ class RawKeyboard {
}
}
static final Map<_ModifierSidePair, Set<LogicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<LogicalKeyboardKey>>{
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.altLeft},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.altRight},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.altLeft, LogicalKeyboardKey.altRight},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.altLeft},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftRight},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.controlLeft},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.controlRight},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.controlLeft, LogicalKeyboardKey.controlRight},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.controlLeft},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.left): <LogicalKeyboardKey>{LogicalKeyboardKey.metaLeft},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.right): <LogicalKeyboardKey>{LogicalKeyboardKey.metaRight},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.metaLeft, LogicalKeyboardKey.metaRight},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.any): <LogicalKeyboardKey>{LogicalKeyboardKey.metaLeft},
const _ModifierSidePair(ModifierKey.capsLockModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.capsLock},
const _ModifierSidePair(ModifierKey.numLockModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.numLock},
const _ModifierSidePair(ModifierKey.scrollLockModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.scrollLock},
const _ModifierSidePair(ModifierKey.functionModifier, KeyboardSide.all): <LogicalKeyboardKey>{LogicalKeyboardKey.fn},
static final Map<_ModifierSidePair, Set<PhysicalKeyboardKey>> _modifierKeyMap = <_ModifierSidePair, Set<PhysicalKeyboardKey>>{
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altLeft},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altRight},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altLeft, PhysicalKeyboardKey.altRight},
const _ModifierSidePair(ModifierKey.altModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.altLeft},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftRight},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft, PhysicalKeyboardKey.shiftRight},
const _ModifierSidePair(ModifierKey.shiftModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.shiftLeft},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlLeft},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlRight},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlLeft, PhysicalKeyboardKey.controlRight},
const _ModifierSidePair(ModifierKey.controlModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.controlLeft},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.left): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaLeft},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.right): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaRight},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaLeft, PhysicalKeyboardKey.metaRight},
const _ModifierSidePair(ModifierKey.metaModifier, KeyboardSide.any): <PhysicalKeyboardKey>{PhysicalKeyboardKey.metaLeft},
const _ModifierSidePair(ModifierKey.capsLockModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.capsLock},
const _ModifierSidePair(ModifierKey.numLockModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.numLock},
const _ModifierSidePair(ModifierKey.scrollLockModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.scrollLock},
const _ModifierSidePair(ModifierKey.functionModifier, KeyboardSide.all): <PhysicalKeyboardKey>{PhysicalKeyboardKey.fn},
// The symbolModifier doesn't have a key representation on any of the
// platforms, so don't map it here.
};
// The list of all modifier keys that are represented in modifier key bit
// The map of all modifier keys except Fn, since that is treated differently
// on some platforms.
static final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _allModifiersExceptFn = <PhysicalKeyboardKey, LogicalKeyboardKey>{
PhysicalKeyboardKey.altLeft: LogicalKeyboardKey.altLeft,
PhysicalKeyboardKey.altRight: LogicalKeyboardKey.altRight,
PhysicalKeyboardKey.shiftLeft: LogicalKeyboardKey.shiftLeft,
PhysicalKeyboardKey.shiftRight: LogicalKeyboardKey.shiftRight,
PhysicalKeyboardKey.controlLeft: LogicalKeyboardKey.controlLeft,
PhysicalKeyboardKey.controlRight: LogicalKeyboardKey.controlRight,
PhysicalKeyboardKey.metaLeft: LogicalKeyboardKey.metaLeft,
PhysicalKeyboardKey.metaRight: LogicalKeyboardKey.metaRight,
PhysicalKeyboardKey.capsLock: LogicalKeyboardKey.capsLock,
PhysicalKeyboardKey.numLock: LogicalKeyboardKey.numLock,
PhysicalKeyboardKey.scrollLock: LogicalKeyboardKey.scrollLock,
};
// The map of all modifier keys that are represented in modifier key bit
// masks on all platforms, so that they can be cleared out of pressedKeys when
// synchronizing.
static final Set<LogicalKeyboardKey> _allModifiers = <LogicalKeyboardKey>{
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.metaRight,
LogicalKeyboardKey.capsLock,
LogicalKeyboardKey.numLock,
LogicalKeyboardKey.scrollLock,
static final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _allModifiers = <PhysicalKeyboardKey, LogicalKeyboardKey>{
PhysicalKeyboardKey.fn: LogicalKeyboardKey.fn,
..._allModifiersExceptFn,
};
void _synchronizeModifiers(RawKeyEvent event) {
// Don't send any key events for these changes, since there *should* be
// separate events for each modifier key down/up that occurs while the app
// has focus. This is just to synchronize the modifier keys when they are
// pressed/released while the app doesn't have focus, to make sure that
// _keysPressed reflects reality at all times.
final Map<ModifierKey, KeyboardSide> modifiersPressed = event.data.modifiersPressed;
final Set<LogicalKeyboardKey> modifierKeys = <LogicalKeyboardKey>{};
final Map<PhysicalKeyboardKey, LogicalKeyboardKey> modifierKeys = <PhysicalKeyboardKey, LogicalKeyboardKey>{};
for (final ModifierKey key in modifiersPressed.keys) {
final Set<LogicalKeyboardKey> mappedKeys = _modifierKeyMap[_ModifierSidePair(key, modifiersPressed[key])];
final Set<PhysicalKeyboardKey> mappedKeys = _modifierKeyMap[_ModifierSidePair(key, modifiersPressed[key])];
assert(mappedKeys != null,
'Platform key support for ${Platform.operatingSystem} is '
'producing unsupported modifier combinations.');
modifierKeys.addAll(mappedKeys);
for (final PhysicalKeyboardKey physicalModifier in mappedKeys) {
modifierKeys[physicalModifier] = _allModifiers[physicalModifier];
}
// Don't send any key events for these changes, since there *should* be
// separate events for each modifier key down/up that occurs while the app
// has focus. This is just to synchronize the modifier keys when they are
// pressed/released while the app doesn't have focus, to make sure that
// _keysPressed reflects reality at all times.
_keysPressed.removeAll(_allModifiers);
}
_allModifiersExceptFn.keys.forEach(_keysPressed.remove);
if (event.data is! RawKeyEventDataFuchsia && event.data is! RawKeyEventDataMacOs) {
// On Fuchsia and macOS, the Fn key is not considered a modifier key.
_keysPressed.remove(LogicalKeyboardKey.fn);
_keysPressed.remove(PhysicalKeyboardKey.fn);
}
_keysPressed.addAll(modifierKeys);
}
final Set<LogicalKeyboardKey> _keysPressed = <LogicalKeyboardKey>{};
final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _keysPressed = <PhysicalKeyboardKey, LogicalKeyboardKey>{};
/// Returns the set of keys currently pressed.
Set<LogicalKeyboardKey> get keysPressed {
return _keysPressed.toSet();
}
Set<LogicalKeyboardKey> get keysPressed => _keysPressed.values.toSet();
/// Returns the set of physical keys currently pressed.
Set<PhysicalKeyboardKey> get physicalKeysPressed => _keysPressed.keys.toSet();
/// Clears the list of keys returned from [keysPressed].
///
/// This is used by the testing framework to make sure tests are hermetic.
@visibleForTesting
void clearKeysPressed() {
_keysPressed.clear();
}
void clearKeysPressed() => _keysPressed.clear();
}
class _ModifierSidePair extends Object {
......
......@@ -73,7 +73,8 @@ void main() {
// The Fn key isn't mapped on linux.
if (platform != 'linux') {
await simulateKeyDownEvent(LogicalKeyboardKey.fn, platform: platform);
expect(RawKeyboard.instance.keysPressed,
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
if (platform != 'macos') LogicalKeyboardKey.fn,
......@@ -105,6 +106,51 @@ void main() {
}
}, skip: kIsWeb);
testWidgets('keysPressed is correct when modifier is released before key', (WidgetTester tester) async {
for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia']) {
RawKeyboard.instance.clearKeysPressed();
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
await simulateKeyDownEvent(LogicalKeyboardKey.shiftLeft, platform: platform, physicalKey: PhysicalKeyboardKey.shiftLeft);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft},
),
reason: 'on $platform',
);
// TODO(gspencergoog): Switch to capital A when the new key event code
// is finished that can simulate real keys.
// https://github.com/flutter/flutter/issues/33521
// This should really be done with a simulated capital A, but the event
// simulation code doesn't really support that, since it only can
// simulate events that appear in the key maps (and capital letters
// don't appear there).
await simulateKeyDownEvent(LogicalKeyboardKey.keyA, platform: platform, physicalKey: PhysicalKeyboardKey.keyA);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.keyA,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.shiftLeft, platform: platform, physicalKey: PhysicalKeyboardKey.shiftLeft);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.keyA,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform, physicalKey: PhysicalKeyboardKey.keyA);
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
}
}, skip: kIsWeb);
testWidgets('keysPressed modifiers are synchronized with key events on macOS', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
......@@ -849,6 +895,7 @@ void main() {
'modifiers': 0x0,
});
}
expect(() => _createFailingKey(), throwsAssertionError);
});
test('Control keyboard keys are correctly translated', () {
......
......@@ -8,6 +8,11 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'test_async_utils.dart';
// TODO(gspencergoog): Replace this with more robust key simulation code once
// the new key event code is in.
// https://github.com/flutter/flutter/issues/33521
// This code can only simulate keys which appear in the key maps.
/// A class that serves as a namespace for a bunch of keyboard-key generation
/// utilities.
class KeyEventSimulator {
......@@ -39,7 +44,7 @@ class KeyEventSimulator {
return false;
}
static int _getScanCode(LogicalKeyboardKey key, String platform) {
static int _getScanCode(PhysicalKeyboardKey key, String platform) {
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
int scanCode;
Map<int, PhysicalKeyboardKey> map;
......@@ -58,7 +63,7 @@ class KeyEventSimulator {
break;
}
for (final int code in map.keys) {
if (key.debugName == map[code].debugName) {
if (key.usbHidUsage == map[code].usbHidUsage) {
scanCode = code;
break;
}
......@@ -85,7 +90,7 @@ class KeyEventSimulator {
break;
}
for (final int code in map.keys) {
if (key.debugName == map[code].debugName) {
if (key.keyId == map[code].keyId) {
keyCode = code;
break;
}
......@@ -93,16 +98,49 @@ class KeyEventSimulator {
return keyCode;
}
static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key, String platform) {
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
Map<int, PhysicalKeyboardKey> map;
switch (platform) {
case 'android':
map = kAndroidToPhysicalKey;
break;
case 'fuchsia':
map = kFuchsiaToPhysicalKey;
break;
case 'macos':
map = kMacOsToPhysicalKey;
break;
case 'linux':
map = kLinuxToPhysicalKey;
break;
}
for (final PhysicalKeyboardKey physicalKey in map.values) {
if (key.debugName == physicalKey.debugName) {
return physicalKey;
}
}
return null;
}
/// Get a raw key data map given a [LogicalKeyboardKey] and a platform.
static Map<String, dynamic> getKeyData(LogicalKeyboardKey key, {String platform, bool isDown = true}) {
static Map<String, dynamic> getKeyData(
LogicalKeyboardKey key, {
String platform,
bool isDown = true,
PhysicalKeyboardKey physicalKey,
}) {
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
key = _getKeySynonym(key);
// Find a suitable physical key if none was supplied.
physicalKey ??= _findPhysicalKey(key, platform);
assert(key.debugName != null);
final int keyCode = platform == 'macos' ? -1 : _getKeyCode(key, platform);
assert(platform == 'macos' || keyCode != null, 'Key $key not found in $platform keyCode map');
final int scanCode = _getScanCode(key, platform);
final int scanCode = _getScanCode(physicalKey, platform);
assert(scanCode != null, 'Physical key for $key not found in $platform scanCode map');
final Map<String, dynamic> result = <String, dynamic>{
......@@ -119,7 +157,7 @@ class KeyEventSimulator {
result['metaState'] = _getAndroidModifierFlags(key, isDown);
break;
case 'fuchsia':
result['hidUsage'] = key.keyId & LogicalKeyboardKey.hidPlane != 0 ? key.keyId & LogicalKeyboardKey.valueMask : null;
result['hidUsage'] = physicalKey?.usbHidUsage ?? (key.keyId & LogicalKeyboardKey.hidPlane != 0 ? key.keyId & LogicalKeyboardKey.valueMask : null);
result['codePoint'] = key.keyLabel?.codeUnitAt(0);
result['modifiers'] = _getFuchsiaModifierFlags(key, isDown);
break;
......@@ -332,12 +370,12 @@ class KeyEventSimulator {
/// See also:
///
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
static Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String platform}) async {
static Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String platform, PhysicalKeyboardKey physicalKey}) async {
return TestAsyncUtils.guard<void>(() async {
platform ??= Platform.operatingSystem;
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
final Map<String, dynamic> data = getKeyData(key, platform: platform, isDown: true);
final Map<String, dynamic> data = getKeyData(key, platform: platform, isDown: true, physicalKey: physicalKey);
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
......@@ -359,12 +397,12 @@ class KeyEventSimulator {
/// See also:
///
/// - [simulateKeyDownEvent] to simulate the corresponding key down event.
static Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String platform}) async {
static Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String platform, PhysicalKeyboardKey physicalKey}) async {
return TestAsyncUtils.guard<void>(() async {
platform ??= Platform.operatingSystem;
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
final Map<String, dynamic> data = getKeyData(key, platform: platform, isDown: false);
final Map<String, dynamic> data = getKeyData(key, platform: platform, isDown: false, physicalKey: physicalKey);
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
......@@ -376,8 +414,11 @@ class KeyEventSimulator {
/// Simulates sending a hardware key down event through the system channel.
///
/// It is intended for use in writing tests.
///
/// This only simulates key presses coming from a physical keyboard, not from a
/// soft keyboard.
/// soft keyboard, and it can only simulate keys that appear in the key maps
/// such as [kAndroidToLogicalKey], [kMacOsToPhysicalKey], etc.
///
/// Specify `platform` as one of the platforms allowed in
/// [Platform.operatingSystem] to make the event appear to be from that type of
......@@ -389,14 +430,17 @@ class KeyEventSimulator {
/// See also:
///
/// - [simulateKeyUpEvent] to simulate the corresponding key up event.
Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String platform}) {
return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform);
Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String platform, PhysicalKeyboardKey physicalKey}) {
return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey);
}
/// Simulates sending a hardware key up event through the system channel.
///
/// It is intended for use in writing tests.
///
/// This only simulates key presses coming from a physical keyboard, not from a
/// soft keyboard.
/// soft keyboard, and it can only simulate keys that appear in the key maps
/// such as [kAndroidToLogicalKey], [kMacOsToPhysicalKey], etc.
///
/// Specify `platform` as one of the platforms allowed in
/// [Platform.operatingSystem] to make the event appear to be from that type of
......@@ -406,6 +450,6 @@ Future<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String platform}) {
/// See also:
///
/// - [simulateKeyDownEvent] to simulate the corresponding key down event.
Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String platform}) {
return KeyEventSimulator.simulateKeyUpEvent(key, platform: platform);
Future<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String platform, PhysicalKeyboardKey physicalKey}) {
return KeyEventSimulator.simulateKeyUpEvent(key, platform: platform, physicalKey: physicalKey);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment