diff --git a/dev/tools/gen_keycodes/bin/gen_keycodes.dart b/dev/tools/gen_keycodes/bin/gen_keycodes.dart index 27bf0ca2ed..12c24aa81a 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 2fca083429..80ddefa6cc 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 47229d24c9..c43c563f60 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 0000000000..4e7942c63d --- /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 19478f476b..d07668d799 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 6a191681a2..60f251b666 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 d5d01db915..1d49bdf060 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 e4d42e81fa..5e071a2305 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 840684b0d1..138fcdfb47 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 b909884da2..5b45c6e308 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 a963eb57af..b6e256c638 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 415ea7f1f2..aa815bf2fa 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 6b11e56c99..c8f97b2146 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 a27ff7033c..d2ada0e187 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 4578adc866..84d0e4c52e 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',