[RawKeyboard] Allow inconsistent modifiers for iOS and Android (#108926)
diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart index 894ba17..0874f1d 100644 --- a/packages/flutter/lib/src/services/raw_keyboard.dart +++ b/packages/flutter/lib/src/services/raw_keyboard.dart
@@ -836,10 +836,13 @@ // exist in the modifier list. Enforce the pressing state. if (event is RawKeyDownEvent && thisKeyModifier != null && !_keysPressed.containsKey(event.physicalKey)) { - // So far this inconsistancy is only found on Linux GTK for AltRight in a - // rare case. (See https://github.com/flutter/flutter/issues/93278 .) In - // other cases, this inconsistancy will be caught by an assertion later. - if (event.data is RawKeyEventDataLinux && event.physicalKey == PhysicalKeyboardKey.altRight) { + // This inconsistancy is found on Linux GTK for AltRight: + // https://github.com/flutter/flutter/issues/93278 + // And also on Android and iOS: + // https://github.com/flutter/flutter/issues/101090 + if ((event.data is RawKeyEventDataLinux && event.physicalKey == PhysicalKeyboardKey.altRight) + || event.data is RawKeyEventDataIos + || event.data is RawKeyEventDataAndroid) { final LogicalKeyboardKey? logicalKey = _allModifiersExceptFn[event.physicalKey]; if (logicalKey != null) { _keysPressed[event.physicalKey] = logicalKey;
diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart index 5cc53d8..5dcb783 100644 --- a/packages/flutter/test/services/raw_keyboard_test.dart +++ b/packages/flutter/test/services/raw_keyboard_test.dart
@@ -759,12 +759,11 @@ } else { keyEventMessage = const <String, dynamic>{ 'type': 'keydown', - 'keymap': 'android', - 'keyCode': 0x3b, // Left shift key keyCode - 'scanCode': 0x2a, - 'metaState': 0x0, // No shift key metaState set! - 'source': 0x101, - 'deviceId': 1, + 'keymap': 'windows', + 'keyCode': 16, // Left shift key keyCode + 'characterCodePoint': 0, + 'scanCode': 42, + 'modifiers': 0x0, // No shift key metaState set! }; } @@ -784,6 +783,65 @@ ); }); + testWidgets('Allows inconsistent modifier for iOS', (WidgetTester _) async { + // Use `testWidgets` for clean-ups. + 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': 'ios', + 'keyCode': 0x00000039, + 'characters': '', + 'charactersIgnoringModifiers': '', + 'modifiers': 0, + }), + (ByteData? data) { }, + ); + + expect(events, hasLength(1)); + final RawKeyEvent capsLockKey = events[0]; + final RawKeyEventDataIos data = capsLockKey.data as RawKeyEventDataIos; + expect(data.physicalKey, equals(PhysicalKeyboardKey.capsLock)); + expect(data.logicalKey, equals(LogicalKeyboardKey.capsLock)); + expect(RawKeyboard.instance.keysPressed, contains(LogicalKeyboardKey.capsLock)); + }, skip: isBrowser); // [intended] This is an iOS-specific group. + + testWidgets('Allows inconsistent modifier for Android', (WidgetTester _) async { + // Use `testWidgets` for clean-ups. + 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': 'android', + 'keyCode': 115, + 'plainCodePoint': 0, + 'codePoint': 0, + 'scanCode': 58, + 'metaState': 0, + 'source': 0x101, + 'deviceId': 1, + }), + (ByteData? data) { }, + ); + + expect(events, hasLength(1)); + final RawKeyEvent capsLockKey = events[0]; + final RawKeyEventDataAndroid data = capsLockKey.data as RawKeyEventDataAndroid; + expect(data.physicalKey, equals(PhysicalKeyboardKey.capsLock)); + expect(data.logicalKey, equals(LogicalKeyboardKey.capsLock)); + expect(RawKeyboard.instance.keysPressed, contains(LogicalKeyboardKey.capsLock)); + }, skip: isBrowser); // [intended] This is an Android-specific group. + testWidgets('Dispatch events to all handlers', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); final List<int> logs = <int>[];