diff --git a/dev/tools/gen_keycodes/data/logical_key_data.json b/dev/tools/gen_keycodes/data/logical_key_data.json index 49c589621be..289a3b7453b 100644 --- a/dev/tools/gen_keycodes/data/logical_key_data.json +++ b/dev/tools/gen_keycodes/data/logical_key_data.json @@ -1552,7 +1552,8 @@ "value": 4294967323, "names": { "web": [ - "Escape" + "Escape", + "Esc" ], "macos": [ "Escape" diff --git a/dev/tools/gen_keycodes/data/physical_key_data.json b/dev/tools/gen_keycodes/data/physical_key_data.json index 1f0c526afa9..270c0545e22 100644 --- a/dev/tools/gen_keycodes/data/physical_key_data.json +++ b/dev/tools/gen_keycodes/data/physical_key_data.json @@ -1192,6 +1192,9 @@ "name": "Escape", "chromium": "Escape" }, + "otherWebCodes": [ + "Esc" + ], "scanCodes": { "android": [ 1 diff --git a/dev/tools/gen_keycodes/data/supplemental_hid_codes.inc b/dev/tools/gen_keycodes/data/supplemental_hid_codes.inc index 8917dcbce25..8b66cc6e71e 100644 --- a/dev/tools/gen_keycodes/data/supplemental_hid_codes.inc +++ b/dev/tools/gen_keycodes/data/supplemental_hid_codes.inc @@ -50,6 +50,11 @@ DOM_CODE(0x05ff1e, 0x0000, 0x0000, 0x0000, 0xffff, "GameButtonY", BUTTON_Y), DOM_CODE(0x05ff1f, 0x0000, 0x0000, 0x0000, 0xffff, "GameButtonZ", BUTTON_Z), + // Sometimes the Escape key produces "Esc" instead of "Escape". This includes + // older IE and Firefox browsers, and the current Cobalt browser. + // See: https://github.com/flutter/flutter/issues/106062 + DOM_CODE(0x070029, 0x0000, 0x0000, 0x0000, 0xffff, "Esc", ESCAPE), + // ============================================================ // Fn key for Mac // ============================================================ @@ -58,4 +63,4 @@ // defined on other platforms. Chromium does define an "Fn" row, but doesn't // give it a Mac keycode. This overrides their definition. // USB HID evdev XKB Win Mac DOMKey Code - DOM_CODE(0x000012, 0x0000, 0x0000, 0x0000, 0x003f, "Fn", FN), + DOM_CODE(0x000012, 0x0000, 0x0000, 0x0000, 0x003f, "Fn", FN), diff --git a/dev/tools/gen_keycodes/data/supplemental_key_data.inc b/dev/tools/gen_keycodes/data/supplemental_key_data.inc index c7c38af1904..c928dc2f425 100644 --- a/dev/tools/gen_keycodes/data/supplemental_key_data.inc +++ b/dev/tools/gen_keycodes/data/supplemental_key_data.inc @@ -76,6 +76,16 @@ DOM_KEY_UNI("Tilde", TILDE, '~'), DOM_KEY_UNI("Bar", BAR, '|'), + // ============================================================ + // Unprintable keys (Unicode plane) + // ============================================================ + + // Key Enum Value + // Sometimes the Escape key produces "Esc" instead of "Escape". This includes + // older IE and Firefox browsers, and the current Cobalt browser. + // See: https://github.com/flutter/flutter/issues/106062 + DOM_KEY_MAP("Esc", ESC, 0x1B), + // The following keys reside in the Flutter plane (0x0100000000). // ============================================================ diff --git a/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart b/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart index dbc7761a48d..1e3542917b4 100644 --- a/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart +++ b/dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart @@ -78,7 +78,7 @@ $otherComments static const PhysicalKeyboardKey ${entry.constantName} = Physica /// Gets the generated definitions of LogicalKeyboardKeys. String get _logicalDefinitions { - final OutputLines lines = OutputLines('Logical debug names'); + final OutputLines lines = OutputLines('Logical debug names', behavior: DeduplicateBehavior.kSkip); void printKey(int flutterId, String constantName, String commentName, {String? otherComments}) { final String firstComment = _wrapString('Represents the logical "$commentName" key on the keyboard.'); otherComments ??= _wrapString('See the function [RawKeyEvent.logicalKey] for more information.'); @@ -122,7 +122,7 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK } String get _logicalKeyLabels { - final OutputLines lines = OutputLines('Logical key labels'); + final OutputLines lines = OutputLines('Logical key labels', behavior: DeduplicateBehavior.kSkip); for (final LogicalKeyEntry entry in logicalData.entries) { lines.add(entry.value, ''' ${toHex(entry.value, digits: 11)}: '${entry.commentName}','''); @@ -141,7 +141,7 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK /// This generates the map of Flutter key codes to logical keys. String get _predefinedKeyCodeMap { - final OutputLines lines = OutputLines('Logical key map'); + final OutputLines lines = OutputLines('Logical key map', behavior: DeduplicateBehavior.kSkip); for (final LogicalKeyEntry entry in logicalData.entries) { lines.add(entry.value, ' ${toHex(entry.value, digits: 11)}: ${entry.constantName},'); } @@ -149,7 +149,7 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK } String get _maskConstantVariables { - final OutputLines lines = OutputLines('Mask constants', checkDuplicate: false); + final OutputLines lines = OutputLines('Mask constants', behavior: DeduplicateBehavior.kKeep); for (final MaskConstant constant in _maskConstants) { lines.add(constant.value, ''' ${_wrapString(constant.description)} /// diff --git a/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart b/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart index 39d4dc25549..28e78b79752 100644 --- a/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart +++ b/dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart @@ -303,7 +303,7 @@ class KeyboardMapsCodeGenerator extends BaseCodeGenerator { /// This generates the map of Web KeyboardEvent codes to physical keys. String get _webPhysicalKeyMap { - final OutputLines lines = OutputLines('Web physical key map'); + final OutputLines lines = OutputLines('Web physical key map', behavior: DeduplicateBehavior.kKeep); for (final PhysicalKeyEntry entry in keyData.entries) { for (final String webCodes in entry.webCodes()) { lines.add(entry.name, " '$webCodes': PhysicalKeyboardKey.${entry.constantName},"); diff --git a/dev/tools/gen_keycodes/lib/logical_key_data.dart b/dev/tools/gen_keycodes/lib/logical_key_data.dart index 5439e922d64..d9431921a29 100644 --- a/dev/tools/gen_keycodes/lib/logical_key_data.dart +++ b/dev/tools/gen_keycodes/lib/logical_key_data.dart @@ -53,8 +53,7 @@ class LogicalKeyData { String glfwNameMap, PhysicalKeyData physicalKeyData, ) { - final Map data = {}; - _readKeyEntries(data, chromiumKeys); + final Map data = _readKeyEntries(chromiumKeys); _readWindowsKeyCodes(data, windowsKeyCodeHeader, parseMapOfListOfString(windowsNameMap)); _readGtkKeyCodes(data, gtkKeyCodeHeader, parseMapOfListOfString(gtkNameMap)); _readAndroidKeyCodes(data, androidKeyCodeHeader, parseMapOfListOfString(androidNameMap)); @@ -130,7 +129,8 @@ class LogicalKeyData { /// The following format should be mapped to the Flutter plane. /// Key Enum Character /// FLUTTER_KEY_MAP("Lang4", LANG4, 0x00013), - static void _readKeyEntries(Map data, String input) { + static Map _readKeyEntries(String input) { + final Map dataByValue = {}; final RegExp domKeyRegExp = RegExp( r'(?DOM|FLUTTER)_KEY_(?UNI|MAP)\s*\(\s*' r'"(?[^\s]+?)",\s*' @@ -162,17 +162,23 @@ class LogicalKeyData { } final bool isPrintable = keyLabel != null; - data.putIfAbsent(name, () { - final LogicalKeyEntry entry = LogicalKeyEntry.fromName( - value: toPlane(value, _sourceToPlane(source, isPrintable)), + final int entryValue = toPlane(value, _sourceToPlane(source, isPrintable)); + final LogicalKeyEntry entry = dataByValue.putIfAbsent(entryValue, () => + LogicalKeyEntry.fromName( + value: entryValue, name: name, keyLabel: keyLabel, - ); - if (source == 'DOM' && !isPrintable) - entry.webNames.add(webName); - return entry; - }); + ), + ); + if (source == 'DOM' && !isPrintable) { + entry.webNames.add(webName); + } } + return Map.fromEntries( + dataByValue.values.map((LogicalKeyEntry entry) => + MapEntry(entry.name, entry), + ), + ); } static void _readMacOsKeyCodes( diff --git a/dev/tools/gen_keycodes/lib/physical_key_data.dart b/dev/tools/gen_keycodes/lib/physical_key_data.dart index 49829930371..5bbe5ab9722 100644 --- a/dev/tools/gen_keycodes/lib/physical_key_data.dart +++ b/dev/tools/gen_keycodes/lib/physical_key_data.dart @@ -171,6 +171,22 @@ class PhysicalKeyData { // Skip key that is not actually generated by any keyboard. continue; } + final PhysicalKeyEntry? existing = entries[usbHidCode]; + // Allow duplicate entries for Fn, which overwrites. + if (existing != null && existing.name != 'Fn') { + // If it's an existing entry, the only thing we currently support is + // to insert an extra DOMKey. The other entries must be empty. + assert(evdevCode == 0 + && xKbScanCode == 0 + && windowsScanCode == 0 + && macScanCode == 0xffff + && chromiumCode != null + && chromiumCode.isNotEmpty, + 'Duplicate usbHidCode ${existing.usbHidCode} of key ${existing.name} ' + 'conflicts with existing ${entries[existing.usbHidCode]!.name}.'); + existing.otherWebCodes.add(chromiumCode!); + continue; + } final PhysicalKeyEntry newEntry = PhysicalKeyEntry( usbHidCode: usbHidCode, androidScanCodes: nameToAndroidScanCodes[name] ?? [], @@ -182,15 +198,6 @@ class PhysicalKeyData { name: name, chromiumCode: chromiumCode, ); - // Remove duplicates: last one wins, so that supplemental codes - // override. - if (entries.containsKey(newEntry.usbHidCode)) { - // This is expected for Fn. Warn for other keys. - if (newEntry.name != 'Fn') { - print('Duplicate usbHidCode ${newEntry.usbHidCode} of key ${newEntry.name} ' - 'conflicts with existing ${entries[newEntry.usbHidCode]!.name}. Keeping the new one.'); - } - } entries[newEntry.usbHidCode] = newEntry; } return entries.map((int code, PhysicalKeyEntry entry) => @@ -216,7 +223,8 @@ class PhysicalKeyEntry { required this.macOSScanCode, required this.iOSScanCode, required this.chromiumCode, - }); + List? otherWebCodes, + }) : otherWebCodes = otherWebCodes ?? []; /// Populates the key from a JSON map. factory PhysicalKeyEntry.fromJsonMapEntry(Map map) { @@ -232,6 +240,7 @@ class PhysicalKeyEntry { windowsScanCode: scanCodes['windows'] as int?, macOSScanCode: scanCodes['macos'] as int?, iOSScanCode: scanCodes['ios'] as int?, + otherWebCodes: (map['otherWebCodes'] as List?)?.cast(), ); } @@ -258,11 +267,14 @@ class PhysicalKeyEntry { final String name; /// The Chromium event code for the key. final String? chromiumCode; + /// Other codes used by Web besides chromiumCode. + final List otherWebCodes; Iterable webCodes() sync* { if (chromiumCode != null) { yield chromiumCode!; } + yield* otherWebCodes; } /// Creates a JSON map from the key data. @@ -272,6 +284,7 @@ class PhysicalKeyEntry { 'name': name, 'chromium': chromiumCode, }, + 'otherWebCodes': otherWebCodes, 'scanCodes': { 'android': androidScanCodes, 'usb': usbHidCode, @@ -323,11 +336,14 @@ class PhysicalKeyEntry { @override String toString() { + final String otherWebStr = otherWebCodes.isEmpty + ? '' + : ', otherWebCodes: [${otherWebCodes.join(', ')}]'; return """'$constantName': (name: "$name", usbHidCode: ${toHex(usbHidCode)}, """ 'linuxScanCode: ${toHex(evdevCode)}, xKbScanCode: ${toHex(xKbScanCode)}, ' 'windowsKeyCode: ${toHex(windowsScanCode)}, macOSScanCode: ${toHex(macOSScanCode)}, ' 'windowsScanCode: ${toHex(windowsScanCode)}, chromiumSymbolName: $chromiumCode ' - 'iOSScanCode: ${toHex(iOSScanCode)})'; + 'iOSScanCode: ${toHex(iOSScanCode)})$otherWebStr'; } static int compareByUsbHidCode(PhysicalKeyEntry a, PhysicalKeyEntry b) => diff --git a/dev/tools/gen_keycodes/lib/testing_key_codes_cc_gen.dart b/dev/tools/gen_keycodes/lib/testing_key_codes_cc_gen.dart index 15d938812b8..69cb48fce67 100644 --- a/dev/tools/gen_keycodes/lib/testing_key_codes_cc_gen.dart +++ b/dev/tools/gen_keycodes/lib/testing_key_codes_cc_gen.dart @@ -30,7 +30,7 @@ constexpr uint64_t kPhysical${_toUpperCammel(entry.constantName)} = ${toHex(entr /// Gets the generated definitions of PhysicalKeyboardKeys. String get _logicalDefinitions { - final OutputLines lines = OutputLines('Logical Key list'); + final OutputLines lines = OutputLines('Logical Key list', behavior: DeduplicateBehavior.kSkip); for (final LogicalKeyEntry entry in logicalData.entries) { lines.add(entry.value, ''' constexpr uint64_t kLogical${_toUpperCammel(entry.constantName)} = ${toHex(entry.value, digits: 11)};'''); diff --git a/dev/tools/gen_keycodes/lib/testing_key_codes_java_gen.dart b/dev/tools/gen_keycodes/lib/testing_key_codes_java_gen.dart index 281f6c26ce0..bd1bd806265 100644 --- a/dev/tools/gen_keycodes/lib/testing_key_codes_java_gen.dart +++ b/dev/tools/gen_keycodes/lib/testing_key_codes_java_gen.dart @@ -42,7 +42,7 @@ class KeyCodesJavaGenerator extends BaseCodeGenerator { /// Gets the generated definitions of PhysicalKeyboardKeys. String get _logicalDefinitions { - final OutputLines lines = OutputLines('Logical Key list'); + final OutputLines lines = OutputLines('Logical Key list', behavior: DeduplicateBehavior.kSkip); for (final LogicalKeyEntry entry in logicalData.entries) { lines.add(entry.value, ''' public static final long LOGICAL_${_toUpperSnake(entry.constantName)} = ${toHex(entry.value, digits: 11)}L;'''); diff --git a/dev/tools/gen_keycodes/lib/utils.dart b/dev/tools/gen_keycodes/lib/utils.dart index 138fcdfb478..9e9b0b8603f 100644 --- a/dev/tools/gen_keycodes/lib/utils.dart +++ b/dev/tools/gen_keycodes/lib/utils.dart @@ -233,12 +233,24 @@ void addNameValue(List names, List values, String name, int value) } } +enum DeduplicateBehavior { + // Warn the duplicate entry. + kWarn, + + // Skip the latter duplicate entry. + kSkip, + + // Keep all duplicate entries. + kKeep, +} + /// The information for a line used by [OutputLines]. class OutputLine> { - const OutputLine(this.key, this.value); + OutputLine(this.key, String value) + : values = [value]; final T key; - final String value; + final List values; } /// A utility class to build join a number of lines in a sorted order. @@ -247,41 +259,43 @@ class OutputLine> { /// get the joined string of these lines joined sorting them in the order of the /// index. class OutputLines> { - OutputLines(this.mapName, {this.checkDuplicate = true}); + OutputLines(this.mapName, {this.behavior = DeduplicateBehavior.kWarn}); - /// If true, then lines with duplicate keys will be warned and discarded. - /// - /// Default to true. - final bool checkDuplicate; + /// What to do if there are entries with the same key. + final DeduplicateBehavior behavior; /// The name for this map. /// /// Used in warning messages. final String mapName; - final Set keys = {}; - final List> lines = >[]; + final Map> lines = >{}; - void add(T code, String line) { - if (checkDuplicate) { - if (keys.contains(code)) { - final OutputLine existing = lines.firstWhere((OutputLine line) => line.key == code); - print('Warn: $mapName is requested to add line $code as:\n $line\n but it already exists as:\n ${existing.value}'); - return; + void add(T key, String line) { + final OutputLine? existing = lines[key]; + if (existing != null) { + switch (behavior) { + case DeduplicateBehavior.kWarn: + print('Warn: Request to add $key to map "$mapName" as:\n $line\n but it already exists as:\n ${existing.values[0]}'); + return; + case DeduplicateBehavior.kSkip: + return; + case DeduplicateBehavior.kKeep: + existing.values.add(line); + return; } - keys.add(code); } - lines.add(OutputLine(code, line)); + lines[key] = OutputLine(key, line); } String join() { - return lines.map((OutputLine line) => line.value).join('\n'); + return lines.values.map((OutputLine line) => line.values.join('\n')).join('\n'); } String sortedJoin() { - return (lines.sublist(0) + return (lines.values.toList() ..sort((OutputLine a, OutputLine b) => a.key.compareTo(b.key))) - .map((OutputLine line) => line.value) + .map((OutputLine line) => line.values.join('\n')) .join('\n'); } } diff --git a/packages/flutter/lib/src/services/keyboard_maps.dart b/packages/flutter/lib/src/services/keyboard_maps.dart index 0c26814e1f9..6d34631d9b9 100644 --- a/packages/flutter/lib/src/services/keyboard_maps.dart +++ b/packages/flutter/lib/src/services/keyboard_maps.dart @@ -2211,6 +2211,7 @@ const Map kWebToLogicalKey = kWebToPhysicalKey = { + 'type': 'keydown', + 'keymap': 'web', + 'code': 'Esc', + 'key': 'Esc', + 'location': 0, + 'metaState': 0x0, + 'keyCode': 0x1B, + }); + final RawKeyEventDataWeb data = escapeKeyEvent.data as RawKeyEventDataWeb; + expect(data.physicalKey, equals(PhysicalKeyboardKey.escape)); + expect(data.logicalKey, equals(LogicalKeyboardKey.escape)); + expect(data.keyLabel, isEmpty); + expect(data.keyCode, equals(0x1B)); + }); + test('Arrow keys from a keyboard give correct physical key mappings', () { final RawKeyEvent arrowKeyDown = RawKeyEvent.fromMessage(const { 'type': 'keydown',