RawKeyboardWindows: Filter out IME events (#77039)

This commit is contained in:
Tong Mu 2021-03-02 13:54:04 -08:00 committed by GitHub
parent 2e8b5c7e69
commit 77925192aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 15 deletions

View file

@ -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();

View file

@ -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"

View file

@ -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.

View file

@ -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', () {