Add a channel to query the engine keyboard state (#122885)
## Description
This PR adds a new channel to query the engine keyboard state.
See https://github.com/flutter/flutter/issues/87391#issuecomment-1228975571 for motivation.
## Related Issue
Framework side implementation for https://github.com/flutter/flutter/issues/87391.
Once approved the framework will try to query the initial keyboard state from the engine. PRs will be needed on the engine side to answer the framework query.
## Tests
Adds 1 test.
diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart
index 8a788d9..75e9969 100644
--- a/packages/flutter/lib/src/services/binding.dart
+++ b/packages/flutter/lib/src/services/binding.dart
@@ -68,8 +68,10 @@
void _initKeyboard() {
_keyboard = HardwareKeyboard();
_keyEventManager = KeyEventManager(_keyboard, RawKeyboard.instance);
- platformDispatcher.onKeyData = _keyEventManager.handleKeyData;
- SystemChannels.keyEvent.setMessageHandler(_keyEventManager.handleRawKeyMessage);
+ _keyboard.syncKeyboardState().then((_) {
+ platformDispatcher.onKeyData = _keyEventManager.handleKeyData;
+ SystemChannels.keyEvent.setMessageHandler(_keyEventManager.handleRawKeyMessage);
+ });
}
/// The default instance of [BinaryMessenger].
diff --git a/packages/flutter/lib/src/services/hardware_keyboard.dart b/packages/flutter/lib/src/services/hardware_keyboard.dart
index 17febb1..414b9c8 100644
--- a/packages/flutter/lib/src/services/hardware_keyboard.dart
+++ b/packages/flutter/lib/src/services/hardware_keyboard.dart
@@ -8,6 +8,7 @@
import 'binding.dart';
import 'raw_keyboard.dart';
+import 'system_channels.dart';
export 'dart:ui' show KeyData;
@@ -494,6 +495,20 @@
}
}
+ /// Query the engine and update _pressedKeys accordingly to the engine answer.
+ Future<void> syncKeyboardState() async {
+ final Map<int, int>? keyboardState = await SystemChannels.keyboard.invokeMapMethod<int, int>(
+ 'getKeyboardState',
+ );
+ if (keyboardState != null) {
+ for (final int key in keyboardState.keys) {
+ final PhysicalKeyboardKey physicalKey = PhysicalKeyboardKey(key);
+ final LogicalKeyboardKey logicalKey = LogicalKeyboardKey(keyboardState[key]!);
+ _pressedKeys[physicalKey] = logicalKey;
+ }
+ }
+ }
+
bool _dispatchKeyEvent(KeyEvent event) {
// This dispatching could have used the same algorithm as [ChangeNotifier],
// but since 1) it shouldn't be necessary to support reentrantly
diff --git a/packages/flutter/lib/src/services/system_channels.dart b/packages/flutter/lib/src/services/system_channels.dart
index de9ba71..fd58eab 100644
--- a/packages/flutter/lib/src/services/system_channels.dart
+++ b/packages/flutter/lib/src/services/system_channels.dart
@@ -488,4 +488,22 @@
'flutter/contextmenu',
JSONMethodCodec(),
);
+
+ /// A [MethodChannel] for retrieving keyboard pressed keys from the engine.
+ ///
+ /// The following outgoing methods are defined for this channel (invoked using
+ /// [OptionalMethodChannel.invokeMethod]):
+ ///
+ /// * `getKeyboardState`: Obtains keyboard pressed keys from the engine.
+ /// The keyboard state is sent as a `Map<int, int>?` where each entry
+ /// represents a pressed keyboard key. The entry key is the physical
+ /// key ID and the entry value is the logical key ID.
+ ///
+ /// See also:
+ ///
+ /// * [HardwareKeyboard.syncKeyboardState], which uses this channel to synchronize
+ /// the `HardwareKeyboard` pressed state.
+ static const MethodChannel keyboard = OptionalMethodChannel(
+ 'flutter/keyboard',
+ );
}
diff --git a/packages/flutter/test/services/binding_test.dart b/packages/flutter/test/services/binding_test.dart
index b086f1f..1aff214 100644
--- a/packages/flutter/test/services/binding_test.dart
+++ b/packages/flutter/test/services/binding_test.dart
@@ -46,7 +46,13 @@
@override
TestDefaultBinaryMessenger createBinaryMessenger() {
- return TestDefaultBinaryMessenger(super.createBinaryMessenger());
+ Future<ByteData?> keyboardHandler(ByteData? message) async {
+ return const StandardMethodCodec().encodeSuccessEnvelope(<int, int>{1:1});
+ }
+ return TestDefaultBinaryMessenger(
+ super.createBinaryMessenger(),
+ outboundHandlers: <String, MessageHandler>{'flutter/keyboard': keyboardHandler},
+ );
}
}
@@ -145,4 +151,14 @@
expect(result, isNotNull);
expect(result!['response'], equals('exit'));
});
+
+ test('initInstances synchronizes keyboard state', () async {
+ final Set<PhysicalKeyboardKey> physicalKeys = HardwareKeyboard.instance.physicalKeysPressed;
+ final Set<LogicalKeyboardKey> logicalKeys = HardwareKeyboard.instance.logicalKeysPressed;
+
+ expect(physicalKeys.length, 1);
+ expect(logicalKeys.length, 1);
+ expect(physicalKeys.first, const PhysicalKeyboardKey(1));
+ expect(logicalKeys.first, const LogicalKeyboardKey(1));
+ });
}
diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart
index 04f836a..a85e639 100644
--- a/packages/flutter_test/lib/src/binding.dart
+++ b/packages/flutter_test/lib/src/binding.dart
@@ -107,7 +107,13 @@
@override
TestDefaultBinaryMessenger createBinaryMessenger() {
- return TestDefaultBinaryMessenger(super.createBinaryMessenger());
+ Future<ByteData?> keyboardHandler(ByteData? message) async {
+ return const StandardMethodCodec().encodeSuccessEnvelope(<int, int>{});
+ }
+ return TestDefaultBinaryMessenger(
+ super.createBinaryMessenger(),
+ outboundHandlers: <String, MessageHandler>{'flutter/keyboard': keyboardHandler},
+ );
}
}
diff --git a/packages/flutter_test/lib/src/test_default_binary_messenger.dart b/packages/flutter_test/lib/src/test_default_binary_messenger.dart
index 3d84112..f5c4e2f 100644
--- a/packages/flutter_test/lib/src/test_default_binary_messenger.dart
+++ b/packages/flutter_test/lib/src/test_default_binary_messenger.dart
@@ -49,7 +49,12 @@
/// Creates a [TestDefaultBinaryMessenger] instance.
///
/// The [delegate] instance must not be null.
- TestDefaultBinaryMessenger(this.delegate);
+ TestDefaultBinaryMessenger(
+ this.delegate, {
+ Map<String, MessageHandler> outboundHandlers = const <String, MessageHandler>{},
+ }) {
+ _outboundHandlers.addAll(outboundHandlers);
+ }
/// The delegate [BinaryMessenger].
final BinaryMessenger delegate;