Skip to content

Commit baac9a8

Browse files
authored
[Keyboard, Windows] Fix that IME events are still dispatched to FocusNode.onKey (#104244)
1 parent a79233c commit baac9a8

File tree

3 files changed

+65
-34
lines changed

3 files changed

+65
-34
lines changed

packages/flutter/lib/src/services/hardware_keyboard.dart

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,11 @@ class KeyEventManager {
766766
// dispatchable [RawKeyEvent] is available.
767767
final List<KeyEvent> _keyEventsSinceLastMessage = <KeyEvent>[];
768768

769+
// When a RawKeyDownEvent is skipped ([RawKeyEventData.shouldDispatchEvent]
770+
// is false), its physical key will be recorded here, so that its up event
771+
// can also be properly skipped.
772+
final Set<PhysicalKeyboardKey> _skippedRawKeysPressed = <PhysicalKeyboardKey>{};
773+
769774
/// Dispatch a key data to global and leaf listeners.
770775
///
771776
/// This method is the handler to the global `onKeyData` API.
@@ -843,21 +848,40 @@ class KeyEventManager {
843848
_rawKeyboard.addListener(_convertRawEventAndStore);
844849
}
845850
final RawKeyEvent rawEvent = RawKeyEvent.fromMessage(message as Map<String, dynamic>);
846-
// The following `handleRawKeyEvent` will call `_convertRawEventAndStore`
847-
// unless the event is not dispatched.
848-
bool handled = _rawKeyboard.handleRawKeyEvent(rawEvent);
849851

850-
for (final KeyEvent event in _keyEventsSinceLastMessage) {
851-
handled = _hardwareKeyboard.handleKeyEvent(event) || handled;
852-
}
853-
if (_transitMode == KeyDataTransitMode.rawKeyData) {
854-
assert(setEquals(_rawKeyboard.physicalKeysPressed, _hardwareKeyboard.physicalKeysPressed),
855-
'RawKeyboard reported ${_rawKeyboard.physicalKeysPressed}, '
856-
'while HardwareKeyboard reported ${_hardwareKeyboard.physicalKeysPressed}');
852+
bool shouldDispatch = true;
853+
if (rawEvent is RawKeyDownEvent) {
854+
if (!rawEvent.data.shouldDispatchEvent()) {
855+
shouldDispatch = false;
856+
_skippedRawKeysPressed.add(rawEvent.physicalKey);
857+
} else {
858+
_skippedRawKeysPressed.remove(rawEvent.physicalKey);
859+
}
860+
} else if (rawEvent is RawKeyUpEvent) {
861+
if (_skippedRawKeysPressed.contains(rawEvent.physicalKey)) {
862+
_skippedRawKeysPressed.remove(rawEvent.physicalKey);
863+
shouldDispatch = false;
864+
}
857865
}
858866

859-
handled = _dispatchKeyMessage(_keyEventsSinceLastMessage, rawEvent) || handled;
860-
_keyEventsSinceLastMessage.clear();
867+
bool handled = true;
868+
if (shouldDispatch) {
869+
// The following `handleRawKeyEvent` will call `_convertRawEventAndStore`
870+
// unless the event is not dispatched.
871+
handled = _rawKeyboard.handleRawKeyEvent(rawEvent);
872+
873+
for (final KeyEvent event in _keyEventsSinceLastMessage) {
874+
handled = _hardwareKeyboard.handleKeyEvent(event) || handled;
875+
}
876+
if (_transitMode == KeyDataTransitMode.rawKeyData) {
877+
assert(setEquals(_rawKeyboard.physicalKeysPressed, _hardwareKeyboard.physicalKeysPressed),
878+
'RawKeyboard reported ${_rawKeyboard.physicalKeysPressed}, '
879+
'while HardwareKeyboard reported ${_hardwareKeyboard.physicalKeysPressed}');
880+
}
881+
882+
handled = _dispatchKeyMessage(_keyEventsSinceLastMessage, rawEvent) || handled;
883+
_keyEventsSinceLastMessage.clear();
884+
}
861885

862886
return <String, dynamic>{ 'handled': handled };
863887
}

packages/flutter/lib/src/services/raw_keyboard.dart

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -661,27 +661,13 @@ class RawKeyboard {
661661
/// Process a new [RawKeyEvent] by recording the state changes and
662662
/// dispatching to listeners.
663663
bool handleRawKeyEvent(RawKeyEvent event) {
664-
bool shouldDispatch = true;
665664
if (event is RawKeyDownEvent) {
666-
if (event.data.shouldDispatchEvent()) {
667-
_keysPressed[event.physicalKey] = event.logicalKey;
668-
} else {
669-
shouldDispatch = false;
670-
_hiddenKeysPressed.add(event.physicalKey);
671-
}
665+
_keysPressed[event.physicalKey] = event.logicalKey;
672666
} else if (event is RawKeyUpEvent) {
673-
if (!_hiddenKeysPressed.contains(event.physicalKey)) {
674-
// Use the physical key in the key up event to find the physical key from
675-
// the corresponding key down event and remove it, even if the logical
676-
// keys don't match.
677-
_keysPressed.remove(event.physicalKey);
678-
} else {
679-
_hiddenKeysPressed.remove(event.physicalKey);
680-
shouldDispatch = false;
681-
}
682-
}
683-
if (!shouldDispatch) {
684-
return true;
667+
// Use the physical key in the key up event to find the physical key from
668+
// the corresponding key down event and remove it, even if the logical
669+
// keys don't match.
670+
_keysPressed.remove(event.physicalKey);
685671
}
686672
// Make sure that the modifiers reflect reality, in case a modifier key was
687673
// pressed/released while the app didn't have focus.
@@ -855,7 +841,6 @@ class RawKeyboard {
855841
}
856842

857843
final Map<PhysicalKeyboardKey, LogicalKeyboardKey> _keysPressed = <PhysicalKeyboardKey, LogicalKeyboardKey>{};
858-
final Set<PhysicalKeyboardKey> _hiddenKeysPressed = <PhysicalKeyboardKey>{};
859844

860845
/// Returns the set of keys currently pressed.
861846
Set<LogicalKeyboardKey> get keysPressed => _keysPressed.values.toSet();

packages/flutter/test/services/raw_keyboard_test.dart

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1936,6 +1936,28 @@ void main() {
19361936
const String platform = 'windows';
19371937
bool lastHandled = true;
19381938
final List<RawKeyEvent> events = <RawKeyEvent>[];
1939+
1940+
// Test both code paths: addListener, and FocusNode.onKey.
1941+
RawKeyboard.instance.addListener(events.add);
1942+
final FocusNode node = FocusNode(
1943+
onKey: (_, RawKeyEvent event) {
1944+
events.add(event);
1945+
return KeyEventResult.ignored;
1946+
},
1947+
);
1948+
await tester.pumpWidget(RawKeyboardListener(
1949+
focusNode: node,
1950+
child: Container(),
1951+
));
1952+
node.requestFocus();
1953+
await tester.pumpAndSettle();
1954+
1955+
// Dispatch an arbitrary key press for the correct transit mode.
1956+
await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
1957+
await simulateKeyUpEvent(LogicalKeyboardKey.keyA);
1958+
expect(events, hasLength(4));
1959+
events.clear();
1960+
19391961
// Simulate raw events because VK_PROCESSKEY does not exist in the key mapping.
19401962
Future<void> simulateKeyEventMessage(String type, int keyCode, int scanCode) {
19411963
return ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
@@ -1953,7 +1975,7 @@ void main() {
19531975
},
19541976
);
19551977
}
1956-
RawKeyboard.instance.addListener(events.add);
1978+
19571979
await simulateKeyEventMessage('keydown', 229, 30);
19581980
expect(events, isEmpty);
19591981
expect(lastHandled, true);
@@ -1962,7 +1984,7 @@ void main() {
19621984
expect(events, isEmpty);
19631985
expect(lastHandled, true);
19641986
expect(RawKeyboard.instance.keysPressed, isEmpty);
1965-
});
1987+
}, variant: KeySimulatorTransitModeVariant.keyDataThenRawKeyData());
19661988

19671989
test('data.toString', () {
19681990
expect(RawKeyEvent.fromMessage(const <String, Object?>{

0 commit comments

Comments
 (0)