mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
RawKeyboardWindows: Filter out IME events (#77039)
This commit is contained in:
parent
2e8b5c7e69
commit
77925192aa
|
@ -226,6 +226,24 @@ abstract class RawKeyEventData {
|
|||
/// interacting with an input method editor (IME).
|
||||
/// {@endtemplate}
|
||||
String get keyLabel;
|
||||
|
||||
/// Whether a key down event, and likewise its accompanying key up event,
|
||||
/// should be disapatched.
|
||||
///
|
||||
/// Certain events on some platforms should not be dispatched to listeners
|
||||
/// according to Flutter's event model. For example, on macOS, Fn keys are
|
||||
/// skipped to be consistant with other platform. On Win32, events dispatched
|
||||
/// for IME (`VK_PROCESSKEY`) are also skipped.
|
||||
///
|
||||
/// This method will be called upon every down events. By default, this method
|
||||
/// always return true. Subclasses should override this method to define the
|
||||
/// filtering rule for the platform. If this method returns false for an event
|
||||
/// message, the event will not be dispatched to listeners, but respond with
|
||||
/// "handled: true" immediately. Moreover, the following up event with the
|
||||
/// same physical key will also be skipped.
|
||||
bool shouldDispatchEvent() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the interface for raw key events.
|
||||
|
@ -614,23 +632,27 @@ class RawKeyboard {
|
|||
|
||||
Future<dynamic> _handleKeyEvent(dynamic message) async {
|
||||
final RawKeyEvent event = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
|
||||
if (event.data is RawKeyEventDataMacOs && event.logicalKey == LogicalKeyboardKey.fn) {
|
||||
// On macOS laptop keyboards, the fn key is used to generate home/end and
|
||||
// f1-f12, but it ALSO generates a separate down/up event for the fn key
|
||||
// itself. Other platforms hide the fn key, and just produce the key that
|
||||
// it is combined with, so to keep it possible to write cross platform
|
||||
// code that looks at which keys are pressed, the fn key is ignored on
|
||||
// macOS.
|
||||
return;
|
||||
}
|
||||
bool shouldDispatch = true;
|
||||
if (event is RawKeyDownEvent) {
|
||||
_keysPressed[event.physicalKey] = event.logicalKey;
|
||||
if (event.data.shouldDispatchEvent()) {
|
||||
_keysPressed[event.physicalKey] = event.logicalKey;
|
||||
} else {
|
||||
shouldDispatch = false;
|
||||
_hiddenKeysPressed.add(event.physicalKey);
|
||||
}
|
||||
} else if (event is RawKeyUpEvent) {
|
||||
if (!_hiddenKeysPressed.contains(event.physicalKey)) {
|
||||
// Use the physical key in the key up event to find the physical key from
|
||||
// the corresponding key down event and remove it, even if the logical
|
||||
// keys don't match.
|
||||
_keysPressed.remove(event.physicalKey);
|
||||
} else {
|
||||
_hiddenKeysPressed.remove(event.physicalKey);
|
||||
shouldDispatch = false;
|
||||
}
|
||||
}
|
||||
if (event is RawKeyUpEvent) {
|
||||
// Use the physical key in the key up event to find the physical key from
|
||||
// the corresponding key down event and remove it, even if the logical
|
||||
// keys don't match.
|
||||
_keysPressed.remove(event.physicalKey);
|
||||
if (!shouldDispatch) {
|
||||
return <String, dynamic>{ 'handled': true };
|
||||
}
|
||||
// Make sure that the modifiers reflect reality, in case a modifier key was
|
||||
// pressed/released while the app didn't have focus.
|
||||
|
@ -742,6 +764,7 @@ class RawKeyboard {
|
|||
}
|
||||
|
||||
final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _keysPressed = <PhysicalKeyboardKey, LogicalKeyboardKey>{};
|
||||
final Set<PhysicalKeyboardKey> _hiddenKeysPressed = <PhysicalKeyboardKey>{};
|
||||
|
||||
/// Returns the set of keys currently pressed.
|
||||
Set<LogicalKeyboardKey> get keysPressed => _keysPressed.values.toSet();
|
||||
|
|
|
@ -221,6 +221,17 @@ class RawKeyEventDataMacOs extends RawKeyEventData {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldDispatchEvent() {
|
||||
// On macOS laptop keyboards, the fn key is used to generate home/end and
|
||||
// f1-f12, but it ALSO generates a separate down/up event for the fn key
|
||||
// itself. Other platforms hide the fn key, and just produce the key that
|
||||
// it is combined with, so to keep it possible to write cross platform
|
||||
// code that looks at which keys are pressed, the fn key is ignored on
|
||||
// macOS.
|
||||
return logicalKey != LogicalKeyboardKey.fn;
|
||||
}
|
||||
|
||||
/// Returns true if the given label represents an unprintable key.
|
||||
///
|
||||
/// Examples of unprintable keys are "NSUpArrowFunctionKey = 0xF700"
|
||||
|
|
|
@ -9,6 +9,11 @@ import 'keyboard_key.dart';
|
|||
import 'keyboard_maps.dart';
|
||||
import 'raw_keyboard.dart';
|
||||
|
||||
// Virtual key VK_PROCESSKEY in Win32 API.
|
||||
//
|
||||
// Key down events related to IME operations use this as keyCode.
|
||||
const int _vkProcessKey = 0xe5;
|
||||
|
||||
/// Platform-specific key event data for Windows.
|
||||
///
|
||||
/// This object contains information about key events obtained from Windows's
|
||||
|
@ -194,6 +199,15 @@ class RawKeyEventDataWindows extends RawKeyEventData {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldDispatchEvent() {
|
||||
// In Win32 API, down events related to IME operations use VK_PROCESSKEY as
|
||||
// keyCode. This event, as well as the following key up event (which uses a
|
||||
// normal keyCode), should be skipped, because the effect of IME operations
|
||||
// will be handled by the text input API.
|
||||
return keyCode != _vkProcessKey;
|
||||
}
|
||||
|
||||
// These are not the values defined by the Windows header for each modifier. Since they
|
||||
// can't be packaged into a single int, we are re-defining them here to reduce the size
|
||||
// of the message from the embedder. Embedders should map these values to the native key codes.
|
||||
|
|
|
@ -1471,6 +1471,37 @@ void main() {
|
|||
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
|
||||
expect(data.logicalKey.keyLabel, isEmpty);
|
||||
});
|
||||
testWidgets('Win32 VK_PROCESSKEY events are skipped', (WidgetTester tester) async {
|
||||
const String platform = 'windows';
|
||||
bool lastHandled = true;
|
||||
final List<RawKeyEvent> events = <RawKeyEvent>[];
|
||||
// Simulate raw events because VK_PROCESSKEY does not exist in the key mapping.
|
||||
Future<void> simulateKeyEventMessage(String type, int keyCode, int scanCode) {
|
||||
return ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
|
||||
SystemChannels.keyEvent.name,
|
||||
SystemChannels.keyEvent.codec.encodeMessage(<String, dynamic>{
|
||||
'type': type,
|
||||
'keymap': platform,
|
||||
'keyCode': keyCode,
|
||||
'scanCode': scanCode,
|
||||
'modifiers': 0,
|
||||
}),
|
||||
(ByteData? data) {
|
||||
final Map<String, dynamic> decoded = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
|
||||
lastHandled = decoded['handled'] as bool;
|
||||
}
|
||||
);
|
||||
}
|
||||
RawKeyboard.instance.addListener(events.add);
|
||||
await simulateKeyEventMessage('keydown', 229, 30);
|
||||
expect(events, isEmpty);
|
||||
expect(lastHandled, true);
|
||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||
await simulateKeyEventMessage('keyup', 65, 30);
|
||||
expect(events, isEmpty);
|
||||
expect(lastHandled, true);
|
||||
expect(RawKeyboard.instance.keysPressed, isEmpty);
|
||||
});
|
||||
}, skip: isBrowser); // This is a Windows-specific group.
|
||||
|
||||
group('RawKeyEventDataLinux-GFLW', () {
|
||||
|
|
Loading…
Reference in a new issue