Unverified Commit f796e62c authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Prepare `ShortcutActivator` and `ShortcutManager` to migrate to `KeyEvent`...

Prepare `ShortcutActivator` and `ShortcutManager` to migrate to `KeyEvent` from `RawKeyEvent`. (#136854)
parent d8ffc739
...@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart'; ...@@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
import 'binding.dart'; import 'binding.dart';
import 'debug.dart'; import 'debug.dart';
import 'raw_keyboard.dart'; import 'raw_keyboard.dart';
import 'raw_keyboard_android.dart';
import 'system_channels.dart'; import 'system_channels.dart';
export 'dart:ui' show KeyData; export 'dart:ui' show KeyData;
...@@ -124,6 +125,7 @@ abstract class KeyEvent with Diagnosticable { ...@@ -124,6 +125,7 @@ abstract class KeyEvent with Diagnosticable {
required this.logicalKey, required this.logicalKey,
this.character, this.character,
required this.timeStamp, required this.timeStamp,
this.deviceType = ui.KeyEventDeviceType.keyboard,
this.synthesized = false, this.synthesized = false,
}); });
...@@ -206,6 +208,13 @@ abstract class KeyEvent with Diagnosticable { ...@@ -206,6 +208,13 @@ abstract class KeyEvent with Diagnosticable {
/// All events share the same timeStamp origin. /// All events share the same timeStamp origin.
final Duration timeStamp; final Duration timeStamp;
/// The source device type for the key event.
///
/// Not all platforms supply an accurate type.
///
/// Defaults to [ui.KeyEventDeviceType.keyboard].
final ui.KeyEventDeviceType deviceType;
/// Whether this event is synthesized by Flutter to synchronize key states. /// Whether this event is synthesized by Flutter to synchronize key states.
/// ///
/// An non-[synthesized] event is converted from a native event, and a native /// An non-[synthesized] event is converted from a native event, and a native
...@@ -253,6 +262,7 @@ class KeyDownEvent extends KeyEvent { ...@@ -253,6 +262,7 @@ class KeyDownEvent extends KeyEvent {
super.character, super.character,
required super.timeStamp, required super.timeStamp,
super.synthesized, super.synthesized,
super.deviceType,
}); });
} }
...@@ -272,6 +282,7 @@ class KeyUpEvent extends KeyEvent { ...@@ -272,6 +282,7 @@ class KeyUpEvent extends KeyEvent {
required super.logicalKey, required super.logicalKey,
required super.timeStamp, required super.timeStamp,
super.synthesized, super.synthesized,
super.deviceType,
}); });
} }
...@@ -295,6 +306,7 @@ class KeyRepeatEvent extends KeyEvent { ...@@ -295,6 +306,7 @@ class KeyRepeatEvent extends KeyEvent {
required super.logicalKey, required super.logicalKey,
super.character, super.character,
required super.timeStamp, required super.timeStamp,
super.deviceType,
}); });
} }
...@@ -719,20 +731,20 @@ enum KeyDataTransitMode { ...@@ -719,20 +731,20 @@ enum KeyDataTransitMode {
/// to both [KeyMessage.events] and [KeyMessage.rawEvent]. /// to both [KeyMessage.events] and [KeyMessage.rawEvent].
rawKeyData, rawKeyData,
/// Key event information is delivered as converted key data, followed /// Key event information is delivered as converted key data, followed by raw
/// by raw key data. /// key data.
/// ///
/// Key data ([ui.KeyData]) is a standardized event stream converted from /// Key data ([ui.KeyData]) is a standardized event stream converted from
/// platform's native key event information, sent through the embedder /// platform's native key event information, sent through the embedder API.
/// API. Its event model is described in [HardwareKeyboard]. /// Its event model is described in [HardwareKeyboard].
/// ///
/// Raw key data is platform's native key event information sent in JSON /// Raw key data is platform's native key event information sent in JSON
/// through a method channel. It is interpreted by subclasses of /// through a method channel. It is interpreted by subclasses of
/// [RawKeyEventData]. /// [RawKeyEventData].
/// ///
/// If the current transit mode is [rawKeyData], the key data is converted to /// If the current transit mode is [keyDataThenRawKeyData], then the
/// [KeyMessage.events], and the raw key data is converted to /// [KeyEventManager] will use the [ui.KeyData] for [KeyMessage.events], and
/// [KeyMessage.rawEvent]. /// the raw data for [KeyMessage.rawEvent].
keyDataThenRawKeyData, keyDataThenRawKeyData,
} }
...@@ -1112,6 +1124,33 @@ class KeyEventManager { ...@@ -1112,6 +1124,33 @@ class KeyEventManager {
return <String, dynamic>{ 'handled': handled }; return <String, dynamic>{ 'handled': handled };
} }
ui.KeyEventDeviceType _convertDeviceType(RawKeyEvent rawEvent) {
final RawKeyEventData data = rawEvent.data;
// Device type is only available from Android.
if (data is! RawKeyEventDataAndroid) {
return ui.KeyEventDeviceType.keyboard;
}
switch (data.eventSource) {
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_KEYBOARD
case 0x00000101:
return ui.KeyEventDeviceType.keyboard;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_DPAD
case 0x00000201:
return ui.KeyEventDeviceType.directionalPad;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_GAMEPAD
case 0x00000401:
return ui.KeyEventDeviceType.gamepad;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_JOYSTICK
case 0x01000010:
return ui.KeyEventDeviceType.joystick;
// https://developer.android.com/reference/android/view/InputDevice#SOURCE_HDMI
case 0x02000001:
return ui.KeyEventDeviceType.hdmi;
}
return ui.KeyEventDeviceType.keyboard;
}
// Convert the raw event to key events, including synthesizing events for // Convert the raw event to key events, including synthesizing events for
// modifiers, and store the key events in `_keyEventsSinceLastMessage`. // modifiers, and store the key events in `_keyEventsSinceLastMessage`.
// //
...@@ -1126,6 +1165,7 @@ class KeyEventManager { ...@@ -1126,6 +1165,7 @@ class KeyEventManager {
final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey); final LogicalKeyboardKey? recordedLogicalMain = _hardwareKeyboard.lookUpLayout(physicalKey);
final Duration timeStamp = ServicesBinding.instance.currentSystemFrameTimeStamp; final Duration timeStamp = ServicesBinding.instance.currentSystemFrameTimeStamp;
final String? character = rawEvent.character == '' ? null : rawEvent.character; final String? character = rawEvent.character == '' ? null : rawEvent.character;
final ui.KeyEventDeviceType deviceType = _convertDeviceType(rawEvent);
if (rawEvent is RawKeyDownEvent) { if (rawEvent is RawKeyDownEvent) {
if (recordedLogicalMain == null) { if (recordedLogicalMain == null) {
mainEvent = KeyDownEvent( mainEvent = KeyDownEvent(
...@@ -1133,6 +1173,7 @@ class KeyEventManager { ...@@ -1133,6 +1173,7 @@ class KeyEventManager {
logicalKey: logicalKey, logicalKey: logicalKey,
character: character, character: character,
timeStamp: timeStamp, timeStamp: timeStamp,
deviceType: deviceType,
); );
physicalKeysPressed.add(physicalKey); physicalKeysPressed.add(physicalKey);
} else { } else {
...@@ -1142,6 +1183,7 @@ class KeyEventManager { ...@@ -1142,6 +1183,7 @@ class KeyEventManager {
logicalKey: recordedLogicalMain, logicalKey: recordedLogicalMain,
character: character, character: character,
timeStamp: timeStamp, timeStamp: timeStamp,
deviceType: deviceType,
); );
} }
} else { } else {
...@@ -1153,6 +1195,7 @@ class KeyEventManager { ...@@ -1153,6 +1195,7 @@ class KeyEventManager {
logicalKey: recordedLogicalMain, logicalKey: recordedLogicalMain,
physicalKey: physicalKey, physicalKey: physicalKey,
timeStamp: timeStamp, timeStamp: timeStamp,
deviceType: deviceType,
); );
physicalKeysPressed.remove(physicalKey); physicalKeysPressed.remove(physicalKey);
} }
...@@ -1167,6 +1210,7 @@ class KeyEventManager { ...@@ -1167,6 +1210,7 @@ class KeyEventManager {
logicalKey: logicalKey, logicalKey: logicalKey,
timeStamp: timeStamp, timeStamp: timeStamp,
synthesized: true, synthesized: true,
deviceType: deviceType,
)); ));
} else { } else {
_keyEventsSinceLastMessage.add(KeyUpEvent( _keyEventsSinceLastMessage.add(KeyUpEvent(
...@@ -1174,6 +1218,7 @@ class KeyEventManager { ...@@ -1174,6 +1218,7 @@ class KeyEventManager {
logicalKey: _hardwareKeyboard.lookUpLayout(key)!, logicalKey: _hardwareKeyboard.lookUpLayout(key)!,
timeStamp: timeStamp, timeStamp: timeStamp,
synthesized: true, synthesized: true,
deviceType: deviceType,
)); ));
} }
} }
...@@ -1183,6 +1228,7 @@ class KeyEventManager { ...@@ -1183,6 +1228,7 @@ class KeyEventManager {
logicalKey: _rawKeyboard.lookUpLayout(key)!, logicalKey: _rawKeyboard.lookUpLayout(key)!,
timeStamp: timeStamp, timeStamp: timeStamp,
synthesized: true, synthesized: true,
deviceType: deviceType,
)); ));
} }
if (mainEvent != null) { if (mainEvent != null) {
...@@ -1220,6 +1266,7 @@ class KeyEventManager { ...@@ -1220,6 +1266,7 @@ class KeyEventManager {
timeStamp: timeStamp, timeStamp: timeStamp,
character: keyData.character, character: keyData.character,
synthesized: keyData.synthesized, synthesized: keyData.synthesized,
deviceType: keyData.deviceType,
); );
case ui.KeyEventType.up: case ui.KeyEventType.up:
assert(keyData.character == null); assert(keyData.character == null);
...@@ -1228,6 +1275,7 @@ class KeyEventManager { ...@@ -1228,6 +1275,7 @@ class KeyEventManager {
logicalKey: logicalKey, logicalKey: logicalKey,
timeStamp: timeStamp, timeStamp: timeStamp,
synthesized: keyData.synthesized, synthesized: keyData.synthesized,
deviceType: keyData.deviceType,
); );
case ui.KeyEventType.repeat: case ui.KeyEventType.repeat:
return KeyRepeatEvent( return KeyRepeatEvent(
...@@ -1235,6 +1283,7 @@ class KeyEventManager { ...@@ -1235,6 +1283,7 @@ class KeyEventManager {
logicalKey: logicalKey, logicalKey: logicalKey,
timeStamp: timeStamp, timeStamp: timeStamp,
character: keyData.character, character: keyData.character,
deviceType: keyData.deviceType,
); );
} }
} }
......
...@@ -213,11 +213,11 @@ void main() { ...@@ -213,11 +213,11 @@ void main() {
testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async { testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async {
// Collect some key events to use for testing. // Collect some key events to use for testing.
final List<RawKeyEvent> events = <RawKeyEvent>[]; final List<KeyEvent> events = <KeyEvent>[];
await tester.pumpWidget( await tester.pumpWidget(
Focus( Focus(
autofocus: true, autofocus: true,
onKey: (FocusNode node, RawKeyEvent event) { onKeyEvent: (FocusNode node, KeyEvent event) {
events.add(event); events.add(event);
return KeyEventResult.ignored; return KeyEventResult.ignored;
}, },
...@@ -229,10 +229,13 @@ void main() { ...@@ -229,10 +229,13 @@ void main() {
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(set, events[0]), isTrue); expect(ShortcutActivator.isActivatedBy(set, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(set, events.last), isTrue);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(set, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
expect(ShortcutActivator.isActivatedBy(set, events[0]), isFalse); expect(ShortcutActivator.isActivatedBy(set, events.last), isFalse);
}); });
test('LogicalKeySet diagnostics work.', () { test('LogicalKeySet diagnostics work.', () {
...@@ -457,11 +460,11 @@ void main() { ...@@ -457,11 +460,11 @@ void main() {
testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async { testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async {
// Collect some key events to use for testing. // Collect some key events to use for testing.
final List<RawKeyEvent> events = <RawKeyEvent>[]; final List<KeyEvent> events = <KeyEvent>[];
await tester.pumpWidget( await tester.pumpWidget(
Focus( Focus(
autofocus: true, autofocus: true,
onKey: (FocusNode node, RawKeyEvent event) { onKeyEvent: (FocusNode node, KeyEvent event) {
events.add(event); events.add(event);
return KeyEventResult.ignored; return KeyEventResult.ignored;
}, },
...@@ -473,10 +476,25 @@ void main() { ...@@ -473,10 +476,25 @@ void main() {
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isTrue);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(singleActivator, events[1]), isTrue); expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
expect(ShortcutActivator.isActivatedBy(singleActivator, events[1]), isFalse); expect(ShortcutActivator.isActivatedBy(singleActivator, events.last), isFalse);
const SingleActivator noRepeatSingleActivator = SingleActivator(LogicalKeyboardKey.keyA, control: true, includeRepeats: false);
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft);
expect(ShortcutActivator.isActivatedBy(noRepeatSingleActivator, events.last), isFalse);
}); });
group('diagnostics.', () { group('diagnostics.', () {
...@@ -1241,11 +1259,11 @@ void main() { ...@@ -1241,11 +1259,11 @@ void main() {
testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async { testWidgetsWithLeakTracking('isActivatedBy works as expected', (WidgetTester tester) async {
// Collect some key events to use for testing. // Collect some key events to use for testing.
final List<RawKeyEvent> events = <RawKeyEvent>[]; final List<KeyEvent> events = <KeyEvent>[];
await tester.pumpWidget( await tester.pumpWidget(
Focus( Focus(
autofocus: true, autofocus: true,
onKey: (FocusNode node, RawKeyEvent event) { onKeyEvent: (FocusNode node, KeyEvent event) {
events.add(event); events.add(event);
return KeyEventResult.ignored; return KeyEventResult.ignored;
}, },
...@@ -1256,8 +1274,20 @@ void main() { ...@@ -1256,8 +1274,20 @@ void main() {
const CharacterActivator characterActivator = CharacterActivator('a'); const CharacterActivator characterActivator = CharacterActivator('a');
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isTrue);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events.last), isFalse);
const CharacterActivator noRepeatCharacterActivator = CharacterActivator('a', includeRepeats: false);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isTrue);
await tester.sendKeyRepeatEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isFalse);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA);
expect(ShortcutActivator.isActivatedBy(characterActivator, events[0]), isTrue); expect(ShortcutActivator.isActivatedBy(noRepeatCharacterActivator, events.last), isFalse);
}); });
group('diagnostics.', () { group('diagnostics.', () {
...@@ -1936,7 +1966,7 @@ class TestAction extends CallbackAction<Intent> { ...@@ -1936,7 +1966,7 @@ class TestAction extends CallbackAction<Intent> {
/// An activator that accepts down events that has [key] as the logical key. /// An activator that accepts down events that has [key] as the logical key.
/// ///
/// This class is used only to tests. It is intentionally designed poorly by /// This class is used only to tests. It is intentionally designed poorly by
/// returning null in [triggers], and checks [key] in [accepts]. /// returning null in [triggers], and checks [key] in [acceptsEvent].
class DumbLogicalActivator extends ShortcutActivator { class DumbLogicalActivator extends ShortcutActivator {
const DumbLogicalActivator(this.key); const DumbLogicalActivator(this.key);
...@@ -1946,8 +1976,8 @@ class DumbLogicalActivator extends ShortcutActivator { ...@@ -1946,8 +1976,8 @@ class DumbLogicalActivator extends ShortcutActivator {
Iterable<LogicalKeyboardKey>? get triggers => null; Iterable<LogicalKeyboardKey>? get triggers => null;
@override @override
bool accepts(RawKeyEvent event, RawKeyboard state) { bool accepts(KeyEvent event, HardwareKeyboard state) {
return event is RawKeyDownEvent return (event is KeyDownEvent || event is KeyRepeatEvent)
&& event.logicalKey == key; && event.logicalKey == key;
} }
...@@ -1980,8 +2010,8 @@ class TestShortcutManager extends ShortcutManager { ...@@ -1980,8 +2010,8 @@ class TestShortcutManager extends ShortcutManager {
List<LogicalKeyboardKey> keys; List<LogicalKeyboardKey> keys;
@override @override
KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) { KeyEventResult handleKeypress(BuildContext context, KeyEvent event) {
if (event is RawKeyDownEvent) { if (event is KeyDownEvent || event is KeyRepeatEvent) {
keys.add(event.logicalKey); keys.add(event.logicalKey);
} }
return super.handleKeypress(context, event); return super.handleKeypress(context, event);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment