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';
8library;
9
10import 'dart:ui' as ui;
11
12import 'package:flutter/foundation.dart';
13
14import 'binding.dart';
15import 'debug.dart';
16import 'raw_keyboard.dart';
17import 'raw_keyboard_android.dart';
18import 'system_channels.dart';
19
20export 'dart:ui' show KeyData;
21
22export 'package:flutter/foundation.dart' show DiagnosticPropertiesBuilder;
23
24export '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.
36bool _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.
68enum 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
123abstract 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.
259class 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.
280class 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.
304class 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.
317typedef 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.
411class 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)
729enum 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
818class 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)
878typedef 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)
934class 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