Skip to content

Commit 0ac05f1

Browse files
authored
[Keyboard, Web] Map from "Esc" to the Escape key (#106133)
* Impl * Fix build * Add test
1 parent df55dbb commit 0ac05f1

13 files changed

+125
-51
lines changed

dev/tools/gen_keycodes/data/logical_key_data.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1552,7 +1552,8 @@
15521552
"value": 4294967323,
15531553
"names": {
15541554
"web": [
1555-
"Escape"
1555+
"Escape",
1556+
"Esc"
15561557
],
15571558
"macos": [
15581559
"Escape"

dev/tools/gen_keycodes/data/physical_key_data.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1192,6 +1192,9 @@
11921192
"name": "Escape",
11931193
"chromium": "Escape"
11941194
},
1195+
"otherWebCodes": [
1196+
"Esc"
1197+
],
11951198
"scanCodes": {
11961199
"android": [
11971200
1

dev/tools/gen_keycodes/data/supplemental_hid_codes.inc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@
5050
DOM_CODE(0x05ff1e, 0x0000, 0x0000, 0x0000, 0xffff, "GameButtonY", BUTTON_Y),
5151
DOM_CODE(0x05ff1f, 0x0000, 0x0000, 0x0000, 0xffff, "GameButtonZ", BUTTON_Z),
5252

53+
// Sometimes the Escape key produces "Esc" instead of "Escape". This includes
54+
// older IE and Firefox browsers, and the current Cobalt browser.
55+
// See: https://github.com/flutter/flutter/issues/106062
56+
DOM_CODE(0x070029, 0x0000, 0x0000, 0x0000, 0xffff, "Esc", ESCAPE),
57+
5358
// ============================================================
5459
// Fn key for Mac
5560
// ============================================================
@@ -58,4 +63,4 @@
5863
// defined on other platforms. Chromium does define an "Fn" row, but doesn't
5964
// give it a Mac keycode. This overrides their definition.
6065
// USB HID evdev XKB Win Mac DOMKey Code
61-
DOM_CODE(0x000012, 0x0000, 0x0000, 0x0000, 0x003f, "Fn", FN),
66+
DOM_CODE(0x000012, 0x0000, 0x0000, 0x0000, 0x003f, "Fn", FN),

dev/tools/gen_keycodes/data/supplemental_key_data.inc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@
7676
DOM_KEY_UNI("Tilde", TILDE, '~'),
7777
DOM_KEY_UNI("Bar", BAR, '|'),
7878

79+
// ============================================================
80+
// Unprintable keys (Unicode plane)
81+
// ============================================================
82+
83+
// Key Enum Value
84+
// Sometimes the Escape key produces "Esc" instead of "Escape". This includes
85+
// older IE and Firefox browsers, and the current Cobalt browser.
86+
// See: https://github.com/flutter/flutter/issues/106062
87+
DOM_KEY_MAP("Esc", ESC, 0x1B),
88+
7989
// The following keys reside in the Flutter plane (0x0100000000).
8090

8191
// ============================================================

dev/tools/gen_keycodes/lib/keyboard_keys_code_gen.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ $otherComments static const PhysicalKeyboardKey ${entry.constantName} = Physica
7878

7979
/// Gets the generated definitions of LogicalKeyboardKeys.
8080
String get _logicalDefinitions {
81-
final OutputLines<int> lines = OutputLines<int>('Logical debug names');
81+
final OutputLines<int> lines = OutputLines<int>('Logical debug names', behavior: DeduplicateBehavior.kSkip);
8282
void printKey(int flutterId, String constantName, String commentName, {String? otherComments}) {
8383
final String firstComment = _wrapString('Represents the logical "$commentName" key on the keyboard.');
8484
otherComments ??= _wrapString('See the function [RawKeyEvent.logicalKey] for more information.');
@@ -122,7 +122,7 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
122122
}
123123

124124
String get _logicalKeyLabels {
125-
final OutputLines<int> lines = OutputLines<int>('Logical key labels');
125+
final OutputLines<int> lines = OutputLines<int>('Logical key labels', behavior: DeduplicateBehavior.kSkip);
126126
for (final LogicalKeyEntry entry in logicalData.entries) {
127127
lines.add(entry.value, '''
128128
${toHex(entry.value, digits: 11)}: '${entry.commentName}',''');
@@ -141,15 +141,15 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
141141

142142
/// This generates the map of Flutter key codes to logical keys.
143143
String get _predefinedKeyCodeMap {
144-
final OutputLines<int> lines = OutputLines<int>('Logical key map');
144+
final OutputLines<int> lines = OutputLines<int>('Logical key map', behavior: DeduplicateBehavior.kSkip);
145145
for (final LogicalKeyEntry entry in logicalData.entries) {
146146
lines.add(entry.value, ' ${toHex(entry.value, digits: 11)}: ${entry.constantName},');
147147
}
148148
return lines.sortedJoin().trimRight();
149149
}
150150

151151
String get _maskConstantVariables {
152-
final OutputLines<int> lines = OutputLines<int>('Mask constants', checkDuplicate: false);
152+
final OutputLines<int> lines = OutputLines<int>('Mask constants', behavior: DeduplicateBehavior.kKeep);
153153
for (final MaskConstant constant in _maskConstants) {
154154
lines.add(constant.value, '''
155155
${_wrapString(constant.description)} ///

dev/tools/gen_keycodes/lib/keyboard_maps_code_gen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ class KeyboardMapsCodeGenerator extends BaseCodeGenerator {
303303

304304
/// This generates the map of Web KeyboardEvent codes to physical keys.
305305
String get _webPhysicalKeyMap {
306-
final OutputLines<String> lines = OutputLines<String>('Web physical key map');
306+
final OutputLines<String> lines = OutputLines<String>('Web physical key map', behavior: DeduplicateBehavior.kKeep);
307307
for (final PhysicalKeyEntry entry in keyData.entries) {
308308
for (final String webCodes in entry.webCodes()) {
309309
lines.add(entry.name, " '$webCodes': PhysicalKeyboardKey.${entry.constantName},");

dev/tools/gen_keycodes/lib/logical_key_data.dart

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ class LogicalKeyData {
5353
String glfwNameMap,
5454
PhysicalKeyData physicalKeyData,
5555
) {
56-
final Map<String, LogicalKeyEntry> data = <String, LogicalKeyEntry>{};
57-
_readKeyEntries(data, chromiumKeys);
56+
final Map<String, LogicalKeyEntry> data = _readKeyEntries(chromiumKeys);
5857
_readWindowsKeyCodes(data, windowsKeyCodeHeader, parseMapOfListOfString(windowsNameMap));
5958
_readGtkKeyCodes(data, gtkKeyCodeHeader, parseMapOfListOfString(gtkNameMap));
6059
_readAndroidKeyCodes(data, androidKeyCodeHeader, parseMapOfListOfString(androidNameMap));
@@ -130,7 +129,8 @@ class LogicalKeyData {
130129
/// The following format should be mapped to the Flutter plane.
131130
/// Key Enum Character
132131
/// FLUTTER_KEY_MAP("Lang4", LANG4, 0x00013),
133-
static void _readKeyEntries(Map<String, LogicalKeyEntry> data, String input) {
132+
static Map<String, LogicalKeyEntry> _readKeyEntries(String input) {
133+
final Map<int, LogicalKeyEntry> dataByValue = <int, LogicalKeyEntry>{};
134134
final RegExp domKeyRegExp = RegExp(
135135
r'(?<source>DOM|FLUTTER)_KEY_(?<kind>UNI|MAP)\s*\(\s*'
136136
r'"(?<name>[^\s]+?)",\s*'
@@ -162,17 +162,23 @@ class LogicalKeyData {
162162
}
163163

164164
final bool isPrintable = keyLabel != null;
165-
data.putIfAbsent(name, () {
166-
final LogicalKeyEntry entry = LogicalKeyEntry.fromName(
167-
value: toPlane(value, _sourceToPlane(source, isPrintable)),
165+
final int entryValue = toPlane(value, _sourceToPlane(source, isPrintable));
166+
final LogicalKeyEntry entry = dataByValue.putIfAbsent(entryValue, () =>
167+
LogicalKeyEntry.fromName(
168+
value: entryValue,
168169
name: name,
169170
keyLabel: keyLabel,
170-
);
171-
if (source == 'DOM' && !isPrintable)
172-
entry.webNames.add(webName);
173-
return entry;
174-
});
171+
),
172+
);
173+
if (source == 'DOM' && !isPrintable) {
174+
entry.webNames.add(webName);
175+
}
175176
}
177+
return Map<String, LogicalKeyEntry>.fromEntries(
178+
dataByValue.values.map((LogicalKeyEntry entry) =>
179+
MapEntry<String, LogicalKeyEntry>(entry.name, entry),
180+
),
181+
);
176182
}
177183

178184
static void _readMacOsKeyCodes(

dev/tools/gen_keycodes/lib/physical_key_data.dart

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,22 @@ class PhysicalKeyData {
171171
// Skip key that is not actually generated by any keyboard.
172172
continue;
173173
}
174+
final PhysicalKeyEntry? existing = entries[usbHidCode];
175+
// Allow duplicate entries for Fn, which overwrites.
176+
if (existing != null && existing.name != 'Fn') {
177+
// If it's an existing entry, the only thing we currently support is
178+
// to insert an extra DOMKey. The other entries must be empty.
179+
assert(evdevCode == 0
180+
&& xKbScanCode == 0
181+
&& windowsScanCode == 0
182+
&& macScanCode == 0xffff
183+
&& chromiumCode != null
184+
&& chromiumCode.isNotEmpty,
185+
'Duplicate usbHidCode ${existing.usbHidCode} of key ${existing.name} '
186+
'conflicts with existing ${entries[existing.usbHidCode]!.name}.');
187+
existing.otherWebCodes.add(chromiumCode!);
188+
continue;
189+
}
174190
final PhysicalKeyEntry newEntry = PhysicalKeyEntry(
175191
usbHidCode: usbHidCode,
176192
androidScanCodes: nameToAndroidScanCodes[name] ?? <int>[],
@@ -182,15 +198,6 @@ class PhysicalKeyData {
182198
name: name,
183199
chromiumCode: chromiumCode,
184200
);
185-
// Remove duplicates: last one wins, so that supplemental codes
186-
// override.
187-
if (entries.containsKey(newEntry.usbHidCode)) {
188-
// This is expected for Fn. Warn for other keys.
189-
if (newEntry.name != 'Fn') {
190-
print('Duplicate usbHidCode ${newEntry.usbHidCode} of key ${newEntry.name} '
191-
'conflicts with existing ${entries[newEntry.usbHidCode]!.name}. Keeping the new one.');
192-
}
193-
}
194201
entries[newEntry.usbHidCode] = newEntry;
195202
}
196203
return entries.map((int code, PhysicalKeyEntry entry) =>
@@ -216,7 +223,8 @@ class PhysicalKeyEntry {
216223
required this.macOSScanCode,
217224
required this.iOSScanCode,
218225
required this.chromiumCode,
219-
});
226+
List<String>? otherWebCodes,
227+
}) : otherWebCodes = otherWebCodes ?? <String>[];
220228

221229
/// Populates the key from a JSON map.
222230
factory PhysicalKeyEntry.fromJsonMapEntry(Map<String, dynamic> map) {
@@ -232,6 +240,7 @@ class PhysicalKeyEntry {
232240
windowsScanCode: scanCodes['windows'] as int?,
233241
macOSScanCode: scanCodes['macos'] as int?,
234242
iOSScanCode: scanCodes['ios'] as int?,
243+
otherWebCodes: (map['otherWebCodes'] as List<dynamic>?)?.cast<String>(),
235244
);
236245
}
237246

@@ -258,11 +267,14 @@ class PhysicalKeyEntry {
258267
final String name;
259268
/// The Chromium event code for the key.
260269
final String? chromiumCode;
270+
/// Other codes used by Web besides chromiumCode.
271+
final List<String> otherWebCodes;
261272

262273
Iterable<String> webCodes() sync* {
263274
if (chromiumCode != null) {
264275
yield chromiumCode!;
265276
}
277+
yield* otherWebCodes;
266278
}
267279

268280
/// Creates a JSON map from the key data.
@@ -272,6 +284,7 @@ class PhysicalKeyEntry {
272284
'name': name,
273285
'chromium': chromiumCode,
274286
},
287+
'otherWebCodes': otherWebCodes,
275288
'scanCodes': <String, dynamic>{
276289
'android': androidScanCodes,
277290
'usb': usbHidCode,
@@ -323,11 +336,14 @@ class PhysicalKeyEntry {
323336

324337
@override
325338
String toString() {
339+
final String otherWebStr = otherWebCodes.isEmpty
340+
? ''
341+
: ', otherWebCodes: [${otherWebCodes.join(', ')}]';
326342
return """'$constantName': (name: "$name", usbHidCode: ${toHex(usbHidCode)}, """
327343
'linuxScanCode: ${toHex(evdevCode)}, xKbScanCode: ${toHex(xKbScanCode)}, '
328344
'windowsKeyCode: ${toHex(windowsScanCode)}, macOSScanCode: ${toHex(macOSScanCode)}, '
329345
'windowsScanCode: ${toHex(windowsScanCode)}, chromiumSymbolName: $chromiumCode '
330-
'iOSScanCode: ${toHex(iOSScanCode)})';
346+
'iOSScanCode: ${toHex(iOSScanCode)})$otherWebStr';
331347
}
332348

333349
static int compareByUsbHidCode(PhysicalKeyEntry a, PhysicalKeyEntry b) =>

dev/tools/gen_keycodes/lib/testing_key_codes_cc_gen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ constexpr uint64_t kPhysical${_toUpperCammel(entry.constantName)} = ${toHex(entr
3030

3131
/// Gets the generated definitions of PhysicalKeyboardKeys.
3232
String get _logicalDefinitions {
33-
final OutputLines<int> lines = OutputLines<int>('Logical Key list');
33+
final OutputLines<int> lines = OutputLines<int>('Logical Key list', behavior: DeduplicateBehavior.kSkip);
3434
for (final LogicalKeyEntry entry in logicalData.entries) {
3535
lines.add(entry.value, '''
3636
constexpr uint64_t kLogical${_toUpperCammel(entry.constantName)} = ${toHex(entry.value, digits: 11)};''');

dev/tools/gen_keycodes/lib/testing_key_codes_java_gen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class KeyCodesJavaGenerator extends BaseCodeGenerator {
4242

4343
/// Gets the generated definitions of PhysicalKeyboardKeys.
4444
String get _logicalDefinitions {
45-
final OutputLines<int> lines = OutputLines<int>('Logical Key list');
45+
final OutputLines<int> lines = OutputLines<int>('Logical Key list', behavior: DeduplicateBehavior.kSkip);
4646
for (final LogicalKeyEntry entry in logicalData.entries) {
4747
lines.add(entry.value, '''
4848
public static final long LOGICAL_${_toUpperSnake(entry.constantName)} = ${toHex(entry.value, digits: 11)}L;''');

dev/tools/gen_keycodes/lib/utils.dart

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,24 @@ void addNameValue(List<String> names, List<int> values, String name, int value)
233233
}
234234
}
235235

236+
enum DeduplicateBehavior {
237+
// Warn the duplicate entry.
238+
kWarn,
239+
240+
// Skip the latter duplicate entry.
241+
kSkip,
242+
243+
// Keep all duplicate entries.
244+
kKeep,
245+
}
246+
236247
/// The information for a line used by [OutputLines].
237248
class OutputLine<T extends Comparable<Object>> {
238-
const OutputLine(this.key, this.value);
249+
OutputLine(this.key, String value)
250+
: values = <String>[value];
239251

240252
final T key;
241-
final String value;
253+
final List<String> values;
242254
}
243255

244256
/// A utility class to build join a number of lines in a sorted order.
@@ -247,41 +259,43 @@ class OutputLine<T extends Comparable<Object>> {
247259
/// get the joined string of these lines joined sorting them in the order of the
248260
/// index.
249261
class OutputLines<T extends Comparable<Object>> {
250-
OutputLines(this.mapName, {this.checkDuplicate = true});
262+
OutputLines(this.mapName, {this.behavior = DeduplicateBehavior.kWarn});
251263

252-
/// If true, then lines with duplicate keys will be warned and discarded.
253-
///
254-
/// Default to true.
255-
final bool checkDuplicate;
264+
/// What to do if there are entries with the same key.
265+
final DeduplicateBehavior behavior;
256266

257267
/// The name for this map.
258268
///
259269
/// Used in warning messages.
260270
final String mapName;
261271

262-
final Set<T> keys = <T>{};
263-
final List<OutputLine<T>> lines = <OutputLine<T>>[];
272+
final Map<T, OutputLine<T>> lines = <T, OutputLine<T>>{};
264273

265-
void add(T code, String line) {
266-
if (checkDuplicate) {
267-
if (keys.contains(code)) {
268-
final OutputLine<T> existing = lines.firstWhere((OutputLine<T> line) => line.key == code);
269-
print('Warn: $mapName is requested to add line $code as:\n $line\n but it already exists as:\n ${existing.value}');
270-
return;
274+
void add(T key, String line) {
275+
final OutputLine<T>? existing = lines[key];
276+
if (existing != null) {
277+
switch (behavior) {
278+
case DeduplicateBehavior.kWarn:
279+
print('Warn: Request to add $key to map "$mapName" as:\n $line\n but it already exists as:\n ${existing.values[0]}');
280+
return;
281+
case DeduplicateBehavior.kSkip:
282+
return;
283+
case DeduplicateBehavior.kKeep:
284+
existing.values.add(line);
285+
return;
271286
}
272-
keys.add(code);
273287
}
274-
lines.add(OutputLine<T>(code, line));
288+
lines[key] = OutputLine<T>(key, line);
275289
}
276290

277291
String join() {
278-
return lines.map((OutputLine<T> line) => line.value).join('\n');
292+
return lines.values.map((OutputLine<T> line) => line.values.join('\n')).join('\n');
279293
}
280294

281295
String sortedJoin() {
282-
return (lines.sublist(0)
296+
return (lines.values.toList()
283297
..sort((OutputLine<T> a, OutputLine<T> b) => a.key.compareTo(b.key)))
284-
.map((OutputLine<T> line) => line.value)
298+
.map((OutputLine<T> line) => line.values.join('\n'))
285299
.join('\n');
286300
}
287301
}

packages/flutter/lib/src/services/keyboard_maps.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2211,6 +2211,7 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
22112211
'EndCall': LogicalKeyboardKey.endCall,
22122212
'Enter': LogicalKeyboardKey.enter,
22132213
'EraseEof': LogicalKeyboardKey.eraseEof,
2214+
'Esc': LogicalKeyboardKey.escape,
22142215
'Escape': LogicalKeyboardKey.escape,
22152216
'ExSel': LogicalKeyboardKey.exSel,
22162217
'Execute': LogicalKeyboardKey.execute,
@@ -2495,6 +2496,7 @@ const Map<String, PhysicalKeyboardKey> kWebToPhysicalKey = <String, PhysicalKeyb
24952496
'Enter': PhysicalKeyboardKey.enter,
24962497
'Equal': PhysicalKeyboardKey.equal,
24972498
'Escape': PhysicalKeyboardKey.escape,
2499+
'Esc': PhysicalKeyboardKey.escape,
24982500
'F1': PhysicalKeyboardKey.f1,
24992501
'F10': PhysicalKeyboardKey.f10,
25002502
'F11': PhysicalKeyboardKey.f11,

packages/flutter/test/services/raw_keyboard_test.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2680,6 +2680,23 @@ void main() {
26802680
expect(data.keyCode, equals(0x10));
26812681
});
26822682

2683+
test('Esc keys generated by older browsers are correctly translated', () {
2684+
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, Object?>{
2685+
'type': 'keydown',
2686+
'keymap': 'web',
2687+
'code': 'Esc',
2688+
'key': 'Esc',
2689+
'location': 0,
2690+
'metaState': 0x0,
2691+
'keyCode': 0x1B,
2692+
});
2693+
final RawKeyEventDataWeb data = escapeKeyEvent.data as RawKeyEventDataWeb;
2694+
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
2695+
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
2696+
expect(data.keyLabel, isEmpty);
2697+
expect(data.keyCode, equals(0x1B));
2698+
});
2699+
26832700
test('Arrow keys from a keyboard give correct physical key mappings', () {
26842701
final RawKeyEvent arrowKeyDown = RawKeyEvent.fromMessage(const <String, Object?>{
26852702
'type': 'keydown',

0 commit comments

Comments
 (0)