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( |
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. |
71 | enum 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 |
125 | abstract 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. |
261 | class 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. |
282 | class 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. |
306 | class 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. |
319 | typedef 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. |
413 | class 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 | ) |
714 | enum 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 |
803 | class 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 | ) |
863 | typedef 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 | ) |
919 | class 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 |
Definitions
- _keyboardDebug
- KeyboardLockMode
- _
- findLockByLogicalKey
- KeyEvent
- KeyEvent
- debugFillProperties
- KeyDownEvent
- KeyDownEvent
- KeyUpEvent
- KeyUpEvent
- KeyRepeatEvent
- KeyRepeatEvent
- HardwareKeyboard
- instance
- physicalKeysPressed
- logicalKeysPressed
- lookUpLayout
- lockModesEnabled
- isLogicalKeyPressed
- isPhysicalKeyPressed
- isControlPressed
- isShiftPressed
- isAltPressed
- isMetaPressed
- _assertEventIsRegular
- addHandler
- removeHandler
- syncKeyboardState
- _dispatchKeyEvent
- _debugPressedKeysDetails
- handleKeyEvent
- clearState
- KeyDataTransitMode
- KeyMessage
- KeyMessage
- toString
- KeyEventManager
- KeyEventManager
- handleKeyData
- _dispatchKeyMessage
- handleRawKeyMessage
- _convertDeviceType
- _convertRawEventAndStore
- clearState
Learn more about Flutter for embedded and desktop on industrialflutter.com