From 65ea76718fad0deb69f6e97364d3fcc2380a3101 Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Wed, 11 May 2022 20:04:14 -0700 Subject: [PATCH] [gen_keycode, RawKeyboard] Apply derived keyboard layout from Linux (#102709) --- dev/tools/gen_keycodes/bin/gen_keycodes.dart | 4 ++ dev/tools/gen_keycodes/data/README.md | 1 + .../gen_keycodes/data/gtk_key_mapping_cc.tmpl | 4 ++ dev/tools/gen_keycodes/data/layout_goals.json | 50 +++++++++++++++++++ dev/tools/gen_keycodes/lib/gtk_code_gen.dart | 20 ++++++++ dev/tools/gen_keycodes/lib/ios_code_gen.dart | 4 +- .../gen_keycodes/lib/macos_code_gen.dart | 35 ++++++------- .../gen_keycodes/lib/physical_key_data.dart | 16 +++--- dev/tools/gen_keycodes/lib/utils.dart | 4 ++ dev/tools/gen_keycodes/pubspec.yaml | 2 +- .../gen_keycodes/test/gen_keycodes_test.dart | 4 ++ .../lib/src/services/raw_keyboard.dart | 1 + .../lib/src/services/raw_keyboard_linux.dart | 15 ++++++ .../lib/src/services/raw_keyboard_macos.dart | 1 + .../test/services/raw_keyboard_test.dart | 15 ++++++ 15 files changed, 146 insertions(+), 30 deletions(-) create mode 100644 dev/tools/gen_keycodes/data/layout_goals.json diff --git a/dev/tools/gen_keycodes/bin/gen_keycodes.dart b/dev/tools/gen_keycodes/bin/gen_keycodes.dart index 27bf0ca2edbc..12c24aa81a51 100644 --- a/dev/tools/gen_keycodes/bin/gen_keycodes.dart +++ b/dev/tools/gen_keycodes/bin/gen_keycodes.dart @@ -206,6 +206,8 @@ Future main(List rawArguments) async { logicalData = LogicalKeyData.fromJson(json.decode(await File(parsedArguments['logical-data'] as String).readAsString()) as Map); } + final Map layoutGoals = parseMapOfBool(readDataFile('layout_goals.json')); + final File codeFile = File(parsedArguments['code'] as String); if (!codeFile.existsSync()) { codeFile.createSync(recursive: true); @@ -236,6 +238,7 @@ Future main(List rawArguments) async { 'macos': MacOSCodeGenerator( physicalData, logicalData, + layoutGoals, ), 'ios': IOSCodeGenerator( physicalData, @@ -251,6 +254,7 @@ Future main(List rawArguments) async { logicalData, readDataFile('gtk_modifier_bit_mapping.json'), readDataFile('gtk_lock_bit_mapping.json'), + layoutGoals, ), 'web': WebCodeGenerator( physicalData, diff --git a/dev/tools/gen_keycodes/data/README.md b/dev/tools/gen_keycodes/data/README.md index 2fca08342904..80ddefa6ccba 100644 --- a/dev/tools/gen_keycodes/data/README.md +++ b/dev/tools/gen_keycodes/data/README.md @@ -11,6 +11,7 @@ | [`chromium_modifiers.json`](chromium_modifiers.json) | Maps the web's `key` for modifier keys to the names of the logical keys for these keys' left and right variations.This is used when generating logical keys to provide independent values for sided logical keys. Web uses the same `key` for modifier keys of different sides, but Flutter's logical key model treats them as different keys.| | [`printable.json`](printable.json) | Maps Flutter key name to its printable character. This character is used as the key label.| | [`synonyms.json`](synonyms.json) | Maps pseudo-keys that represent other keys to the sets of keys they represent. For example, this contains the "shift" key that represents either a "shiftLeft" or "shiftRight" key.| +| [`layout_goals.json`](layout_goals.json) | A list of layout goals, keys that the platform keyboard manager should find mappings for. Each key in this file is the key name of the goal, both logical and physical simultaneously, while its value represents whether the goal is mandatory. A mandatory goal must be fulfilled, and the manager will use the default value from this file if a mapping can not be found. A non-mandatory goal is suggestive, only used if the key mapping information is malformed (e.g. contains no ASCII characters.) | ### Framework diff --git a/dev/tools/gen_keycodes/data/gtk_key_mapping_cc.tmpl b/dev/tools/gen_keycodes/data/gtk_key_mapping_cc.tmpl index 47229d24c928..c43c563f60f1 100644 --- a/dev/tools/gen_keycodes/data/gtk_key_mapping_cc.tmpl +++ b/dev/tools/gen_keycodes/data/gtk_key_mapping_cc.tmpl @@ -35,4 +35,8 @@ void initialize_lock_bit_to_checked_keys(GHashTable* table) { @@@GTK_MODE_BIT_MAP@@@ } +const std::vector layout_goals = { +@@@LAYOUT_GOALS@@@ +}; + @@@MASK_CONSTANTS@@@ diff --git a/dev/tools/gen_keycodes/data/layout_goals.json b/dev/tools/gen_keycodes/data/layout_goals.json new file mode 100644 index 000000000000..4e7942c63d49 --- /dev/null +++ b/dev/tools/gen_keycodes/data/layout_goals.json @@ -0,0 +1,50 @@ +{ + "KeyA": true, + "KeyB": true, + "KeyC": true, + "KeyD": true, + "KeyE": true, + "KeyF": true, + "KeyG": true, + "KeyH": true, + "KeyI": true, + "KeyJ": true, + "KeyK": true, + "KeyL": true, + "KeyM": true, + "KeyN": true, + "KeyO": true, + "KeyP": true, + "KeyQ": true, + "KeyR": true, + "KeyS": true, + "KeyT": true, + "KeyU": true, + "KeyV": true, + "KeyW": true, + "KeyX": true, + "KeyY": true, + "KeyZ": true, + "Digit1": true, + "Digit2": true, + "Digit3": true, + "Digit4": true, + "Digit5": true, + "Digit6": true, + "Digit7": true, + "Digit8": true, + "Digit9": true, + "Digit0": true, + "Quote": false, + "Comma": false, + "Minus": false, + "Period": false, + "Slash": false, + "Semicolon": false, + "Equal": false, + "BracketLeft": false, + "Backslash": false, + "BracketRight": false, + "Backquote": false, + "IntlBackslash": false +} diff --git a/dev/tools/gen_keycodes/lib/gtk_code_gen.dart b/dev/tools/gen_keycodes/lib/gtk_code_gen.dart index 19478f476b46..d07668d7996c 100644 --- a/dev/tools/gen_keycodes/lib/gtk_code_gen.dart +++ b/dev/tools/gen_keycodes/lib/gtk_code_gen.dart @@ -19,6 +19,7 @@ class GtkCodeGenerator extends PlatformCodeGenerator { super.logicalData, String modifierBitMapping, String lockBitMapping, + this._layoutGoals, ) : _modifierBitMapping = parseMapOfListOfString(modifierBitMapping), _lockBitMapping = parseMapOfListOfString(lockBitMapping); @@ -91,6 +92,24 @@ class GtkCodeGenerator extends PlatformCodeGenerator { } final Map> _lockBitMapping; + final Map _layoutGoals; + String get _layoutGoalsString { + final OutputLines lines = OutputLines('GTK layout goals'); + _layoutGoals.forEach((String name, bool mandatory) { + final PhysicalKeyEntry physicalEntry = keyData.entryByName(name); + final LogicalKeyEntry logicalEntry = logicalData.entryByName(name); + final String line = 'LayoutGoal{' + '${toHex(physicalEntry.xKbScanCode, digits: 2)}, ' + '${toHex(logicalEntry.value, digits: 2)}, ' + '${mandatory ? 'true' : 'false'}' + '},'; + lines.add(logicalEntry.value, + ' ${line.padRight(39)}' + '// ${logicalEntry.name}'); + }); + return lines.sortedJoin().trimRight(); + } + /// This generates the mask values for the part of a key code that defines its plane. String get _maskConstants { final StringBuffer buffer = StringBuffer(); @@ -120,6 +139,7 @@ class GtkCodeGenerator extends PlatformCodeGenerator { 'GTK_MODIFIER_BIT_MAP': _gtkModifierBitMap, 'GTK_MODE_BIT_MAP': _gtkModeBitMap, 'MASK_CONSTANTS': _maskConstants, + 'LAYOUT_GOALS': _layoutGoalsString, }; } } diff --git a/dev/tools/gen_keycodes/lib/ios_code_gen.dart b/dev/tools/gen_keycodes/lib/ios_code_gen.dart index 6a191681a292..60f251b666f5 100644 --- a/dev/tools/gen_keycodes/lib/ios_code_gen.dart +++ b/dev/tools/gen_keycodes/lib/ios_code_gen.dart @@ -89,7 +89,7 @@ class IOSCodeGenerator extends PlatformCodeGenerator { String get _keyToModifierFlagMap { final StringBuffer modifierKeyMap = StringBuffer(); for (final String name in kModifiersOfInterest) { - final String line =' {${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}, kModifierFlag${lowerCamelToUpperCamel(name)}},'; + final String line = '{${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}, kModifierFlag${lowerCamelToUpperCamel(name)}},'; modifierKeyMap.writeln(' ${line.padRight(42)}// $name'); } return modifierKeyMap.toString().trimRight(); @@ -99,7 +99,7 @@ class IOSCodeGenerator extends PlatformCodeGenerator { String get _modifierFlagToKeyMap { final StringBuffer modifierKeyMap = StringBuffer(); for (final String name in kModifiersOfInterest) { - final String line =' {kModifierFlag${lowerCamelToUpperCamel(name)}, ${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}},'; + final String line = '{kModifierFlag${lowerCamelToUpperCamel(name)}, ${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}},'; modifierKeyMap.writeln(' ${line.padRight(42)}// $name'); } return modifierKeyMap.toString().trimRight(); diff --git a/dev/tools/gen_keycodes/lib/macos_code_gen.dart b/dev/tools/gen_keycodes/lib/macos_code_gen.dart index d5d01db915fa..1d49bdf060eb 100644 --- a/dev/tools/gen_keycodes/lib/macos_code_gen.dart +++ b/dev/tools/gen_keycodes/lib/macos_code_gen.dart @@ -28,7 +28,7 @@ const List kSpecialLogicalKeys = ['CapsLock']; /// Generates the key mapping for macOS, based on the information in the key /// data structure given to it. class MacOSCodeGenerator extends PlatformCodeGenerator { - MacOSCodeGenerator(super.keyData, super.logicalData); + MacOSCodeGenerator(super.keyData, super.logicalData, this._layoutGoals); /// This generates the map of macOS key codes to physical keys. String get _scanCodeMap { @@ -96,24 +96,21 @@ class MacOSCodeGenerator extends PlatformCodeGenerator { return specialKeyConstants.toString().trimRight(); } - String get _layoutGoals { + final Map _layoutGoals; + String get _layoutGoalsString { final OutputLines lines = OutputLines('macOS layout goals'); - final Iterable asciiEntries = logicalData.entries.where( - (LogicalKeyEntry entry) => entry.value <= 128); - for (final LogicalKeyEntry logicalEntry in asciiEntries) { - final int value = logicalEntry.value; - final PhysicalKeyEntry? physicalEntry = keyData.tryEntryByName(logicalEntry.name); - if (physicalEntry == null) { - continue; - } - final bool mandatory = (value >= '0'.codeUnitAt(0) && value <= '9'.codeUnitAt(0)) - || (value >= 'a'.codeUnitAt(0) && value <= 'z'.codeUnitAt(0)); - lines.add(value, - ' LayoutGoal{${toHex(physicalEntry.macOSScanCode, digits: 2)}, ' - '${toHex(value, digits: 2)}, ' - '${mandatory ? 'true}, ' : 'false},'}' - ' // ${logicalEntry.name}'); - } + _layoutGoals.forEach((String name, bool mandatory) { + final PhysicalKeyEntry physicalEntry = keyData.entryByName(name); + final LogicalKeyEntry logicalEntry = logicalData.entryByName(name); + final String line = 'LayoutGoal{' + '${toHex(physicalEntry.macOSScanCode, digits: 2)}, ' + '${toHex(logicalEntry.value, digits: 2)}, ' + '${mandatory ? 'true' : 'false'}' + '},'; + lines.add(logicalEntry.value, + ' ${line.padRight(39)}' + '// ${logicalEntry.name}'); + }); return lines.sortedJoin().trimRight(); } @@ -136,7 +133,7 @@ class MacOSCodeGenerator extends PlatformCodeGenerator { 'KEYCODE_TO_MODIFIER_FLAG_MAP': _keyToModifierFlagMap, 'MODIFIER_FLAG_TO_KEYCODE_MAP': _modifierFlagToKeyMap, 'SPECIAL_KEY_CONSTANTS': _specialKeyConstants, - 'LAYOUT_GOALS': _layoutGoals, + 'LAYOUT_GOALS': _layoutGoalsString, }; } } diff --git a/dev/tools/gen_keycodes/lib/physical_key_data.dart b/dev/tools/gen_keycodes/lib/physical_key_data.dart index e4d42e81fa5f..5e071a2305e5 100644 --- a/dev/tools/gen_keycodes/lib/physical_key_data.dart +++ b/dev/tools/gen_keycodes/lib/physical_key_data.dart @@ -158,7 +158,7 @@ class PhysicalKeyData { input = input.replaceAll(commentRegExp, ''); for (final RegExpMatch match in usbMapRegExp.allMatches(input)) { final int usbHidCode = getHex(match.namedGroup('usb')!); - final int linuxScanCode = getHex(match.namedGroup('evdev')!); + final int evdevCode = getHex(match.namedGroup('evdev')!); final int xKbScanCode = getHex(match.namedGroup('xkb')!); final int windowsScanCode = getHex(match.namedGroup('win')!); final int macScanCode = getHex(match.namedGroup('mac')!); @@ -174,7 +174,7 @@ class PhysicalKeyData { final PhysicalKeyEntry newEntry = PhysicalKeyEntry( usbHidCode: usbHidCode, androidScanCodes: nameToAndroidScanCodes[name] ?? [], - linuxScanCode: linuxScanCode == 0 ? null : linuxScanCode, + evdevCode: evdevCode == 0 ? null : evdevCode, xKbScanCode: xKbScanCode == 0 ? null : xKbScanCode, windowsScanCode: windowsScanCode == 0 ? null : windowsScanCode, macOSScanCode: macScanCode == 0xffff ? null : macScanCode, @@ -210,7 +210,7 @@ class PhysicalKeyEntry { required this.usbHidCode, required this.name, required this.androidScanCodes, - required this.linuxScanCode, + required this.evdevCode, required this.xKbScanCode, required this.windowsScanCode, required this.macOSScanCode, @@ -227,7 +227,7 @@ class PhysicalKeyEntry { chromiumCode: names['chromium'] as String?, usbHidCode: scanCodes['usb'] as int, androidScanCodes: (scanCodes['android'] as List?)?.cast() ?? [], - linuxScanCode: scanCodes['linux'] as int?, + evdevCode: scanCodes['linux'] as int?, xKbScanCode: scanCodes['xkb'] as int?, windowsScanCode: scanCodes['windows'] as int?, macOSScanCode: scanCodes['macos'] as int?, @@ -238,8 +238,8 @@ class PhysicalKeyEntry { /// The USB HID code of the key final int usbHidCode; - /// The Linux scan code of the key, from Chromium's header file. - final int? linuxScanCode; + /// The Evdev scan code of the key, from Chromium's header file. + final int? evdevCode; /// The XKb scan code of the key from Chromium's header file. final int? xKbScanCode; /// The Windows scan code of the key from Chromium's header file. @@ -269,7 +269,7 @@ class PhysicalKeyEntry { 'scanCodes': { 'android': androidScanCodes, 'usb': usbHidCode, - 'linux': linuxScanCode, + 'linux': evdevCode, 'xkb': xKbScanCode, 'windows': windowsScanCode, 'macos': macOSScanCode, @@ -318,7 +318,7 @@ class PhysicalKeyEntry { @override String toString() { return """'$constantName': (name: "$name", usbHidCode: ${toHex(usbHidCode)}, """ - 'linuxScanCode: ${toHex(linuxScanCode)}, xKbScanCode: ${toHex(xKbScanCode)}, ' + 'linuxScanCode: ${toHex(evdevCode)}, xKbScanCode: ${toHex(xKbScanCode)}, ' 'windowsKeyCode: ${toHex(windowsScanCode)}, macOSScanCode: ${toHex(macOSScanCode)}, ' 'windowsScanCode: ${toHex(windowsScanCode)}, chromiumSymbolName: $chromiumCode ' 'iOSScanCode: ${toHex(iOSScanCode)})'; diff --git a/dev/tools/gen_keycodes/lib/utils.dart b/dev/tools/gen_keycodes/lib/utils.dart index 840684b0d1fe..138fcdfb4786 100644 --- a/dev/tools/gen_keycodes/lib/utils.dart +++ b/dev/tools/gen_keycodes/lib/utils.dart @@ -184,6 +184,10 @@ Map> parseMapOfListOfNullableString(String jsonString) { }); } +Map parseMapOfBool(String jsonString) { + return (json.decode(jsonString) as Map).cast(); +} + /// Reverse the map of { fromValue -> list of toValue } to { toValue -> fromValue } and return. Map reverseMapOfListOfString(Map> inMap, void Function(String fromValue, String newToValue) onDuplicate) { final Map result = {}; diff --git a/dev/tools/gen_keycodes/pubspec.yaml b/dev/tools/gen_keycodes/pubspec.yaml index b909884da2a7..5b45c6e30822 100644 --- a/dev/tools/gen_keycodes/pubspec.yaml +++ b/dev/tools/gen_keycodes/pubspec.yaml @@ -2,7 +2,7 @@ name: gen_keycodes description: Generates keycode source files from various resources. environment: - sdk: ">=2.17.0-0 <3.0.0" + sdk: ">=2.18.0-0 <3.0.0" dependencies: args: 2.3.0 diff --git a/dev/tools/gen_keycodes/test/gen_keycodes_test.dart b/dev/tools/gen_keycodes/test/gen_keycodes_test.dart index a963eb57af0d..b6e256c638f0 100644 --- a/dev/tools/gen_keycodes/test/gen_keycodes_test.dart +++ b/dev/tools/gen_keycodes/test/gen_keycodes_test.dart @@ -26,6 +26,8 @@ final PhysicalKeyData physicalData = PhysicalKeyData.fromJson( json.decode(readDataFile('physical_key_data.json')) as Map); final LogicalKeyData logicalData = LogicalKeyData.fromJson( json.decode(readDataFile('logical_key_data.json')) as Map); +final Map keyGoals = parseMapOfBool( + readDataFile('layout_goals.json')); void main() { setUp(() { @@ -65,6 +67,7 @@ void main() { final PlatformCodeGenerator codeGenerator = MacOSCodeGenerator( physicalData, logicalData, + keyGoals, ); final String output = codeGenerator.generate(); @@ -119,6 +122,7 @@ void main() { logicalData, readDataFile(path.join(dataRoot, 'gtk_modifier_bit_mapping.json')), readDataFile(path.join(dataRoot, 'gtk_lock_bit_mapping.json')), + keyGoals, ); final String output = codeGenerator.generate(); diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart index 415ea7f1f24b..aa815bf2faee 100644 --- a/packages/flutter/lib/src/services/raw_keyboard.dart +++ b/packages/flutter/lib/src/services/raw_keyboard.dart @@ -364,6 +364,7 @@ abstract class RawKeyEvent with Diagnosticable { scanCode: message['scanCode'] as int? ?? 0, modifiers: message['modifiers'] as int? ?? 0, isDown: message['type'] == 'keydown', + specifiedLogicalKey: message['specifiedLogicalKey'] as int?, ); if (unicodeScalarValues != 0) { character = String.fromCharCode(unicodeScalarValues); diff --git a/packages/flutter/lib/src/services/raw_keyboard_linux.dart b/packages/flutter/lib/src/services/raw_keyboard_linux.dart index 6b11e56c9990..c8f97b214668 100644 --- a/packages/flutter/lib/src/services/raw_keyboard_linux.dart +++ b/packages/flutter/lib/src/services/raw_keyboard_linux.dart @@ -28,6 +28,7 @@ class RawKeyEventDataLinux extends RawKeyEventData { this.keyCode = 0, this.modifiers = 0, required this.isDown, + this.specifiedLogicalKey, }) : assert(scanCode != null), assert(unicodeScalarValues != null), assert((unicodeScalarValues & ~LogicalKeyboardKey.valueMask) == 0), @@ -68,6 +69,15 @@ class RawKeyEventDataLinux extends RawKeyEventData { /// Whether or not this key event is a key down (true) or key up (false). final bool isDown; + /// A logical key specified by the embedding that should be used instead of + /// deriving from raw data. + /// + /// The GTK embedding detects the keyboard layout and maps some keys to + /// logical keys in a way that can not be derived from per-key information. + /// + /// This is not part of the native GTK key event. + final int? specifiedLogicalKey; + @override String get keyLabel => unicodeScalarValues == 0 ? '' : String.fromCharCode(unicodeScalarValues); @@ -76,6 +86,10 @@ class RawKeyEventDataLinux extends RawKeyEventData { @override LogicalKeyboardKey get logicalKey { + if (specifiedLogicalKey != null) { + final int key = specifiedLogicalKey!; + return LogicalKeyboardKey.findKeyByKeyId(key) ?? LogicalKeyboardKey(key); + } // Look to see if the keyCode is a printable number pad key, so that a // difference between regular keys (e.g. "=") and the number pad version // (e.g. the "=" on the number pad) can be determined. @@ -124,6 +138,7 @@ class RawKeyEventDataLinux extends RawKeyEventData { properties.add(DiagnosticsProperty('keyCode', keyCode)); properties.add(DiagnosticsProperty('modifiers', modifiers)); properties.add(DiagnosticsProperty('isDown', isDown)); + properties.add(DiagnosticsProperty('specifiedLogicalKey', specifiedLogicalKey, defaultValue: null)); } @override diff --git a/packages/flutter/lib/src/services/raw_keyboard_macos.dart b/packages/flutter/lib/src/services/raw_keyboard_macos.dart index a27ff7033cb0..d2ada0e18718 100644 --- a/packages/flutter/lib/src/services/raw_keyboard_macos.dart +++ b/packages/flutter/lib/src/services/raw_keyboard_macos.dart @@ -246,6 +246,7 @@ class RawKeyEventDataMacOs extends RawKeyEventData { properties.add(DiagnosticsProperty('charactersIgnoringModifiers', charactersIgnoringModifiers)); properties.add(DiagnosticsProperty('keyCode', keyCode)); properties.add(DiagnosticsProperty('modifiers', modifiers)); + properties.add(DiagnosticsProperty('specifiedLogicalKey', specifiedLogicalKey, defaultValue: null)); } @override diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart index 4578adc86602..84d0e4c52e62 100644 --- a/packages/flutter/test/services/raw_keyboard_test.dart +++ b/packages/flutter/test/services/raw_keyboard_test.dart @@ -2421,6 +2421,21 @@ void main() { expect(data.keyLabel, isEmpty); }); + test('Prioritize logical key from specifiedLogicalKey', () { + final RawKeyEvent digit1FromFrench = RawKeyEvent.fromMessage(const { + 'type': 'keydown', + 'keymap': 'linux', + 'toolkit': 'gtk', + 'keyCode': 0x6c6, + 'scanCode': 0x26, + 'unicodeScalarValues': 0x424, + 'specifiedLogicalKey': 0x61, + }); + final RawKeyEventDataLinux data = digit1FromFrench.data as RawKeyEventDataLinux; + expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA)); + expect(data.logicalKey, equals(LogicalKeyboardKey.keyA)); + }); + test('data.toString', () { expect(RawKeyEvent.fromMessage(const { 'type': 'keydown',