mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[Keyboard, Web] Map from "Esc" to the Escape key (#106133)
* Impl * Fix build * Add test
This commit is contained in:
parent
df55dbb9d9
commit
0ac05f1c5c
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user