diff --git a/dev/tools/gen_keycodes/data/keyboard_key.tmpl b/dev/tools/gen_keycodes/data/keyboard_key.tmpl index e0b8f0fb40..eaed68c35d 100644 --- a/dev/tools/gen_keycodes/data/keyboard_key.tmpl +++ b/dev/tools/gen_keycodes/data/keyboard_key.tmpl @@ -327,9 +327,6 @@ class LogicalKeyboardKey extends KeyboardKey { @@@LOGICAL_KEY_DEFINITIONS@@@ - /// A list of all predefined constant [LogicalKeyboardKey]s. - static Iterable get knownLogicalKeys => _knownLogicalKeys.values; - // A list of all predefined constant LogicalKeyboardKeys so they can be // searched. static const Map _knownLogicalKeys = { @@ -492,9 +489,6 @@ class PhysicalKeyboardKey extends KeyboardKey { @@@PHYSICAL_KEY_DEFINITIONS@@@ - /// A list of all predefined constant [PhysicalKeyboardKey]s. - static Iterable get knownPhysicalKeys => _knownPhysicalKeys.values; - // A list of all the predefined constant PhysicalKeyboardKeys so that they // can be searched. static const Map _knownPhysicalKeys = { diff --git a/dev/tools/gen_keycodes/data/windows_logical_to_window_vk.json b/dev/tools/gen_keycodes/data/windows_logical_to_window_vk.json index 5d8b68d7f8..e4803cbe24 100644 --- a/dev/tools/gen_keycodes/data/windows_logical_to_window_vk.json +++ b/dev/tools/gen_keycodes/data/windows_logical_to_window_vk.json @@ -176,5 +176,41 @@ "Zoom": ["ZOOM"], "Noname": ["NONAME"], "Pa1": ["PA1"], - "OemClear": ["OEM_CLEAR"] + "OemClear": ["OEM_CLEAR"], + "0": ["0"], + "1": ["1"], + "2": ["2"], + "3": ["3"], + "4": ["4"], + "5": ["5"], + "6": ["6"], + "7": ["7"], + "8": ["8"], + "9": ["9"], + "KeyA": ["A"], + "KeyB": ["B"], + "KeyC": ["C"], + "KeyD": ["D"], + "KeyE": ["E"], + "KeyF": ["F"], + "KeyG": ["G"], + "KeyH": ["H"], + "KeyI": ["I"], + "KeyJ": ["J"], + "KeyK": ["K"], + "KeyL": ["L"], + "KeyM": ["M"], + "KeyN": ["N"], + "KeyO": ["O"], + "KeyP": ["P"], + "KeyQ": ["Q"], + "KeyR": ["R"], + "KeyS": ["S"], + "KeyT": ["T"], + "KeyU": ["U"], + "KeyV": ["V"], + "KeyW": ["W"], + "KeyX": ["X"], + "KeyY": ["Y"], + "KeyZ": ["Z"] } diff --git a/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart b/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart index 27eee82bbb..6a21b9974d 100644 --- a/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart +++ b/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart @@ -24,16 +24,6 @@ bool _isAsciiLetter(String? char) { || (charCode >= charLowerA && charCode <= charLowerZ); } -bool _isDigit(String? char) { - if (char == null) - return false; - final int charDigit0 = '0'.codeUnitAt(0); - final int charDigit9 = '9'.codeUnitAt(0); - assert(char.length == 1); - final int charCode = char.codeUnitAt(0); - return charCode >= charDigit0 && charCode <= charDigit9; -} - /// Generates the keyboard_maps.dart files, based on the information in the key /// data structure given to it. class KeyboardMapsCodeGenerator extends BaseCodeGenerator { @@ -181,9 +171,7 @@ class KeyboardMapsCodeGenerator extends BaseCodeGenerator { // because they are not used by the embedding. Add them manually. final List? keyCodes = entry.windowsValues.isNotEmpty ? entry.windowsValues - : (_isAsciiLetter(entry.keyLabel) ? [entry.keyLabel!.toUpperCase().codeUnitAt(0)] : - _isDigit(entry.keyLabel) ? [entry.keyLabel!.toUpperCase().codeUnitAt(0)] : - null); + : (_isAsciiLetter(entry.keyLabel) ? [entry.keyLabel!.toUpperCase().codeUnitAt(0)] : null); if (keyCodes != null) { for (final int code in keyCodes) { lines.add(code, ' $code: LogicalKeyboardKey.${entry.constantName},'); diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index 0d7ec01b35..e6a266f9a9 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -15,11 +15,9 @@ export 'src/services/autofill.dart'; export 'src/services/binary_messenger.dart'; export 'src/services/binding.dart'; export 'src/services/clipboard.dart'; -export 'src/services/debug.dart'; export 'src/services/deferred_component.dart'; export 'src/services/font_loader.dart'; export 'src/services/haptic_feedback.dart'; -export 'src/services/hardware_keyboard.dart'; export 'src/services/keyboard_key.dart'; export 'src/services/keyboard_maps.dart'; export 'src/services/message_codec.dart'; diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index c7858db5c4..b5d2c39199 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -14,9 +14,7 @@ import 'package:flutter/scheduler.dart'; import 'asset_bundle.dart'; import 'binary_messenger.dart'; -import 'hardware_keyboard.dart'; import 'message_codec.dart'; -import 'raw_keyboard.dart'; import 'restoration.dart'; import 'system_channels.dart'; @@ -33,7 +31,6 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { _instance = this; _defaultBinaryMessenger = createBinaryMessenger(); _restorationManager = createRestorationManager(); - _initKeyboard(); initLicenses(); SystemChannels.system.setMessageHandler((dynamic message) => handleSystemMessage(message as Object)); SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage); @@ -45,23 +42,6 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { static ServicesBinding? get instance => _instance; static ServicesBinding? _instance; - /// The global singleton instance of [HardwareKeyboard], which can be used to - /// query keyboard states. - HardwareKeyboard get keyboard => _keyboard; - late final HardwareKeyboard _keyboard; - - /// The global singleton instance of [KeyEventManager], which is used - /// internally to dispatch key messages. - KeyEventManager get keyEventManager => _keyEventManager; - late final KeyEventManager _keyEventManager; - - void _initKeyboard() { - _keyboard = HardwareKeyboard(); - _keyEventManager = KeyEventManager(_keyboard, RawKeyboard.instance); - window.onKeyData = _keyEventManager.handleKeyData; - SystemChannels.keyEvent.setMessageHandler(_keyEventManager.handleRawKeyMessage); - } - /// The default instance of [BinaryMessenger]. /// /// This is used to send messages from the application to the platform, and diff --git a/packages/flutter/lib/src/services/debug.dart b/packages/flutter/lib/src/services/debug.dart deleted file mode 100644 index 3e0421ae40..0000000000 --- a/packages/flutter/lib/src/services/debug.dart +++ /dev/null @@ -1,30 +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. - -import 'package:flutter/foundation.dart'; - -import 'hardware_keyboard.dart'; - -/// Override the transit mode with which key events are simulated. -/// -/// Setting [debugKeyEventSimulatorTransitModeOverride] is a good way to make -/// certain tests simulate the behavior of different type of platforms in terms -/// of their extent of support for keyboard API. -KeyDataTransitMode? debugKeyEventSimulatorTransitModeOverride; - -/// Returns true if none of the widget library debug variables have been changed. -/// -/// This function is used by the test framework to ensure that debug variables -/// haven't been inadvertently changed. -/// -/// See [the services library](services/services-library.html) for a complete list. -bool debugAssertAllServicesVarsUnset(String reason) { - assert(() { - if (debugKeyEventSimulatorTransitModeOverride != null) { - throw FlutterError(reason); - } - return true; - }()); - return true; -} diff --git a/packages/flutter/lib/src/services/hardware_keyboard.dart b/packages/flutter/lib/src/services/hardware_keyboard.dart deleted file mode 100644 index dc579922d9..0000000000 --- a/packages/flutter/lib/src/services/hardware_keyboard.dart +++ /dev/null @@ -1,941 +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. - -import 'dart:ui' as ui; - -import 'package:flutter/foundation.dart'; -import 'binding.dart'; -import 'keyboard_key.dart'; -import 'raw_keyboard.dart'; - -/// Represents a lock mode of a keyboard, such as [KeyboardLockMode.capsLock]. -/// -/// A lock mode locks some of a keyboard's keys into a distinct mode of operation, -/// depending on the lock settings selected. The status of the mode is toggled -/// with each key down of its corresponding logical key. A [KeyboardLockMode] -/// object is used to query whether this mode is enabled on the keyboard. -/// -/// Only a limited number of modes are supported, which are enumerated as -/// static members of this class. Manual constructing of this class is -/// prohibited. -@immutable -class KeyboardLockMode { - // KeyboardLockMode has a fixed pool of supported keys, enumerated as static - // members of this class, therefore constructing is prohibited. - const KeyboardLockMode._(this.logicalKey); - - /// The logical key that triggers this lock mode. - final LogicalKeyboardKey logicalKey; - - /// Represents the number lock mode on the keyboard. - /// - /// On supporting systems, enabling number lock mode usually allows key - /// presses of the number pad to input numbers, instead of acting as up, down, - /// left, right, page up, end, etc. - static const KeyboardLockMode numLock = KeyboardLockMode._(LogicalKeyboardKey.numLock); - - /// Represents the scrolling lock mode on the keyboard. - /// - /// On supporting systems and applications (such as a spreadsheet), enabling - /// scrolling lock mode usually allows key presses of the cursor keys to - /// scroll the document instead of the cursor. - static const KeyboardLockMode scrollLock = KeyboardLockMode._(LogicalKeyboardKey.scrollLock); - - /// Represents the capital letters lock mode on the keyboard. - /// - /// On supporting systems, enabling capital lock mode allows key presses of - /// the letter keys to input uppercase letters instead of lowercase. - static const KeyboardLockMode capsLock = KeyboardLockMode._(LogicalKeyboardKey.capsLock); - - static final Map _knownLockModes = { - numLock.logicalKey.keyId: numLock, - scrollLock.logicalKey.keyId: scrollLock, - capsLock.logicalKey.keyId: capsLock, - }; - - /// Returns the [KeyboardLockMode] constant from the logical key, or - /// null, if not found. - static KeyboardLockMode? findLockByLogicalKey(LogicalKeyboardKey logicalKey) => _knownLockModes[logicalKey.keyId]; -} - -/// Defines the interface for keyboard key events. -/// -/// The [KeyEvent] provides a universal model for key event information from a -/// hardware keyboard across platforms. -/// -/// See also: -/// -/// * [HardwareKeyboard] for full introduction to key event model and handling. -/// * [KeyDownEvent], a subclass for events representing the user pressing a -/// key. -/// * [KeyRepeatEvent], a subclass for events representing the user holding a -/// key, causing repeated events. -/// * [KeyUpEvent], a subclass for events representing the user releasing a -/// key. -@immutable -abstract class KeyEvent with Diagnosticable { - /// Create a const KeyEvent by providing each field. - const KeyEvent({ - required this.physicalKey, - required this.logicalKey, - this.character, - required this.timeStamp, - this.synthesized = false, - }); - - /// Returns an object representing the physical location of this key. - /// - /// A [PhysicalKeyboardKey] represents a USB HID code sent from the keyboard, - /// ignoring the key map, modifier keys (like SHIFT), and the label on the key. - /// - /// [PhysicalKeyboardKey]s are used to describe and test for keys in a - /// particular location. A [PhysicalKeyboardKey] may have a name, but the name - /// is a mnemonic ("keyA" is easier to remember than 0x70004), derived from the - /// key's effect on a QWERTY keyboard. The name does not represent the key's - /// effect whatsoever (a physical "keyA" can be the Q key on an AZERTY - /// keyboard.) - /// - /// For instance, if you wanted to make a game where the key to the right of - /// the CAPS LOCK key made the player move left, you would be comparing a - /// physical key with [PhysicalKeyboardKey.keyA], since that is the key next to - /// the CAPS LOCK key on a QWERTY keyboard. This would return the same thing - /// even on an AZERTY keyboard where the key next to the CAPS LOCK produces a - /// "Q" when pressed. - /// - /// If you want to make your app respond to a key with a particular character - /// on it regardless of location of the key, use [KeyEvent.logicalKey] instead. - /// - /// Also, even though physical keys are defined with USB HID codes, their - /// values are not necessarily the same HID codes produced by the hardware and - /// presented to the driver, because on most platforms Flutter has to map the - /// platform representation back to an HID code since the original HID - /// code is not provided. USB HID is simply a conveniently well-defined - /// standard to list possible keys that a Flutter app can encounter. - /// - /// See also: - /// - /// * [logicalKey] for the non-location specific key generated by this event. - /// * [character] for the character generated by this keypress (if any). - final PhysicalKeyboardKey physicalKey; - - /// Returns an object representing the logical key that was pressed. - /// - /// {@template flutter.services.KeyEvent.logicalKey} - /// This method takes into account the key map and modifier keys (like SHIFT) - /// to determine which logical key to return. - /// - /// If you are looking for the character produced by a key event, use - /// [KeyEvent.character] instead. - /// - /// If you are collecting text strings, use the [TextField] or - /// [CupertinoTextField] widgets, since those automatically handle many of the - /// complexities of managing keyboard input, like showing a soft keyboard or - /// interacting with an input method editor (IME). - /// {@endtemplate} - final LogicalKeyboardKey logicalKey; - - /// Returns the Unicode character (grapheme cluster) completed by this - /// keystroke, if any. - /// - /// This will only return a character if this keystroke, combined with any - /// preceding keystroke(s), generats a character, and only on a "key down" - /// event. It will return null if no character has been generated by the - /// keystroke (e.g. a "dead" or "combining" key), or if the corresponding key - /// is a key without a visual representation, such as a modifier key or a - /// control key. It will also return null if this is a "key up" event. - /// - /// This can return multiple Unicode code points, since some characters (more - /// accurately referred to as grapheme clusters) are made up of more than one - /// code point. - /// - /// The [character] doesn't take into account edits by an input method editor - /// (IME), or manage the visibility of the soft keyboard on touch devices. For - /// composing text, use the [TextField] or [CupertinoTextField] widgets, since - /// those automatically handle many of the complexities of managing keyboard - /// input. - /// - /// The [character] is not available on [KeyUpEvent]s. - final String? character; - - /// Time of event, relative to an arbitrary start point. - /// - /// All events share the same timeStamp origin. - final Duration timeStamp; - - /// Whether this event is synthesized by Flutter to synchronize key states. - /// - /// An non-[synthesized] event is converted from a native event, and a native - /// event can only be converted to one non-[synthesized] event. Some properties - /// might be changed during the conversion (for example, a native repeat event - /// might be converted to a Flutter down event when necessary.) - /// - /// A [synthesized] event is created without a source native event in order to - /// synthronize key states. For example, if the native platform shows that a - /// shift key that was previously held has been released somehow without the - /// key up event dispatched (probably due to loss of focus), a synthesized key - /// up event will be added to regularized the event stream. - /// - /// For detailed introduction to the regularized event model, see - /// [HardwareKeyboard]. - /// - /// Defaults to false. - final bool synthesized; - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('physicalKey', physicalKey)); - properties.add(DiagnosticsProperty('logicalKey', logicalKey)); - properties.add(StringProperty('character', character)); - properties.add(DiagnosticsProperty('timeStamp', timeStamp)); - properties.add(FlagProperty('synthesized', value: synthesized, ifTrue: 'synthesized')); - } -} - -/// An event indicating that the user has pressed a key down on the keyboard. -/// -/// See also: -/// -/// * [KeyRepeatEvent], a key event representing the user -/// holding a key, causing repeated events. -/// * [KeyUpEvent], a key event representing the user -/// releasing a key. -/// * [HardwareKeyboard], which produces this event. -class KeyDownEvent extends KeyEvent { - /// Creates a key event that represents the user pressing a key. - const KeyDownEvent({ - required PhysicalKeyboardKey physicalKey, - required LogicalKeyboardKey logicalKey, - String? character, - required Duration timeStamp, - bool synthesized = false, - }) : super( - physicalKey: physicalKey, - logicalKey: logicalKey, - character: character, - timeStamp: timeStamp, - synthesized: synthesized, - ); -} - -/// An event indicating that the user has released a key on the keyboard. -/// -/// See also: -/// -/// * [KeyDownEvent], a key event representing the user -/// pressing a key. -/// * [KeyRepeatEvent], a key event representing the user -/// holding a key, causing repeated events. -/// * [HardwareKeyboard], which produces this event. -class KeyUpEvent extends KeyEvent { - /// Creates a key event that represents the user pressing a key. - const KeyUpEvent({ - required PhysicalKeyboardKey physicalKey, - required LogicalKeyboardKey logicalKey, - required Duration timeStamp, - bool synthesized = false, - }) : super( - physicalKey: physicalKey, - logicalKey: logicalKey, - timeStamp: timeStamp, - synthesized: synthesized, - ); -} - -/// An event indicating that the user has been holding a key on the keyboard -/// and causing repeated events. -/// -/// See also: -/// -/// * [KeyDownEvent], a key event representing the user -/// pressing a key. -/// * [KeyUpEvent], a key event representing the user -/// releasing a key. -/// * [HardwareKeyboard], which produces this event. -class KeyRepeatEvent extends KeyEvent { - /// Creates a key event that represents the user pressing a key. - const KeyRepeatEvent({ - required PhysicalKeyboardKey physicalKey, - required LogicalKeyboardKey logicalKey, - String? character, - required Duration timeStamp, - }) : super( - physicalKey: physicalKey, - logicalKey: logicalKey, - character: character, - timeStamp: timeStamp, - ); -} - -/// The signature for [HardwareKeyboard.addHandler], a callback to to decide whether -/// the entire framework handles a key event. -typedef KeyEventCallback = bool Function(KeyEvent event); - -/// Manages key events from hardware keyboards. -/// -/// [HardwareKeyboard] manages all key events of the Flutter application from -/// hardware keyboards (in contrast to on-screen keyboards). It receives key -/// data from the native platform, dispatches key events to registered -/// handlers, and records the keyboard state. -/// -/// To stay notified whenever keys are pressed, held, or released, add a -/// handler with [addHandler]. To only be notified when a specific part of the -/// app is focused, use a [Focus] widget's `onFocusChanged` attribute instead -/// of [addHandler]. Handlers should be removed with [removeHandler] when -/// notification is no longer necessary, or when the handler is being disposed. -/// -/// To query whether a key is being held, or a lock mode is enabled, use -/// [physicalKeysPressed], [logicalKeysPressed], or [lockModesEnabled]. -/// These states will have been updated with the event when used during a key -/// event handler. -/// -/// The singleton [HardwareKeyboard] instance is held by the [ServicesBinding] -/// as [ServicesBinding.keyboard], and can be conveniently accessed using the -/// [HardwareKeyboard.instance] static accessor. -/// -/// ## Event model -/// -/// Flutter uses a universal event model ([KeyEvent]) and key options -/// ([LogicalKeyboardKey] and [PhysicalKeyboardKey]) regardless of the native -/// platform, while preserving platform-specific features as much as -/// possible. -/// -/// [HardwareKeyboard] guarantees that the key model is "regularized": The key -/// event stream consists of "key tap sequences", where a key tap sequence is -/// defined as one [KeyDownEvent], zero or more [KeyRepeatEvent]s, and one -/// [KeyUpEvent] in order, all with the same physical key and logical key. -/// -/// Example: -/// -/// * Tap and hold key A, US layout: -/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a") -/// * KeyRepeatEvent(physicalKey: keyA, logicalKey: keyA, character: "a") -/// * KeyUpEvent(physicalKey: keyA, logicalKey: keyA) -/// * Press ShiftLeft, tap key A, then release ShiftLeft, US layout: -/// * KeyDownEvent(physicalKey: shiftLeft, logicalKey: shiftLeft) -/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "A") -/// * KeyRepeatEvent(physicalKey: keyA, logicalKey: keyA, character: "A") -/// * KeyUpEvent(physicalKey: keyA, logicalKey: keyA) -/// * KeyUpEvent(physicalKey: shiftLeft, logicalKey: shiftLeft) -/// * Tap key Q, French layout: -/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyQ, character: "q") -/// * KeyUpEvent(physicalKey: keyA, logicalKey: keyQ) -/// * Tap CapsLock: -/// * KeyDownEvent(physicalKey: capsLock, logicalKey: capsLock) -/// * KeyUpEvent(physicalKey: capsLock, logicalKey: capsLock) -/// -/// When the Flutter application starts, all keys are released, and all lock -/// modes are disabled. Upon key events, [HardwareKeyboard] will update its -/// states, then dispatch callbacks: [KeyDownEvent]s and [KeyUpEvent]s set -/// or reset the pressing state, while [KeyDownEvent]s also toggle lock modes. -/// -/// Flutter will try to synchronize with the ground truth of keyboard states -/// using synthesized events ([KeyEvent.synthesized]), subject to the -/// availability of the platform. The desynchronization can be caused by -/// non-empty initial state or a change in the focused window or application. -/// For example, if CapsLock is enabled when the application starts, then -/// immediately before the first key event, a synthesized [KeyDownEvent] and -/// [KeyUpEvent] of CapsLock will be dispatched. -/// -/// The resulting event stream does not map one-to-one to the native key event -/// stream. Some native events might be skipped, while some events might be -/// synthesized and do not correspond to native events. Synthesized events will -/// be indicated by [KeyEvent.synthesized]. -/// -/// Example: -/// -/// * Flutter starts with CapsLock on, the first press of keyA: -/// * KeyDownEvent(physicalKey: capsLock, logicalKey: capsLock, synthesized: true) -/// * KeyUpEvent(physicalKey: capsLock, logicalKey: capsLock, synthesized: true) -/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a") -/// * While holding ShiftLeft, lose window focus, release shiftLeft, then focus -/// back and press keyA: -/// * KeyUpEvent(physicalKey: shiftLeft, logicalKey: shiftLeft, synthesized: true) -/// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a") -/// -/// Flutter does not distinguish between multiple keyboards. Flutter will -/// process all events as if they come from a single keyboard, and try to -/// resolve any conflicts and provide a regularized key event stream, which -/// can deviate from the ground truth. -/// -/// ## Compared to [RawKeyboard] -/// -/// [RawKeyboard] is the legacy API, and will be deprecated and removed in the -/// future. It is recommended to always use [HardwareKeyboard] and [KeyEvent] -/// APIs (such as [FocusNode.onKeyEvent]) to handle key events. -/// -/// Behavior-wise, [RawKeyboard] provides a less unified, less regular -/// event model than [HardwareKeyboard]. For example: -/// -/// * Down events might not be matched with an up event, and vice versa (the -/// set of pressed keys is silently updated). -/// * The logical key of the down event might not be the same as that of the up -/// event. -/// * Down events and repeat events are not easily distinguishable (must be -/// tracked manually). -/// * Lock modes (such as CapsLock) only have their "enabled" state recorded. -/// There's no way to acquire their pressing state. -/// -/// See also: -/// -/// * [KeyDownEvent], [KeyRepeatEvent], and [KeyUpEvent], the classes used to -/// describe specific key events. -/// * [instance], the singleton instance of this class. -/// * [RawKeyboard], the legacy API that dispatches key events containing raw -/// system data. -class HardwareKeyboard { - /// Provides convenient access to the current [HardwareKeyboard] singleton from - /// the [ServicesBinding] instance. - static HardwareKeyboard get instance => ServicesBinding.instance!.keyboard; - - final Map _pressedKeys = {}; - - /// The set of [PhysicalKeyboardKey]s that are pressed. - /// - /// If called from a key event handler, the result will already include the effect - /// of the event. - /// - /// See also: - /// - /// * [logicalKeysPressed], which tells if a logical key is being pressed. - Set get physicalKeysPressed => _pressedKeys.keys.toSet(); - - /// The set of [LogicalKeyboardKey]s that are pressed. - /// - /// If called from a key event handler, the result will already include the effect - /// of the event. - /// - /// See also: - /// - /// * [physicalKeysPressed], which tells if a physical key is being pressed. - Set get logicalKeysPressed => _pressedKeys.values.toSet(); - - /// Returns the logical key that corresponds to the given pressed physical key. - /// - /// Returns null if the physical key is not currently pressed. - LogicalKeyboardKey? lookUpLayout(PhysicalKeyboardKey physicalKey) => _pressedKeys[physicalKey]; - - final Set _lockModes = {}; - /// The set of [KeyboardLockMode] that are enabled. - /// - /// Lock keys, such as CapsLock, are logical keys that toggle their - /// respective boolean states on key down events. Such flags are usually used - /// as modifier to other keys or events. - /// - /// If called from a key event handler, the result will already include the effect - /// of the event. - Set get lockModesEnabled => _lockModes; - - void _assertEventIsRegular(KeyEvent event) { - assert(() { - const String common = 'If this occurs in real application, please report this ' - 'bug to Flutter. If this occurs in unit tests, please ensure that ' - "simulated events follow Flutter's event model as documented in " - '`HardwareKeyboard`. This was the event: '; - if (event is KeyDownEvent) { - assert(!_pressedKeys.containsKey(event.physicalKey), - 'A ${event.runtimeType} is dispatched, but the state shows that the physical ' - 'key is already pressed. $common$event'); - } else if (event is KeyRepeatEvent || event is KeyUpEvent) { - assert(_pressedKeys.containsKey(event.physicalKey), - 'A ${event.runtimeType} is dispatched, but the state shows that the physical ' - 'key is not pressed. $common$event'); - assert(_pressedKeys[event.physicalKey] == event.logicalKey, - 'A ${event.runtimeType} is dispatched, but the state shows that the physical ' - 'key is pressed on a different logical key. $common$event ' - 'and the recorded logical key ${_pressedKeys[event.physicalKey]}'); - } else { - assert(false, 'Unexpected key event class ${event.runtimeType}'); - } - return true; - }()); - } - - List _handlers = []; - bool _duringDispatch = false; - List? _modifiedHandlers; - - /// Register a listener that is called every time a hardware key event - /// occurs. - /// - /// All registered handlers will be invoked in order regardless of - /// their return value. The return value indicates whether Flutter - /// "handles" the event. If any handler returns true, the event - /// will not be propagated to other native components in the add-to-app - /// scenario. - /// - /// If an object added a handler, it must remove the handler before it is - /// disposed. - /// - /// If used during event dispatching, the addition will not take effect - /// until after the dispatching. - /// - /// See also: - /// - /// * [removeHandler], which removes the handler. - void addHandler(KeyEventCallback handler) { - if (_duringDispatch) { - _modifiedHandlers ??= [..._handlers]; - _modifiedHandlers!.add(handler); - } else { - _handlers.add(handler); - } - } - - /// Stop calling the given listener every time a hardware key event - /// occurs. - /// - /// The `handler` argument must be [identical] to the one used in - /// [addHandler]. If multiple exist, the first one will be removed. - /// If none is found, then this method is a no-op. - /// - /// If used during event dispatching, the removal will not take effect - /// until after the event has been dispatched. - void removeHandler(KeyEventCallback handler) { - if (_duringDispatch) { - _modifiedHandlers ??= [..._handlers]; - _modifiedHandlers!.remove(handler); - } else { - _handlers.remove(handler); - } - } - - bool _dispatchKeyEvent(KeyEvent event) { - // This dispatching could have used the same algorithm as [ChangeNotifier], - // but since 1) it shouldn't be necessary to support reentrantly - // dispatching, 2) there shouldn't be many handlers (most apps should use - // only 1, this function just uses a simpler algorithm. - assert(!_duringDispatch, 'Nested keyboard dispatching is not supported'); - _duringDispatch = true; - bool handled = false; - for (final KeyEventCallback handler in _handlers) { - try { - final bool thisResult = handler(event); - handled = handled || thisResult; - } catch (exception, stack) { - FlutterError.reportError(FlutterErrorDetails( - exception: exception, - stack: stack, - library: 'services library', - context: ErrorDescription('while dispatching notifications for $runtimeType'), - informationCollector: () sync* { - yield DiagnosticsProperty( - 'The $runtimeType sending notification was', - this, - style: DiagnosticsTreeStyle.errorProperty, - ); - }, - )); - } - } - _duringDispatch = false; - if (_modifiedHandlers != null) { - _handlers = _modifiedHandlers!; - _modifiedHandlers = null; - } - return handled; - } - - /// Process a new [KeyEvent] by recording the state changes and dispatching - /// to handlers. - bool handleKeyEvent(KeyEvent event) { - _assertEventIsRegular(event); - final PhysicalKeyboardKey physicalKey = event.physicalKey; - final LogicalKeyboardKey logicalKey = event.logicalKey; - if (event is KeyDownEvent) { - _pressedKeys[physicalKey] = logicalKey; - final KeyboardLockMode? lockMode = KeyboardLockMode.findLockByLogicalKey(event.logicalKey); - if (lockMode != null) { - if (_lockModes.contains(lockMode)) { - _lockModes.remove(lockMode); - } else { - _lockModes.add(lockMode); - } - } - } else if (event is KeyUpEvent) { - _pressedKeys.remove(physicalKey); - } else if (event is KeyRepeatEvent) { - // Empty - } - - return _dispatchKeyEvent(event); - } - - /// Clear all keyboard states and additional handlers. - /// - /// All handlers are removed except for the first one, which is added by - /// [ServicesBinding]. - /// - /// This is used by the testing framework to make sure that tests are hermetic. - @visibleForTesting - void clearState() { - _pressedKeys.clear(); - _lockModes.clear(); - _handlers.clear(); - assert(_modifiedHandlers == null); - } -} - -/// The mode in which information of key messages is delivered. -/// -/// Different platforms use different methods, classes, and models to inform the -/// framework of native key events, which is called "transit mode". -/// -/// The framework must determine which transit mode the current platform -/// implements and behave accordingly (such as transforming and synthesizing -/// events if necessary). Unit tests related to keyboard might also want to -/// simulate platform of each transit mode. -/// -/// The transit mode of the current platform is inferred by [KeyEventManager] at -/// the start of the application. -/// -/// See also: -/// -/// * [KeyEventManager], which infers the transit mode of the current platform -/// and guides how key messages are dispatched. -/// * [debugKeyEventSimulatorTransitModeOverride], overrides the transit mode -/// used to simulate key events. -/// * [KeySimulatorTransitModeVariant], an easier way to set -/// [debugKeyEventSimulatorTransitModeOverride] in widget tests. -enum KeyDataTransitMode { - /// Key event information is delivered as raw key data. - /// - /// Raw key data is platform's native key event information sent in JSON - /// through a method channel, which is then interpreted as a platform subclass - /// of [RawKeyEventData]. - /// - /// If the current transit mode is [rawKeyData], the raw key data is converted - /// to both [KeyMessage.events] and [KeyMessage.rawEvent]. - rawKeyData, - - /// Key event information is delivered as converted key data, followed - /// by raw key data. - /// - /// Key data ([ui.KeyData]) is a standardized event stream converted from - /// platform's native key event information, sent through the embedder - /// API. Its event model is described in [HardwareKeyboard]. - /// - /// Raw key data is platform's native key event information sent in JSON - /// through a method channel. It is interpreted by subclasses of - /// [RawKeyEventData]. - /// - /// If the current transit mode is [rawKeyData], the key data is converted to - /// [KeyMessage.events], and the raw key data is converted to - /// [KeyMessage.rawEvent]. - keyDataThenRawKeyData, -} - -/// The assumbled information corresponding to a native key message. -/// -/// While Flutter's [KeyEvent]s are created from key messages from the native -/// platform, every native message might result in multiple [KeyEvent]s. For -/// example, this might happen in order to synthesize missed modifier key -/// presses or releases. -/// A [KeyMessage] bundles all information related to a native key message -/// together for the convenience of propagation on the [FocusNode] tree. -/// -/// When dispatched to handlers or listeners, or propagated through the -/// [FocusNode] tree, all handlers or listeners belonging to a node are -/// executed regardless of their [KeyEventResult], and all results are combined -/// into the result of the node using [combineKeyEventResults]. -/// -/// In very rare cases, a native key message might not result in a [KeyMessage]. -/// For example, key messages for Fn key are ignored on macOS for the -/// convenience of cross-platform code. -@immutable -class KeyMessage { - /// Create a [KeyMessage] by providing all information. - /// - /// The [events] might be empty. - const KeyMessage(this.events, this.rawEvent); - - /// The list of [KeyEvent]s converted from the native key message. - /// - /// A native key message is converted into multiple [KeyEvent]s in a regular - /// event model. The [events] might contain zero or any number of - /// [KeyEvent]s. - /// - /// See also: - /// - /// * [HardwareKeyboard], which describes the regular event model. - /// * [HardwareKeyboard.addHandler], [KeyboardListener], [Focus.onKeyEvent], - /// where [KeyEvent]s are commonly used. - final List events; - - /// The native key message in the form of a raw key event. - /// - /// A native key message is sent to the framework in JSON and represented - /// in a platform-specific form as [RawKeyEventData] and a platform-neutral - /// form as [RawKeyEvent]. Their stream is not as regular as [KeyEvent]'s, - /// but keeps as much native information and structure as possible. - /// - /// The [rawEvent] will be deprecated in the future. - /// - /// See also: - /// - /// * [RawKeyboard.addListener], [RawKeyboardListener], [Focus.onKey], - /// where [RawKeyEvent]s are commonly used. - final RawKeyEvent rawEvent; - - @override - String toString() { - return 'KeyMessage($events)'; - } -} - -/// The signature for [KeyEventManager.keyMessageHandler]. -/// -/// A [KeyMessageHandler] processes a [KeyMessage] and returns whether -/// the message is considered handled. Handled messages should not be -/// propagated to other native components. -typedef KeyMessageHandler = bool Function(KeyMessage message); - -/// A singleton class that processes key messages from the platform and -/// dispatches converted messages accordingly. -/// -/// [KeyEventManager] receives platform key messages by [handleKeyData] -/// and [handleRawKeyMessage], sends converted events to [HardwareKeyboard] -/// and [RawKeyboard] for recording keeping, and then dispatches the [KeyMessage] -/// to [keyMessageHandler], the global message handler. -/// -/// [KeyEventManager] also resolves cross-platform compatibility of keyboard -/// implementations. Legacy platforms might have not implemented the new key -/// data API and only send raw key data on each key message. [KeyEventManager] -/// recognize platform types as [KeyDataTransitMode] and dispatches events in -/// different ways accordingly. -/// -/// [KeyEventManager] is typically created, owned, and invoked by -/// [ServicesBinding]. -class KeyEventManager { - /// Create an instance. - /// - /// This is typically only called by [ServicesBinding]. - KeyEventManager(this._hardwareKeyboard, this._rawKeyboard); - - /// The global handler for all hardware key messages of Flutter. - /// - /// Key messages received from the platform are first sent to [RawKeyboard]'s - /// listeners and [HardwareKeyboard]'s handlers, then sent to - /// [keyMessageHandler], regardless of the results of [HardwareKeyboard]'s - /// handlers. The result from the handlers and [keyMessageHandler] are - /// combined and returned to the platform. The handler result is explained below. - /// - /// This handler is normally set by the [FocusManager] so that it can control - /// the key event propagation to focused widgets. Applications that use the - /// focus system (see [Focus] and [FocusManager]) to receive key events - /// do not need to set this field. - /// - /// ## Handler result - /// - /// Key messages on the platform are given to Flutter to be handled by the - /// engine. If they are not handled, then the platform will continue to - /// distribute the keys (i.e. propagate them) to other (possibly non-Flutter) - /// components in the application. The return value from this handler tells - /// the platform to either stop propagation (by returning true: "event - /// handled"), or pass the event on to other controls (false: "event not - /// handled"). Some platforms might trigger special alerts if the event - /// is not handled by other controls either (such as the "bonk" noise on - /// macOS). - /// - /// If you are not using the [FocusManager] to manage focus, set this - /// attribute to a [KeyMessageHandler] that returns true if the propagation - /// on the platform should not be continued. Otherwise, key events will be - /// assumed to not have been handled by Flutter, and will also be sent to - /// other (possibly non-Flutter) controls in the application. - /// - /// See also: - /// - /// * [Focus.onKeyEvent], a [Focus] callback attribute that will be given - /// key events distributed by the [FocusManager] based on the current - /// primary focus. - /// * [HardwareKeyboard.addHandler], which accepts multiple handlers to - /// control the handler result but only accepts [KeyEvent] instead of - /// [KeyMessage]. - KeyMessageHandler? keyMessageHandler; - - final HardwareKeyboard _hardwareKeyboard; - final RawKeyboard _rawKeyboard; - - // The [KeyDataTransitMode] of the current platform. - // - // The `_transitMode` is guaranteed to be non-null during key event callbacks. - // - // The `_transitMode` is null before the first event, after which it is inferred - // and will not change throughout the lifecycle of the application. - // - // The `_transitMode` can be reset to null in tests using [clearState]. - KeyDataTransitMode? _transitMode; - - // The accumulated [KeyEvent]s since the last time the message is dispatched. - // - // If _transitMode is [KeyDataTransitMode.keyDataThenRawKeyData], then [KeyEvent]s - // are directly received from [handleKeyData]. If _transitMode is - // [KeyDataTransitMode.rawKeyData], then [KeyEvent]s are converted from the raw - // key data of [handleRawKeyMessage]. Either way, the accumulated key - // events are only dispatched along with the next [KeyMessage] when a - // dispatchable [RawKeyEvent] is available. - final List _keyEventsSinceLastMessage = []; - - /// Dispatch a key data to global and leaf listeners. - /// - /// This method is the handler to the global `onKeyData` API. - bool handleKeyData(ui.KeyData data) { - _transitMode ??= KeyDataTransitMode.keyDataThenRawKeyData; - switch (_transitMode!) { - case KeyDataTransitMode.rawKeyData: - assert(false, 'Should never encounter KeyData when transitMode is rawKeyData.'); - return false; - case KeyDataTransitMode.keyDataThenRawKeyData: - // Postpone key event dispatching until the handleRawKeyMessage. - _keyEventsSinceLastMessage.add(_eventFromData(data)); - return false; - } - } - - /// Handles a raw key message. - /// - /// This method is the handler to [SystemChannels.keyEvent], processing - /// the JSON form of the native key message and returns the responds for the - /// channel. - Future> handleRawKeyMessage(dynamic message) async { - if (_transitMode == null) { - _transitMode = KeyDataTransitMode.rawKeyData; - // Convert raw events using a listener so that conversion only occurs if - // the raw event should be dispatched. - _rawKeyboard.addListener(_convertRawEventAndStore); - } - final RawKeyEvent rawEvent = RawKeyEvent.fromMessage(message as Map); - // The following `handleRawKeyEvent` will call `_convertRawEventAndStore` - // unless the event is not dispatched. - bool handled = _rawKeyboard.handleRawKeyEvent(rawEvent); - - for (final KeyEvent event in _keyEventsSinceLastMessage) { - handled = _hardwareKeyboard.handleKeyEvent(event) || handled; - } - if (_transitMode == KeyDataTransitMode.rawKeyData) { - assert(setEquals(_rawKeyboard.physicalKeysPressed, _hardwareKeyboard.physicalKeysPressed), - 'RawKeyboard reported ${_rawKeyboard.physicalKeysPressed}, ' - 'while HardwareKeyboard reported ${_hardwareKeyboard.physicalKeysPressed}'); - } - - if (keyMessageHandler != null) { - handled = keyMessageHandler!(KeyMessage(_keyEventsSinceLastMessage, rawEvent)) || handled; - } - _keyEventsSinceLastMessage.clear(); - - return { 'handled': handled }; - } - - // Convert the raw event to key events, including synthesizing events for - // modifiers, and store the key events in `_keyEventsSinceLastMessage`. - // - // This is only called when `_transitMode` is `rawKeyEvent` and if the raw - // event should be dispatched. - void _convertRawEventAndStore(RawKeyEvent rawEvent) { - final PhysicalKeyboardKey physicalKey = rawEvent.physicalKey; - final LogicalKeyboardKey logicalKey = rawEvent.logicalKey; - final Set physicalKeysPressed = _hardwareKeyboard.physicalKeysPressed; - final KeyEvent? mainEvent; - final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey); - final Duration timeStamp = ServicesBinding.instance!.currentSystemFrameTimeStamp; - final String? character = rawEvent.character == '' ? null : rawEvent.character; - if (rawEvent is RawKeyDownEvent) { - if (recordedLogicalMain == null) { - mainEvent = KeyDownEvent( - physicalKey: physicalKey, - logicalKey: logicalKey, - character: character, - timeStamp: timeStamp, - ); - physicalKeysPressed.add(physicalKey); - } else { - assert(physicalKeysPressed.contains(physicalKey)); - mainEvent = KeyRepeatEvent( - physicalKey: physicalKey, - logicalKey: recordedLogicalMain, - character: character, - timeStamp: timeStamp, - ); - } - } else { - assert(rawEvent is RawKeyUpEvent, 'Unexpected subclass of RawKeyEvent: ${rawEvent.runtimeType}'); - if (recordedLogicalMain == null) { - mainEvent = null; - } else { - mainEvent = KeyUpEvent( - logicalKey: recordedLogicalMain, - physicalKey: physicalKey, - timeStamp: timeStamp, - ); - physicalKeysPressed.remove(physicalKey); - } - } - for (final PhysicalKeyboardKey key in physicalKeysPressed.difference(_rawKeyboard.physicalKeysPressed)) { - _keyEventsSinceLastMessage.add(KeyUpEvent( - physicalKey: key, - logicalKey: _hardwareKeyboard.lookUpLayout(physicalKey)!, - timeStamp: timeStamp, - synthesized: true, - )); - } - for (final PhysicalKeyboardKey key in _rawKeyboard.physicalKeysPressed.difference(physicalKeysPressed)) { - _keyEventsSinceLastMessage.add(KeyDownEvent( - physicalKey: key, - logicalKey: _rawKeyboard.lookUpLayout(physicalKey)!, - timeStamp: timeStamp, - synthesized: true, - )); - } - if (mainEvent != null) - _keyEventsSinceLastMessage.add(mainEvent); - } - - /// Reset the inferred platform transit mode and related states. - /// - /// This method is only used in unit tests. In release mode, this is a no-op. - @visibleForTesting - void clearState() { - assert(() { - _transitMode = null; - _rawKeyboard.removeListener(_convertRawEventAndStore); - _keyEventsSinceLastMessage.clear(); - return true; - }()); - } - - static KeyEvent _eventFromData(ui.KeyData keyData) { - final PhysicalKeyboardKey physicalKey = - PhysicalKeyboardKey.findKeyByCode(keyData.physical) ?? - PhysicalKeyboardKey(keyData.physical); - final LogicalKeyboardKey logicalKey = - LogicalKeyboardKey.findKeyByKeyId(keyData.logical) ?? - LogicalKeyboardKey(keyData.logical); - final Duration timeStamp = keyData.timeStamp; - switch (keyData.type) { - case ui.KeyEventType.down: - return KeyDownEvent( - physicalKey: physicalKey, - logicalKey: logicalKey, - timeStamp: timeStamp, - character: keyData.character, - synthesized: keyData.synthesized, - ); - case ui.KeyEventType.up: - assert(keyData.character == null); - return KeyUpEvent( - physicalKey: physicalKey, - logicalKey: logicalKey, - timeStamp: timeStamp, - synthesized: keyData.synthesized, - ); - case ui.KeyEventType.repeat: - return KeyRepeatEvent( - physicalKey: physicalKey, - logicalKey: logicalKey, - timeStamp: timeStamp, - character: keyData.character, - ); - } - } -} diff --git a/packages/flutter/lib/src/services/keyboard_key.dart b/packages/flutter/lib/src/services/keyboard_key.dart index 7262413d86..de4d6cdf28 100644 --- a/packages/flutter/lib/src/services/keyboard_key.dart +++ b/packages/flutter/lib/src/services/keyboard_key.dart @@ -2624,9 +2624,6 @@ class LogicalKeyboardKey extends KeyboardKey { /// See the function [RawKeyEvent.logicalKey] for more information. static const LogicalKeyboardKey gameButtonZ = LogicalKeyboardKey(0x0020000031f); - /// A list of all predefined constant [LogicalKeyboardKey]s. - static Iterable get knownLogicalKeys => _knownLogicalKeys.values; - // A list of all predefined constant LogicalKeyboardKeys so they can be // searched. static const Map _knownLogicalKeys = { @@ -5139,9 +5136,6 @@ class PhysicalKeyboardKey extends KeyboardKey { /// See the function [RawKeyEvent.physicalKey] for more information. static const PhysicalKeyboardKey showAllWindows = PhysicalKeyboardKey(0x000c029f); - /// A list of all predefined constant [PhysicalKeyboardKey]s. - static Iterable get knownPhysicalKeys => _knownPhysicalKeys.values; - // A list of all the predefined constant PhysicalKeyboardKeys so that they // can be searched. static const Map _knownPhysicalKeys = { diff --git a/packages/flutter/lib/src/services/keyboard_maps.dart b/packages/flutter/lib/src/services/keyboard_maps.dart index 74cea741ea..d8940f1bad 100644 --- a/packages/flutter/lib/src/services/keyboard_maps.dart +++ b/packages/flutter/lib/src/services/keyboard_maps.dart @@ -2953,16 +2953,6 @@ const Map kWindowsToLogicalKey = handler(message.rawEvent); - ServicesBinding.instance!.keyEventManager.keyMessageHandler = _cachedKeyMessageHandler; - } + /// Key events on the platform are given to Flutter to be handled by the + /// engine. If they are not handled, then the platform will continue to + /// distribute the keys (i.e. propagate them) to other (possibly non-Flutter) + /// components in the application. The return value from this handler tells + /// the platform to either stop propagation (by returning true: "event + /// handled"), or pass the event on to other controls (false: "event not + /// handled"). + /// + /// This handler is normally set by the [FocusManager] so that it can control + /// the key event propagation to focused widgets. + /// + /// Most applications can use the focus system (see [Focus] and + /// [FocusManager]) to receive key events. If you are not using the + /// [FocusManager] to manage focus, then to be able to stop propagation of the + /// event by indicating that the event was handled, set this attribute to a + /// [RawKeyEventHandler]. Otherwise, key events will be assumed to not have + /// been handled by Flutter, and will also be sent to other (possibly + /// non-Flutter) controls in the application. + /// + /// See also: + /// + /// * [Focus.onKey], a [Focus] callback attribute that will be given key + /// events distributed by the [FocusManager] based on the current primary + /// focus. + /// * [addListener], to add passive key event listeners that do not stop event + /// propagation. + RawKeyEventHandler? keyEventHandler; - /// Process a new [RawKeyEvent] by recording the state changes and - /// dispatching to listeners. - bool handleRawKeyEvent(RawKeyEvent event) { + Future _handleKeyEvent(dynamic message) async { + final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map); bool shouldDispatch = true; if (event is RawKeyDownEvent) { if (event.data.shouldDispatchEvent()) { @@ -659,7 +659,7 @@ class RawKeyboard { } } if (!shouldDispatch) { - return true; + return { 'handled': true }; } // Make sure that the modifiers reflect reality, in case a modifier key was // pressed/released while the app didn't have focus. @@ -678,7 +678,12 @@ class RawKeyboard { } } - return false; + // Send the key event to the keyEventHandler, then send the appropriate + // response to the platform so that it can resolve the event's handling. + // Defaults to false if keyEventHandler is null. + final bool handled = keyEventHandler != null && keyEventHandler!(event); + assert(handled != null, 'keyEventHandler returned null, which is not allowed'); + return { 'handled': handled }; } static final Map<_ModifierSidePair, Set> _modifierKeyMap = <_ModifierSidePair, Set>{ @@ -803,11 +808,6 @@ class RawKeyboard { /// Returns the set of physical keys currently pressed. Set get physicalKeysPressed => _keysPressed.keys.toSet(); - /// Returns the logical key that corresponds to the given pressed physical key. - /// - /// Returns null if the physical key is not currently pressed. - LogicalKeyboardKey? lookUpLayout(PhysicalKeyboardKey physicalKey) => _keysPressed[physicalKey]; - /// Clears the list of keys returned from [keysPressed]. /// /// This is used by the testing framework to make sure tests are hermetic. @@ -832,5 +832,5 @@ class _ModifierSidePair { } @override - int get hashCode => ui.hashValues(modifier, side); + int get hashCode => hashValues(modifier, side); } diff --git a/packages/flutter/lib/src/services/raw_keyboard_web.dart b/packages/flutter/lib/src/services/raw_keyboard_web.dart index 70e2b94ee2..257346d6b2 100644 --- a/packages/flutter/lib/src/services/raw_keyboard_web.dart +++ b/packages/flutter/lib/src/services/raw_keyboard_web.dart @@ -83,11 +83,13 @@ class RawKeyEventDataWeb extends RawKeyEventData { @override LogicalKeyboardKey get logicalKey { - // Look to see if the keyCode is a key based on location. Typically they are - // numpad keys (versus main area keys) and left/right modifiers. - final LogicalKeyboardKey? maybeLocationKey = kWebLocationMap[key]?[location]; - if (maybeLocationKey != null) - return maybeLocationKey; + // Look to see if the keyCode is a printable number pad key, so that a + // difference between regular keys (e.g. ".") and the number pad version + // (e.g. the "." on the number pad) can be determined. + final LogicalKeyboardKey? numPadKey = kWebNumPadMap[code]; + if (numPadKey != null) { + return numPadKey; + } // Look to see if the [code] is one we know about and have a mapping for. final LogicalKeyboardKey? newKey = kWebToLogicalKey[code]; diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index cfd2100d00..6c442f73a8 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -36,7 +36,7 @@ bool _focusDebug(String message, [Iterable? details]) { } /// An enum that describes how to handle a key event handled by a -/// [FocusOnKeyCallback] or [FocusOnKeyEventCallback]. +/// [FocusOnKeyCallback]. enum KeyEventResult { /// The key event has been handled, and the event should not be propagated to /// other key event handlers. @@ -52,32 +52,6 @@ enum KeyEventResult { skipRemainingHandlers, } -/// Combine the results returned by multiple [FocusOnKeyCallback]s or -/// [FocusOnKeyEventCallback]s. -/// -/// If any callback returns [KeyEventResult.handled], the node considers the -/// message handled; otherwise, if any callback returns -/// [KeyEventResult.skipRemainingHandlers], the node skips the remaining -/// handlers without preventing the platform to handle; otherwise the node is -/// ignored. -KeyEventResult combineKeyEventResults(Iterable results) { - bool hasSkipRemainingHandlers = false; - for (final KeyEventResult result in results) { - switch (result) { - case KeyEventResult.handled: - return KeyEventResult.handled; - case KeyEventResult.skipRemainingHandlers: - hasSkipRemainingHandlers = true; - break; - default: - break; - } - } - return hasSkipRemainingHandlers ? - KeyEventResult.skipRemainingHandlers : - KeyEventResult.ignored; -} - /// Signature of a callback used by [Focus.onKey] and [FocusScope.onKey] /// to receive key events. /// @@ -87,15 +61,6 @@ KeyEventResult combineKeyEventResults(Iterable results) { /// was handled. typedef FocusOnKeyCallback = KeyEventResult Function(FocusNode node, RawKeyEvent event); -/// Signature of a callback used by [Focus.onKeyEvent] and [FocusScope.onKeyEvent] -/// to receive key events. -/// -/// The [node] is the node that received the event. -/// -/// Returns a [KeyEventResult] that describes how, and whether, the key event -/// was handled. -typedef FocusOnKeyEventCallback = KeyEventResult Function(FocusNode node, KeyEvent event); - /// An attachment point for a [FocusNode]. /// /// Using a [FocusAttachment] is rarely needed, unless you are building @@ -306,13 +271,12 @@ enum UnfocusDisposition { /// {@template flutter.widgets.FocusNode.keyEvents} /// ## Key Event Propagation /// -/// The [FocusManager] receives key events from [RawKeyboard] and -/// [HardwareKeyboard] and will pass them to the focused nodes. It starts with -/// the node with the primary focus, and will call the [onKey] or [onKeyEvent] -/// callback for that node. If the callback returns false, indicating that it did -/// not handle the event, the [FocusManager] will move to the parent of that node -/// and call its [onKey] or [onKeyEvent]. If that [onKey] or [onKeyEvent] returns -/// true, then it will stop propagating the event. If it reaches the root +/// The [FocusManager] receives key events from [RawKeyboard] and will pass them +/// to the focused nodes. It starts with the node with the primary focus, and +/// will call the [onKey] callback for that node. If the callback returns false, +/// indicating that it did not handle the event, the [FocusManager] will move to +/// the parent of that node and call its [onKey]. If that [onKey] returns true, +/// then it will stop propagating the event. If it reaches the root /// [FocusScopeNode], [FocusManager.rootScope], the event is discarded. /// {@endtemplate} /// @@ -469,14 +433,9 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { /// /// The [skipTraversal], [descendantsAreFocusable], and [canRequestFocus] /// arguments must not be null. - /// - /// To receive key events that focuses on this node, pass a listener to `onKeyEvent`. - /// The `onKey` is a legacy API based on [RawKeyEvent] and will be deprecated - /// in the future. FocusNode({ String? debugLabel, this.onKey, - this.onKeyEvent, bool skipTraversal = false, bool canRequestFocus = true, bool descendantsAreFocusable = true, @@ -615,18 +574,9 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { /// Called if this focus node receives a key event while focused (i.e. when /// [hasFocus] returns true). /// - /// This is a legacy API based on [RawKeyEvent] and will be deprecated in the - /// future. Prefer [onKeyEvent] instead. - /// /// {@macro flutter.widgets.FocusNode.keyEvents} FocusOnKeyCallback? onKey; - /// Called if this focus node receives a key event while focused (i.e. when - /// [hasFocus] returns true). - /// - /// {@macro flutter.widgets.FocusNode.keyEvents} - FocusOnKeyEventCallback? onKeyEvent; - FocusManager? _manager; List? _ancestors; List? _descendants; @@ -1078,19 +1028,10 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier { /// need to be attached. [FocusAttachment.detach] should be called on the old /// node, and then [attach] called on the new node. This typically happens in /// the [State.didUpdateWidget] method. - /// - /// To receive key events that focuses on this node, pass a listener to `onKeyEvent`. - /// The `onKey` is a legacy API based on [RawKeyEvent] and will be deprecated - /// in the future. @mustCallSuper - FocusAttachment attach( - BuildContext? context, { - FocusOnKeyEventCallback? onKeyEvent, - FocusOnKeyCallback? onKey, - }) { + FocusAttachment attach(BuildContext? context, {FocusOnKeyCallback? onKey}) { _context = context; this.onKey = onKey ?? this.onKey; - this.onKeyEvent = onKeyEvent ?? this.onKeyEvent; _attachment = FocusAttachment._(this); return _attachment!; } @@ -1284,7 +1225,6 @@ class FocusScopeNode extends FocusNode { /// All parameters are optional. FocusScopeNode({ String? debugLabel, - FocusOnKeyEventCallback? onKeyEvent, FocusOnKeyCallback? onKey, bool skipTraversal = false, bool canRequestFocus = true, @@ -1292,7 +1232,6 @@ class FocusScopeNode extends FocusNode { assert(canRequestFocus != null), super( debugLabel: debugLabel, - onKeyEvent: onKeyEvent, onKey: onKey, canRequestFocus: canRequestFocus, descendantsAreFocusable: true, @@ -1524,15 +1463,15 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { /// When this focus manager is no longer needed, calling [dispose] on it will /// unregister these handlers. void registerGlobalHandlers() { - assert(ServicesBinding.instance!.keyEventManager.keyMessageHandler == null); - ServicesBinding.instance!.keyEventManager.keyMessageHandler = _handleKeyMessage; + assert(RawKeyboard.instance.keyEventHandler == null); + RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent; GestureBinding.instance!.pointerRouter.addGlobalRoute(_handlePointerEvent); } @override void dispose() { - if (ServicesBinding.instance!.keyEventManager.keyMessageHandler == _handleKeyMessage) { - ServicesBinding.instance!.keyEventManager.keyMessageHandler = null; + if (RawKeyboard.instance.keyEventHandler == _handleRawKeyEvent) { + RawKeyboard.instance.keyEventHandler = null; GestureBinding.instance!.pointerRouter.removeGlobalRoute(_handlePointerEvent); } super.dispose(); @@ -1721,15 +1660,15 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { } } - bool _handleKeyMessage(KeyMessage message) { + bool _handleRawKeyEvent(RawKeyEvent event) { // Update highlightMode first, since things responding to the keys might // look at the highlight mode, and it should be accurate. _lastInteractionWasTouch = false; _updateHighlightMode(); - assert(_focusDebug('Received key event $message')); + assert(_focusDebug('Received key event ${event.logicalKey}')); if (_primaryFocus == null) { - assert(_focusDebug('No primary focus for key event, ignored: $message')); + assert(_focusDebug('No primary focus for key event, ignored: $event')); return false; } @@ -1738,35 +1677,25 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { // stop propagation, stop. bool handled = false; for (final FocusNode node in [_primaryFocus!, ..._primaryFocus!.ancestors]) { - final List results = []; - if (node.onKeyEvent != null) { - for (final KeyEvent event in message.events) { - results.add(node.onKeyEvent!(node, event)); - } - } if (node.onKey != null) { - results.add(node.onKey!(node, message.rawEvent)); + final KeyEventResult result = node.onKey!(node, event); + switch (result) { + case KeyEventResult.handled: + assert(_focusDebug('Node $node handled key event $event.')); + handled = true; + break; + case KeyEventResult.skipRemainingHandlers: + assert(_focusDebug('Node $node stopped key event propagation: $event.')); + handled = false; + break; + case KeyEventResult.ignored: + continue; + } + break; } - final KeyEventResult result = combineKeyEventResults(results); - switch (result) { - case KeyEventResult.ignored: - continue; - case KeyEventResult.handled: - assert(_focusDebug('Node $node handled key event $message.')); - handled = true; - break; - case KeyEventResult.skipRemainingHandlers: - assert(_focusDebug('Node $node stopped key event propagation: $message.')); - handled = false; - break; - } - // Only KeyEventResult.ignored will continue the for loop. All other - // options will stop the event propagation. - assert(result != KeyEventResult.ignored); - break; } if (!handled) { - assert(_focusDebug('Key event not handled by anyone: $message.')); + assert(_focusDebug('Key event not handled by anyone: $event.')); } return handled; } diff --git a/packages/flutter/lib/src/widgets/focus_scope.dart b/packages/flutter/lib/src/widgets/focus_scope.dart index 673429a4b1..25c171e064 100644 --- a/packages/flutter/lib/src/widgets/focus_scope.dart +++ b/packages/flutter/lib/src/widgets/focus_scope.dart @@ -283,7 +283,6 @@ class Focus extends StatefulWidget { this.autofocus = false, this.onFocusChange, this.onKey, - this.onKeyEvent, this.debugLabel, this.canRequestFocus, this.descendantsAreFocusable = true, @@ -316,24 +315,6 @@ class Focus extends StatefulWidget { /// focus. /// /// Key events are first given to the [FocusNode] that has primary focus, and - /// if its [onKeyEvent] method return false, then they are given to each - /// ancestor node up the focus hierarchy in turn. If an event reaches the root - /// of the hierarchy, it is discarded. - /// - /// This is not the way to get text input in the manner of a text field: it - /// leaves out support for input method editors, and doesn't support soft - /// keyboards in general. For text input, consider [TextField], - /// [EditableText], or [CupertinoTextField] instead, which do support these - /// things. - final FocusOnKeyEventCallback? onKeyEvent; - - /// Handler for keys pressed when this object or one of its children has - /// focus. - /// - /// This is a legacy API based on [RawKeyEvent] and will be deprecated in the - /// future. Prefer [onKeyEvent] instead. - /// - /// Key events are first given to the [FocusNode] that has primary focus, and /// if its [onKey] method return false, then they are given to each ancestor /// node up the focus hierarchy in turn. If an event reaches the root of the /// hierarchy, it is discarded. @@ -559,7 +540,7 @@ class Focus extends StatefulWidget { } @override - State createState() => _FocusState(); + State createState() => _FocusState(); } class _FocusState extends State { @@ -594,7 +575,7 @@ class _FocusState extends State { _canRequestFocus = focusNode.canRequestFocus; _descendantsAreFocusable = focusNode.descendantsAreFocusable; _hasPrimaryFocus = focusNode.hasPrimaryFocus; - _focusAttachment = focusNode.attach(context, onKeyEvent: widget.onKeyEvent, onKey: widget.onKey); + _focusAttachment = focusNode.attach(context, onKey: widget.onKey); // Add listener even if the _internalNode existed before, since it should // not be listening now if we're re-using a previous one because it should @@ -933,7 +914,6 @@ class FocusScope extends Focus { ValueChanged? onFocusChange, bool? canRequestFocus, bool? skipTraversal, - FocusOnKeyEventCallback? onKeyEvent, FocusOnKeyCallback? onKey, String? debugLabel, }) : assert(child != null), @@ -946,7 +926,6 @@ class FocusScope extends Focus { onFocusChange: onFocusChange, canRequestFocus: canRequestFocus, skipTraversal: skipTraversal, - onKeyEvent: onKeyEvent, onKey: onKey, debugLabel: debugLabel, ); diff --git a/packages/flutter/lib/src/widgets/keyboard_listener.dart b/packages/flutter/lib/src/widgets/keyboard_listener.dart deleted file mode 100644 index 6c1bb82fdb..0000000000 --- a/packages/flutter/lib/src/widgets/keyboard_listener.dart +++ /dev/null @@ -1,95 +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. - -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import 'focus_manager.dart'; -import 'focus_scope.dart'; -import 'framework.dart'; - -export 'package:flutter/services.dart' show KeyEvent; - -/// A widget that calls a callback whenever the user presses or releases a key -/// on a keyboard. -/// -/// A [KeyboardListener] is useful for listening to key events and -/// hardware buttons that are represented as keys. Typically used by games and -/// other apps that use keyboards for purposes other than text entry. -/// -/// For text entry, consider using a [EditableText], which integrates with -/// on-screen keyboards and input method editors (IMEs). -/// -/// The [KeyboardListener] is different from [RawKeyboardListener] in that -/// [KeyboardListener] uses the newer [HardwareKeyboard] API, which is -/// preferrable. -/// -/// See also: -/// -/// * [EditableText], which should be used instead of this widget for text -/// entry. -/// * [RawKeyboardListener], a similar widget based on the old [RawKeyboard] -/// API. -class KeyboardListener extends StatelessWidget { - /// Creates a widget that receives keyboard events. - /// - /// For text entry, consider using a [EditableText], which integrates with - /// on-screen keyboards and input method editors (IMEs). - /// - /// The [focusNode] and [child] arguments are required and must not be null. - /// - /// The [autofocus] argument must not be null. - /// - /// The `key` is an identifier for widgets, and is unrelated to keyboards. - /// See [Widget.key]. - const KeyboardListener({ - Key? key, - required this.focusNode, - this.autofocus = false, - this.includeSemantics = true, - this.onKeyEvent, - required this.child, - }) : assert(focusNode != null), - assert(autofocus != null), - assert(includeSemantics != null), - assert(child != null), - super(key: key); - - /// Controls whether this widget has keyboard focus. - final FocusNode focusNode; - - /// {@macro flutter.widgets.Focus.autofocus} - final bool autofocus; - - /// {@macro flutter.widgets.Focus.includeSemantics} - final bool includeSemantics; - - /// Called whenever this widget receives a keyboard event. - final ValueChanged? onKeyEvent; - - /// The widget below this widget in the tree. - /// - /// {@macro flutter.widgets.ProxyWidget.child} - final Widget child; - - @override - Widget build(BuildContext context) { - return Focus( - focusNode: focusNode, - autofocus: autofocus, - includeSemantics: includeSemantics, - onKeyEvent: (FocusNode node, KeyEvent event) { - onKeyEvent?.call(event); - return KeyEventResult.ignored; - }, - child: child, - ); - } - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('focusNode', focusNode)); - } -} diff --git a/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart b/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart index db8f5bef6e..21d7e33fce 100644 --- a/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart +++ b/packages/flutter/lib/src/widgets/raw_keyboard_listener.dart @@ -21,16 +21,10 @@ export 'package:flutter/services.dart' show RawKeyEvent; /// For text entry, consider using a [EditableText], which integrates with /// on-screen keyboards and input method editors (IMEs). /// -/// The [RawKeyboardListener] is different from [KeyboardListener] in that -/// [RawKeyboardListener] uses the legacy [RawKeyboard] API. Use -/// [KeyboardListener] if possible. -/// /// See also: /// /// * [EditableText], which should be used instead of this widget for text /// entry. -/// * [KeyboardListener], a similar widget based on the newer -/// [HardwareKeyboard] API. class RawKeyboardListener extends StatefulWidget { /// Creates a widget that receives raw keyboard events. /// diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart index 941f7e96e6..36297c8fca 100644 --- a/packages/flutter/lib/src/widgets/shortcuts.dart +++ b/packages/flutter/lib/src/widgets/shortcuts.dart @@ -194,13 +194,13 @@ abstract class ShortcutActivator { /// event. /// /// For example, for `Ctrl-A`, it has to check if the event is a - /// [KeyDownEvent], if either side of the Ctrl key is pressed, and none of + /// [RawKeyDownEvent], if either side of the Ctrl key is pressed, and none of /// the Shift keys, Alt keys, or Meta keys are pressed; it doesn't have to /// check if KeyA is pressed, since it's already guaranteed. /// /// This method must not cause any side effects for the `state`. Typically - /// this is only used to query whether [HardwareKeyboard.logicalKeysPressed] - /// contains a key. + /// this is only used to query whether [RawKeyboard.keysPressed] contains + /// a key. /// /// Since [ShortcutActivator] accepts all event types, subclasses might want /// to check the event type in [accepts]. @@ -314,13 +314,11 @@ class LogicalKeySet extends KeySet with Diagnosticable @override bool accepts(RawKeyEvent event, RawKeyboard state) { - if (event is! RawKeyDownEvent) - return false; final Set collapsedRequired = LogicalKeyboardKey.collapseSynonyms(keys); final Set collapsedPressed = LogicalKeyboardKey.collapseSynonyms(state.keysPressed); final bool keysEqual = collapsedRequired.difference(collapsedPressed).isEmpty && collapsedRequired.length == collapsedPressed.length; - return keysEqual; + return event is RawKeyDownEvent && keysEqual; } static final Set _modifiers = { @@ -427,8 +425,7 @@ class ShortcutMapProperty extends DiagnosticsProperty callback, { - bool? skip, - test_package.Timeout? timeout, - TestVariant variant = const DefaultTestVariant(), - dynamic tags, -}) { - assert(variant != null); - assert(variant.values.isNotEmpty, 'There must be at least one value to test in the testing variant.'); - for (final dynamic value in variant.values) { - final String variationDescription = variant.describeValue(value); - final String combinedDescription = variationDescription.isNotEmpty ? '$description ($variationDescription)' : description; - test( - combinedDescription, - () async { - Object? memento; - try { - memento = await variant.setUp(value); - await callback(); - } finally { - await variant.tearDown(value, memento); - } - }, - skip: skip, - timeout: timeout, - tags: tags, - ); - } -} - void main() { test('RenderEditable respects clipBehavior', () { const BoxConstraints viewport = BoxConstraints(maxHeight: 100.0, maxWidth: 100.0); @@ -1380,7 +1343,7 @@ void main() { expect(currentSelection.extentOffset, 1); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068 - testVariants('respects enableInteractiveSelection', () async { + test('respects enableInteractiveSelection', () async { const String text = '012345'; final TextSelectionDelegate delegate = FakeEditableTextState() ..textEditingValue = const TextEditingValue(text: text); @@ -1440,7 +1403,7 @@ void main() { await simulateKeyUpEvent(wordModifier); await simulateKeyUpEvent(LogicalKeyboardKey.shift); - }, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/58068 + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068 group('delete', () { test('when as a non-collapsed selection, it should delete a selection', () async { diff --git a/packages/flutter/test/services/hardware_keyboard_test.dart b/packages/flutter/test/services/hardware_keyboard_test.dart deleted file mode 100644 index 634a2891de..0000000000 --- a/packages/flutter/test/services/hardware_keyboard_test.dart +++ /dev/null @@ -1,165 +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. - -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('HardwareKeyboard records pressed keys and enabled locks', (WidgetTester tester) async { - await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows'); - expect(HardwareKeyboard.instance.physicalKeysPressed, - equals({PhysicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.logicalKeysPressed, - equals({LogicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.lockModesEnabled, - equals({KeyboardLockMode.numLock})); - - await simulateKeyDownEvent(LogicalKeyboardKey.numpad1, platform: 'windows'); - expect(HardwareKeyboard.instance.physicalKeysPressed, - equals({PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.logicalKeysPressed, - equals({LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.lockModesEnabled, - equals({KeyboardLockMode.numLock})); - - await simulateKeyRepeatEvent(LogicalKeyboardKey.numpad1, platform: 'windows'); - expect(HardwareKeyboard.instance.physicalKeysPressed, - equals({PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.logicalKeysPressed, - equals({LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.lockModesEnabled, - equals({KeyboardLockMode.numLock})); - - await simulateKeyUpEvent(LogicalKeyboardKey.numLock); - expect(HardwareKeyboard.instance.physicalKeysPressed, - equals({PhysicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.logicalKeysPressed, - equals({LogicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.lockModesEnabled, - equals({KeyboardLockMode.numLock})); - - await simulateKeyDownEvent(LogicalKeyboardKey.numLock, platform: 'windows'); - expect(HardwareKeyboard.instance.physicalKeysPressed, - equals({PhysicalKeyboardKey.numLock, PhysicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.logicalKeysPressed, - equals({LogicalKeyboardKey.numLock, LogicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.lockModesEnabled, - equals({})); - - await simulateKeyUpEvent(LogicalKeyboardKey.numpad1, platform: 'windows'); - expect(HardwareKeyboard.instance.physicalKeysPressed, - equals({PhysicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.logicalKeysPressed, - equals({LogicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.lockModesEnabled, - equals({})); - - await simulateKeyUpEvent(LogicalKeyboardKey.numLock, platform: 'windows'); - expect(HardwareKeyboard.instance.physicalKeysPressed, - equals({})); - expect(HardwareKeyboard.instance.logicalKeysPressed, - equals({})); - expect(HardwareKeyboard.instance.lockModesEnabled, - equals({})); - }, variant: KeySimulatorTransitModeVariant.keyDataThenRawKeyData()); - - testWidgets('Dispatch events to all handlers', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - final List logs = []; - - await tester.pumpWidget( - KeyboardListener( - autofocus: true, - focusNode: focusNode, - child: Container(), - onKeyEvent: (KeyEvent event) { - logs.add(1); - }, - ), - ); - - // Only the Service binding handler. - - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [1]); - logs.clear(); - - // Add a handler. - - bool handler2Result = false; - bool handler2(KeyEvent event) { - logs.add(2); - return handler2Result; - } - HardwareKeyboard.instance.addHandler(handler2); - - expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [2, 1]); - logs.clear(); - - handler2Result = true; - - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - true); - expect(logs, [2, 1]); - logs.clear(); - - // Add another handler. - - handler2Result = false; - bool handler3Result = false; - bool handler3(KeyEvent event) { - logs.add(3); - return handler3Result; - } - HardwareKeyboard.instance.addHandler(handler3); - - expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [2, 3, 1]); - logs.clear(); - - handler2Result = true; - - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - true); - expect(logs, [2, 3, 1]); - logs.clear(); - - handler3Result = true; - - expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), - true); - expect(logs, [2, 3, 1]); - logs.clear(); - - // Add handler2 again. - - HardwareKeyboard.instance.addHandler(handler2); - - handler3Result = false; - handler2Result = false; - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [2, 3, 2, 1]); - logs.clear(); - - handler2Result = true; - expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), - true); - expect(logs, [2, 3, 2, 1]); - logs.clear(); - - // Remove handler2 once. - - HardwareKeyboard.instance.removeHandler(handler2); - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - true); - expect(logs, [3, 2, 1]); - logs.clear(); - }, variant: KeySimulatorTransitModeVariant.all()); -} diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart index 5db4e25b6d..44dc576857 100644 --- a/packages/flutter/test/services/raw_keyboard_test.dart +++ b/packages/flutter/test/services/raw_keyboard_test.dart @@ -201,7 +201,7 @@ void main() { 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. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'macos', isDown: true, @@ -226,7 +226,7 @@ void main() { testWidgets('keysPressed modifiers are synchronized with key events on iOS', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'ios', isDown: true, @@ -251,7 +251,7 @@ void main() { testWidgets('keysPressed modifiers are synchronized with key events on Windows', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'windows', isDown: true, @@ -276,7 +276,7 @@ void main() { testWidgets('keysPressed modifiers are synchronized with key events on android', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'android', isDown: true, @@ -301,7 +301,7 @@ void main() { testWidgets('keysPressed modifiers are synchronized with key events on fuchsia', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'fuchsia', isDown: true, @@ -326,7 +326,7 @@ void main() { testWidgets('keysPressed modifiers are synchronized with key events on Linux GLFW', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'linux', isDown: true, @@ -359,7 +359,7 @@ void main() { // Generate the data for a regular key down event. Change the modifiers so // that they show the shift key as already down when this event is // received, but it's not in keysPressed yet. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'web', isDown: true, @@ -384,7 +384,7 @@ void main() { // Generate the data for a regular key up event. Don't set the modifiers // for shift so that they show the shift key as already up when this event // is received, and it's in keysPressed. - final Map data2 = KeyEventSimulator.getRawKeyData( + final Map data2 = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'web', isDown: false, @@ -403,7 +403,7 @@ void main() { ); // Press right modifier key - final Map data3 = KeyEventSimulator.getRawKeyData( + final Map data3 = KeyEventSimulator.getKeyData( LogicalKeyboardKey.shiftRight, platform: 'web', isDown: true, @@ -425,7 +425,7 @@ void main() { ); // Release the key - final Map data4 = KeyEventSimulator.getRawKeyData( + final Map data4 = KeyEventSimulator.getKeyData( LogicalKeyboardKey.shiftRight, platform: 'web', isDown: false, @@ -447,7 +447,7 @@ void main() { testWidgets('sided modifiers without a side set return all sides on Android', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'android', isDown: true, @@ -485,7 +485,7 @@ void main() { testWidgets('sided modifiers without a side set return all sides on macOS', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'macos', isDown: true, @@ -523,7 +523,7 @@ void main() { testWidgets('sided modifiers without a side set return all sides on iOS', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'ios', isDown: true, @@ -561,7 +561,7 @@ void main() { testWidgets('sided modifiers without a side set return all sides on Windows', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'windows', isDown: true, @@ -597,7 +597,7 @@ void main() { testWidgets('sided modifiers without a side set return all sides on Linux GLFW', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'linux', isDown: true, @@ -636,7 +636,7 @@ void main() { testWidgets('sided modifiers without a side set return left sides on web', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'web', isDown: true, @@ -703,70 +703,6 @@ void main() { )), ); }); - - testWidgets('Dispatch events to all handlers', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - final List logs = []; - - await tester.pumpWidget( - RawKeyboardListener( - autofocus: true, - focusNode: focusNode, - child: Container(), - onKey: (RawKeyEvent event) { - logs.add(1); - }, - ), - ); - - // Only the Service binding handler. - - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [1]); - logs.clear(); - - // Add a handler. - - void handler2(RawKeyEvent event) { - logs.add(2); - } - RawKeyboard.instance.addListener(handler2); - - expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [1, 2]); - logs.clear(); - - // Add another handler. - - void handler3(RawKeyEvent event) { - logs.add(3); - } - RawKeyboard.instance.addListener(handler3); - - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [1, 2, 3]); - logs.clear(); - - // Add handler2 again. - - RawKeyboard.instance.addListener(handler2); - - expect(await simulateKeyUpEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [1, 2, 3, 2]); - logs.clear(); - - // Remove handler2 once. - - RawKeyboard.instance.removeListener(handler2); - expect(await simulateKeyDownEvent(LogicalKeyboardKey.keyA), - false); - expect(logs, [1, 3, 2]); - logs.clear(); - }, variant: KeySimulatorTransitModeVariant.all()); }); group('RawKeyEventDataAndroid', () { @@ -1005,7 +941,7 @@ void main() { testWidgets('Key events are responded to correctly.', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. - final Map data = KeyEventSimulator.getRawKeyData( + final Map data = KeyEventSimulator.getKeyData( LogicalKeyboardKey.keyA, platform: 'android', isDown: true, @@ -1019,7 +955,6 @@ void main() { }, ); expect(message, equals({ 'handled': false })); - message = null; // Set up a widget that will receive focused text events. final FocusNode focusNode = FocusNode(debugLabel: 'Test Node'); diff --git a/packages/flutter/test/widgets/app_test.dart b/packages/flutter/test/widgets/app_test.dart index f8c03ebf84..944d9c7766 100644 --- a/packages/flutter/test/widgets/app_test.dart +++ b/packages/flutter/test/widgets/app_test.dart @@ -143,7 +143,7 @@ void main() { await tester.sendKeyEvent(LogicalKeyboardKey.gameButtonA); await tester.pumpAndSettle(); expect(checked, isTrue); - }, variant: KeySimulatorTransitModeVariant.all()); + }); group('error control test', () { Future expectFlutterError({ diff --git a/packages/flutter/test/widgets/default_text_editing_actions_test.dart b/packages/flutter/test/widgets/default_text_editing_actions_test.dart index 8718d710ce..91ca5a8c01 100644 --- a/packages/flutter/test/widgets/default_text_editing_actions_test.dart +++ b/packages/flutter/test/widgets/default_text_editing_actions_test.dart @@ -114,5 +114,5 @@ void main() { await tester.pump(); expect(leftCalled, isFalse); expect(rightCalled, isTrue); - }, variant: KeySimulatorTransitModeVariant.all()); + }); } diff --git a/packages/flutter/test/widgets/editable_text_cursor_test.dart b/packages/flutter/test/widgets/editable_text_cursor_test.dart index e7e44f8543..b069b4e977 100644 --- a/packages/flutter/test/widgets/editable_text_cursor_test.dart +++ b/packages/flutter/test/widgets/editable_text_cursor_test.dart @@ -328,8 +328,6 @@ void main() { }); testWidgets('Cursor animation restarts when it is moved using keys on desktop', (WidgetTester tester) async { - debugDefaultTargetPlatformOverride = TargetPlatform.macOS; - const String testText = 'Some text long enough to move the cursor around'; final TextEditingController controller = TextEditingController(text: testText); final Widget widget = MaterialApp( @@ -402,9 +400,7 @@ void main() { await tester.pump(const Duration(milliseconds: 1)); expect(renderEditable.cursorColor!.alpha, 0); expect(renderEditable, paintsExactlyCountTimes(#drawRect, 0)); - - debugDefaultTargetPlatformOverride = null; - }, variant: KeySimulatorTransitModeVariant.all()); + }, variant: const TargetPlatformVariant({ TargetPlatform.macOS })); testWidgets('Cursor does not show when showCursor set to false', (WidgetTester tester) async { const Widget widget = MaterialApp( diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 63b55d7959..0bce261c2b 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -4864,23 +4864,8 @@ void main() { expect(controller.text, isEmpty, reason: 'on $platform'); } - testWidgets('keyboard text selection works (RawKeyEvent)', (WidgetTester tester) async { - debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData; - + testWidgets('keyboard text selection works', (WidgetTester tester) async { await testTextEditing(tester, targetPlatform: defaultTargetPlatform); - - debugKeyEventSimulatorTransitModeOverride = null; - - // On web, using keyboard for selection is handled by the browser. - }, skip: kIsWeb, variant: TargetPlatformVariant.all()); - - testWidgets('keyboard text selection works (ui.KeyData then RawKeyEvent)', (WidgetTester tester) async { - debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; - - await testTextEditing(tester, targetPlatform: defaultTargetPlatform); - - debugKeyEventSimulatorTransitModeOverride = null; - // On web, using keyboard for selection is handled by the browser. }, skip: kIsWeb, variant: TargetPlatformVariant.all()); @@ -7690,7 +7675,7 @@ void main() { expect(controller.selection.isCollapsed, true); expect(controller.selection.baseOffset, 1); } - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('the toolbar is disposed when selection changes and there is no selectionControls', (WidgetTester tester) async { late StateSetter setState; diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 949cd233c0..881c203814 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -165,101 +165,7 @@ void main() { 'hasPrimaryFocus: false', ]); }); - - testWidgets('onKeyEvent and onKey correctly cooperate', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(debugLabel: 'Test Node 3'); - List> results = >[ - [KeyEventResult.ignored, KeyEventResult.ignored], - [KeyEventResult.ignored, KeyEventResult.ignored], - [KeyEventResult.ignored, KeyEventResult.ignored], - ]; - final List logs = []; - - await tester.pumpWidget( - Focus( - focusNode: FocusNode(debugLabel: 'Test Node 1'), - onKeyEvent: (_, KeyEvent event) { - logs.add(0); - return results[0][0]; - }, - onKey: (_, RawKeyEvent event) { - logs.add(1); - return results[0][1]; - }, - child: Focus( - focusNode: FocusNode(debugLabel: 'Test Node 2'), - onKeyEvent: (_, KeyEvent event) { - logs.add(10); - return results[1][0]; - }, - onKey: (_, RawKeyEvent event) { - logs.add(11); - return results[1][1]; - }, - child: Focus( - focusNode: focusNode, - onKeyEvent: (_, KeyEvent event) { - logs.add(20); - return results[2][0]; - }, - onKey: (_, RawKeyEvent event) { - logs.add(21); - return results[2][1]; - }, - child: const SizedBox(width: 200, height: 100), - ), - ), - ), - ); - focusNode.requestFocus(); - await tester.pump(); - - // All ignored. - results = >[ - [KeyEventResult.ignored, KeyEventResult.ignored], - [KeyEventResult.ignored, KeyEventResult.ignored], - [KeyEventResult.ignored, KeyEventResult.ignored], - ]; - expect(await simulateKeyDownEvent(LogicalKeyboardKey.digit1), - false); - expect(logs, [20, 21, 10, 11, 0, 1]); - logs.clear(); - - // The onKeyEvent should be able to stop propagation. - results = >[ - [KeyEventResult.ignored, KeyEventResult.ignored], - [KeyEventResult.handled, KeyEventResult.ignored], - [KeyEventResult.ignored, KeyEventResult.ignored], - ]; - expect(await simulateKeyUpEvent(LogicalKeyboardKey.digit1), - true); - expect(logs, [20, 21, 10, 11]); - logs.clear(); - - // The onKey should be able to stop propagation. - results = >[ - [KeyEventResult.ignored, KeyEventResult.ignored], - [KeyEventResult.ignored, KeyEventResult.handled], - [KeyEventResult.ignored, KeyEventResult.ignored], - ]; - expect(await simulateKeyDownEvent(LogicalKeyboardKey.digit1), - true); - expect(logs, [20, 21, 10, 11]); - logs.clear(); - - // KeyEventResult.skipRemainingHandlers works. - results = >[ - [KeyEventResult.ignored, KeyEventResult.ignored], - [KeyEventResult.skipRemainingHandlers, KeyEventResult.ignored], - [KeyEventResult.ignored, KeyEventResult.ignored], - ]; - expect(await simulateKeyUpEvent(LogicalKeyboardKey.digit1), - false); - expect(logs, [20, 21, 10, 11]); - logs.clear(); - }, variant: KeySimulatorTransitModeVariant.all()); }); - group(FocusScopeNode, () { testWidgets('Can setFirstFocus on a scope with no manager.', (WidgetTester tester) async { @@ -1029,7 +935,7 @@ void main() { // Since none of the focused nodes handle this event, nothing should // receive it. expect(receivedAnEvent, isEmpty); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Initial highlight mode guesses correctly.', (WidgetTester tester) async { FocusManager.instance.highlightStrategy = FocusHighlightStrategy.automatic; diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index 8fc023d99d..d514039575 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -1678,7 +1678,7 @@ void main() { expect(Focus.of(lowerLeftKey.currentContext!).hasPrimaryFocus, isTrue); await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); expect(Focus.of(upperLeftKey.currentContext!).hasPrimaryFocus, isTrue); - }, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347 + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347 testWidgets('Focus traversal inside a vertical scrollable scrolls to stay visible.', (WidgetTester tester) async { final List items = List.generate(11, (int index) => index).toList(); @@ -1776,7 +1776,7 @@ void main() { await tester.pump(); expect(topNode.hasPrimaryFocus, isTrue); expect(controller.offset, equals(0.0)); - }, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347 + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347 testWidgets('Focus traversal inside a horizontal scrollable scrolls to stay visible.', (WidgetTester tester) async { final List items = List.generate(11, (int index) => index).toList(); @@ -1874,7 +1874,7 @@ void main() { await tester.pump(); expect(leftNode.hasPrimaryFocus, isTrue); expect(controller.offset, equals(0.0)); - }, skip: isBrowser, variant: KeySimulatorTransitModeVariant.all()); // https://github.com/flutter/flutter/issues/35347 + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347 testWidgets('Arrow focus traversal actions can be re-enabled for text fields.', (WidgetTester tester) async { final GlobalKey upperLeftKey = GlobalKey(debugLabel: 'upperLeftKey'); @@ -1997,10 +1997,10 @@ void main() { expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue); await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); expect(focusNodeUpperLeft.hasPrimaryFocus, isTrue); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Focus traversal does not break when no focusable is available on a MaterialApp', (WidgetTester tester) async { - final List events = []; + final List events = []; await tester.pumpWidget(MaterialApp(home: Container())); @@ -2013,7 +2013,7 @@ void main() { await tester.idle(); expect(events.length, 2); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Focus traversal does not throw when no focusable is available in a group', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp(home: Scaffold(body: ListTile(title: Text('title'))))); @@ -2047,7 +2047,7 @@ void main() { await tester.idle(); expect(events.length, 2); - }, variant: KeySimulatorTransitModeVariant.all()); + }); }); group(FocusTraversalGroup, () { testWidgets("Focus traversal group doesn't introduce a Semantics node", (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/keyboard_listener_test.dart b/packages/flutter/test/widgets/keyboard_listener_test.dart deleted file mode 100644 index 7164077fe6..0000000000 --- a/packages/flutter/test/widgets/keyboard_listener_test.dart +++ /dev/null @@ -1,106 +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. - -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Can dispose without keyboard', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget(KeyboardListener(focusNode: focusNode, onKeyEvent: null, child: Container())); - await tester.pumpWidget(KeyboardListener(focusNode: focusNode, onKeyEvent: null, child: Container())); - await tester.pumpWidget(Container()); - }); - - testWidgets('Fuchsia key event', (WidgetTester tester) async { - final List events = []; - - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - KeyboardListener( - focusNode: focusNode, - onKeyEvent: events.add, - child: Container(), - ), - ); - - focusNode.requestFocus(); - await tester.idle(); - - await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); - await tester.idle(); - - expect(events.length, 2); - expect(events[0], isA()); - expect(events[0].physicalKey, PhysicalKeyboardKey.metaLeft); - expect(events[0].logicalKey, LogicalKeyboardKey.metaLeft); - - await tester.pumpWidget(Container()); - focusNode.dispose(); - }, skip: isBrowser); // This is a Fuchsia-specific test. - - testWidgets('Web key event', (WidgetTester tester) async { - final List events = []; - - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - KeyboardListener( - focusNode: focusNode, - onKeyEvent: events.add, - child: Container(), - ), - ); - - focusNode.requestFocus(); - await tester.idle(); - - await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'web'); - await tester.idle(); - - expect(events.length, 2); - expect(events[0], isA()); - expect(events[0].physicalKey, PhysicalKeyboardKey.metaLeft); - expect(events[0].logicalKey, LogicalKeyboardKey.metaLeft); - - await tester.pumpWidget(Container()); - focusNode.dispose(); - }); - - testWidgets('Defunct listeners do not receive events', (WidgetTester tester) async { - final List events = []; - - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - KeyboardListener( - focusNode: focusNode, - onKeyEvent: events.add, - child: Container(), - ), - ); - - focusNode.requestFocus(); - await tester.idle(); - - await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); - await tester.idle(); - - expect(events.length, 2); - events.clear(); - - await tester.pumpWidget(Container()); - - await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); - - await tester.idle(); - - expect(events.length, 0); - - await tester.pumpWidget(Container()); - focusNode.dispose(); - }); -} diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index 082fe7043d..9df01fce9a 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -520,7 +520,7 @@ void main() { await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect(tester.getRect(find.byKey(const ValueKey('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0))); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); @@ -571,7 +571,7 @@ void main() { await tester.sendKeyEvent(LogicalKeyboardKey.pageUp); await tester.pumpAndSettle(); expect(tester.getRect(find.byKey(const ValueKey('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0))); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); @@ -617,7 +617,7 @@ void main() { await tester.sendKeyUpEvent(modifierKey); await tester.pumpAndSettle(); expect(tester.getRect(find.byKey(const ValueKey('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 0.0, 50.0, 600.0))); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Horizontal scrollables are scrolled the correct direction in RTL locales.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); @@ -666,7 +666,7 @@ void main() { await tester.sendKeyUpEvent(modifierKey); await tester.pumpAndSettle(); expect(tester.getRect(find.byKey(const ValueKey('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(750.0, 0.0, 800.0, 600.0))); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Reversed vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); @@ -720,7 +720,7 @@ void main() { await tester.sendKeyEvent(LogicalKeyboardKey.pageDown); await tester.pumpAndSettle(); expect(tester.getRect(find.byKey(const ValueKey('Box 0'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 550.0, 800.0, 600.0))); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Reversed horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); @@ -768,7 +768,7 @@ void main() { if (!kIsWeb) await tester.sendKeyUpEvent(modifierKey); await tester.pumpAndSettle(); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Custom scrollables with a center sliver are scrolled when activated via keyboard.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); @@ -828,7 +828,7 @@ void main() { // Goes up two past "center" where it started, so negative. expect(controller.position.pixels, equals(-100.0)); expect(tester.getRect(find.byKey(const ValueKey('Item 10'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0))); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Can recommendDeferredLoadingForContext - animation', (WidgetTester tester) async { final List widgetTracker = []; diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index d19e6b221f..f3b9175f00 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -1570,7 +1570,7 @@ void main() { await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft); expect(controller.selection.extentOffset - controller.selection.baseOffset, -1); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Shift test 2', (WidgetTester tester) async { await setupWidget(tester, 'abcdefghi'); @@ -1582,7 +1582,7 @@ void main() { await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 1); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Control Shift test', (WidgetTester tester) async { await setupWidget(tester, 'their big house'); @@ -1594,7 +1594,7 @@ void main() { await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, -5); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Down and up test', (WidgetTester tester) async { await setupWidget(tester, 'a big house'); @@ -1612,7 +1612,7 @@ void main() { await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 0); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Down and up test 2', (WidgetTester tester) async { await setupWidget(tester, 'a big house\njumped over a mouse\nOne more line yay'); @@ -1663,7 +1663,7 @@ void main() { await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, -5); - }, variant: KeySimulatorTransitModeVariant.all()); + }); }); testWidgets('Copy test', (WidgetTester tester) async { @@ -1722,7 +1722,7 @@ void main() { await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Select all test', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); @@ -1757,7 +1757,7 @@ void main() { expect(controller.selection.baseOffset, 0); expect(controller.selection.extentOffset, 31); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('keyboard selection should call onSelectionChanged', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); @@ -1804,7 +1804,7 @@ void main() { expect(newSelection!.extentOffset, i + 1); newSelection = null; } - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Changing positions of selectable text', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); @@ -1894,7 +1894,7 @@ void main() { c1 = editableTextWidget.controller; expect(c1.selection.extentOffset - c1.selection.baseOffset, -6); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Changing focus test', (WidgetTester tester) async { @@ -1964,7 +1964,7 @@ void main() { expect(c1.selection.extentOffset - c1.selection.baseOffset, 0); expect(c2.selection.extentOffset - c2.selection.baseOffset, -5); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('Caret works when maxLines is null', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart index 42c3c2da8f..f52c03be2b 100644 --- a/packages/flutter/test/widgets/shortcuts_test.dart +++ b/packages/flutter/test/widgets/shortcuts_test.dart @@ -435,33 +435,7 @@ void main() { invoked = 0; expect(RawKeyboard.instance.keysPressed, isEmpty); - }, variant: KeySimulatorTransitModeVariant.all()); - - testWidgets('handles repeated events', (WidgetTester tester) async { - int invoked = 0; - await tester.pumpWidget(activatorTester( - const SingleActivator( - LogicalKeyboardKey.keyC, - control: true, - ), - (Intent intent) { invoked += 1; }, - )); - await tester.pump(); - - // LCtrl -> KeyC: Accept - await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); - expect(invoked, 0); - await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC); - expect(invoked, 1); - await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyC); - expect(invoked, 2); - await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC); - await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); - expect(invoked, 2); - invoked = 0; - - expect(RawKeyboard.instance.keysPressed, isEmpty); - }, variant: KeySimulatorTransitModeVariant.all()); + }); testWidgets('handles Shift-Ctrl-C', (WidgetTester tester) async { int invoked = 0; @@ -1101,27 +1075,7 @@ void main() { await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft); expect(invoked, 1); invoked = 0; - }, variant: KeySimulatorTransitModeVariant.all()); - - testWidgets('handles repeated events', (WidgetTester tester) async { - int invoked = 0; - await tester.pumpWidget(activatorTester( - const CharacterActivator('?'), - (Intent intent) { invoked += 1; }, - )); - await tester.pump(); - - // Press KeyC: Accepted by DumbLogicalActivator - await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft); - await tester.sendKeyDownEvent(LogicalKeyboardKey.slash, character: '?'); - expect(invoked, 1); - await tester.sendKeyRepeatEvent(LogicalKeyboardKey.slash, character: '?'); - expect(invoked, 2); - await tester.sendKeyUpEvent(LogicalKeyboardKey.slash); - await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft); - expect(invoked, 2); - invoked = 0; - }, variant: KeySimulatorTransitModeVariant.all()); + }); }); group('CallbackShortcuts', () { diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 42b37a7409..8f2c485698 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -863,9 +863,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase assert(debugAssertAllSchedulerVarsUnset( 'The value of a scheduler debug variable was changed by the test.', )); - assert(debugAssertAllServicesVarsUnset( - 'The value of a services debug variable was changed by the test.', - )); } void _verifyAutoUpdateGoldensUnset(bool valueBeforeTest) { @@ -932,10 +929,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase // tests. // ignore: invalid_use_of_visible_for_testing_member RawKeyboard.instance.clearKeysPressed(); - // ignore: invalid_use_of_visible_for_testing_member - HardwareKeyboard.instance.clearState(); - // ignore: invalid_use_of_visible_for_testing_member - keyEventManager.clearState(); assert(!RendererBinding.instance!.mouseTracker.mouseIsConnected, 'The MouseTracker thinks that there is still a mouse connected, which indicates that a ' 'test has not removed the mouse pointer which it added. Call removePointer on the ' diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index a6466a3358..65d702bd2f 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -973,7 +973,7 @@ abstract class WidgetController { return box.size; } - /// Simulates sending physical key down and up events. + /// Simulates sending physical key down and up events through the system channel. /// /// This only simulates key events coming from a physical keyboard, not from a /// soft keyboard. @@ -984,9 +984,6 @@ abstract class WidgetController { /// else. Must not be null. Some platforms (e.g. Windows, iOS) are not yet /// supported. /// - /// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is - /// controlled by [debugKeyEventSimulatorTransitModeOverride]. - /// /// Keys that are down when the test completes are cleared after each test. /// /// This method sends both the key down and the key up events, to simulate a @@ -1007,7 +1004,7 @@ abstract class WidgetController { return handled; } - /// Simulates sending a physical key down event. + /// Simulates sending a physical key down event through the system channel. /// /// This only simulates key down events coming from a physical keyboard, not /// from a soft keyboard. @@ -1018,17 +1015,13 @@ abstract class WidgetController { /// else. Must not be null. Some platforms (e.g. Windows, iOS) are not yet /// supported. /// - /// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is - /// controlled by [debugKeyEventSimulatorTransitModeOverride]. - /// /// Keys that are down when the test completes are cleared after each test. /// /// Returns true if the key event was handled by the framework. /// /// See also: /// - /// - [sendKeyUpEvent] and [sendKeyRepeatEvent] to simulate the corresponding - /// key up and repeat event. + /// - [sendKeyUpEvent] to simulate the corresponding key up event. /// - [sendKeyEvent] to simulate both the key up and key down in the same call. Future sendKeyDownEvent(LogicalKeyboardKey key, { String? character, String platform = _defaultPlatform }) async { assert(platform != null); @@ -1046,15 +1039,11 @@ abstract class WidgetController { /// that type of system. Defaults to "web" on web, and "android" everywhere /// else. May not be null. /// - /// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is - /// controlled by [debugKeyEventSimulatorTransitModeOverride]. - /// /// Returns true if the key event was handled by the framework. /// /// See also: /// - /// - [sendKeyDownEvent] and [sendKeyRepeatEvent] to simulate the - /// corresponding key down and repeat event. + /// - [sendKeyDownEvent] to simulate the corresponding key down event. /// - [sendKeyEvent] to simulate both the key up and key down in the same call. Future sendKeyUpEvent(LogicalKeyboardKey key, { String platform = _defaultPlatform }) async { assert(platform != null); @@ -1062,35 +1051,6 @@ abstract class WidgetController { return simulateKeyUpEvent(key, platform: platform); } - /// Simulates sending a physical key repeat event. - /// - /// This only simulates key repeat events coming from a physical keyboard, not - /// from a soft keyboard. - /// - /// Specify `platform` as one of the platforms allowed in - /// [platform.Platform.operatingSystem] to make the event appear to be from that type - /// of system. Defaults to "web" on web, and "android" everywhere else. Must not be - /// null. Some platforms (e.g. Windows, iOS) are not yet supported. - /// - /// Whether the event is sent through [RawKeyEvent] or [KeyEvent] is - /// controlled by [debugKeyEventSimulatorTransitModeOverride]. If through [RawKeyEvent], - /// this method is equivalent to [sendKeyDownEvent]. - /// - /// Keys that are down when the test completes are cleared after each test. - /// - /// Returns true if the key event was handled by the framework. - /// - /// See also: - /// - /// - [sendKeyDownEvent] and [sendKeyUpEvent] to simulate the corresponding - /// key down and up event. - /// - [sendKeyEvent] to simulate both the key up and key down in the same call. - Future sendKeyRepeatEvent(LogicalKeyboardKey key, { String? character, String platform = _defaultPlatform }) async { - assert(platform != null); - // Internally wrapped in async guard. - return simulateKeyRepeatEvent(key, character: character, platform: platform); - } - /// Returns the rect of the given widget. This is only valid once /// the widget's render object has been laid out at least once. Rect getRect(Finder finder) => getTopLeft(finder) & getSize(finder); diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart index 6688637062..4b897e8bb6 100644 --- a/packages/flutter_test/lib/src/event_simulation.dart +++ b/packages/flutter_test/lib/src/event_simulation.dart @@ -4,12 +4,9 @@ import 'dart:async'; import 'dart:io'; -import 'dart:ui' as ui; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'binding.dart'; import 'test_async_utils.dart'; @@ -19,14 +16,13 @@ import 'test_async_utils.dart'; // https://github.com/flutter/flutter/issues/33521 // This code can only simulate keys which appear in the key maps. -String? _keyLabel(LogicalKeyboardKey key) { +String _keyLabel(LogicalKeyboardKey key) { final String keyLabel = key.keyLabel; if (keyLabel.length == 1) return keyLabel.toLowerCase(); - return null; + return ''; } -// ignore: avoid_classes_with_only_static_members /// A class that serves as a namespace for a bunch of keyboard-key generation /// utilities. class KeyEventSimulator { @@ -157,7 +153,7 @@ class KeyEventSimulator { return result!; } - static PhysicalKeyboardKey _findPhysicalKeyByPlatform(LogicalKeyboardKey key, String platform) { + static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key, String platform) { assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); late Map map; if (kIsWeb) { @@ -200,7 +196,7 @@ class KeyEventSimulator { } /// Get a raw key data map given a [LogicalKeyboardKey] and a platform. - static Map getRawKeyData( + static Map getKeyData( LogicalKeyboardKey key, { required String platform, bool isDown = true, @@ -212,7 +208,7 @@ class KeyEventSimulator { key = _getKeySynonym(key); // Find a suitable physical key if none was supplied. - physicalKey ??= _findPhysicalKeyByPlatform(key, platform); + physicalKey ??= _findPhysicalKey(key, platform); assert(key.debugName != null); final int keyCode = _getKeyCode(key, platform); @@ -223,7 +219,7 @@ class KeyEventSimulator { 'keymap': platform, }; - final String resultCharacter = character ?? _keyLabel(key) ?? ''; + final String resultCharacter = character ?? _keyLabel(key); void assignWeb() { result['code'] = _getWebKeyCode(key); result['key'] = resultCharacter; @@ -629,64 +625,7 @@ class KeyEventSimulator { return result; } - static Future _simulateKeyEventByRawEvent(ValueGetter> getRawKeyData) async { - return TestAsyncUtils.guard(() async { - final Completer result = Completer(); - await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.keyEvent.name, - SystemChannels.keyEvent.codec.encodeMessage(getRawKeyData()), - (ByteData? data) { - if (data == null) { - result.complete(false); - return; - } - final Map decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map; - result.complete(decoded['handled'] as bool); - } - ); - return result.future; - }); - } - - static late final Map _debugNameToPhysicalKey = (() { - final Map result = {}; - for (final PhysicalKeyboardKey key in PhysicalKeyboardKey.knownPhysicalKeys) { - final String? debugName = key.debugName; - if (debugName != null) - result[debugName] = key; - } - return result; - })(); - static PhysicalKeyboardKey _findPhysicalKey(LogicalKeyboardKey key) { - final PhysicalKeyboardKey? result = _debugNameToPhysicalKey[key.debugName]; - assert(result != null, 'Physical key for $key not found in known physical keys'); - return result!; - } - - static const KeyDataTransitMode _defaultTransitMode = KeyDataTransitMode.rawKeyData; - - // The simulation transit mode for [simulateKeyDownEvent], [simulateKeyUpEvent], - // and [simulateKeyRepeatEvent]. - // - // Simulation transit mode is the mode that simulated key events are constructed - // and delivered. For detailed introduction, see [KeyDataTransitMode] and - // its values. - // - // The `_transitMode` defaults to [KeyDataTransitMode.rawKeyEvent], and can be - // overridden with [debugKeyEventSimulatorTransitModeOverride]. In widget tests, it - // is often set with [KeySimulationModeVariant]. - static KeyDataTransitMode get _transitMode { - KeyDataTransitMode? result; - assert(() { - result = debugKeyEventSimulatorTransitModeOverride; - return true; - }()); - return result ?? _defaultTransitMode; - } - - static String get _defaultPlatform => kIsWeb ? 'web' : Platform.operatingSystem; - - /// Simulates sending a hardware key down event. + /// Simulates sending a hardware key down event through the system channel. /// /// This only simulates key presses coming from a physical keyboard, not from a /// soft keyboard. @@ -702,36 +641,29 @@ class KeyEventSimulator { /// /// See also: /// - /// * [simulateKeyUpEvent] to simulate the corresponding key up event. - static Future simulateKeyDownEvent( - LogicalKeyboardKey key, { - String? platform, - PhysicalKeyboardKey? physicalKey, - String? character, - }) async { - Future _simulateByRawEvent() { - return _simulateKeyEventByRawEvent(() { - platform ??= _defaultPlatform; - return getRawKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey, character: character); - }); - } - switch (_transitMode) { - case KeyDataTransitMode.rawKeyData: - return _simulateByRawEvent(); - case KeyDataTransitMode.keyDataThenRawKeyData: - final LogicalKeyboardKey logicalKey = _getKeySynonym(key); - final bool resultByKeyEvent = ServicesBinding.instance!.keyEventManager.handleKeyData( - ui.KeyData( - type: ui.KeyEventType.down, - physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage, - logical: logicalKey.keyId, - timeStamp: Duration.zero, - character: character ?? _keyLabel(key), - synthesized: false, - ), - ); - return (await _simulateByRawEvent()) || resultByKeyEvent; - } + /// - [simulateKeyUpEvent] to simulate the corresponding key up event. + static Future simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey, String? character}) async { + return TestAsyncUtils.guard(() async { + platform ??= Platform.operatingSystem; + assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation'); + + + final Map data = getKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey, character: character); + final Completer result = Completer(); + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage(data), + (ByteData? data) { + if (data == null) { + result.complete(false); + return; + } + final Map decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map; + result.complete(decoded['handled'] as bool); + } + ); + return result.future; + }); } /// Simulates sending a hardware key up event through the system channel. @@ -748,81 +680,29 @@ class KeyEventSimulator { /// /// See also: /// - /// * [simulateKeyDownEvent] to simulate the corresponding key down event. - static Future simulateKeyUpEvent( - LogicalKeyboardKey key, { - String? platform, - PhysicalKeyboardKey? physicalKey, - }) async { - Future _simulateByRawEvent() { - return _simulateKeyEventByRawEvent(() { - platform ??= _defaultPlatform; - return getRawKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey); - }); - } - switch (_transitMode) { - case KeyDataTransitMode.rawKeyData: - return _simulateByRawEvent(); - case KeyDataTransitMode.keyDataThenRawKeyData: - final LogicalKeyboardKey logicalKey = _getKeySynonym(key); - final bool resultByKeyEvent = ServicesBinding.instance!.keyEventManager.handleKeyData( - ui.KeyData( - type: ui.KeyEventType.up, - physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage, - logical: logicalKey.keyId, - timeStamp: Duration.zero, - character: null, - synthesized: false, - ), - ); - return (await _simulateByRawEvent()) || resultByKeyEvent; - } - } + /// - [simulateKeyDownEvent] to simulate the corresponding key down event. + static Future simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) async { + return TestAsyncUtils.guard(() async { + platform ??= Platform.operatingSystem; + assert(_osIsSupported(platform!), 'Platform $platform not supported for key simulation'); - /// Simulates sending a hardware key repeat event through the system channel. - /// - /// This only simulates key presses coming from a physical keyboard, not from a - /// soft keyboard. - /// - /// Specify `platform` as one of the platforms allowed in - /// [Platform.operatingSystem] to make the event appear to be from that type of - /// system. Defaults to the operating system that the test is running on. Some - /// platforms (e.g. Windows, iOS) are not yet supported. - /// - /// Returns true if the key event was handled by the framework. - /// - /// See also: - /// - /// * [simulateKeyDownEvent] to simulate the corresponding key down event. - static Future simulateKeyRepeatEvent( - LogicalKeyboardKey key, { - String? platform, - PhysicalKeyboardKey? physicalKey, - String? character, - }) async { - Future _simulateByRawEvent() { - return _simulateKeyEventByRawEvent(() { - platform ??= _defaultPlatform; - return getRawKeyData(key, platform: platform!, isDown: true, physicalKey: physicalKey, character: character); - }); - } - switch (_transitMode) { - case KeyDataTransitMode.rawKeyData: - return _simulateByRawEvent(); - case KeyDataTransitMode.keyDataThenRawKeyData: - final LogicalKeyboardKey logicalKey = _getKeySynonym(key); - final bool resultByKeyEvent = ServicesBinding.instance!.keyEventManager.handleKeyData( - ui.KeyData( - type: ui.KeyEventType.repeat, - physical: (physicalKey ?? _findPhysicalKey(logicalKey)).usbHidUsage, - logical: logicalKey.keyId, - timeStamp: Duration.zero, - character: character ?? _keyLabel(key), - synthesized: false, - ), - ); - return (await _simulateByRawEvent()) || resultByKeyEvent; - } + final Map data = getKeyData(key, platform: platform!, isDown: false, physicalKey: physicalKey); + bool result = false; + await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage(data), + (ByteData? data) { + if (data == null) { + return; + } + final Map decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map; + if (decoded['handled'] as bool) { + result = true; + } + } + ); + return result; + }); } } @@ -845,14 +725,8 @@ class KeyEventSimulator { /// /// See also: /// -/// * [simulateKeyUpEvent] and [simulateKeyRepeatEvent] to simulate the -/// corresponding key up and repeat event. -Future simulateKeyDownEvent( - LogicalKeyboardKey key, { - String? platform, - PhysicalKeyboardKey? physicalKey, - String? character, -}) { +/// - [simulateKeyUpEvent] to simulate the corresponding key up event. +Future simulateKeyDownEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey, String? character}) { return KeyEventSimulator.simulateKeyDownEvent(key, platform: platform, physicalKey: physicalKey, character: character); } @@ -873,85 +747,7 @@ Future simulateKeyDownEvent( /// /// See also: /// -/// * [simulateKeyDownEvent] and [simulateKeyRepeatEvent] to simulate the -/// corresponding key down and repeat event. -Future simulateKeyUpEvent( - LogicalKeyboardKey key, { - String? platform, - PhysicalKeyboardKey? physicalKey, -}) { +/// - [simulateKeyDownEvent] to simulate the corresponding key down event. +Future simulateKeyUpEvent(LogicalKeyboardKey key, {String? platform, PhysicalKeyboardKey? physicalKey}) { return KeyEventSimulator.simulateKeyUpEvent(key, platform: platform, physicalKey: physicalKey); } - -/// Simulates sending a hardware key repeat event through the system channel. -/// -/// This only simulates key presses coming from a physical keyboard, not from a -/// soft keyboard. -/// -/// Specify `platform` as one of the platforms allowed in -/// [Platform.operatingSystem] to make the event appear to be from that type of -/// system. Defaults to the operating system that the test is running on. Some -/// platforms (e.g. Windows, iOS) are not yet supported. -/// -/// Returns true if the key event was handled by the framework. -/// -/// See also: -/// -/// - [simulateKeyDownEvent] and [simulateKeyUpEvent] to simulate the -/// corresponding key down and up event. -Future simulateKeyRepeatEvent( - LogicalKeyboardKey key, { - String? platform, - PhysicalKeyboardKey? physicalKey, - String? character, -}) { - return KeyEventSimulator.simulateKeyRepeatEvent(key, platform: platform, physicalKey: physicalKey, character: character); -} - -/// A [TestVariant] that runs tests with transit modes set to different values -/// of [KeyDataTransitMode]. -class KeySimulatorTransitModeVariant extends TestVariant { - /// Creates a [KeySimulatorTransitModeVariant] that tests the given [values]. - const KeySimulatorTransitModeVariant(this.values); - - /// Creates a [KeySimulatorTransitModeVariant] for each value option of - /// [KeyDataTransitMode]. - KeySimulatorTransitModeVariant.all() - : this(KeyDataTransitMode.values.toSet()); - - /// Creates a [KeySimulatorTransitModeVariant] that only contains - /// [KeyDataTransitMode.keyDataThenRawKeyData]. - KeySimulatorTransitModeVariant.keyDataThenRawKeyData() - : this({KeyDataTransitMode.keyDataThenRawKeyData}); - - @override - final Set values; - - @override - String describeValue(KeyDataTransitMode value) { - switch (value) { - case KeyDataTransitMode.rawKeyData: - return 'RawKeyEvent'; - case KeyDataTransitMode.keyDataThenRawKeyData: - return 'ui.KeyData then RawKeyEvent'; - } - } - - @override - Future setUp(KeyDataTransitMode value) async { - final KeyDataTransitMode? previousSetting = debugKeyEventSimulatorTransitModeOverride; - debugKeyEventSimulatorTransitModeOverride = value; - return previousSetting; - } - - @override - Future tearDown(KeyDataTransitMode value, KeyDataTransitMode? memento) async { - // ignore: invalid_use_of_visible_for_testing_member - RawKeyboard.instance.clearKeysPressed(); - // ignore: invalid_use_of_visible_for_testing_member - HardwareKeyboard.instance.clearState(); - // ignore: invalid_use_of_visible_for_testing_member - ServicesBinding.instance!.keyEventManager.clearState(); - debugKeyEventSimulatorTransitModeOverride = memento; - } -} diff --git a/packages/flutter_test/lib/src/test_pointer.dart b/packages/flutter_test/lib/src/test_pointer.dart index 7ded5c08a1..77bd8bdd17 100644 --- a/packages/flutter_test/lib/src/test_pointer.dart +++ b/packages/flutter_test/lib/src/test_pointer.dart @@ -230,7 +230,6 @@ class TestPointer { timeStamp: timeStamp, kind: kind, device: _device, - pointer: pointer, position: _location ?? Offset.zero, ); } @@ -256,7 +255,6 @@ class TestPointer { timeStamp: timeStamp, kind: kind, device: _device, - pointer: pointer, position: newLocation, delta: delta, ); diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index 7cbcf52a8b..63b50f91fe 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -525,7 +525,7 @@ Future expectLater( /// Class that programmatically interacts with widgets and the test environment. /// /// For convenience, instances of this class (such as the one provided by -/// `testWidgets`) can be used as the `vsync` for `AnimationController` objects. +/// `testWidget`) can be used as the `vsync` for `AnimationController` objects. class WidgetTester extends WidgetController implements HitTestDispatcher, TickerProvider { WidgetTester._(TestWidgetsFlutterBinding binding) : super(binding) { if (binding is LiveTestWidgetsFlutterBinding) diff --git a/packages/flutter_test/test/event_simulation_test.dart b/packages/flutter_test/test/event_simulation_test.dart index 0dfc774e15..38cdd8faeb 100644 --- a/packages/flutter_test/test/event_simulation_test.dart +++ b/packages/flutter_test/test/event_simulation_test.dart @@ -2,44 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; const List platforms = ['linux', 'macos', 'android', 'fuchsia']; -void _verifyKeyEvent(KeyEvent event, PhysicalKeyboardKey physical, LogicalKeyboardKey logical, String? character) { - expect(event, isA()); - expect(event.physicalKey, physical); - expect(event.logicalKey, logical); - expect(event.character, character); - expect(event.synthesized, false); -} - -void _verifyRawKeyEvent(RawKeyEvent event, PhysicalKeyboardKey physical, LogicalKeyboardKey logical, String? character) { - expect(event, isA()); - expect(event.physicalKey, physical); - expect(event.logicalKey, logical); - expect(event.character, character); -} - -Future _shouldThrow(AsyncValueGetter func) async { - bool hasError = false; - try { - await func(); - } catch (e) { - expect(e, isA()); - hasError = true; - } finally { - expect(hasError, true); - } -} - void main() { - testWidgets('simulates keyboard events (RawEvent)', (WidgetTester tester) async { - debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData; - + testWidgets('simulates keyboard events', (WidgetTester tester) async { final List events = []; final FocusNode focusNode = FocusNode(); @@ -81,247 +51,5 @@ void main() { await tester.pumpWidget(Container()); focusNode.dispose(); - - debugKeyEventSimulatorTransitModeOverride = null; - }); - - testWidgets('simulates keyboard events (KeyData then RawKeyEvent)', (WidgetTester tester) async { - debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; - - final List events = []; - - final FocusNode focusNode = FocusNode(); - - await tester.pumpWidget( - KeyboardListener( - focusNode: focusNode, - onKeyEvent: events.add, - child: Container(), - ), - ); - - focusNode.requestFocus(); - await tester.idle(); - - // Key press shiftLeft - await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft); - expect(events.length, 1); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.shiftLeft})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.shiftLeft})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyRepeatEvent(LogicalKeyboardKey.shiftLeft); - expect(events.length, 1); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.shiftLeft})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.shiftLeft})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyUpEvent(LogicalKeyboardKey.shiftLeft); - expect(events.length, 1); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftLeft, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - // Key press keyA - await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); - expect(events.length, 1); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.keyA})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.keyA})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.keyA})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.keyA})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - // Key press numpad1 - await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad1); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numpad1); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.numpad1})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad1); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numpad1, LogicalKeyboardKey.numpad1, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - // Key press numLock (1st time) - await tester.sendKeyDownEvent(LogicalKeyboardKey.numLock); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.lockModesEnabled, equals({KeyboardLockMode.numLock})); - events.clear(); - - await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numLock); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.lockModesEnabled, equals({KeyboardLockMode.numLock})); - events.clear(); - - await tester.sendKeyUpEvent(LogicalKeyboardKey.numLock); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.lockModesEnabled, equals({KeyboardLockMode.numLock})); - events.clear(); - - // Key press numLock (2nd time) - await tester.sendKeyDownEvent(LogicalKeyboardKey.numLock); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyRepeatEvent(LogicalKeyboardKey.numLock); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, equals({PhysicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.logicalKeysPressed, equals({LogicalKeyboardKey.numLock})); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.sendKeyUpEvent(LogicalKeyboardKey.numLock); - _verifyKeyEvent(events[0], PhysicalKeyboardKey.numLock, LogicalKeyboardKey.numLock, null); - expect(HardwareKeyboard.instance.physicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.logicalKeysPressed, isEmpty); - expect(HardwareKeyboard.instance.lockModesEnabled, isEmpty); - events.clear(); - - await tester.idle(); - - await tester.pumpWidget(Container()); - focusNode.dispose(); - - debugKeyEventSimulatorTransitModeOverride = null; - }); - - testWidgets('simulates using the correct transit mode: rawKeyData', (WidgetTester tester) async { - debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.rawKeyData; - - final List events = []; - - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget( - Focus( - focusNode: focusNode, - onKey: (FocusNode node, RawKeyEvent event) { - events.add(event); - return KeyEventResult.ignored; - }, - onKeyEvent: (FocusNode node, KeyEvent event) { - events.add(event); - return KeyEventResult.ignored; - }, - child: Container(), - ), - ); - - focusNode.requestFocus(); - await tester.idle(); - - // A (physical keyA, logical keyA) is pressed. - await simulateKeyDownEvent(LogicalKeyboardKey.keyA); - expect(events.length, 2); - expect(events[0], isA()); - _verifyKeyEvent(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); - expect(events[1], isA()); - _verifyRawKeyEvent(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); - events.clear(); - - // A (physical keyA, logical keyB) is released. - // - // Since this event was synthesized and regularized before being sent to - // HardwareKeyboard, this event will be accepted. - await simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA); - expect(events.length, 2); - expect(events[0], isA()); - _verifyKeyEvent(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, null); - expect(events[1], isA()); - _verifyRawKeyEvent(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyB, null); - events.clear(); - - // Manually switch the transit mode to `keyDataThenRawKeyData`. This will - // never happen in real applications so the assertion error can verify that - // the transit mode is correctly applied. - debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; - - await _shouldThrow(() => - simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA)); - - debugKeyEventSimulatorTransitModeOverride = null; - }); - - testWidgets('simulates using the correct transit mode: keyDataThenRawKeyData', (WidgetTester tester) async { - debugKeyEventSimulatorTransitModeOverride = KeyDataTransitMode.keyDataThenRawKeyData; - - final List events = []; - - final FocusNode focusNode = FocusNode(); - await tester.pumpWidget( - Focus( - focusNode: focusNode, - onKey: (FocusNode node, RawKeyEvent event) { - events.add(event); - return KeyEventResult.ignored; - }, - onKeyEvent: (FocusNode node, KeyEvent event) { - events.add(event); - return KeyEventResult.ignored; - }, - child: Container(), - ), - ); - - focusNode.requestFocus(); - await tester.idle(); - - // A (physical keyA, logical keyA) is pressed. - await simulateKeyDownEvent(LogicalKeyboardKey.keyA); - expect(events.length, 2); - expect(events[0], isA()); - _verifyKeyEvent(events[0] as KeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); - expect(events[1], isA()); - _verifyRawKeyEvent(events[1] as RawKeyEvent, PhysicalKeyboardKey.keyA, LogicalKeyboardKey.keyA, 'a'); - events.clear(); - - // A (physical keyA, logical keyB) is released. - // - // Since this event is transmitted to HardwareKeyboard as-is, it will be rejected due to - // inconsistent logical key. This does not indicate behaviral difference, - // since KeyData is will never send malformed data sequence in real applications. - await _shouldThrow(() => - simulateKeyUpEvent(LogicalKeyboardKey.keyB, physicalKey: PhysicalKeyboardKey.keyA)); - - debugKeyEventSimulatorTransitModeOverride = null; }); }