mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
657 lines
25 KiB
Dart
657 lines
25 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import 'constants.dart';
|
|
import 'physical_key_data.dart';
|
|
import 'utils.dart';
|
|
|
|
bool _isControlCharacter(int codeUnit) {
|
|
return (codeUnit <= 0x1f && codeUnit >= 0x00) || (codeUnit >= 0x7f && codeUnit <= 0x9f);
|
|
}
|
|
|
|
/// A pair of strings that represents left and right modifiers.
|
|
class _ModifierPair {
|
|
const _ModifierPair(this.left, this.right);
|
|
|
|
final String left;
|
|
final String right;
|
|
}
|
|
|
|
// Return map[key1][key2] as a non-nullable List<T>, where both map[key1] or
|
|
// map[key1][key2] might be null.
|
|
List<T> _getGrandchildList<T>(Map<String, dynamic> map, String key1, String key2) {
|
|
final dynamic value = (map[key1] as Map<String, dynamic>?)?[key2];
|
|
final List<dynamic>? dynamicNullableList = value as List<dynamic>?;
|
|
final List<dynamic> dynamicList = dynamicNullableList ?? <dynamic>[];
|
|
return dynamicList.cast<T>();
|
|
}
|
|
|
|
/// The data structure used to manage keyboard key entries.
|
|
///
|
|
/// The main constructor parses the given input data into the data structure.
|
|
///
|
|
/// The data structure can be also loaded and saved to JSON, with the
|
|
/// [LogicalKeyData.fromJson] constructor and [toJson] method, respectively.
|
|
class LogicalKeyData {
|
|
factory LogicalKeyData(
|
|
String chromiumKeys,
|
|
String gtkKeyCodeHeader,
|
|
String gtkNameMap,
|
|
String windowsKeyCodeHeader,
|
|
String windowsNameMap,
|
|
String androidKeyCodeHeader,
|
|
String androidNameMap,
|
|
String macosLogicalToPhysical,
|
|
String iosLogicalToPhysical,
|
|
String glfwHeaderFile,
|
|
String glfwNameMap,
|
|
PhysicalKeyData physicalKeyData,
|
|
) {
|
|
final Map<String, LogicalKeyEntry> data = _readKeyEntries(chromiumKeys);
|
|
_readWindowsKeyCodes(data, windowsKeyCodeHeader, parseMapOfListOfString(windowsNameMap));
|
|
_readGtkKeyCodes(data, gtkKeyCodeHeader, parseMapOfListOfString(gtkNameMap));
|
|
_readAndroidKeyCodes(data, androidKeyCodeHeader, parseMapOfListOfString(androidNameMap));
|
|
_readMacOsKeyCodes(data, physicalKeyData, parseMapOfListOfString(macosLogicalToPhysical));
|
|
_readIosKeyCodes(data, physicalKeyData, parseMapOfListOfString(iosLogicalToPhysical));
|
|
_readFuchsiaKeyCodes(data, physicalKeyData);
|
|
_readGlfwKeyCodes(data, glfwHeaderFile, parseMapOfListOfString(glfwNameMap));
|
|
// Sort entries by value
|
|
final List<MapEntry<String, LogicalKeyEntry>> sortedEntries = data.entries.toList()..sort(
|
|
(MapEntry<String, LogicalKeyEntry> a, MapEntry<String, LogicalKeyEntry> b) =>
|
|
LogicalKeyEntry.compareByValue(a.value, b.value),
|
|
);
|
|
data
|
|
..clear()
|
|
..addEntries(sortedEntries);
|
|
return LogicalKeyData._(data);
|
|
}
|
|
|
|
/// Parses the given JSON data and populates the data structure from it.
|
|
factory LogicalKeyData.fromJson(Map<String, dynamic> contentMap) {
|
|
final Map<String, LogicalKeyEntry> data = <String, LogicalKeyEntry>{};
|
|
data.addEntries(contentMap.values.map((dynamic value) {
|
|
final LogicalKeyEntry entry = LogicalKeyEntry.fromJsonMapEntry(value as Map<String, dynamic>);
|
|
return MapEntry<String, LogicalKeyEntry>(entry.name, entry);
|
|
}));
|
|
return LogicalKeyData._(data);
|
|
}
|
|
|
|
/// Parses the input data given in from the various data source files,
|
|
/// populating the data structure.
|
|
///
|
|
/// None of the parameters may be null.
|
|
LogicalKeyData._(this._data);
|
|
|
|
/// Converts the data structure into a JSON structure that can be parsed by
|
|
/// [LogicalKeyData.fromJson].
|
|
Map<String, dynamic> toJson() {
|
|
final Map<String, dynamic> outputMap = <String, dynamic>{};
|
|
for (final LogicalKeyEntry entry in _data.values) {
|
|
outputMap[entry.name] = entry.toJson();
|
|
}
|
|
return outputMap;
|
|
}
|
|
|
|
/// Find an entry from name.
|
|
///
|
|
/// Asserts if the name is not found.
|
|
LogicalKeyEntry entryByName(String name) {
|
|
assert(_data.containsKey(name),
|
|
'Unable to find logical entry by name $name.');
|
|
return _data[name]!;
|
|
}
|
|
|
|
/// All entries.
|
|
Iterable<LogicalKeyEntry> get entries => _data.values;
|
|
|
|
// Keys mapped from their names.
|
|
final Map<String, LogicalKeyEntry> _data;
|
|
|
|
/// Parses entries from Chromium's key mapping header file.
|
|
///
|
|
/// Lines in this file look like either of these (without the ///):
|
|
/// Key Enum Unicode code point
|
|
/// DOM_KEY_UNI("Backspace", BACKSPACE, 0x0008),
|
|
/// Key Enum Value
|
|
/// DOM_KEY_MAP("Accel", ACCEL, 0x0101),
|
|
///
|
|
/// Flutter's supplemental_key_data.inc also has some new formats.
|
|
/// The following format uses a character as the 3rd argument.
|
|
/// Key Enum Character
|
|
/// DOM_KEY_UNI("KeyB", KEY_B, 'b'),
|
|
///
|
|
/// The following format should be mapped to the Flutter plane.
|
|
/// Key Enum Character
|
|
/// FLUTTER_KEY_MAP("Lang4", LANG4, 0x00013),
|
|
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*'
|
|
r'(?<enum>[^\s]+?),\s*'
|
|
r"(?:0[xX](?<unicode>[a-fA-F0-9]+)|'(?<char>.)')\s*"
|
|
r'\)',
|
|
// Multiline is necessary because some definitions spread across
|
|
// multiple lines.
|
|
multiLine: true,
|
|
);
|
|
final RegExp commentRegExp = RegExp(r'//.*$', multiLine: true);
|
|
input = input.replaceAll(commentRegExp, '');
|
|
for (final RegExpMatch match in domKeyRegExp.allMatches(input)) {
|
|
final String source = match.namedGroup('source')!;
|
|
final String webName = match.namedGroup('name')!;
|
|
// ".AltGraphLatch" is consumed internally and not expressed to the Web.
|
|
if (webName.startsWith('.')) {
|
|
continue;
|
|
}
|
|
final String name = LogicalKeyEntry.computeName(webName.replaceAll(RegExp('[^A-Za-z0-9]'), ''));
|
|
final int value = match.namedGroup('unicode') != null ?
|
|
getHex(match.namedGroup('unicode')!) :
|
|
match.namedGroup('char')!.codeUnitAt(0);
|
|
final String? keyLabel = (match.namedGroup('kind')! == 'UNI' && !_isControlCharacter(value)) ?
|
|
String.fromCharCode(value) : null;
|
|
// Skip modifier keys from DOM. They will be added with supplemental data.
|
|
if (_chromeModifiers.containsKey(name) && source == 'DOM') {
|
|
continue;
|
|
}
|
|
|
|
final bool isPrintable = keyLabel != null;
|
|
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 Map<String, LogicalKeyEntry>.fromEntries(
|
|
dataByValue.values.map((LogicalKeyEntry entry) =>
|
|
MapEntry<String, LogicalKeyEntry>(entry.name, entry),
|
|
),
|
|
);
|
|
}
|
|
|
|
static void _readMacOsKeyCodes(
|
|
Map<String, LogicalKeyEntry> data,
|
|
PhysicalKeyData physicalKeyData,
|
|
Map<String, List<String>> logicalToPhysical,
|
|
) {
|
|
final Map<String, String> physicalToLogical = reverseMapOfListOfString(logicalToPhysical,
|
|
(String logicalKeyName, String physicalKeyName) { print('Duplicate logical key name $logicalKeyName for macOS'); });
|
|
|
|
physicalToLogical.forEach((String physicalKeyName, String logicalKeyName) {
|
|
final PhysicalKeyEntry physicalEntry = physicalKeyData.entryByName(physicalKeyName);
|
|
assert(physicalEntry.macOSScanCode != null,
|
|
'Physical entry $physicalKeyName does not have a macOSScanCode.');
|
|
final LogicalKeyEntry? logicalEntry = data[logicalKeyName];
|
|
assert(logicalEntry != null,
|
|
'Unable to find logical entry by name $logicalKeyName.');
|
|
logicalEntry!.macOSKeyCodeNames.add(physicalEntry.name);
|
|
logicalEntry.macOSKeyCodeValues.add(physicalEntry.macOSScanCode!);
|
|
});
|
|
}
|
|
|
|
static void _readIosKeyCodes(
|
|
Map<String, LogicalKeyEntry> data,
|
|
PhysicalKeyData physicalKeyData,
|
|
Map<String, List<String>> logicalToPhysical,
|
|
) {
|
|
final Map<String, String> physicalToLogical = reverseMapOfListOfString(logicalToPhysical,
|
|
(String logicalKeyName, String physicalKeyName) { print('Duplicate logical key name $logicalKeyName for iOS'); });
|
|
|
|
physicalToLogical.forEach((String physicalKeyName, String logicalKeyName) {
|
|
final PhysicalKeyEntry physicalEntry = physicalKeyData.entryByName(physicalKeyName);
|
|
assert(physicalEntry.iOSScanCode != null,
|
|
'Physical entry $physicalKeyName does not have an iosScanCode.');
|
|
final LogicalKeyEntry? logicalEntry = data[logicalKeyName];
|
|
assert(logicalEntry != null,
|
|
'Unable to find logical entry by name $logicalKeyName.');
|
|
logicalEntry!.iOSKeyCodeNames.add(physicalEntry.name);
|
|
logicalEntry.iOSKeyCodeValues.add(physicalEntry.iOSScanCode!);
|
|
});
|
|
}
|
|
|
|
/// Parses entries from GTK's gdkkeysyms.h key code data file.
|
|
///
|
|
/// Lines in this file look like this (without the ///):
|
|
/// /** Space key. */
|
|
/// #define GDK_KEY_space 0x020
|
|
static void _readGtkKeyCodes(Map<String, LogicalKeyEntry> data, String headerFile, Map<String, List<String>> nameToGtkName) {
|
|
final RegExp definedCodes = RegExp(
|
|
r'#define '
|
|
r'GDK_KEY_(?<name>[a-zA-Z0-9_]+)\s*'
|
|
r'0x(?<value>[0-9a-f]+),?',
|
|
);
|
|
final Map<String, String> gtkNameToFlutterName = reverseMapOfListOfString(nameToGtkName,
|
|
(String flutterName, String gtkName) { print('Duplicate GTK logical name $gtkName'); });
|
|
|
|
for (final RegExpMatch match in definedCodes.allMatches(headerFile)) {
|
|
final String gtkName = match.namedGroup('name')!;
|
|
final String? name = gtkNameToFlutterName[gtkName];
|
|
final int value = int.parse(match.namedGroup('value')!, radix: 16);
|
|
if (name == null) {
|
|
// print('Unmapped GTK logical entry $gtkName');
|
|
continue;
|
|
}
|
|
|
|
final LogicalKeyEntry? entry = data[name];
|
|
if (entry == null) {
|
|
print('Invalid logical entry by name $name (from GTK $gtkName)');
|
|
continue;
|
|
}
|
|
entry
|
|
..gtkNames.add(gtkName)
|
|
..gtkValues.add(value);
|
|
}
|
|
}
|
|
|
|
static void _readWindowsKeyCodes(Map<String, LogicalKeyEntry> data, String headerFile, Map<String, List<String>> nameMap) {
|
|
// The mapping from the Flutter name (e.g. "enter") to the Windows name (e.g.
|
|
// "RETURN").
|
|
final Map<String, String> nameToFlutterName = reverseMapOfListOfString(nameMap,
|
|
(String flutterName, String windowsName) { print('Duplicate Windows logical name $windowsName'); });
|
|
|
|
final RegExp definedCodes = RegExp(
|
|
r'define '
|
|
r'VK_(?<name>[A-Z0-9_]+)\s*'
|
|
r'(?<value>[A-Z0-9_x]+),?',
|
|
);
|
|
for (final RegExpMatch match in definedCodes.allMatches(headerFile)) {
|
|
final String windowsName = match.namedGroup('name')!;
|
|
final String? name = nameToFlutterName[windowsName];
|
|
final int value = int.tryParse(match.namedGroup('value')!)!;
|
|
if (name == null) {
|
|
print('Unmapped Windows logical entry $windowsName');
|
|
continue;
|
|
}
|
|
final LogicalKeyEntry? entry = data[name];
|
|
if (entry == null) {
|
|
print('Invalid logical entry by name $name (from Windows $windowsName)');
|
|
continue;
|
|
}
|
|
addNameValue(
|
|
entry.windowsNames,
|
|
entry.windowsValues,
|
|
windowsName,
|
|
value,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Parses entries from Android's keycodes.h key code data file.
|
|
///
|
|
/// Lines in this file look like this (without the ///):
|
|
/// /** Left Control modifier key. */
|
|
/// AKEYCODE_CTRL_LEFT = 113,
|
|
static void _readAndroidKeyCodes(Map<String, LogicalKeyEntry> data, String headerFile, Map<String, List<String>> nameMap) {
|
|
final Map<String, String> nameToFlutterName = reverseMapOfListOfString(nameMap,
|
|
(String flutterName, String androidName) { print('Duplicate Android logical name $androidName'); });
|
|
|
|
final RegExp enumBlock = RegExp(r'enum\s*\{(.*)\};', multiLine: true);
|
|
// Eliminate everything outside of the enum block.
|
|
headerFile = headerFile.replaceAllMapped(enumBlock, (Match match) => match.group(1)!);
|
|
final RegExp enumEntry = RegExp(
|
|
r'AKEYCODE_(?<name>[A-Z0-9_]+)\s*'
|
|
r'=\s*'
|
|
r'(?<value>[0-9]+),?',
|
|
);
|
|
for (final RegExpMatch match in enumEntry.allMatches(headerFile)) {
|
|
final String androidName = match.namedGroup('name')!;
|
|
final String? name = nameToFlutterName[androidName];
|
|
final int value = int.tryParse(match.namedGroup('value')!)!;
|
|
if (name == null) {
|
|
print('Unmapped Android logical entry $androidName');
|
|
continue;
|
|
}
|
|
final LogicalKeyEntry? entry = data[name];
|
|
if (entry == null) {
|
|
print('Invalid logical entry by name $name (from Android $androidName)');
|
|
continue;
|
|
}
|
|
entry
|
|
..androidNames.add(androidName)
|
|
..androidValues.add(value);
|
|
}
|
|
}
|
|
|
|
static void _readFuchsiaKeyCodes(Map<String, LogicalKeyEntry> data, PhysicalKeyData physicalData) {
|
|
for (final LogicalKeyEntry entry in data.values) {
|
|
final int? value = (() {
|
|
if (entry.value == 0) // "None" key
|
|
return 0;
|
|
final String? keyLabel = printable[entry.constantName];
|
|
if (keyLabel != null && !entry.constantName.startsWith('numpad')) {
|
|
return toPlane(keyLabel.codeUnitAt(0), kUnicodePlane.value);
|
|
} else {
|
|
final PhysicalKeyEntry? physicalEntry = physicalData.tryEntryByName(entry.name);
|
|
if (physicalEntry != null) {
|
|
return toPlane(physicalEntry.usbHidCode, kFuchsiaPlane.value);
|
|
}
|
|
}
|
|
})();
|
|
if (value != null)
|
|
entry.fuchsiaValues.add(value);
|
|
}
|
|
}
|
|
|
|
/// Parses entries from GLFW's keycodes.h key code data file.
|
|
///
|
|
/// Lines in this file look like this (without the ///):
|
|
/// /** Space key. */
|
|
/// #define GLFW_KEY_SPACE 32,
|
|
/// #define GLFW_KEY_LAST GLFW_KEY_MENU
|
|
static void _readGlfwKeyCodes(Map<String, LogicalKeyEntry> data, String headerFile, Map<String, List<String>> nameMap) {
|
|
final Map<String, String> nameToFlutterName = reverseMapOfListOfString(nameMap,
|
|
(String flutterName, String glfwName) { print('Duplicate GLFW logical name $glfwName'); });
|
|
|
|
// Only get the KEY definitions, ignore the rest (mouse, joystick, etc).
|
|
final RegExp definedCodes = RegExp(
|
|
r'define\s+'
|
|
r'GLFW_KEY_(?<name>[A-Z0-9_]+)\s+'
|
|
r'(?<value>[A-Z0-9_]+),?',
|
|
);
|
|
final Map<String, dynamic> replaced = <String, dynamic>{};
|
|
for (final RegExpMatch match in definedCodes.allMatches(headerFile)) {
|
|
final String name = match.namedGroup('name')!;
|
|
final String value = match.namedGroup('value')!;
|
|
replaced[name] = int.tryParse(value) ?? value.replaceAll('GLFW_KEY_', '');
|
|
}
|
|
final Map<String, int> glfwNameToKeyCode = <String, int>{};
|
|
replaced.forEach((String key, dynamic value) {
|
|
// Some definition values point to other definitions (e.g #define GLFW_KEY_LAST GLFW_KEY_MENU).
|
|
if (value is String) {
|
|
glfwNameToKeyCode[key] = replaced[value] as int;
|
|
} else {
|
|
glfwNameToKeyCode[key] = value as int;
|
|
}
|
|
});
|
|
|
|
glfwNameToKeyCode.forEach((String glfwName, int value) {
|
|
final String? name = nameToFlutterName[glfwName];
|
|
if (name == null) {
|
|
return;
|
|
}
|
|
final LogicalKeyEntry? entry = data[nameToFlutterName[glfwName]];
|
|
if (entry == null) {
|
|
print('Invalid logical entry by name $name (from GLFW $glfwName)');
|
|
return;
|
|
}
|
|
addNameValue(
|
|
entry.glfwNames,
|
|
entry.glfwValues,
|
|
glfwName,
|
|
value,
|
|
);
|
|
});
|
|
}
|
|
|
|
// Map Web key to the pair of key names
|
|
static final Map<String, _ModifierPair> _chromeModifiers = () {
|
|
final String rawJson = File(path.join(dataRoot, 'chromium_modifiers.json',)).readAsStringSync();
|
|
return (json.decode(rawJson) as Map<String, dynamic>).map((String key, dynamic value) {
|
|
final List<dynamic> pair = value as List<dynamic>;
|
|
return MapEntry<String, _ModifierPair>(key, _ModifierPair(pair[0] as String, pair[1] as String));
|
|
});
|
|
}();
|
|
|
|
/// Returns the static map of printable representations.
|
|
static final Map<String, String> printable = (() {
|
|
final String printableKeys = File(path.join(dataRoot, 'printable.json',)).readAsStringSync();
|
|
return (json.decode(printableKeys) as Map<String, dynamic>)
|
|
.cast<String, String>();
|
|
})();
|
|
|
|
/// Returns the static map of synonym representations.
|
|
///
|
|
/// These include synonyms for keys which don't have printable
|
|
/// representations, and appear in more than one place on the keyboard (e.g.
|
|
/// SHIFT, ALT, etc.).
|
|
static final Map<String, List<String>> synonyms = (() {
|
|
final String synonymKeys = File(path.join(dataRoot, 'synonyms.json',)).readAsStringSync();
|
|
final Map<String, dynamic> dynamicSynonym = json.decode(synonymKeys) as Map<String, dynamic>;
|
|
return dynamicSynonym.map((String name, dynamic values) {
|
|
// The keygen and algorithm of macOS relies on synonyms being pairs.
|
|
// See siblingKeyMap in macos_code_gen.dart.
|
|
final List<String> names = (values as List<dynamic>).whereType<String>().toList();
|
|
assert(names.length == 2);
|
|
return MapEntry<String, List<String>>(name, names);
|
|
});
|
|
})();
|
|
|
|
static int _sourceToPlane(String source, bool isPrintable) {
|
|
if (isPrintable)
|
|
return kUnicodePlane.value;
|
|
switch (source) {
|
|
case 'DOM':
|
|
return kUnprintablePlane.value;
|
|
case 'FLUTTER':
|
|
return kFlutterPlane.value;
|
|
default:
|
|
assert(false, 'Unrecognized logical key source $source');
|
|
return kFlutterPlane.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// A single entry in the key data structure.
|
|
///
|
|
/// Can be read from JSON with the [LogicalKeyEntry.fromJsonMapEntry] constructor, or
|
|
/// written with the [toJson] method.
|
|
class LogicalKeyEntry {
|
|
/// Creates a single key entry from available data.
|
|
LogicalKeyEntry({
|
|
required this.value,
|
|
required this.name,
|
|
this.keyLabel,
|
|
}) : webNames = <String>[],
|
|
macOSKeyCodeNames = <String>[],
|
|
macOSKeyCodeValues = <int>[],
|
|
iOSKeyCodeNames = <String>[],
|
|
iOSKeyCodeValues = <int>[],
|
|
gtkNames = <String>[],
|
|
gtkValues = <int>[],
|
|
windowsNames = <String>[],
|
|
windowsValues = <int>[],
|
|
androidNames = <String>[],
|
|
androidValues = <int>[],
|
|
fuchsiaValues = <int>[],
|
|
glfwNames = <String>[],
|
|
glfwValues = <int>[];
|
|
|
|
LogicalKeyEntry.fromName({
|
|
required int value,
|
|
required String name,
|
|
String? keyLabel,
|
|
}) : this(
|
|
value: value,
|
|
name: name,
|
|
keyLabel: keyLabel,
|
|
);
|
|
|
|
/// Populates the key from a JSON map.
|
|
LogicalKeyEntry.fromJsonMapEntry(Map<String, dynamic> map)
|
|
: value = map['value'] as int,
|
|
name = map['name'] as String,
|
|
webNames = _getGrandchildList<String>(map, 'names', 'web'),
|
|
macOSKeyCodeNames = _getGrandchildList<String>(map, 'names', 'macos'),
|
|
macOSKeyCodeValues = _getGrandchildList<int>(map, 'values', 'macos'),
|
|
iOSKeyCodeNames = _getGrandchildList<String>(map, 'names', 'ios'),
|
|
iOSKeyCodeValues = _getGrandchildList<int>(map, 'values', 'ios'),
|
|
gtkNames = _getGrandchildList<String>(map, 'names', 'gtk'),
|
|
gtkValues = _getGrandchildList<int>(map, 'values', 'gtk'),
|
|
windowsNames = _getGrandchildList<String>(map, 'names', 'windows'),
|
|
windowsValues = _getGrandchildList<int>(map, 'values', 'windows'),
|
|
androidNames = _getGrandchildList<String>(map, 'names', 'android'),
|
|
androidValues = _getGrandchildList<int>(map, 'values', 'android'),
|
|
fuchsiaValues = _getGrandchildList<int>(map, 'values', 'fuchsia'),
|
|
glfwNames = _getGrandchildList<String>(map, 'names', 'glfw'),
|
|
glfwValues = _getGrandchildList<int>(map, 'values', 'glfw'),
|
|
keyLabel = map['keyLabel'] as String?;
|
|
|
|
final int value;
|
|
|
|
final String name;
|
|
|
|
/// The name of the key suitable for placing in comments.
|
|
String get commentName => computeCommentName(name);
|
|
|
|
String get constantName => computeConstantName(commentName);
|
|
|
|
/// The name of the key, mostly derived from the DomKey name in Chromium,
|
|
/// but where there was no DomKey representation, derived from the Chromium
|
|
/// symbol name.
|
|
final List<String> webNames;
|
|
|
|
/// The names of the key codes that corresponds to this logical key on macOS,
|
|
/// created from the corresponding physical keys.
|
|
final List<String> macOSKeyCodeNames;
|
|
|
|
/// The key codes that corresponds to this logical key on macOS, created from
|
|
/// the physical key list substituted with the key mapping.
|
|
final List<int> macOSKeyCodeValues;
|
|
|
|
/// The names of the key codes that corresponds to this logical key on iOS,
|
|
/// created from the corresponding physical keys.
|
|
final List<String> iOSKeyCodeNames;
|
|
|
|
/// The key codes that corresponds to this logical key on iOS, created from the
|
|
/// physical key list substituted with the key mapping.
|
|
final List<int> iOSKeyCodeValues;
|
|
|
|
/// The list of names that GTK gives to this key (symbol names minus the
|
|
/// prefix).
|
|
final List<String> gtkNames;
|
|
|
|
/// The list of GTK key codes matching this key, created by looking up the
|
|
/// Linux name in the GTK data, and substituting the GTK key code
|
|
/// value.
|
|
final List<int> gtkValues;
|
|
|
|
/// The list of names that Windows gives to this key (symbol names minus the
|
|
/// prefix).
|
|
final List<String> windowsNames;
|
|
|
|
/// The list of Windows key codes matching this key, created by looking up the
|
|
/// Windows name in the Chromium data, and substituting the Windows key code
|
|
/// value.
|
|
final List<int> windowsValues;
|
|
|
|
/// The list of names that Android gives to this key (symbol names minus the
|
|
/// prefix).
|
|
final List<String> androidNames;
|
|
|
|
/// The list of Android key codes matching this key, created by looking up the
|
|
/// Android name in the Chromium data, and substituting the Android key code
|
|
/// value.
|
|
final List<int> androidValues;
|
|
|
|
final List<int> fuchsiaValues;
|
|
|
|
/// The list of names that GLFW gives to this key (symbol names minus the
|
|
/// prefix).
|
|
final List<String> glfwNames;
|
|
|
|
/// The list of GLFW key codes matching this key, created by looking up the
|
|
/// GLFW name in the Chromium data, and substituting the GLFW key code
|
|
/// value.
|
|
final List<int> glfwValues;
|
|
|
|
/// A string indicating the letter on the keycap of a letter key.
|
|
///
|
|
/// This is only used to generate the key label mapping in keyboard_map.dart.
|
|
/// [LogicalKeyboardKey.keyLabel] uses a different definition and is generated
|
|
/// differently.
|
|
final String? keyLabel;
|
|
|
|
/// Creates a JSON map from the key data.
|
|
Map<String, dynamic> toJson() {
|
|
return removeEmptyValues(<String, dynamic>{
|
|
'name': name,
|
|
'value': value,
|
|
'keyLabel': keyLabel,
|
|
'names': <String, dynamic>{
|
|
'web': webNames,
|
|
'macos': macOSKeyCodeNames,
|
|
'ios': iOSKeyCodeNames,
|
|
'gtk': gtkNames,
|
|
'windows': windowsNames,
|
|
'android': androidNames,
|
|
'glfw': glfwNames,
|
|
},
|
|
'values': <String, List<int>>{
|
|
'macos': macOSKeyCodeValues,
|
|
'ios': iOSKeyCodeValues,
|
|
'gtk': gtkValues,
|
|
'windows': windowsValues,
|
|
'android': androidValues,
|
|
'fuchsia': fuchsiaValues,
|
|
'glfw': glfwValues,
|
|
},
|
|
});
|
|
}
|
|
|
|
@override
|
|
String toString() {
|
|
return "'$name': (value: ${toHex(value)}) ";
|
|
}
|
|
|
|
/// Gets the named used for the key constant in the definitions in
|
|
/// keyboard_key.dart.
|
|
///
|
|
/// If set by the constructor, returns the name set, but otherwise constructs
|
|
/// the name from the various different names available, making sure that the
|
|
/// name isn't a Dart reserved word (if it is, then it adds the word "Key" to
|
|
/// the end of the name).
|
|
static String computeName(String rawName) {
|
|
final String result = rawName.replaceAll('PinP', 'PInP');
|
|
if (kDartReservedWords.contains(result)) {
|
|
return '${result}Key';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/// Takes the [name] and converts it from lower camel case to capitalized
|
|
/// separate words (e.g. "wakeUp" converts to "Wake Up").
|
|
static String computeCommentName(String name) {
|
|
final String replaced = name.replaceAllMapped(
|
|
RegExp(r'(Digit|Numpad|Lang|Button|Left|Right)([0-9]+)'), (Match match) => '${match.group(1)} ${match.group(2)}',
|
|
);
|
|
return replaced
|
|
// 'fooBar' => 'foo Bar', 'fooBAR' => 'foo BAR'
|
|
.replaceAllMapped(RegExp(r'([^A-Z])([A-Z])'), (Match match) => '${match.group(1)} ${match.group(2)}')
|
|
// 'ABCDoo' => 'ABC Doo'
|
|
.replaceAllMapped(RegExp(r'([A-Z])([A-Z])([a-z])'), (Match match) => '${match.group(1)} ${match.group(2)}${match.group(3)}')
|
|
// 'AB1' => 'AB 1', 'F1' => 'F1'
|
|
.replaceAllMapped(RegExp(r'([A-Z]{2,})([0-9])'), (Match match) => '${match.group(1)} ${match.group(2)}')
|
|
// 'Foo1' => 'Foo 1'
|
|
.replaceAllMapped(RegExp(r'([a-z])([0-9])'), (Match match) => '${match.group(1)} ${match.group(2)}')
|
|
.trim();
|
|
}
|
|
|
|
static String computeConstantName(String commentName) {
|
|
// Convert the first word in the comment name.
|
|
final String lowerCamelSpace = commentName.replaceFirstMapped(RegExp(r'^[^ ]+'),
|
|
(Match match) => match[0]!.toLowerCase(),
|
|
);
|
|
final String result = lowerCamelSpace.replaceAll(' ', '');
|
|
if (kDartReservedWords.contains(result)) {
|
|
return '${result}Key';
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int compareByValue(LogicalKeyEntry a, LogicalKeyEntry b) =>
|
|
a.value.compareTo(b.value);
|
|
}
|