[Keyboard, Web] Map from "Esc" to the Escape key (#106133)
* Impl
* Fix build
* Add test
diff --git a/dev/tools/gen_keycodes/data/logical_key_data.json b/dev/tools/gen_keycodes/data/logical_key_data.json
index 49c5896..289a3b7 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 1f0c526..270c054 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 8917dcb..8b66cc6 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 c7c38af..c928dc2 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 dbc7761..1e35429 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 @@
/// Gets the generated definitions of LogicalKeyboardKeys.
String get _logicalDefinitions {
- final OutputLines<int> lines = OutputLines<int>('Logical debug names');
+ final OutputLines<int> lines = OutputLines<int>('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 @@
}
String get _logicalKeyLabels {
- final OutputLines<int> lines = OutputLines<int>('Logical key labels');
+ final OutputLines<int> lines = OutputLines<int>('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 @@
/// This generates the map of Flutter key codes to logical keys.
String get _predefinedKeyCodeMap {
- final OutputLines<int> lines = OutputLines<int>('Logical key map');
+ final OutputLines<int> lines = OutputLines<int>('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 @@
}
String get _maskConstantVariables {
- final OutputLines<int> lines = OutputLines<int>('Mask constants', checkDuplicate: false);
+ final OutputLines<int> lines = OutputLines<int>('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 39d4dc2..28e78b7 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 @@
/// This generates the map of Web KeyboardEvent codes to physical keys.
String get _webPhysicalKeyMap {
- final OutputLines<String> lines = OutputLines<String>('Web physical key map');
+ final OutputLines<String> lines = OutputLines<String>('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 5439e92..d943192 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 @@
String glfwNameMap,
PhysicalKeyData physicalKeyData,
) {
- final Map<String, LogicalKeyEntry> data = <String, LogicalKeyEntry>{};
- _readKeyEntries(data, chromiumKeys);
+ final Map<String, LogicalKeyEntry> data = _readKeyEntries(chromiumKeys);
_readWindowsKeyCodes(data, windowsKeyCodeHeader, parseMapOfListOfString(windowsNameMap));
_readGtkKeyCodes(data, gtkKeyCodeHeader, parseMapOfListOfString(gtkNameMap));
_readAndroidKeyCodes(data, androidKeyCodeHeader, parseMapOfListOfString(androidNameMap));
@@ -130,7 +129,8 @@
/// The following format should be mapped to the Flutter plane.
/// Key Enum Character
/// FLUTTER_KEY_MAP("Lang4", LANG4, 0x00013),
- static void _readKeyEntries(Map<String, LogicalKeyEntry> data, String input) {
+ static Map<String, LogicalKeyEntry> _readKeyEntries(String input) {
+ final Map<int, LogicalKeyEntry> dataByValue = <int, LogicalKeyEntry>{};
final RegExp domKeyRegExp = RegExp(
r'(?<source>DOM|FLUTTER)_KEY_(?<kind>UNI|MAP)\s*\(\s*'
r'"(?<name>[^\s]+?)",\s*'
@@ -162,17 +162,23 @@
}
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<String, LogicalKeyEntry>.fromEntries(
+ dataByValue.values.map((LogicalKeyEntry entry) =>
+ MapEntry<String, LogicalKeyEntry>(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 4982993..5bbe5ab 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 @@
// 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] ?? <int>[],
@@ -182,15 +198,6 @@
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 @@
required this.macOSScanCode,
required this.iOSScanCode,
required this.chromiumCode,
- });
+ List<String>? otherWebCodes,
+ }) : otherWebCodes = otherWebCodes ?? <String>[];
/// Populates the key from a JSON map.
factory PhysicalKeyEntry.fromJsonMapEntry(Map<String, dynamic> map) {
@@ -232,6 +240,7 @@
windowsScanCode: scanCodes['windows'] as int?,
macOSScanCode: scanCodes['macos'] as int?,
iOSScanCode: scanCodes['ios'] as int?,
+ otherWebCodes: (map['otherWebCodes'] as List<dynamic>?)?.cast<String>(),
);
}
@@ -258,11 +267,14 @@
final String name;
/// The Chromium event code for the key.
final String? chromiumCode;
+ /// Other codes used by Web besides chromiumCode.
+ final List<String> otherWebCodes;
Iterable<String> webCodes() sync* {
if (chromiumCode != null) {
yield chromiumCode!;
}
+ yield* otherWebCodes;
}
/// Creates a JSON map from the key data.
@@ -272,6 +284,7 @@
'name': name,
'chromium': chromiumCode,
},
+ 'otherWebCodes': otherWebCodes,
'scanCodes': <String, dynamic>{
'android': androidScanCodes,
'usb': usbHidCode,
@@ -323,11 +336,14 @@
@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 15d9388..69cb48f 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 @@
/// Gets the generated definitions of PhysicalKeyboardKeys.
String get _logicalDefinitions {
- final OutputLines<int> lines = OutputLines<int>('Logical Key list');
+ final OutputLines<int> lines = OutputLines<int>('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 281f6c2..bd1bd80 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 @@
/// Gets the generated definitions of PhysicalKeyboardKeys.
String get _logicalDefinitions {
- final OutputLines<int> lines = OutputLines<int>('Logical Key list');
+ final OutputLines<int> lines = OutputLines<int>('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 138fcdf..9e9b0b8 100644
--- a/dev/tools/gen_keycodes/lib/utils.dart
+++ b/dev/tools/gen_keycodes/lib/utils.dart
@@ -233,12 +233,24 @@
}
}
+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<T extends Comparable<Object>> {
- const OutputLine(this.key, this.value);
+ OutputLine(this.key, String value)
+ : values = <String>[value];
final T key;
- final String value;
+ final List<String> values;
}
/// A utility class to build join a number of lines in a sorted order.
@@ -247,41 +259,43 @@
/// get the joined string of these lines joined sorting them in the order of the
/// index.
class OutputLines<T extends Comparable<Object>> {
- 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<T> keys = <T>{};
- final List<OutputLine<T>> lines = <OutputLine<T>>[];
+ final Map<T, OutputLine<T>> lines = <T, OutputLine<T>>{};
- void add(T code, String line) {
- if (checkDuplicate) {
- if (keys.contains(code)) {
- final OutputLine<T> existing = lines.firstWhere((OutputLine<T> 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<T>? 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<T>(code, line));
+ lines[key] = OutputLine<T>(key, line);
}
String join() {
- return lines.map((OutputLine<T> line) => line.value).join('\n');
+ return lines.values.map((OutputLine<T> line) => line.values.join('\n')).join('\n');
}
String sortedJoin() {
- return (lines.sublist(0)
+ return (lines.values.toList()
..sort((OutputLine<T> a, OutputLine<T> b) => a.key.compareTo(b.key)))
- .map((OutputLine<T> line) => line.value)
+ .map((OutputLine<T> 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 0c26814..6d34631 100644
--- a/packages/flutter/lib/src/services/keyboard_maps.dart
+++ b/packages/flutter/lib/src/services/keyboard_maps.dart
@@ -2211,6 +2211,7 @@
'EndCall': LogicalKeyboardKey.endCall,
'Enter': LogicalKeyboardKey.enter,
'EraseEof': LogicalKeyboardKey.eraseEof,
+ 'Esc': LogicalKeyboardKey.escape,
'Escape': LogicalKeyboardKey.escape,
'ExSel': LogicalKeyboardKey.exSel,
'Execute': LogicalKeyboardKey.execute,
@@ -2495,6 +2496,7 @@
'Enter': PhysicalKeyboardKey.enter,
'Equal': PhysicalKeyboardKey.equal,
'Escape': PhysicalKeyboardKey.escape,
+ 'Esc': PhysicalKeyboardKey.escape,
'F1': PhysicalKeyboardKey.f1,
'F10': PhysicalKeyboardKey.f10,
'F11': PhysicalKeyboardKey.f11,
diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart
index 69b4b0f..5cc53d8 100644
--- a/packages/flutter/test/services/raw_keyboard_test.dart
+++ b/packages/flutter/test/services/raw_keyboard_test.dart
@@ -2680,6 +2680,23 @@
expect(data.keyCode, equals(0x10));
});
+ test('Esc keys generated by older browsers are correctly translated', () {
+ final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, Object?>{
+ '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 <String, Object?>{
'type': 'keydown',