Unverified Commit 0ac05f1c authored by Tong Mu's avatar Tong Mu Committed by GitHub

[Keyboard, Web] Map from "Esc" to the Escape key (#106133)

* Impl

* Fix build

* Add test
parent df55dbb9
......@@ -1552,7 +1552,8 @@
"value": 4294967323,
"names": {
"web": [
"Escape"
"Escape",
"Esc"
],
"macos": [
"Escape"
......
......@@ -1192,6 +1192,9 @@
"name": "Escape",
"chromium": "Escape"
},
"otherWebCodes": [
"Esc"
],
"scanCodes": {
"android": [
1
......
......@@ -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),
......@@ -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).
// ============================================================
......
......@@ -78,7 +78,7 @@ $otherComments static const PhysicalKeyboardKey ${entry.constantName} = Physica
/// 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 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
}
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 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
/// 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 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
}
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)} ///
......
......@@ -303,7 +303,7 @@ class KeyboardMapsCodeGenerator extends BaseCodeGenerator {
/// 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},");
......
......@@ -53,8 +53,7 @@ class LogicalKeyData {
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 @@ 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<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 @@ 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<String, LogicalKeyEntry>.fromEntries(
dataByValue.values.map((LogicalKeyEntry entry) =>
MapEntry<String, LogicalKeyEntry>(entry.name, entry),
),
);
}
static void _readMacOsKeyCodes(
......
......@@ -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] ?? <int>[],
......@@ -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<String>? otherWebCodes,
}) : otherWebCodes = otherWebCodes ?? <String>[];
/// Populates the key from a JSON map.
factory PhysicalKeyEntry.fromJsonMapEntry(Map<String, dynamic> 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<dynamic>?)?.cast<String>(),
);
}
......@@ -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<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 @@ class PhysicalKeyEntry {
'name': name,
'chromium': chromiumCode,
},
'otherWebCodes': otherWebCodes,
'scanCodes': <String, dynamic>{
'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) =>
......
......@@ -30,7 +30,7 @@ constexpr uint64_t kPhysical${_toUpperCammel(entry.constantName)} = ${toHex(entr
/// 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)};''');
......
......@@ -42,7 +42,7 @@ class KeyCodesJavaGenerator extends BaseCodeGenerator {
/// 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;''');
......
......@@ -233,12 +233,24 @@ void addNameValue(List<String> names, List<int> 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<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 @@ class OutputLine<T extends Comparable<Object>> {
/// 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');
}
}
......
......@@ -2211,6 +2211,7 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'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 @@ const Map<String, PhysicalKeyboardKey> kWebToPhysicalKey = <String, PhysicalKeyb
'Enter': PhysicalKeyboardKey.enter,
'Equal': PhysicalKeyboardKey.equal,
'Escape': PhysicalKeyboardKey.escape,
'Esc': PhysicalKeyboardKey.escape,
'F1': PhysicalKeyboardKey.f1,
'F10': PhysicalKeyboardKey.f10,
'F11': PhysicalKeyboardKey.f11,
......
......@@ -2680,6 +2680,23 @@ void main() {
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',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment