| 1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | /// @docImport 'package:flutter/cupertino.dart'; |
| 6 | /// @docImport 'package:flutter/material.dart'; |
| 7 | /// @docImport 'package:flutter_test/flutter_test.dart'; |
| 8 | library; |
| 9 | |
| 10 | import 'dart:ui' as ui; |
| 11 | |
| 12 | import 'package:flutter/foundation.dart'; |
| 13 | |
| 14 | import 'binding.dart'; |
| 15 | import 'debug.dart'; |
| 16 | import 'raw_keyboard.dart'; |
| 17 | import 'raw_keyboard_android.dart'; |
| 18 | import 'system_channels.dart'; |
| 19 | |
| 20 | export 'dart:ui' show KeyData; |
| 21 | |
| 22 | export 'package:flutter/foundation.dart' show DiagnosticPropertiesBuilder; |
| 23 | |
| 24 | export 'keyboard_key.g.dart' show LogicalKeyboardKey, PhysicalKeyboardKey; |
| 25 | |
| 26 | // When using _keyboardDebug, always call it like so: |
| 27 | // |
| 28 | // assert(_keyboardDebug(() => 'Blah $foo')); |
| 29 | // |
| 30 | // It needs to be inside the assert in order to be removed in release mode, and |
| 31 | // it needs to use a closure to generate the string in order to avoid string |
| 32 | // interpolation when debugPrintKeyboardEvents is false. |
| 33 | // |
| 34 | // It will throw a StateError if you try to call it when the app is in release |
| 35 | // mode. |
| 36 | bool _keyboardDebug(String Function() messageFunc, [Iterable<Object> Function()? detailsFunc]) { |
| 37 | if (kReleaseMode) { |
| 38 | throw StateError( |
| 39 | '_keyboardDebug was called in Release mode, which means they are called ' |
| 40 | 'without being wrapped in an assert. Always call _keyboardDebug like so:\n' |
| 41 | r" assert(_keyboardDebug(() => 'Blah $foo'));" , |
| 42 | ); |
| 43 | } |
| 44 | if (!debugPrintKeyboardEvents) { |
| 45 | return true; |
| 46 | } |
| 47 | debugPrint('KEYBOARD: ${messageFunc()}' ); |
| 48 | final Iterable<Object> details = detailsFunc?.call() ?? const <Object>[]; |
| 49 | if (details.isNotEmpty) { |
| 50 | for (final Object detail in details) { |
| 51 | debugPrint(' $detail' ); |
| 52 | } |
| 53 | } |
| 54 | // Return true so that it can be used inside of an assert. |
| 55 | return true; |
| 56 | } |
| 57 | |
| 58 | /// Represents a lock mode of a keyboard, such as [KeyboardLockMode.capsLock]. |
| 59 | /// |
| 60 | /// A lock mode locks some of a keyboard's keys into a distinct mode of operation, |
| 61 | /// depending on the lock settings selected. The status of the mode is toggled |
| 62 | /// with each key down of its corresponding logical key. A [KeyboardLockMode] |
| 63 | /// object is used to query whether this mode is enabled on the keyboard. |
| 64 | /// |
| 65 | /// Only a limited number of modes are supported, which are enumerated as |
| 66 | /// static members of this class. Manual constructing of this class is |
| 67 | /// prohibited. |
| 68 | enum KeyboardLockMode { |
| 69 | /// Represents the number lock mode on the keyboard. |
| 70 | /// |
| 71 | /// On supporting systems, enabling number lock mode usually allows key |
| 72 | /// presses of the number pad to input numbers, instead of acting as up, down, |
| 73 | /// left, right, page up, end, etc. |
| 74 | numLock._(LogicalKeyboardKey.numLock), |
| 75 | |
| 76 | /// Represents the scrolling lock mode on the keyboard. |
| 77 | /// |
| 78 | /// On supporting systems and applications (such as a spreadsheet), enabling |
| 79 | /// scrolling lock mode usually allows key presses of the cursor keys to |
| 80 | /// scroll the document instead of the cursor. |
| 81 | scrollLock._(LogicalKeyboardKey.scrollLock), |
| 82 | |
| 83 | /// Represents the capital letters lock mode on the keyboard. |
| 84 | /// |
| 85 | /// On supporting systems, enabling capital lock mode allows key presses of |
| 86 | /// the letter keys to input uppercase letters instead of lowercase. |
| 87 | capsLock._(LogicalKeyboardKey.capsLock); |
| 88 | |
| 89 | // KeyboardLockMode has a fixed pool of supported keys, enumerated as static |
| 90 | // members of this class, therefore constructing is prohibited. |
| 91 | const KeyboardLockMode._(this.logicalKey); |
| 92 | |
| 93 | /// The logical key that triggers this lock mode. |
| 94 | final LogicalKeyboardKey logicalKey; |
| 95 | |
| 96 | static final Map<int, KeyboardLockMode> _knownLockModes = <int, KeyboardLockMode>{ |
| 97 | numLock.logicalKey.keyId: numLock, |
| 98 | scrollLock.logicalKey.keyId: scrollLock, |
| 99 | capsLock.logicalKey.keyId: capsLock, |
| 100 | }; |
| 101 | |
| 102 | /// Returns the [KeyboardLockMode] constant from the logical key, or |
| 103 | /// null, if not found. |
| 104 | static KeyboardLockMode? findLockByLogicalKey(LogicalKeyboardKey logicalKey) => |
| 105 | _knownLockModes[logicalKey.keyId]; |
| 106 | } |
| 107 | |
| 108 | /// Defines the interface for keyboard key events. |
| 109 | /// |
| 110 | /// The [KeyEvent] provides a universal model for key event information from a |
| 111 | /// hardware keyboard across platforms. |
| 112 | /// |
| 113 | /// See also: |
| 114 | /// |
| 115 | /// * [HardwareKeyboard] for full introduction to key event model and handling. |
| 116 | /// * [KeyDownEvent], a subclass for events representing the user pressing a |
| 117 | /// key. |
| 118 | /// * [KeyRepeatEvent], a subclass for events representing the user holding a |
| 119 | /// key, causing repeated events. |
| 120 | /// * [KeyUpEvent], a subclass for events representing the user releasing a |
| 121 | /// key. |
| 122 | @immutable |
| 123 | abstract class KeyEvent with Diagnosticable { |
| 124 | /// Create a const KeyEvent by providing each field. |
| 125 | const KeyEvent({ |
| 126 | required this.physicalKey, |
| 127 | required this.logicalKey, |
| 128 | this.character, |
| 129 | required this.timeStamp, |
| 130 | this.deviceType = ui.KeyEventDeviceType.keyboard, |
| 131 | this.synthesized = false, |
| 132 | }); |
| 133 | |
| 134 | /// Returns an object representing the physical location of this key. |
| 135 | /// |
| 136 | /// A [PhysicalKeyboardKey] represents a USB HID code sent from the keyboard, |
| 137 | /// ignoring the key map, modifier keys (like SHIFT), and the label on the key. |
| 138 | /// |
| 139 | /// [PhysicalKeyboardKey]s are used to describe and test for keys in a |
| 140 | /// particular location. A [PhysicalKeyboardKey] may have a name, but the name |
| 141 | /// is a mnemonic ("keyA" is easier to remember than 0x70004), derived from the |
| 142 | /// key's effect on a QWERTY keyboard. The name does not represent the key's |
| 143 | /// effect whatsoever (a physical "keyA" can be the Q key on an AZERTY |
| 144 | /// keyboard). |
| 145 | /// |
| 146 | /// For instance, if you wanted to make a game where the key to the right of |
| 147 | /// the CAPS LOCK key made the player move left, you would be comparing a |
| 148 | /// physical key with [PhysicalKeyboardKey.keyA], since that is the key next to |
| 149 | /// the CAPS LOCK key on a QWERTY keyboard. This would return the same thing |
| 150 | /// even on an AZERTY keyboard where the key next to the CAPS LOCK produces a |
| 151 | /// "Q" when pressed. |
| 152 | /// |
| 153 | /// If you want to make your app respond to a key with a particular character |
| 154 | /// on it regardless of location of the key, use [KeyEvent.logicalKey] instead. |
| 155 | /// |
| 156 | /// Also, even though physical keys are defined with USB HID codes, their |
| 157 | /// values are not necessarily the same HID codes produced by the hardware and |
| 158 | /// presented to the driver. On most platforms, Flutter has to map the |
| 159 | /// platform representation back to a HID code because the original HID |
| 160 | /// code is not provided. USB HID was chosen because it is a well-defined |
| 161 | /// standard for referring to keys such as those a Flutter app may encounter. |
| 162 | /// |
| 163 | /// See also: |
| 164 | /// |
| 165 | /// * [logicalKey] for the non-location specific key generated by this event. |
| 166 | /// * [character] for the character generated by this keypress (if any). |
| 167 | final PhysicalKeyboardKey physicalKey; |
| 168 | |
| 169 | /// Returns an object representing the logical key that was pressed. |
| 170 | /// |
| 171 | /// {@template flutter.services.KeyEvent.logicalKey} |
| 172 | /// This method takes into account the key map and modifier keys (like SHIFT) |
| 173 | /// to determine which logical key to return. |
| 174 | /// |
| 175 | /// If you are looking for the character produced by a key event, use |
| 176 | /// [KeyEvent.character] instead. |
| 177 | /// |
| 178 | /// If you are collecting text strings, use the [TextField] or |
| 179 | /// [CupertinoTextField] widgets, since those automatically handle many of the |
| 180 | /// complexities of managing keyboard input, like showing a soft keyboard or |
| 181 | /// interacting with an input method editor (IME). |
| 182 | /// {@endtemplate} |
| 183 | final LogicalKeyboardKey logicalKey; |
| 184 | |
| 185 | /// Returns the Unicode character (grapheme cluster) completed by this |
| 186 | /// keystroke, if any. |
| 187 | /// |
| 188 | /// This will only return a character if this keystroke, combined with any |
| 189 | /// preceding keystroke(s), generates a character, and only on a "key down" |
| 190 | /// event. It will return null if no character has been generated by the |
| 191 | /// keystroke (e.g. a "dead" or "combining" key), or if the corresponding key |
| 192 | /// is a key without a visual representation, such as a modifier key or a |
| 193 | /// control key. It will also return null if this is a "key up" event. |
| 194 | /// |
| 195 | /// This can return multiple Unicode code points, since some characters (more |
| 196 | /// accurately referred to as grapheme clusters) are made up of more than one |
| 197 | /// code point. |
| 198 | /// |
| 199 | /// The [character] doesn't take into account edits by an input method editor |
| 200 | /// (IME), or manage the visibility of the soft keyboard on touch devices. For |
| 201 | /// composing text, use the [TextField] or [CupertinoTextField] widgets, since |
| 202 | /// those automatically handle many of the complexities of managing keyboard |
| 203 | /// input. |
| 204 | /// |
| 205 | /// The [character] is not available on [KeyUpEvent]s. |
| 206 | final String? character; |
| 207 | |
| 208 | /// Time of event, relative to an arbitrary start point. |
| 209 | /// |
| 210 | /// All events share the same timeStamp origin. |
| 211 | final Duration timeStamp; |
| 212 | |
| 213 | /// The source device type for the key event. |
| 214 | /// |
| 215 | /// Not all platforms supply an accurate type. |
| 216 | /// |
| 217 | /// Defaults to [ui.KeyEventDeviceType.keyboard]. |
| 218 | final ui.KeyEventDeviceType deviceType; |
| 219 | |
| 220 | /// Whether this event is synthesized by Flutter to synchronize key states. |
| 221 | /// |
| 222 | /// An non-[synthesized] event is converted from a native event, and a native |
| 223 | /// event can only be converted to one non-[synthesized] event. Some properties |
| 224 | /// might be changed during the conversion (for example, a native repeat event |
| 225 | /// might be converted to a Flutter down event when necessary.) |
| 226 | /// |
| 227 | /// A [synthesized] event is created without a source native event in order to |
| 228 | /// synchronize key states. For example, if the native platform shows that a |
| 229 | /// shift key that was previously held has been released somehow without the |
| 230 | /// key up event dispatched (probably due to loss of focus), a synthesized key |
| 231 | /// up event will be added to regularized the event stream. |
| 232 | /// |
| 233 | /// For detailed introduction to the regularized event model, see |
| 234 | /// [HardwareKeyboard]. |
| 235 | /// |
| 236 | /// Defaults to false. |
| 237 | final bool synthesized; |
| 238 | |
| 239 | @override |
| 240 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { |
| 241 | super.debugFillProperties(properties); |
| 242 | properties.add(DiagnosticsProperty<PhysicalKeyboardKey>('physicalKey' , physicalKey)); |
| 243 | properties.add(DiagnosticsProperty<LogicalKeyboardKey>('logicalKey' , logicalKey)); |
| 244 | properties.add(StringProperty('character' , character)); |
| 245 | properties.add(DiagnosticsProperty<Duration>('timeStamp' , timeStamp)); |
| 246 | properties.add(FlagProperty('synthesized' , value: synthesized, ifTrue: 'synthesized' )); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | /// An event indicating that the user has pressed a key down on the keyboard. |
| 251 | /// |
| 252 | /// See also: |
| 253 | /// |
| 254 | /// * [KeyRepeatEvent], a key event representing the user |
| 255 | /// holding a key, causing repeated events. |
| 256 | /// * [KeyUpEvent], a key event representing the user |
| 257 | /// releasing a key. |
| 258 | /// * [HardwareKeyboard], which produces this event. |
| 259 | class KeyDownEvent extends KeyEvent { |
| 260 | /// Creates a key event that represents the user pressing a key. |
| 261 | const KeyDownEvent({ |
| 262 | required super.physicalKey, |
| 263 | required super.logicalKey, |
| 264 | super.character, |
| 265 | required super.timeStamp, |
| 266 | super.synthesized, |
| 267 | super.deviceType, |
| 268 | }); |
| 269 | } |
| 270 | |
| 271 | /// An event indicating that the user has released a key on the keyboard. |
| 272 | /// |
| 273 | /// See also: |
| 274 | /// |
| 275 | /// * [KeyDownEvent], a key event representing the user |
| 276 | /// pressing a key. |
| 277 | /// * [KeyRepeatEvent], a key event representing the user |
| 278 | /// holding a key, causing repeated events. |
| 279 | /// * [HardwareKeyboard], which produces this event. |
| 280 | class KeyUpEvent extends KeyEvent { |
| 281 | /// Creates a key event that represents the user pressing a key. |
| 282 | const KeyUpEvent({ |
| 283 | required super.physicalKey, |
| 284 | required super.logicalKey, |
| 285 | required super.timeStamp, |
| 286 | super.synthesized, |
| 287 | super.deviceType, |
| 288 | }); |
| 289 | } |
| 290 | |
| 291 | /// An event indicating that the user has been holding a key on the keyboard |
| 292 | /// and causing repeated events. |
| 293 | /// |
| 294 | /// Repeat events are not guaranteed and are provided only if supported by the |
| 295 | /// underlying platform. |
| 296 | /// |
| 297 | /// See also: |
| 298 | /// |
| 299 | /// * [KeyDownEvent], a key event representing the user |
| 300 | /// pressing a key. |
| 301 | /// * [KeyUpEvent], a key event representing the user |
| 302 | /// releasing a key. |
| 303 | /// * [HardwareKeyboard], which produces this event. |
| 304 | class KeyRepeatEvent extends KeyEvent { |
| 305 | /// Creates a key event that represents the user pressing a key. |
| 306 | const KeyRepeatEvent({ |
| 307 | required super.physicalKey, |
| 308 | required super.logicalKey, |
| 309 | super.character, |
| 310 | required super.timeStamp, |
| 311 | super.deviceType, |
| 312 | }); |
| 313 | } |
| 314 | |
| 315 | /// The signature for [HardwareKeyboard.addHandler], a callback to decide whether |
| 316 | /// the entire framework handles a key event. |
| 317 | typedef KeyEventCallback = bool Function(KeyEvent event); |
| 318 | |
| 319 | /// Manages key events from hardware keyboards. |
| 320 | /// |
| 321 | /// [HardwareKeyboard] manages all key events of the Flutter application from |
| 322 | /// hardware keyboards (in contrast to on-screen keyboards). It receives key |
| 323 | /// data from the native platform, dispatches key events to registered |
| 324 | /// handlers, and records the keyboard state. |
| 325 | /// |
| 326 | /// To stay notified whenever keys are pressed, held, or released, add a |
| 327 | /// handler with [addHandler]. To only be notified when a specific part of the |
| 328 | /// app is focused, use a [Focus] widget's `onFocusChanged` attribute instead |
| 329 | /// of [addHandler]. Handlers should be removed with [removeHandler] when |
| 330 | /// notification is no longer necessary, or when the handler is being disposed. |
| 331 | /// |
| 332 | /// To query whether a key is being held, or a lock mode is enabled, use |
| 333 | /// [physicalKeysPressed], [logicalKeysPressed], or [lockModesEnabled]. |
| 334 | /// These states will have been updated with the event when used during a key |
| 335 | /// event handler. |
| 336 | /// |
| 337 | /// The singleton [HardwareKeyboard] instance is held by the [ServicesBinding] |
| 338 | /// as [ServicesBinding.keyboard], and can be conveniently accessed using the |
| 339 | /// [HardwareKeyboard.instance] static accessor. |
| 340 | /// |
| 341 | /// ## Event model |
| 342 | /// |
| 343 | /// Flutter uses a universal event model ([KeyEvent]) and key options |
| 344 | /// ([LogicalKeyboardKey] and [PhysicalKeyboardKey]) regardless of the native |
| 345 | /// platform, while preserving platform-specific features as much as |
| 346 | /// possible. |
| 347 | /// |
| 348 | /// [HardwareKeyboard] guarantees that the key model is "regularized": The key |
| 349 | /// event stream consists of "key tap sequences", where a key tap sequence is |
| 350 | /// defined as one [KeyDownEvent], zero or more [KeyRepeatEvent]s, and one |
| 351 | /// [KeyUpEvent] in order, all with the same physical key and logical key. |
| 352 | /// |
| 353 | /// Example: |
| 354 | /// |
| 355 | /// * Tap and hold key A, US layout: |
| 356 | /// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a") |
| 357 | /// * KeyRepeatEvent(physicalKey: keyA, logicalKey: keyA, character: "a") |
| 358 | /// * KeyUpEvent(physicalKey: keyA, logicalKey: keyA) |
| 359 | /// * Press ShiftLeft, tap key A, then release ShiftLeft, US layout: |
| 360 | /// * KeyDownEvent(physicalKey: shiftLeft, logicalKey: shiftLeft) |
| 361 | /// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "A") |
| 362 | /// * KeyRepeatEvent(physicalKey: keyA, logicalKey: keyA, character: "A") |
| 363 | /// * KeyUpEvent(physicalKey: keyA, logicalKey: keyA) |
| 364 | /// * KeyUpEvent(physicalKey: shiftLeft, logicalKey: shiftLeft) |
| 365 | /// * Tap key Q, French layout: |
| 366 | /// * KeyDownEvent(physicalKey: keyA, logicalKey: keyQ, character: "q") |
| 367 | /// * KeyUpEvent(physicalKey: keyA, logicalKey: keyQ) |
| 368 | /// * Tap CapsLock: |
| 369 | /// * KeyDownEvent(physicalKey: capsLock, logicalKey: capsLock) |
| 370 | /// * KeyUpEvent(physicalKey: capsLock, logicalKey: capsLock) |
| 371 | /// |
| 372 | /// When the Flutter application starts, all keys are released, and all lock |
| 373 | /// modes are disabled. Upon key events, [HardwareKeyboard] will update its |
| 374 | /// states, then dispatch callbacks: [KeyDownEvent]s and [KeyUpEvent]s set |
| 375 | /// or reset the pressing state, while [KeyDownEvent]s also toggle lock modes. |
| 376 | /// |
| 377 | /// Flutter will try to synchronize with the ground truth of keyboard states |
| 378 | /// using synthesized events ([KeyEvent.synthesized]), subject to the |
| 379 | /// availability of the platform. The desynchronization can be caused by |
| 380 | /// non-empty initial state or a change in the focused window or application. |
| 381 | /// For example, if CapsLock is enabled when the application starts, then |
| 382 | /// immediately before the first key event, a synthesized [KeyDownEvent] and |
| 383 | /// [KeyUpEvent] of CapsLock will be dispatched. |
| 384 | /// |
| 385 | /// The resulting event stream does not map one-to-one to the native key event |
| 386 | /// stream. Some native events might be skipped, while some events might be |
| 387 | /// synthesized and do not correspond to native events. Synthesized events will |
| 388 | /// be indicated by [KeyEvent.synthesized]. |
| 389 | /// |
| 390 | /// Example: |
| 391 | /// |
| 392 | /// * Flutter starts with CapsLock on, the first press of keyA: |
| 393 | /// * KeyDownEvent(physicalKey: capsLock, logicalKey: capsLock, synthesized: true) |
| 394 | /// * KeyUpEvent(physicalKey: capsLock, logicalKey: capsLock, synthesized: true) |
| 395 | /// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a") |
| 396 | /// * While holding ShiftLeft, lose window focus, release shiftLeft, then focus |
| 397 | /// back and press keyA: |
| 398 | /// * KeyUpEvent(physicalKey: shiftLeft, logicalKey: shiftLeft, synthesized: true) |
| 399 | /// * KeyDownEvent(physicalKey: keyA, logicalKey: keyA, character: "a") |
| 400 | /// |
| 401 | /// Flutter does not distinguish between multiple keyboards. Flutter will |
| 402 | /// process all events as if they come from a single keyboard, and try to |
| 403 | /// resolve any conflicts and provide a regularized key event stream, which |
| 404 | /// can deviate from the ground truth. |
| 405 | /// |
| 406 | /// See also: |
| 407 | /// |
| 408 | /// * [KeyDownEvent], [KeyRepeatEvent], and [KeyUpEvent], the classes used to |
| 409 | /// describe specific key events. |
| 410 | /// * [instance], the singleton instance of this class. |
| 411 | class HardwareKeyboard { |
| 412 | /// Provides convenient access to the current [HardwareKeyboard] singleton from |
| 413 | /// the [ServicesBinding] instance. |
| 414 | static HardwareKeyboard get instance => ServicesBinding.instance.keyboard; |
| 415 | |
| 416 | final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _pressedKeys = |
| 417 | <PhysicalKeyboardKey, LogicalKeyboardKey>{}; |
| 418 | |
| 419 | /// The set of [PhysicalKeyboardKey]s that are pressed. |
| 420 | /// |
| 421 | /// If called from a key event handler, the result will already include the effect |
| 422 | /// of the event. |
| 423 | /// |
| 424 | /// See also: |
| 425 | /// |
| 426 | /// * [logicalKeysPressed], which tells if a logical key is being pressed. |
| 427 | Set<PhysicalKeyboardKey> get physicalKeysPressed => _pressedKeys.keys.toSet(); |
| 428 | |
| 429 | /// The set of [LogicalKeyboardKey]s that are pressed. |
| 430 | /// |
| 431 | /// If called from a key event handler, the result will already include the effect |
| 432 | /// of the event. |
| 433 | /// |
| 434 | /// See also: |
| 435 | /// |
| 436 | /// * [physicalKeysPressed], which tells if a physical key is being pressed. |
| 437 | Set<LogicalKeyboardKey> get logicalKeysPressed => _pressedKeys.values.toSet(); |
| 438 | |
| 439 | /// Returns the logical key that corresponds to the given pressed physical key. |
| 440 | /// |
| 441 | /// Returns null if the physical key is not currently pressed. |
| 442 | LogicalKeyboardKey? lookUpLayout(PhysicalKeyboardKey physicalKey) => _pressedKeys[physicalKey]; |
| 443 | |
| 444 | final Set<KeyboardLockMode> _lockModes = <KeyboardLockMode>{}; |
| 445 | |
| 446 | /// The set of [KeyboardLockMode] that are enabled. |
| 447 | /// |
| 448 | /// Lock keys, such as CapsLock, are logical keys that toggle their |
| 449 | /// respective boolean states on key down events. Such flags are usually used |
| 450 | /// as modifier to other keys or events. |
| 451 | /// |
| 452 | /// If called from a key event handler, the result will already include the effect |
| 453 | /// of the event. |
| 454 | Set<KeyboardLockMode> get lockModesEnabled => _lockModes; |
| 455 | |
| 456 | /// Returns true if the given [LogicalKeyboardKey] is pressed, according to |
| 457 | /// the [HardwareKeyboard]. |
| 458 | bool isLogicalKeyPressed(LogicalKeyboardKey key) => _pressedKeys.values.contains(key); |
| 459 | |
| 460 | /// Returns true if the given [PhysicalKeyboardKey] is pressed, according to |
| 461 | /// the [HardwareKeyboard]. |
| 462 | bool isPhysicalKeyPressed(PhysicalKeyboardKey key) => _pressedKeys.containsKey(key); |
| 463 | |
| 464 | /// Returns true if a logical CTRL modifier key is pressed, regardless of |
| 465 | /// which side of the keyboard it is on. |
| 466 | /// |
| 467 | /// Use [isLogicalKeyPressed] if you need to know which control key was |
| 468 | /// pressed. |
| 469 | bool get isControlPressed { |
| 470 | return isLogicalKeyPressed(LogicalKeyboardKey.controlLeft) || |
| 471 | isLogicalKeyPressed(LogicalKeyboardKey.controlRight); |
| 472 | } |
| 473 | |
| 474 | /// Returns true if a logical SHIFT modifier key is pressed, regardless of |
| 475 | /// which side of the keyboard it is on. |
| 476 | /// |
| 477 | /// Use [isLogicalKeyPressed] if you need to know which shift key was pressed. |
| 478 | bool get isShiftPressed { |
| 479 | return isLogicalKeyPressed(LogicalKeyboardKey.shiftLeft) || |
| 480 | isLogicalKeyPressed(LogicalKeyboardKey.shiftRight); |
| 481 | } |
| 482 | |
| 483 | /// Returns true if a logical ALT modifier key is pressed, regardless of which |
| 484 | /// side of the keyboard it is on. |
| 485 | /// |
| 486 | /// The `AltGr` key that appears on some keyboards is considered to be the |
| 487 | /// same as [LogicalKeyboardKey.altRight] on some platforms (notably Android). |
| 488 | /// On platforms that can distinguish between `altRight` and `altGr`, a press |
| 489 | /// of `AltGr` will not return true here, and will need to be tested for |
| 490 | /// separately. |
| 491 | /// |
| 492 | /// Use [isLogicalKeyPressed] if you need to know which alt key was pressed. |
| 493 | bool get isAltPressed { |
| 494 | return isLogicalKeyPressed(LogicalKeyboardKey.altLeft) || |
| 495 | isLogicalKeyPressed(LogicalKeyboardKey.altRight); |
| 496 | } |
| 497 | |
| 498 | /// Returns true if a logical META modifier key is pressed, regardless of |
| 499 | /// which side of the keyboard it is on. |
| 500 | /// |
| 501 | /// Use [isLogicalKeyPressed] if you need to know which meta key was pressed. |
| 502 | bool get isMetaPressed { |
| 503 | return isLogicalKeyPressed(LogicalKeyboardKey.metaLeft) || |
| 504 | isLogicalKeyPressed(LogicalKeyboardKey.metaRight); |
| 505 | } |
| 506 | |
| 507 | void _assertEventIsRegular(KeyEvent event) { |
| 508 | assert(() { |
| 509 | const String common = |
| 510 | 'If this occurs in real application, please report this ' |
| 511 | 'bug to Flutter. If this occurs in unit tests, please ensure that ' |
| 512 | "simulated events follow Flutter's event model as documented in " |
| 513 | '`HardwareKeyboard`. This was the event: ' ; |
| 514 | if (event is KeyDownEvent) { |
| 515 | assert( |
| 516 | !_pressedKeys.containsKey(event.physicalKey), |
| 517 | 'A ${event.runtimeType} is dispatched, but the state shows that the physical ' |
| 518 | 'key is already pressed. $common$event' , |
| 519 | ); |
| 520 | } else if (event is KeyRepeatEvent || event is KeyUpEvent) { |
| 521 | assert( |
| 522 | _pressedKeys.containsKey(event.physicalKey), |
| 523 | 'A ${event.runtimeType} is dispatched, but the state shows that the physical ' |
| 524 | 'key is not pressed. $common$event' , |
| 525 | );
|
| 526 | assert(
|
| 527 | _pressedKeys[event.physicalKey] == event.logicalKey,
|
| 528 | 'A ${event.runtimeType} is dispatched, but the state shows that the physical '
|
| 529 | 'key is pressed on a different logical key. $common$event '
|
| 530 | 'and the recorded logical key ${_pressedKeys[event.physicalKey]}' ,
|
| 531 | );
|
| 532 | } else {
|
| 533 | assert(false, 'Unexpected key event class ${event.runtimeType}' );
|
| 534 | }
|
| 535 | return true;
|
| 536 | }());
|
| 537 | }
|
| 538 |
|
| 539 | List<KeyEventCallback> _handlers = <KeyEventCallback>[];
|
| 540 | bool _duringDispatch = false;
|
| 541 | List<KeyEventCallback>? _modifiedHandlers;
|
| 542 |
|
| 543 | /// Register a listener that is called every time a hardware key event
|
| 544 | /// occurs.
|
| 545 | ///
|
| 546 | /// All registered handlers will be invoked in order regardless of
|
| 547 | /// their return value. The return value indicates whether Flutter
|
| 548 | /// "handles" the event. If any handler returns true, the event
|
| 549 | /// will not be propagated to other native components in the add-to-app
|
| 550 | /// scenario.
|
| 551 | ///
|
| 552 | /// If an object added a handler, it must remove the handler before it is
|
| 553 | /// disposed.
|
| 554 | ///
|
| 555 | /// If used during event dispatching, the addition will not take effect
|
| 556 | /// until after the dispatching.
|
| 557 | ///
|
| 558 | /// See also:
|
| 559 | ///
|
| 560 | /// * [removeHandler], which removes the handler.
|
| 561 | void addHandler(KeyEventCallback handler) {
|
| 562 | if (_duringDispatch) {
|
| 563 | _modifiedHandlers ??= <KeyEventCallback>[..._handlers];
|
| 564 | _modifiedHandlers!.add(handler);
|
| 565 | } else {
|
| 566 | _handlers.add(handler);
|
| 567 | }
|
| 568 | }
|
| 569 |
|
| 570 | /// Stop calling the given listener every time a hardware key event
|
| 571 | /// occurs.
|
| 572 | ///
|
| 573 | /// The `handler` argument must be [identical] to the one used in
|
| 574 | /// [addHandler]. If multiple exist, the first one will be removed.
|
| 575 | /// If none is found, then this method is a no-op.
|
| 576 | ///
|
| 577 | /// If used during event dispatching, the removal will not take effect
|
| 578 | /// until after the event has been dispatched.
|
| 579 | void removeHandler(KeyEventCallback handler) {
|
| 580 | if (_duringDispatch) {
|
| 581 | _modifiedHandlers ??= <KeyEventCallback>[..._handlers];
|
| 582 | _modifiedHandlers!.remove(handler);
|
| 583 | } else {
|
| 584 | _handlers.remove(handler);
|
| 585 | }
|
| 586 | }
|
| 587 |
|
| 588 | /// Query the engine and update _pressedKeys accordingly to the engine answer.
|
| 589 | //
|
| 590 | /// Both the framework and the engine maintain a state of the current pressed
|
| 591 | /// keys. There are edge cases, related to startup and restart, where the framework
|
| 592 | /// needs to resynchronize its keyboard state.
|
| 593 | Future<void> syncKeyboardState() async {
|
| 594 | final Map<int, int>? keyboardState = await SystemChannels.keyboard.invokeMapMethod<int, int>(
|
| 595 | 'getKeyboardState' ,
|
| 596 | );
|
| 597 | if (keyboardState != null) {
|
| 598 | for (final int key in keyboardState.keys) {
|
| 599 | final PhysicalKeyboardKey physicalKey = PhysicalKeyboardKey(key);
|
| 600 | final LogicalKeyboardKey logicalKey = LogicalKeyboardKey(keyboardState[key]!);
|
| 601 | _pressedKeys[physicalKey] = logicalKey;
|
| 602 | }
|
| 603 | }
|
| 604 | }
|
| 605 |
|
| 606 | bool _dispatchKeyEvent(KeyEvent event) {
|
| 607 | // This dispatching could have used the same algorithm as [ChangeNotifier],
|
| 608 | // but since 1) it shouldn't be necessary to support reentrantly
|
| 609 | // dispatching, 2) there shouldn't be many handlers (most apps should use
|
| 610 | // only 1, this function just uses a simpler algorithm.
|
| 611 | assert(!_duringDispatch, 'Nested keyboard dispatching is not supported' );
|
| 612 | _duringDispatch = true;
|
| 613 | bool handled = false;
|
| 614 | for (final KeyEventCallback handler in _handlers) {
|
| 615 | try {
|
| 616 | final bool thisResult = handler(event);
|
| 617 | handled = handled || thisResult;
|
| 618 | } catch (exception, stack) {
|
| 619 | InformationCollector? collector;
|
| 620 | assert(() {
|
| 621 | collector = () => <DiagnosticsNode>[DiagnosticsProperty<KeyEvent>('Event' , event)];
|
| 622 | return true;
|
| 623 | }());
|
| 624 | FlutterError.reportError(
|
| 625 | FlutterErrorDetails(
|
| 626 | exception: exception,
|
| 627 | stack: stack,
|
| 628 | library: 'services library' ,
|
| 629 | context: ErrorDescription('while processing a key handler' ),
|
| 630 | informationCollector: collector,
|
| 631 | ),
|
| 632 | );
|
| 633 | }
|
| 634 | }
|
| 635 | _duringDispatch = false;
|
| 636 | if (_modifiedHandlers != null) {
|
| 637 | _handlers = _modifiedHandlers!;
|
| 638 | _modifiedHandlers = null;
|
| 639 | }
|
| 640 | return handled;
|
| 641 | }
|
| 642 |
|
| 643 | List<String> _debugPressedKeysDetails() {
|
| 644 | return <String>[
|
| 645 | if (_pressedKeys.isEmpty)
|
| 646 | 'Empty'
|
| 647 | else
|
| 648 | for (final PhysicalKeyboardKey physicalKey in _pressedKeys.keys)
|
| 649 | ' $physicalKey: ${_pressedKeys[physicalKey]}' ,
|
| 650 | ];
|
| 651 | }
|
| 652 |
|
| 653 | /// Process a new [KeyEvent] by recording the state changes and dispatching
|
| 654 | /// to handlers.
|
| 655 | bool handleKeyEvent(KeyEvent event) {
|
| 656 | assert(_keyboardDebug(() => 'Key event received: $event' ));
|
| 657 | assert(
|
| 658 | _keyboardDebug(() => 'Pressed state before processing the event:' , _debugPressedKeysDetails),
|
| 659 | );
|
| 660 | _assertEventIsRegular(event);
|
| 661 | final PhysicalKeyboardKey physicalKey = event.physicalKey;
|
| 662 | final LogicalKeyboardKey logicalKey = event.logicalKey;
|
| 663 | if (event is KeyDownEvent) {
|
| 664 | _pressedKeys[physicalKey] = logicalKey;
|
| 665 | final KeyboardLockMode? lockMode = KeyboardLockMode.findLockByLogicalKey(event.logicalKey);
|
| 666 | if (lockMode != null) {
|
| 667 | if (_lockModes.contains(lockMode)) {
|
| 668 | _lockModes.remove(lockMode);
|
| 669 | } else {
|
| 670 | _lockModes.add(lockMode);
|
| 671 | }
|
| 672 | }
|
| 673 | } else if (event is KeyUpEvent) {
|
| 674 | _pressedKeys.remove(physicalKey);
|
| 675 | } else if (event is KeyRepeatEvent) {
|
| 676 | // Empty
|
| 677 | }
|
| 678 |
|
| 679 | assert(
|
| 680 | _keyboardDebug(() => 'Pressed state after processing the event:' , _debugPressedKeysDetails),
|
| 681 | );
|
| 682 | return _dispatchKeyEvent(event);
|
| 683 | }
|
| 684 |
|
| 685 | /// Clear all keyboard states and additional handlers.
|
| 686 | ///
|
| 687 | /// All handlers are removed except for the first one, which is added by
|
| 688 | /// [ServicesBinding].
|
| 689 | ///
|
| 690 | /// This is used by the testing framework to make sure that tests are hermetic.
|
| 691 | @visibleForTesting
|
| 692 | void clearState() {
|
| 693 | _pressedKeys.clear();
|
| 694 | _lockModes.clear();
|
| 695 | _handlers.clear();
|
| 696 | assert(_modifiedHandlers == null);
|
| 697 | }
|
| 698 | }
|
| 699 |
|
| 700 | /// The mode in which information of key messages is delivered.
|
| 701 | ///
|
| 702 | /// This enum is deprecated and will be removed. There is no direct substitute
|
| 703 | /// planned, since this enum will no longer be necessary once [RawKeyEvent] and
|
| 704 | /// associated APIs are removed.
|
| 705 | ///
|
| 706 | /// Different platforms use different methods, classes, and models to inform the
|
| 707 | /// framework of native key events, which is called "transit mode".
|
| 708 | ///
|
| 709 | /// The framework must determine which transit mode the current platform
|
| 710 | /// implements and behave accordingly (such as transforming and synthesizing
|
| 711 | /// events if necessary). Unit tests related to keyboard might also want to
|
| 712 | /// simulate platform of each transit mode.
|
| 713 | ///
|
| 714 | /// The transit mode of the current platform is inferred by [KeyEventManager] at
|
| 715 | /// the start of the application.
|
| 716 | ///
|
| 717 | /// See also:
|
| 718 | ///
|
| 719 | /// * [KeyEventManager], which infers the transit mode of the current platform
|
| 720 | /// and guides how key messages are dispatched.
|
| 721 | /// * [debugKeyEventSimulatorTransitModeOverride], overrides the transit mode
|
| 722 | /// used to simulate key events.
|
| 723 | /// * [KeySimulatorTransitModeVariant], an easier way to set
|
| 724 | /// [debugKeyEventSimulatorTransitModeOverride] in widget tests.
|
| 725 | @Deprecated(
|
| 726 | 'No longer supported. Transit mode is always key data only. '
|
| 727 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 728 | )
|
| 729 | enum KeyDataTransitMode {
|
| 730 | /// Key event information is delivered as raw key data.
|
| 731 | ///
|
| 732 | /// Raw key data is platform's native key event information sent in JSON
|
| 733 | /// through a method channel, which is then interpreted as a platform subclass
|
| 734 | /// of [RawKeyEventData].
|
| 735 | ///
|
| 736 | /// If the current transit mode is [rawKeyData], the raw key data is converted
|
| 737 | /// to both [KeyMessage.events] and [KeyMessage.rawEvent].
|
| 738 | @Deprecated(
|
| 739 | 'No longer supported. Transit mode is always key data only. '
|
| 740 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 741 | )
|
| 742 | rawKeyData,
|
| 743 |
|
| 744 | /// Key event information is delivered as converted key data, followed by raw
|
| 745 | /// key data.
|
| 746 | ///
|
| 747 | /// Key data ([ui.KeyData]) is a standardized event stream converted from
|
| 748 | /// platform's native key event information, sent through the embedder API.
|
| 749 | /// Its event model is described in [HardwareKeyboard].
|
| 750 | ///
|
| 751 | /// Raw key data is platform's native key event information sent in JSON
|
| 752 | /// through a method channel. It is interpreted by subclasses of
|
| 753 | /// [RawKeyEventData].
|
| 754 | ///
|
| 755 | /// If the current transit mode is [keyDataThenRawKeyData], then the
|
| 756 | /// [KeyEventManager] will use the [ui.KeyData] for [KeyMessage.events], and
|
| 757 | /// the raw data for [KeyMessage.rawEvent].
|
| 758 | @Deprecated(
|
| 759 | 'No longer supported. Transit mode is always key data only. '
|
| 760 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 761 | )
|
| 762 | keyDataThenRawKeyData,
|
| 763 | }
|
| 764 |
|
| 765 | /// The assembled information converted from a native key message.
|
| 766 | ///
|
| 767 | /// This class is deprecated, and will be removed. There is no direct substitute
|
| 768 | /// planned, since this class will no longer be necessary once [RawKeyEvent] and
|
| 769 | /// associated APIs are removed.
|
| 770 | ///
|
| 771 | /// Native key messages, produced by physically pressing or releasing keyboard
|
| 772 | /// keys, are translated into two different event streams in Flutter:
|
| 773 | ///
|
| 774 | /// * The [KeyEvent] stream, represented by [KeyMessage.events] (recommended).
|
| 775 | /// * The [RawKeyEvent] stream, represented by [KeyMessage.rawEvent] (legacy, to
|
| 776 | /// be deprecated).
|
| 777 | ///
|
| 778 | /// Either the [KeyEvent] stream or the [RawKeyEvent] stream alone provides a
|
| 779 | /// complete description of the keyboard messages, but in different event
|
| 780 | /// models. Flutter is still transitioning from the legacy model to the new
|
| 781 | /// model, therefore it dispatches both streams simultaneously until the
|
| 782 | /// transition is completed. [KeyMessage] is used to bundle the stream segments
|
| 783 | /// of both models from a native key message together for the convenience of
|
| 784 | /// propagation.
|
| 785 | ///
|
| 786 | /// Typically, an application either processes [KeyMessage.events] or
|
| 787 | /// [KeyMessage.rawEvent], not both. For example, handling a [KeyMessage], means
|
| 788 | /// handling each event in [KeyMessage.events].
|
| 789 | ///
|
| 790 | /// In advanced cases, a widget needs to process both streams at the same time.
|
| 791 | /// For example, [FocusNode] has an `onKey` that dispatches [RawKeyEvent]s and
|
| 792 | /// an `onKeyEvent` that dispatches [KeyEvent]s. To processes a [KeyMessage], it
|
| 793 | /// first calls `onKeyEvent` with each [KeyEvent] of [events], and then `onKey`
|
| 794 | /// with [rawEvent]. All callbacks are invoked regardless of their
|
| 795 | /// [KeyEventResult]. Their results are combined into the result of the node
|
| 796 | /// using [combineKeyEventResults].
|
| 797 | ///
|
| 798 | /// ```dart
|
| 799 | /// void handleMessage(FocusNode node, KeyMessage message) {
|
| 800 | /// final List<KeyEventResult> results = <KeyEventResult>[];
|
| 801 | /// if (node.onKeyEvent != null) {
|
| 802 | /// for (final KeyEvent event in message.events) {
|
| 803 | /// results.add(node.onKeyEvent!(node, event));
|
| 804 | /// }
|
| 805 | /// }
|
| 806 | /// if (node.onKey != null && message.rawEvent != null) {
|
| 807 | /// results.add(node.onKey!(node, message.rawEvent!));
|
| 808 | /// }
|
| 809 | /// final KeyEventResult result = combineKeyEventResults(results);
|
| 810 | /// // Progress based on `result`...
|
| 811 | /// }
|
| 812 | /// ```
|
| 813 | @Deprecated(
|
| 814 | 'No longer supported. Once RawKeyEvent is removed, it will no longer be needed. '
|
| 815 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 816 | )
|
| 817 | @immutable
|
| 818 | class KeyMessage {
|
| 819 | /// Create a [KeyMessage] by providing all information.
|
| 820 | ///
|
| 821 | /// The [events] might be empty.
|
| 822 | @Deprecated(
|
| 823 | 'No longer supported. Once RawKeyEvent is removed, will no longer be needed. '
|
| 824 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 825 | )
|
| 826 | const KeyMessage(this.events, this.rawEvent);
|
| 827 |
|
| 828 | /// The list of [KeyEvent]s converted from the native key message.
|
| 829 | ///
|
| 830 | /// A native key message is converted into multiple [KeyEvent]s in a regular
|
| 831 | /// event model. The [events] might contain zero or any number of
|
| 832 | /// [KeyEvent]s.
|
| 833 | ///
|
| 834 | /// See also:
|
| 835 | ///
|
| 836 | /// * [HardwareKeyboard], which describes the regular event model.
|
| 837 | /// * [HardwareKeyboard.addHandler], [KeyboardListener], [Focus.onKeyEvent],
|
| 838 | /// where [KeyEvent]s are commonly used.
|
| 839 | final List<KeyEvent> events;
|
| 840 |
|
| 841 | /// The native key message in the form of a raw key event.
|
| 842 | ///
|
| 843 | /// A native key message is sent to the framework in JSON and represented
|
| 844 | /// in a platform-specific form as [RawKeyEventData] and a platform-neutral
|
| 845 | /// form as [RawKeyEvent]. Their stream is not as regular as [KeyEvent]'s,
|
| 846 | /// but keeps as much native information and structure as possible.
|
| 847 | ///
|
| 848 | /// The [rawEvent] field might be empty, for example, when the event
|
| 849 | /// converting system dispatches solitary synthesized events.
|
| 850 | ///
|
| 851 | /// The [rawEvent] field will be deprecated in the future.
|
| 852 | ///
|
| 853 | /// See also:
|
| 854 | ///
|
| 855 | /// * [RawKeyboard.addListener], [RawKeyboardListener], [Focus.onKey],
|
| 856 | /// where [RawKeyEvent]s are commonly used.
|
| 857 | final RawKeyEvent? rawEvent;
|
| 858 |
|
| 859 | @override
|
| 860 | String toString() {
|
| 861 | return 'KeyMessage( $events)' ;
|
| 862 | }
|
| 863 | }
|
| 864 |
|
| 865 | /// The signature for [KeyEventManager.keyMessageHandler].
|
| 866 | ///
|
| 867 | /// A [KeyMessageHandler] processes a [KeyMessage] and returns whether the
|
| 868 | /// message is considered handled. Handled messages should not be propagated to
|
| 869 | /// other native components.
|
| 870 | ///
|
| 871 | /// This message handler signature is deprecated, and will be removed. There is
|
| 872 | /// no direct substitute planned, since this handler type will no longer be
|
| 873 | /// necessary once [RawKeyEvent] and associated APIs are removed.
|
| 874 | @Deprecated(
|
| 875 | 'No longer supported. Once KeyMessage is removed, will no longer be needed. '
|
| 876 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 877 | )
|
| 878 | typedef KeyMessageHandler = bool Function(KeyMessage message);
|
| 879 |
|
| 880 | /// A singleton class that processes key messages from the platform and
|
| 881 | /// dispatches converted messages accordingly.
|
| 882 | ///
|
| 883 | /// This class is deprecated, and will be removed. There is no direct substitute
|
| 884 | /// planned, since this class will no longer be necessary once [RawKeyEvent] and
|
| 885 | /// associated APIs are removed.
|
| 886 | ///
|
| 887 | /// [KeyEventManager] receives platform key messages by [handleKeyData] and
|
| 888 | /// [handleRawKeyMessage], sends converted events to [HardwareKeyboard] and
|
| 889 | /// [RawKeyboard] for recording keeping, and then dispatches the [KeyMessage] to
|
| 890 | /// [keyMessageHandler], the global message handler.
|
| 891 | ///
|
| 892 | /// [KeyEventManager] is typically created, owned, and invoked by
|
| 893 | /// [ServicesBinding].
|
| 894 | ///
|
| 895 | /// ## On embedder implementation
|
| 896 | ///
|
| 897 | /// Currently, Flutter has two sets of key event API pathways running in
|
| 898 | /// parallel.
|
| 899 | ///
|
| 900 | /// * The "hardware key event" pathway receives messages from the
|
| 901 | /// "flutter/keydata" message channel (embedder API
|
| 902 | /// `FlutterEngineSendKeyEvent`) and dispatches [KeyEvent] to
|
| 903 | /// [HardwareKeyboard] and some methods such as [Focus.onKeyEvent].
|
| 904 | /// * The deprecated "raw key event" pathway receives messages from the
|
| 905 | /// "flutter/keyevent" message channel ([SystemChannels.keyEvent]) and
|
| 906 | /// dispatches [RawKeyEvent] to [RawKeyboard] and [Focus.onKey] as well as
|
| 907 | /// similar methods. This pathway will be removed at a future date.
|
| 908 | ///
|
| 909 | /// [KeyEventManager] resolves cross-platform compatibility of keyboard
|
| 910 | /// implementations, since legacy platforms might have not implemented the new
|
| 911 | /// key data API and only send raw key data on each key message.
|
| 912 | /// [KeyEventManager] recognizes the platform support by detecting whether a
|
| 913 | /// message comes from platform channel "flutter/keyevent" before one from
|
| 914 | /// "flutter/keydata", or vice versa, at the beginning of the app.
|
| 915 | ///
|
| 916 | /// * If a "flutter/keydata" message is received first, then this platform is
|
| 917 | /// considered a modern platform. The hardware key events are stored, and
|
| 918 | /// dispatched only when a raw key message is received.
|
| 919 | /// * If a "flutter/keyevent" message is received first, then this platform is
|
| 920 | /// considered a legacy platform. The raw key event is transformed into a
|
| 921 | /// hardware key event at best effort. No messages from "flutter/keydata" are
|
| 922 | /// expected. This behavior has been deprecated, and will be removed at a
|
| 923 | /// future date.
|
| 924 | ///
|
| 925 | /// Therefore, to correctly implement a platform that supports
|
| 926 | /// `FlutterEngineSendKeyEvent`, the platform must ensure that
|
| 927 | /// `FlutterEngineSendKeyEvent` is called before sending a message to
|
| 928 | /// "flutter/keyevent" at the beginning of the app, and every physical key event
|
| 929 | /// is ended with a "flutter/keyevent" message.
|
| 930 | @Deprecated(
|
| 931 | 'No longer supported. Once RawKeyEvent is removed, will no longer be needed. '
|
| 932 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 933 | )
|
| 934 | class KeyEventManager {
|
| 935 | /// Create an instance.
|
| 936 | ///
|
| 937 | /// This is typically only called by [ServicesBinding].
|
| 938 | @Deprecated(
|
| 939 | 'No longer supported. Once RawKeyEvent is removed, will no longer be needed. '
|
| 940 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 941 | )
|
| 942 | KeyEventManager(this._hardwareKeyboard, this._rawKeyboard);
|
| 943 |
|
| 944 | /// The global entrance which handles all key events sent to Flutter.
|
| 945 | ///
|
| 946 | /// This handler is deprecated and will be removed. Use
|
| 947 | /// [HardwareKeyboard.addHandler]/[HardwareKeyboard.removeHandler] instead.
|
| 948 | ///
|
| 949 | /// Typical applications use [WidgetsBinding], where this field is set by the
|
| 950 | /// focus system (see `FocusManager`) on startup to a function that dispatches
|
| 951 | /// incoming events to the focus system, including `FocusNode.onKey`,
|
| 952 | /// `FocusNode.onKeyEvent`, and `Shortcuts`. In this case, the application
|
| 953 | /// does not need to read, assign, or invoke this value.
|
| 954 | ///
|
| 955 | /// For advanced uses, the application can "patch" this callback. See below
|
| 956 | /// for details.
|
| 957 | ///
|
| 958 | /// ## Handlers and event results
|
| 959 | ///
|
| 960 | /// Roughly speaking, Flutter processes each native key event with the
|
| 961 | /// following phases:
|
| 962 | ///
|
| 963 | /// 1. Platform-side pre-filtering, sometimes used for IME.
|
| 964 | /// 2. The key event system.
|
| 965 | /// 3. The text input system.
|
| 966 | /// 4. Other native components (possibly non-Flutter).
|
| 967 | ///
|
| 968 | /// Each phase will conclude with a boolean called an "event result". If the
|
| 969 | /// result is true, this phase _handles_ the event and prevents the event from
|
| 970 | /// being propagated to the next phase. This mechanism allows shortcuts such
|
| 971 | /// as "Ctrl-C" to not generate a text "C" in the text field, or shortcuts
|
| 972 | /// that are not handled by any components to trigger special alerts (such as
|
| 973 | /// the "bonk" noise on macOS).
|
| 974 | ///
|
| 975 | /// In the second phase, known as "the key event system", the event is
|
| 976 | /// dispatched to several destinations: [RawKeyboard]'s listeners,
|
| 977 | /// [HardwareKeyboard]'s handlers, and [keyMessageHandler]. All destinations
|
| 978 | /// will always receive the event regardless of the handlers' results. If any
|
| 979 | /// handler's result is true, then the overall result of the second phase is
|
| 980 | /// true, and event propagation is stopped.
|
| 981 | ///
|
| 982 | /// See also:
|
| 983 | ///
|
| 984 | /// * [RawKeyboard.addListener], which adds a raw keyboard listener.
|
| 985 | /// * [RawKeyboardListener], which is also implemented by adding a raw
|
| 986 | /// keyboard listener.
|
| 987 | /// * [HardwareKeyboard.addHandler], which adds a hardware keyboard handler.
|
| 988 | ///
|
| 989 | /// ## Advanced usages: Manual assignment or patching
|
| 990 | ///
|
| 991 | /// If you are not using the focus system to manage focus, set this attribute
|
| 992 | /// to a [KeyMessageHandler] that returns true if the propagation on the
|
| 993 | /// platform should not be continued. If this field is null, key events will
|
| 994 | /// be assumed to not have been handled by Flutter, a result of "false".
|
| 995 | ///
|
| 996 | /// Even if you are using the focus system, you might also want to do more
|
| 997 | /// than the focus system allows. In these cases, you can _patch_
|
| 998 | /// [keyMessageHandler] by setting it to a callback that performs your tasks
|
| 999 | /// and calls the original callback in between (or not at all.)
|
| 1000 | ///
|
| 1001 | /// Patching [keyMessageHandler] can not be reverted. You should always assume
|
| 1002 | /// that another component might have patched it before you and after you.
|
| 1003 | /// This means that you might want to write your own global notification
|
| 1004 | /// manager, to which callbacks can be added and removed.
|
| 1005 | ///
|
| 1006 | /// You should not patch [keyMessageHandler] until the `FocusManager` has
|
| 1007 | /// assigned its callback. This is assured during any time within the widget
|
| 1008 | /// lifecycle (such as `initState`), or after calling
|
| 1009 | /// `WidgetManager.instance`.
|
| 1010 | ///
|
| 1011 | /// {@tool dartpad}
|
| 1012 | /// This example shows how to process key events that are not
|
| 1013 | /// handled by any focus handler (such as `Shortcuts`) by patching
|
| 1014 | /// [keyMessageHandler].
|
| 1015 | ///
|
| 1016 | /// The app prints out any key events that are not handled by the app body.
|
| 1017 | /// Try typing something in the first text field. These key presses are not
|
| 1018 | /// handled by `Shortcuts` and will be sent to the fallback handler and
|
| 1019 | /// printed out. Now try some text shortcuts, such as Ctrl+A. The KeyA press
|
| 1020 | /// is handled as a shortcut, and is not sent to the fallback handler and so
|
| 1021 | /// is not printed out.
|
| 1022 | ///
|
| 1023 | /// The key widget is `FallbackKeyEventRegistrar`, a necessity class to allow
|
| 1024 | /// reversible patching. `FallbackFocus` and `FallbackFocusNode` are also
|
| 1025 | /// useful to recognize the widget tree's structure. `FallbackDemo` is an
|
| 1026 | /// example of using them in an app.
|
| 1027 | ///
|
| 1028 | /// ** See code in examples/api/lib/widgets/hardware_keyboard/key_event_manager.0.dart **
|
| 1029 | /// {@end-tool}
|
| 1030 | ///
|
| 1031 | /// See also:
|
| 1032 | ///
|
| 1033 | /// * [HardwareKeyboard.addHandler], which accepts multiple global handlers to
|
| 1034 | /// process [KeyEvent]s
|
| 1035 | @Deprecated(
|
| 1036 | 'No longer supported. Once RawKeyEvent is removed, will no longer be needed. '
|
| 1037 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 1038 | )
|
| 1039 | KeyMessageHandler? keyMessageHandler;
|
| 1040 |
|
| 1041 | final HardwareKeyboard _hardwareKeyboard;
|
| 1042 | final RawKeyboard _rawKeyboard;
|
| 1043 |
|
| 1044 | // The [KeyDataTransitMode] of the current platform.
|
| 1045 | //
|
| 1046 | // The `_transitMode` is guaranteed to be non-null during key event callbacks.
|
| 1047 | //
|
| 1048 | // The `_transitMode` is null before the first event, after which it is inferred
|
| 1049 | // and will not change throughout the lifecycle of the application.
|
| 1050 | //
|
| 1051 | // The `_transitMode` can be reset to null in tests using [clearState].
|
| 1052 | KeyDataTransitMode? _transitMode;
|
| 1053 |
|
| 1054 | // The accumulated [KeyEvent]s since the last time the message is dispatched.
|
| 1055 | //
|
| 1056 | // If _transitMode is [KeyDataTransitMode.keyDataThenRawKeyData], then [KeyEvent]s
|
| 1057 | // are directly received from [handleKeyData]. If _transitMode is
|
| 1058 | // [KeyDataTransitMode.rawKeyData], then [KeyEvent]s are converted from the raw
|
| 1059 | // key data of [handleRawKeyMessage]. Either way, the accumulated key
|
| 1060 | // events are only dispatched along with the next [KeyMessage] when a
|
| 1061 | // dispatchable [RawKeyEvent] is available.
|
| 1062 | final List<KeyEvent> _keyEventsSinceLastMessage = <KeyEvent>[];
|
| 1063 |
|
| 1064 | // When a RawKeyDownEvent is skipped ([RawKeyEventData.shouldDispatchEvent]
|
| 1065 | // is false), its physical key will be recorded here, so that its up event
|
| 1066 | // can also be properly skipped.
|
| 1067 | final Set<PhysicalKeyboardKey> _skippedRawKeysPressed = <PhysicalKeyboardKey>{};
|
| 1068 |
|
| 1069 | /// Dispatch a key data to global and leaf listeners.
|
| 1070 | ///
|
| 1071 | /// This method is the handler to the global `onKeyData` API.
|
| 1072 | ///
|
| 1073 | /// This handler is deprecated, and will be removed. Use
|
| 1074 | /// [HardwareKeyboard.addHandler] instead.
|
| 1075 | @Deprecated(
|
| 1076 | 'No longer supported. Use HardwareKeyboard.instance.addHandler instead. '
|
| 1077 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 1078 | )
|
| 1079 | bool handleKeyData(ui.KeyData data) {
|
| 1080 | _transitMode ??= KeyDataTransitMode.keyDataThenRawKeyData;
|
| 1081 | switch (_transitMode!) {
|
| 1082 | case KeyDataTransitMode.rawKeyData:
|
| 1083 | assert(false, 'Should never encounter KeyData when transitMode is rawKeyData.' );
|
| 1084 | return false;
|
| 1085 | case KeyDataTransitMode.keyDataThenRawKeyData:
|
| 1086 | // Having 0 as the physical and logical ID indicates an empty key data
|
| 1087 | // (the only occasion either field can be 0,) transmitted to ensure
|
| 1088 | // that the transit mode is correctly inferred. These events should be
|
| 1089 | // ignored.
|
| 1090 | if (data.physical == 0 && data.logical == 0) {
|
| 1091 | return false;
|
| 1092 | }
|
| 1093 | assert(data.physical != 0 && data.logical != 0);
|
| 1094 | final KeyEvent event = _eventFromData(data);
|
| 1095 | if (data.synthesized && _keyEventsSinceLastMessage.isEmpty) {
|
| 1096 | // Dispatch the event instantly if both conditions are met:
|
| 1097 | //
|
| 1098 | // - The event is synthesized, therefore the result does not matter.
|
| 1099 | // - The current queue is empty, therefore the order does not matter.
|
| 1100 | //
|
| 1101 | // This allows solitary synthesized `KeyEvent`s to be dispatched,
|
| 1102 | // since they won't be followed by `RawKeyEvent`s.
|
| 1103 | _hardwareKeyboard.handleKeyEvent(event);
|
| 1104 | _dispatchKeyMessage(<KeyEvent>[event], null);
|
| 1105 | } else {
|
| 1106 | // Otherwise, postpone key event dispatching until the next raw
|
| 1107 | // event. Normal key presses always send 0 or more `KeyEvent`s first,
|
| 1108 | // then 1 `RawKeyEvent`.
|
| 1109 | _keyEventsSinceLastMessage.add(event);
|
| 1110 | }
|
| 1111 | return false;
|
| 1112 | }
|
| 1113 | }
|
| 1114 |
|
| 1115 | bool _dispatchKeyMessage(List<KeyEvent> keyEvents, RawKeyEvent? rawEvent) {
|
| 1116 | if (keyMessageHandler != null) {
|
| 1117 | final KeyMessage message = KeyMessage(keyEvents, rawEvent);
|
| 1118 | try {
|
| 1119 | return keyMessageHandler!(message);
|
| 1120 | } catch (exception, stack) {
|
| 1121 | InformationCollector? collector;
|
| 1122 | assert(() {
|
| 1123 | collector = () => <DiagnosticsNode>[
|
| 1124 | DiagnosticsProperty<KeyMessage>('KeyMessage' , message),
|
| 1125 | ];
|
| 1126 | return true;
|
| 1127 | }());
|
| 1128 | FlutterError.reportError(
|
| 1129 | FlutterErrorDetails(
|
| 1130 | exception: exception,
|
| 1131 | stack: stack,
|
| 1132 | library: 'services library' ,
|
| 1133 | context: ErrorDescription('while processing the key message handler' ),
|
| 1134 | informationCollector: collector,
|
| 1135 | ),
|
| 1136 | );
|
| 1137 | }
|
| 1138 | }
|
| 1139 | return false;
|
| 1140 | }
|
| 1141 |
|
| 1142 | /// Handles a raw key message.
|
| 1143 | ///
|
| 1144 | /// This method is the handler to [SystemChannels.keyEvent], processing the
|
| 1145 | /// JSON form of the native key message and returns the responds for the
|
| 1146 | /// channel.
|
| 1147 | ///
|
| 1148 | /// This handler is deprecated, and will be removed. Use
|
| 1149 | /// [HardwareKeyboard.addHandler] instead.
|
| 1150 | @Deprecated(
|
| 1151 | 'No longer supported. Use HardwareKeyboard.instance.addHandler instead. '
|
| 1152 | 'This feature was deprecated after v3.18.0-2.0.pre.' ,
|
| 1153 | )
|
| 1154 | Future<Map<String, dynamic>> handleRawKeyMessage(dynamic message) async {
|
| 1155 | if (_transitMode == null) {
|
| 1156 | _transitMode = KeyDataTransitMode.rawKeyData;
|
| 1157 | // Convert raw events using a listener so that conversion only occurs if
|
| 1158 | // the raw event should be dispatched.
|
| 1159 | _rawKeyboard.addListener(_convertRawEventAndStore);
|
| 1160 | }
|
| 1161 | final RawKeyEvent rawEvent = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
|
| 1162 |
|
| 1163 | bool shouldDispatch = true;
|
| 1164 | if (rawEvent is RawKeyDownEvent) {
|
| 1165 | if (!rawEvent.data.shouldDispatchEvent()) {
|
| 1166 | shouldDispatch = false;
|
| 1167 | _skippedRawKeysPressed.add(rawEvent.physicalKey);
|
| 1168 | } else {
|
| 1169 | _skippedRawKeysPressed.remove(rawEvent.physicalKey);
|
| 1170 | }
|
| 1171 | } else if (rawEvent is RawKeyUpEvent) {
|
| 1172 | if (_skippedRawKeysPressed.contains(rawEvent.physicalKey)) {
|
| 1173 | _skippedRawKeysPressed.remove(rawEvent.physicalKey);
|
| 1174 | shouldDispatch = false;
|
| 1175 | }
|
| 1176 | }
|
| 1177 |
|
| 1178 | bool handled = true;
|
| 1179 | if (shouldDispatch) {
|
| 1180 | // The following `handleRawKeyEvent` will call `_convertRawEventAndStore`
|
| 1181 | // unless the event is not dispatched.
|
| 1182 | handled = _rawKeyboard.handleRawKeyEvent(rawEvent);
|
| 1183 |
|
| 1184 | for (final KeyEvent event in _keyEventsSinceLastMessage) {
|
| 1185 | handled = _hardwareKeyboard.handleKeyEvent(event) || handled;
|
| 1186 | }
|
| 1187 | if (_transitMode == KeyDataTransitMode.rawKeyData) {
|
| 1188 | assert(
|
| 1189 | setEquals(_rawKeyboard.physicalKeysPressed, _hardwareKeyboard.physicalKeysPressed),
|
| 1190 | 'RawKeyboard reported ${_rawKeyboard.physicalKeysPressed}, '
|
| 1191 | 'while HardwareKeyboard reported ${_hardwareKeyboard.physicalKeysPressed}' ,
|
| 1192 | );
|
| 1193 | }
|
| 1194 |
|
| 1195 | handled = _dispatchKeyMessage(_keyEventsSinceLastMessage, rawEvent) || handled;
|
| 1196 | _keyEventsSinceLastMessage.clear();
|
| 1197 | }
|
| 1198 |
|
| 1199 | return <String, dynamic>{'handled' : handled};
|
| 1200 | }
|
| 1201 |
|
| 1202 | ui.KeyEventDeviceType _convertDeviceType(RawKeyEvent rawEvent) {
|
| 1203 | final RawKeyEventData data = rawEvent.data;
|
| 1204 | // Device type is only available from Android.
|
| 1205 | if (data is! RawKeyEventDataAndroid) {
|
| 1206 | return ui.KeyEventDeviceType.keyboard;
|
| 1207 | }
|
| 1208 |
|
| 1209 | switch (data.eventSource) {
|
| 1210 | // https://developer.android.com/reference/android/view/InputDevice#SOURCE_KEYBOARD
|
| 1211 | case 0x00000101:
|
| 1212 | return ui.KeyEventDeviceType.keyboard;
|
| 1213 | // https://developer.android.com/reference/android/view/InputDevice#SOURCE_DPAD
|
| 1214 | case 0x00000201:
|
| 1215 | return ui.KeyEventDeviceType.directionalPad;
|
| 1216 | // https://developer.android.com/reference/android/view/InputDevice#SOURCE_GAMEPAD
|
| 1217 | case 0x00000401:
|
| 1218 | return ui.KeyEventDeviceType.gamepad;
|
| 1219 | // https://developer.android.com/reference/android/view/InputDevice#SOURCE_JOYSTICK
|
| 1220 | case 0x01000010:
|
| 1221 | return ui.KeyEventDeviceType.joystick;
|
| 1222 | // https://developer.android.com/reference/android/view/InputDevice#SOURCE_HDMI
|
| 1223 | case 0x02000001:
|
| 1224 | return ui.KeyEventDeviceType.hdmi;
|
| 1225 | }
|
| 1226 | return ui.KeyEventDeviceType.keyboard;
|
| 1227 | }
|
| 1228 |
|
| 1229 | // Convert the raw event to key events, including synthesizing events for
|
| 1230 | // modifiers, and store the key events in `_keyEventsSinceLastMessage`.
|
| 1231 | //
|
| 1232 | // This is only called when `_transitMode` is `rawKeyEvent` and if the raw
|
| 1233 | // event should be dispatched.
|
| 1234 | void _convertRawEventAndStore(RawKeyEvent rawEvent) {
|
| 1235 | final PhysicalKeyboardKey physicalKey = rawEvent.physicalKey;
|
| 1236 | final LogicalKeyboardKey logicalKey = rawEvent.logicalKey;
|
| 1237 | final Set<PhysicalKeyboardKey> physicalKeysPressed = _hardwareKeyboard.physicalKeysPressed;
|
| 1238 | final List<KeyEvent> eventAfterwards = <KeyEvent>[];
|
| 1239 | final KeyEvent? mainEvent;
|
| 1240 | final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey);
|
| 1241 | final Duration timeStamp = ServicesBinding.instance.currentSystemFrameTimeStamp;
|
| 1242 | final String? character = rawEvent.character == '' ? null : rawEvent.character;
|
| 1243 | final ui.KeyEventDeviceType deviceType = _convertDeviceType(rawEvent);
|
| 1244 | if (rawEvent is RawKeyDownEvent) {
|
| 1245 | if (recordedLogicalMain == null) {
|
| 1246 | mainEvent = KeyDownEvent(
|
| 1247 | physicalKey: physicalKey,
|
| 1248 | logicalKey: logicalKey,
|
| 1249 | character: character,
|
| 1250 | timeStamp: timeStamp,
|
| 1251 | deviceType: deviceType,
|
| 1252 | );
|
| 1253 | physicalKeysPressed.add(physicalKey);
|
| 1254 | } else {
|
| 1255 | assert(physicalKeysPressed.contains(physicalKey));
|
| 1256 | mainEvent = KeyRepeatEvent(
|
| 1257 | physicalKey: physicalKey,
|
| 1258 | logicalKey: recordedLogicalMain,
|
| 1259 | character: character,
|
| 1260 | timeStamp: timeStamp,
|
| 1261 | deviceType: deviceType,
|
| 1262 | );
|
| 1263 | }
|
| 1264 | } else {
|
| 1265 | assert(
|
| 1266 | rawEvent is RawKeyUpEvent,
|
| 1267 | 'Unexpected subclass of RawKeyEvent: ${rawEvent.runtimeType}' ,
|
| 1268 | );
|
| 1269 | if (recordedLogicalMain == null) {
|
| 1270 | mainEvent = null;
|
| 1271 | } else {
|
| 1272 | mainEvent = KeyUpEvent(
|
| 1273 | logicalKey: recordedLogicalMain,
|
| 1274 | physicalKey: physicalKey,
|
| 1275 | timeStamp: timeStamp,
|
| 1276 | deviceType: deviceType,
|
| 1277 | );
|
| 1278 | physicalKeysPressed.remove(physicalKey);
|
| 1279 | }
|
| 1280 | }
|
| 1281 | for (final PhysicalKeyboardKey key in physicalKeysPressed.difference(
|
| 1282 | _rawKeyboard.physicalKeysPressed,
|
| 1283 | )) {
|
| 1284 | if (key == physicalKey) {
|
| 1285 | // Somehow, a down event is dispatched but the key is absent from
|
| 1286 | // keysPressed. Synthesize a up event for the key, but this event must
|
| 1287 | // be added after the main key down event.
|
| 1288 | eventAfterwards.add(
|
| 1289 | KeyUpEvent(
|
| 1290 | physicalKey: key,
|
| 1291 | logicalKey: logicalKey,
|
| 1292 | timeStamp: timeStamp,
|
| 1293 | synthesized: true,
|
| 1294 | deviceType: deviceType,
|
| 1295 | ),
|
| 1296 | );
|
| 1297 | } else {
|
| 1298 | _keyEventsSinceLastMessage.add(
|
| 1299 | KeyUpEvent(
|
| 1300 | physicalKey: key,
|
| 1301 | logicalKey: _hardwareKeyboard.lookUpLayout(key)!,
|
| 1302 | timeStamp: timeStamp,
|
| 1303 | synthesized: true,
|
| 1304 | deviceType: deviceType,
|
| 1305 | ),
|
| 1306 | );
|
| 1307 | }
|
| 1308 | }
|
| 1309 | for (final PhysicalKeyboardKey key in _rawKeyboard.physicalKeysPressed.difference(
|
| 1310 | physicalKeysPressed,
|
| 1311 | )) {
|
| 1312 | _keyEventsSinceLastMessage.add(
|
| 1313 | KeyDownEvent(
|
| 1314 | physicalKey: key,
|
| 1315 | logicalKey: _rawKeyboard.lookUpLayout(key)!,
|
| 1316 | timeStamp: timeStamp,
|
| 1317 | synthesized: true,
|
| 1318 | deviceType: deviceType,
|
| 1319 | ),
|
| 1320 | );
|
| 1321 | }
|
| 1322 | if (mainEvent != null) {
|
| 1323 | _keyEventsSinceLastMessage.add(mainEvent);
|
| 1324 | }
|
| 1325 | _keyEventsSinceLastMessage.addAll(eventAfterwards);
|
| 1326 | }
|
| 1327 |
|
| 1328 | /// Reset the inferred platform transit mode and related states.
|
| 1329 | ///
|
| 1330 | /// This method is only used in unit tests. In release mode, this is a no-op.
|
| 1331 | @visibleForTesting
|
| 1332 | void clearState() {
|
| 1333 | assert(() {
|
| 1334 | _transitMode = null;
|
| 1335 | _rawKeyboard.removeListener(_convertRawEventAndStore);
|
| 1336 | _keyEventsSinceLastMessage.clear();
|
| 1337 | return true;
|
| 1338 | }());
|
| 1339 | }
|
| 1340 |
|
| 1341 | static KeyEvent _eventFromData(ui.KeyData keyData) {
|
| 1342 | final PhysicalKeyboardKey physicalKey =
|
| 1343 | PhysicalKeyboardKey.findKeyByCode(keyData.physical) ??
|
| 1344 | PhysicalKeyboardKey(keyData.physical);
|
| 1345 | final LogicalKeyboardKey logicalKey =
|
| 1346 | LogicalKeyboardKey.findKeyByKeyId(keyData.logical) ?? LogicalKeyboardKey(keyData.logical);
|
| 1347 | final Duration timeStamp = keyData.timeStamp;
|
| 1348 | switch (keyData.type) {
|
| 1349 | case ui.KeyEventType.down:
|
| 1350 | return KeyDownEvent(
|
| 1351 | physicalKey: physicalKey,
|
| 1352 | logicalKey: logicalKey,
|
| 1353 | timeStamp: timeStamp,
|
| 1354 | character: keyData.character,
|
| 1355 | synthesized: keyData.synthesized,
|
| 1356 | deviceType: keyData.deviceType,
|
| 1357 | );
|
| 1358 | case ui.KeyEventType.up:
|
| 1359 | assert(keyData.character == null);
|
| 1360 | return KeyUpEvent(
|
| 1361 | physicalKey: physicalKey,
|
| 1362 | logicalKey: logicalKey,
|
| 1363 | timeStamp: timeStamp,
|
| 1364 | synthesized: keyData.synthesized,
|
| 1365 | deviceType: keyData.deviceType,
|
| 1366 | );
|
| 1367 | case ui.KeyEventType.repeat:
|
| 1368 | return KeyRepeatEvent(
|
| 1369 | physicalKey: physicalKey,
|
| 1370 | logicalKey: logicalKey,
|
| 1371 | timeStamp: timeStamp,
|
| 1372 | character: keyData.character,
|
| 1373 | deviceType: keyData.deviceType,
|
| 1374 | );
|
| 1375 | }
|
| 1376 | }
|
| 1377 | }
|
| 1378 |
|