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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com