Skip to content

Commit

Permalink
[RawKeyboard] Fix Linux remapped CapsLock throws (#115009)
Browse files Browse the repository at this point in the history
Co-authored-by: Bruno Leroux <[email protected]>
  • Loading branch information
bleroux and bleroux authored Nov 21, 2022
1 parent 567d004 commit 073cefa
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 36 deletions.
15 changes: 12 additions & 3 deletions packages/flutter/lib/src/services/raw_keyboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -824,9 +824,18 @@ class RawKeyboard {
modifierKeys[physicalModifier] = _allModifiers[physicalModifier]!;
}
}
_allModifiersExceptFn.keys
.where((PhysicalKeyboardKey key) => !anySideKeys.contains(key))
.forEach(_keysPressed.remove);
// On Linux, CapsLock key can be mapped to a non-modifier logical key:
// https://github.com/flutter/flutter/issues/114591.
// This is also affecting Flutter Web on Linux.
final bool nonModifierCapsLock = (event.data is RawKeyEventDataLinux || event.data is RawKeyEventDataWeb)
&& _keysPressed[PhysicalKeyboardKey.capsLock] != null
&& _keysPressed[PhysicalKeyboardKey.capsLock] != LogicalKeyboardKey.capsLock;
for (final PhysicalKeyboardKey physicalKey in _allModifiersExceptFn.keys) {
final bool skipReleasingKey = nonModifierCapsLock && physicalKey == PhysicalKeyboardKey.capsLock;
if (!anySideKeys.contains(physicalKey) && !skipReleasingKey) {
_keysPressed.remove(physicalKey);
}
}
if (event.data is! RawKeyEventDataFuchsia && event.data is! RawKeyEventDataMacOs) {
// On Fuchsia and macOS, the Fn key is not considered a modifier key.
_keysPressed.remove(PhysicalKeyboardKey.fn);
Expand Down
4 changes: 2 additions & 2 deletions packages/flutter/lib/src/services/raw_keyboard_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ class RawKeyEventDataWeb extends RawKeyEventData {
return maybeLocationKey;
}

// Look to see if the [code] is one we know about and have a mapping for.
final LogicalKeyboardKey? newKey = kWebToLogicalKey[code];
// Look to see if the [key] is one we know about and have a mapping for.
final LogicalKeyboardKey? newKey = kWebToLogicalKey[key];
if (newKey != null) {
return newKey;
}
Expand Down
112 changes: 81 additions & 31 deletions packages/flutter/test/services/raw_keyboard_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -350,45 +350,45 @@ void main() {
);
}, skip: isBrowser); // [intended] This is a GLFW-specific test.

Future<void> simulateGTKKeyEvent(bool keyDown, int scancode, int keycode, int modifiers) async {
final Map<String, dynamic> data = <String, dynamic>{
'type': keyDown ? 'keydown' : 'keyup',
'keymap': 'linux',
'toolkit': 'gtk',
'scanCode': scancode,
'keyCode': keycode,
'modifiers': modifiers,
};
// Dispatch an empty key data to disable HardwareKeyboard sanity check,
// since we're only testing if the raw keyboard can handle the message.
// In a real application, the embedder responder will send the correct key data
// (which is tested in the engine).
TestDefaultBinaryMessengerBinding.instance!.keyEventManager.handleKeyData(const ui.KeyData(
type: ui.KeyEventType.down,
timeStamp: Duration.zero,
logical: 0,
physical: 0,
character: null,
synthesized: false,
));
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
}

// Regression test for https://github.com/flutter/flutter/issues/93278 .
//
// GTK has some weird behavior where the tested key event sequence will
// result in a AltRight down event without Alt bitmask.
testWidgets('keysPressed modifiers are synchronized with key events on Linux GTK (down events)', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
Future<void> simulate(bool keyDown, int scancode, int keycode, int modifiers) async {
final Map<String, dynamic> data = <String, dynamic>{
'type': keyDown ? 'keydown' : 'keyup',
'keymap': 'linux',
'toolkit': 'gtk',
'scanCode': scancode,
'keyCode': keycode,
'modifiers': modifiers,
};
// Dispatch an empty key data to disable HardwareKeyboard sanity check,
// since we're only testing if the raw keyboard can handle the message.
// In real application the embedder responder will send correct key data
// (which is tested in the engine.)
TestDefaultBinaryMessengerBinding.instance!.keyEventManager.handleKeyData(const ui.KeyData(
type: ui.KeyEventType.down,
timeStamp: Duration.zero,
logical: 0,
physical: 0,
character: null,
synthesized: false,
));
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
}

await simulate(true, 0x6c/*AltRight*/, 0xffea/*AltRight*/, 0x2000000);
await simulate(true, 0x32/*ShiftLeft*/, 0xfe08/*NextGroup*/, 0x2000008/*MOD3*/);
await simulate(false, 0x6c/*AltRight*/, 0xfe03/*AltRight*/, 0x2002008/*MOD3|Reserve14*/);
await simulate(true, 0x6c/*AltRight*/, 0xfe03/*AltRight*/, 0x2002000/*Reserve14*/);
await simulateGTKKeyEvent(true, 0x6c/*AltRight*/, 0xffea/*AltRight*/, 0x2000000);
await simulateGTKKeyEvent(true, 0x32/*ShiftLeft*/, 0xfe08/*NextGroup*/, 0x2000008/*MOD3*/);
await simulateGTKKeyEvent(false, 0x6c/*AltRight*/, 0xfe03/*AltRight*/, 0x2002008/*MOD3|Reserve14*/);
await simulateGTKKeyEvent(true, 0x6c/*AltRight*/, 0xfe03/*AltRight*/, 0x2002000/*Reserve14*/);
expect(
RawKeyboard.instance.keysPressed,
equals(
Expand All @@ -399,6 +399,56 @@ void main() {
);
}, skip: isBrowser); // [intended] This is a GTK-specific test.

// Regression test for https://github.com/flutter/flutter/issues/114591 .
//
// On Linux, CapsLock can be remapped to a non-modifier key.
testWidgets('CapsLock should not be release when remapped on Linux', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);

await simulateGTKKeyEvent(true, 0x42/*CapsLock*/, 0xff08/*Backspace*/, 0x2000000);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.backspace,
},
),
);
}, skip: isBrowser); // [intended] This is a GTK-specific test.

// Regression test for https://github.com/flutter/flutter/issues/114591 .
//
// On Web, CapsLock can be remapped to a non-modifier key.
testWidgets('CapsLock should not be release when remapped on Web', (WidgetTester _) async {
final List<RawKeyEvent> events = <RawKeyEvent>[];
RawKeyboard.instance.addListener(events.add);
addTearDown(() {
RawKeyboard.instance.removeListener(events.add);
});
await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'CapsLock',
'key': 'Backspace',
'location': 0,
'metaState': 0,
'keyCode': 8,
}),
(ByteData? data) { },
);

expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.backspace,
},
),
);
}, skip: !isBrowser); // [intended] This is a Browser-specific test.

testWidgets('keysPressed modifiers are synchronized with key events on web', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event. Change the modifiers so
Expand Down

0 comments on commit 073cefa

Please sign in to comment.