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