diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml
index 276a75842a7..69e62ada815 100644
--- a/dev/devicelab/pubspec.yaml
+++ b/dev/devicelab/pubspec.yaml
@@ -10,7 +10,7 @@ environment:
dependencies:
args: 1.5.1
file: 5.0.7
- image: 2.0.6
+ image: 2.0.7
meta: 1.1.6
path: 1.6.2
platform: 2.2.0
@@ -74,4 +74,4 @@ dev_dependencies:
watcher: 0.9.7+10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
yaml: 2.1.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
-# PUBSPEC CHECKSUM: bc81
+# PUBSPEC CHECKSUM: 5d82
diff --git a/dev/integration_tests/ui/pubspec.yaml b/dev/integration_tests/ui/pubspec.yaml
index ea71ee6bc93..2b00664aeb9 100644
--- a/dev/integration_tests/ui/pubspec.yaml
+++ b/dev/integration_tests/ui/pubspec.yaml
@@ -6,7 +6,7 @@ environment:
sdk: ">=2.0.0-dev.68.0 <3.0.0"
dependencies:
- image: 2.0.6
+ image: 2.0.7
flutter:
sdk: flutter
flutter_driver:
@@ -80,4 +80,4 @@ dev_dependencies:
flutter:
uses-material-design: true
-# PUBSPEC CHECKSUM: e153
+# PUBSPEC CHECKSUM: 8654
diff --git a/dev/tools/gen_keycodes/README.md b/dev/tools/gen_keycodes/README.md
new file mode 100644
index 00000000000..a1d0796b719
--- /dev/null
+++ b/dev/tools/gen_keycodes/README.md
@@ -0,0 +1,168 @@
+## Keycode Generator
+
+This directory contains a keycode generator that can generate Dart code for
+the `LogicalKeyboardKey` and `PhysicalKeyboardKey` classes. It draws information
+from both the Chromium and Android source bases, and incorporates the
+information it finds in those sources into a single key database in JSON form.
+
+It then generates `keyboard_key.dart` (containing the `LogicalKeyboardKey` and
+`PhysicalKeyboardKey` classes), and `keyboard_maps.dart`, containing
+platform-specific immutable maps for translating platform keycodes and
+information into the pre-defined key values in the `LogicalKeyboardKey` and
+`PhysicalKeyboardKey` classes.
+
+The `data` subdirectory contains both some local data files, and the templates
+used to generate the source files.
+
+ - `data/key_data.json`: contains the merged data from all the other sources.
+ This file will be regenerated if "--collect" is specified for the
+ gen_keycodes script.
+ - `data/key_name_to_android_name.json`: contains a mapping from Flutter key
+ names to Android keycode names (with the "KEY_" prefix stripped off).
+ - `data/keyboard_key.tmpl`: contains the template for the `keyboard_key.dart`
+ file. Markers that begin and end with "@@@" denote the locations where
+ generated data will be inserted.
+ - `data/keyboard_maps.tmpl`: contains the template for the `keyboard_maps.dart`
+ file. Markers that begin and end with "@@@" denote the locations where
+ generated data will be inserted.
+ - `data/printable.json`: contains a mapping between Flutter key name and its
+ printable character. This character is used as the key label.
+
+ ## Running the tool
+
+To run the `gen_keycodes` tool using the checked in `key_data.json` file, run
+it like so:
+
+```bash
+$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart bin/gen_keycodes.dart
+```
+
+This will rengerate the `keyboard_key.dart` and `keyboard_maps.dart` files in
+place.
+
+If you wish to incorporate and parse changes from the Chromium and Android
+source trees, add `--collect` to the command line. The script will download and
+incorporate the changed data automatically. Note that the parsing is specific to
+the format of the source code that it is reading, so if the format of those
+files changes appreciably, you will need to update the parser.
+
+There are other options for manually specifying the file to read in place of the
+downloaded files, use `--help` to see what is available.
+
+If the data in those files changes in the future to be unhelpful, then we can
+switch to another data source, or abandon the parsing and maintain
+`key_data.json` manually. All output files and local input files should be
+checked in.
+
+## Key Code ID Scheme
+
+In order to provide keys with unique ID codes, Flutter uses a scheme to assign
+codes which keeps us out of the business of minting new codes ourselves.
+
+The codes are meant to be opaque to the user, and should never be unpacked for
+meaning, since the code scheme could change at any time, and the meaning is
+likely to be retrievable in a more reliable and correct manner from the API.
+
+However, if you are porting Flutter to a new platform, you should follow the
+following guidelines for specifying key codes.
+
+The key code is a 37-bit integer in a namespace that we control and define. It
+has values in the following ranges.
+
+ - **0x00 0000 0000 - 0x0 0010 FFFF**: For keys that generate Unicode
+ characters when pressed (this includes dead keys, but not e.g. function keys
+ or shift keys), the logical key code is the Unicode code point corresponding
+ to the representation of the key in the current keyboard mapping. The
+ Unicode code point might not actually match the string that is generated for
+ an unshifted key press of that key, for example we would use U+0034 for the
+ “4 $” key in the US layout, and also the “4 ;” key in the Russian layout,
+ and also, maybe less intuitively, for the “' 4 {“ in French layout (where in
+ the latter case, an unshifted press gets you a ', not a 4). Similarly, the Q
+ key in the US layout outputs a q in normal usage, but its code would be 0x0
+ 0000 0051 (U+00051 being the code for the uppercase Q).
+
+ - **0x01 0000 0000 - 0x01 FFFF FFFF**: For keys that are defined by the USB HID
+ standard, the key code consists of the 32 bit USB extended usage code. For
+ example, the Enter key would have code 0x0 0007 0028. Only keys that fall
+ into collections "Keyboard", "Keypad", and "Tablet PC System Controls" are
+ considered for this API; for example, a mixing desk with multiple
+ collections of volume controls would not be exposed via DOWN and UP events,
+ nor would a mouse, joystick, or golf simulator control.
+
+ - **0x02 0000 0000 - 0xFF FFFF FFFF**: For keys that aren't defined in USB at the
+ time of implementation, but that we need to support. For example, if Flutter
+ were ever ported to the Symbolics LM-2, the "thumb up" key might be given
+ the code 0x14 0000 0001, where 0x14 is defined as the “Symbolics” platform
+ range. Where possible, we will use specific subranges of this space to reuse
+ keys from other platforms. When this is not possible, the prefix 0xFF is
+ reserved for “Custom” codes. Each platform from which we take codes will get
+ a unique prefix in the range 0x2-0xFE. If multiple systems define keys with
+ the same usage (not the same number), then the value with the lowest prefix
+ is used as the defining code.
+
+ Prefixes will be:
+
+ |Code|Platform|
+ |----|--------|
+ |0x02| Android|
+ |0x03|Fuchsia |
+ |0x04|iOS |
+ |0x05|macOS |
+ |0x06|Linux |
+ |0x07|Windows |
+ |0x08|Web |
+ |0xFF|Custom |
+
+ Further ranges will be added as platforms are added. The platform prefix
+ does not define the platform it is used on, it is just the platform that
+ decides what the value is: the codes are mapped to the same value on all
+ platforms.
+
+ - **0x100 0000 0000 - 0x1FF FFFF FFFF**: For keys that have no definition yet in
+ Flutter, but that are encountered in the field, this range is used to embed
+ the platform-specific keycode in an ID that must be tested for in a platform
+ specific way. For instance, if a platform generates a new USB HID code 0x07
+ 00E8 that a Flutter app wasn’t compiled with, then it would appear in the
+ app as 0x100 0007 00E8, and the app could test against that code. Yes, this
+ also means that once they recompile with a version of Flutter that supports
+ this new HID code, apps looking for this code will break. This situation is
+ only meant to provide a fallback ability for apps to handle esoteric codes
+ that their version of Flutter doesn’t support yet. The prefix for this code
+ is the platform prefix from the previous sections, plus 0x100.
+
+**This is intended to get us out of the business of defining key codes where
+possible.** We still have to have mapping tables, but at least the actual minting
+of codes is deferred to other organizations to a large extent. Coming up with a
+code is a mechanical process consisting of just picking the lowest number code
+possible that matches the semantic meaning of the key according to the
+definitions above.
+
+Here are some examples:
+
+For example, on a French keyboard layout, pressing CAPS LOCK then pressing
+SHIFT + Y would generate the following sequence:
+
+DOWN, code 0x00070039. (CAPS LOCK DOWN)
+UP, code 0x00070039. (CAPS LOCK UP)
+DOWN, code 0x000700E1 (SHIFT DOWN)
+DOWN, code 0x0007001D, string U+00059 (Y DOWN, the code is for the "Z" key, but
+string is the character, "Y")
+UP, code 0x0007001D (Y UP)
+UP, code 0x000700E1 (SHIFT UP)
+
+Here's another example. On a German keyboard layout, you press ^e (the ^ key is
+at the top left of the keyboard and is a dead key) to produce a “ê”:
+
+DOWN, code 0x00070035 (GRAVE DOWN) Assuming that the keymap maps it to the same
+logical key, it produces no string, because it's a dead key. The HID key is for
+"Keyboard grave accent and tilde" in AT-101 keyboard typical position 1.
+UP, code 0x00070035 (GRAVE UP)
+DOWN, code 0x00070008, string U+000EA (Unicode for ê) (E DOWN).
+UP, code 0x00070008. (E UP).
+
+It is an important point that even though we’re representing many keys with USB
+HID codes, these are not necessarily the same HID codes produced by the hardware
+and presented to the driver, since on most platforms we have to map the platform
+representation back to a HID code because we don’t have access to the original
+HID code. USB HID is simply a conveniently well-defined standard that includes
+many of the keys we would want.
\ No newline at end of file
diff --git a/dev/tools/gen_keycodes/bin/gen_keycodes.dart b/dev/tools/gen_keycodes/bin/gen_keycodes.dart
new file mode 100644
index 00000000000..1b1ccb43d07
--- /dev/null
+++ b/dev/tools/gen_keycodes/bin/gen_keycodes.dart
@@ -0,0 +1,161 @@
+// Copyright 2019 The Chromium 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:async';
+import 'dart:convert';
+import 'dart:io' hide Platform;
+
+import 'package:args/args.dart';
+import 'package:http/http.dart' as http;
+import 'package:path/path.dart' as path;
+
+import 'package:gen_keycodes/code_gen.dart';
+import 'package:gen_keycodes/key_data.dart';
+import 'package:gen_keycodes/utils.dart';
+
+/// Get contents of the file that contains the key code mapping in Chromium
+/// source.
+Future getChromiumConversions() async {
+ final Uri keyCodeMapUri = Uri.parse('https://cs.chromium.org/codesearch/f/chromium/src/ui/events/keycodes/dom/keycode_converter_data.inc');
+ return await http.read(keyCodeMapUri);
+}
+
+/// Get contents of the file that contains the key codes in Android source.
+Future getAndroidKeyCodes() async {
+ final Uri keyCodesUri = Uri.parse('https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h?format=TEXT');
+ return utf8.decode(base64.decode(await http.read(keyCodesUri)));
+}
+
+/// Get contents of the file that contains the scan codes in Android source.
+/// Yes, this is just the generic keyboard layout file for base Android distro
+/// This is because there isn't any facility in Android to get the keyboard
+/// layout, so we're using this to match scan codes with symbol names for most
+/// common keyboards. Other than some special keyboards and game pads, this
+/// should be OK.
+Future getAndroidScanCodes() async {
+ final Uri scanCodesUri = Uri.parse('https://android.googlesource.com/platform/frameworks/base/+/master/data/keyboards/Generic.kl?format=TEXT');
+ return utf8.decode(base64.decode(await http.read(scanCodesUri)));
+}
+
+Future main(List rawArguments) async {
+ final ArgParser argParser = ArgParser();
+ argParser.addOption(
+ 'chromium-hid-codes',
+ defaultsTo: null,
+ help: 'The path to where the Chromium HID code mapping file should be '
+ 'read. If --chromium-hid-codes is not specified, the input will be read '
+ 'from the correct file in the Chromium repository.',
+ );
+ argParser.addOption(
+ 'android-keycodes',
+ defaultsTo: null,
+ help: 'The path to where the Android keycodes header file should be read. '
+ 'If --android-keycodes is not specified, the input will be read from the '
+ 'correct file in the Android repository.',
+ );
+ argParser.addOption(
+ 'android-scancodes',
+ defaultsTo: null,
+ help: 'The path to where the Android scancodes header file should be read. '
+ 'If --android-scancodes is not specified, the input will be read from the '
+ 'correct file in the Android repository.',
+ );
+ argParser.addOption(
+ 'android-domkey',
+ defaultsTo: path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'key_name_to_android_name.json'),
+ help: 'The path to where the Android keycode to DomKey mapping is.',
+ );
+ argParser.addOption(
+ 'data',
+ defaultsTo: path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'key_data.json'),
+ help: 'The path to where the key code data file should be written when '
+ 'collected, and read from when generating output code. If --data is '
+ 'not specified, the output will be written to/read from the current '
+ "directory. If the output directory doesn't exist, it, and the path to "
+ 'it, will be created.',
+ );
+ argParser.addOption(
+ 'code',
+ defaultsTo: path.join(flutterRoot.path, 'packages', 'flutter', 'lib', 'src', 'services', 'keyboard_key.dart'),
+ help: 'The path to where the output "keyboard_keys.dart" file should be'
+ 'written. If --code is not specified, the output will be written to the '
+ 'correct directory in the flutter tree. If the output directory does not '
+ 'exist, it, and the path to it, will be created.',
+ );
+ argParser.addOption(
+ 'maps',
+ defaultsTo: path.join(flutterRoot.path, 'packages', 'flutter', 'lib', 'src', 'services', 'keyboard_maps.dart'),
+ help: 'The path to where the output "keyboard_maps.dart" file should be'
+ 'written. If --maps is not specified, the output will be written to the '
+ 'correct directory in the flutter tree. If the output directory does not '
+ 'exist, it, and the path to it, will be created.',
+ );
+ argParser.addFlag(
+ 'collect',
+ defaultsTo: false,
+ negatable: false,
+ help: 'If this flag is set, then collect and parse header files from '
+ 'Chromium and Android instead of reading pre-parsed data from '
+ '"key_data.json", and then update "key_data.json" with the fresh data.',
+ );
+ argParser.addFlag(
+ 'help',
+ defaultsTo: false,
+ negatable: false,
+ help: 'Print help for this command.',
+ );
+
+ final ArgResults parsedArguments = argParser.parse(rawArguments);
+
+ if (parsedArguments['help']) {
+ print(argParser.usage);
+ exit(0);
+ }
+
+ KeyData data;
+ if (parsedArguments['collect']) {
+ String hidCodes;
+ if (parsedArguments['chromium-hid-codes'] == null) {
+ hidCodes = await getChromiumConversions();
+ } else {
+ hidCodes = File(parsedArguments['chromium-hid-codes']).readAsStringSync();
+ }
+
+ String androidKeyCodes;
+ if (parsedArguments['android-keycodes'] == null) {
+ androidKeyCodes = await getAndroidKeyCodes();
+ } else {
+ androidKeyCodes = File(parsedArguments['android-keycodes']).readAsStringSync();
+ }
+
+ String androidScanCodes;
+ if (parsedArguments['android-scancodes'] == null) {
+ androidScanCodes = await getAndroidScanCodes();
+ } else {
+ androidScanCodes = File(parsedArguments['android-scancodes']).readAsStringSync();
+ }
+
+ final String androidToDomKey = File(parsedArguments['android-domkey']).readAsStringSync();
+ data = KeyData(hidCodes, androidScanCodes, androidKeyCodes, androidToDomKey);
+
+ const JsonEncoder encoder = JsonEncoder.withIndent(' ');
+ File(parsedArguments['data']).writeAsStringSync(encoder.convert(data.toJson()));
+ } else {
+ data = KeyData.fromJson(json.decode(await File(parsedArguments['data']).readAsString()));
+ }
+
+ final File codeFile = File(parsedArguments['code']);
+ if (!codeFile.existsSync()) {
+ codeFile.createSync(recursive: true);
+ }
+
+ final File mapsFile = File(parsedArguments['maps']);
+ if (!mapsFile.existsSync()) {
+ mapsFile.createSync(recursive: true);
+ }
+
+ final CodeGenerator generator = CodeGenerator(data);
+ await codeFile.writeAsString(generator.generateKeyboardKeys());
+ await mapsFile.writeAsString(generator.generateKeyboardMaps());
+}
diff --git a/dev/tools/gen_keycodes/data/key_data.json b/dev/tools/gen_keycodes/data/key_data.json
new file mode 100644
index 00000000000..585295f0eba
--- /dev/null
+++ b/dev/tools/gen_keycodes/data/key_data.json
@@ -0,0 +1,5384 @@
+{
+ "none": {
+ "names": {
+ "domkey": "None",
+ "android": [
+ "UNKNOWN"
+ ],
+ "english": "None",
+ "chromium": "none"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 0,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 0
+ ]
+ }
+ },
+ "hyper": {
+ "names": {
+ "domkey": "Hyper",
+ "android": null,
+ "english": "Hyper",
+ "chromium": "hyper"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 16,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "superKey": {
+ "names": {
+ "domkey": "Super",
+ "android": null,
+ "english": "Super Key",
+ "chromium": "super"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 17,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "fn": {
+ "names": {
+ "domkey": "Fn",
+ "android": [
+ "FUNCTION"
+ ],
+ "english": "Fn",
+ "chromium": "fn"
+ },
+ "scanCodes": {
+ "android": [
+ 464
+ ],
+ "usb": 18,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 119
+ ]
+ }
+ },
+ "fnLock": {
+ "names": {
+ "domkey": "FnLock",
+ "android": null,
+ "english": "Fn Lock",
+ "chromium": "fnLock"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 19,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "suspend": {
+ "names": {
+ "domkey": "Suspend",
+ "android": [
+ "SUSPEND"
+ ],
+ "english": "Suspend",
+ "chromium": "suspend"
+ },
+ "scanCodes": {
+ "android": [
+ 205
+ ],
+ "usb": 20,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "resume": {
+ "names": {
+ "domkey": "Resume",
+ "android": null,
+ "english": "Resume",
+ "chromium": "resume"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 21,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "turbo": {
+ "names": {
+ "domkey": "Turbo",
+ "android": null,
+ "english": "Turbo",
+ "chromium": "turbo"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 22,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchAssistant": {
+ "names": {
+ "domkey": "LaunchAssistant",
+ "android": [
+ "ASSIST"
+ ],
+ "english": "Launch Assistant",
+ "chromium": "launchAssistant"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 23,
+ "linux": 583,
+ "xkb": 591,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 219
+ ]
+ }
+ },
+ "sleep": {
+ "names": {
+ "domkey": "Sleep",
+ "android": [
+ "SLEEP"
+ ],
+ "english": "Sleep",
+ "chromium": "sleep"
+ },
+ "scanCodes": {
+ "android": [
+ 142
+ ],
+ "usb": 65666,
+ "linux": 142,
+ "xkb": 150,
+ "windows": 57439,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 223
+ ]
+ }
+ },
+ "wakeUp": {
+ "names": {
+ "domkey": "WakeUp",
+ "android": [
+ "WAKEUP"
+ ],
+ "english": "Wake Up",
+ "chromium": "wakeUp"
+ },
+ "scanCodes": {
+ "android": [
+ 143
+ ],
+ "usb": 65667,
+ "linux": 143,
+ "xkb": 151,
+ "windows": 57443,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 224
+ ]
+ }
+ },
+ "usbReserved": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Usb Reserved",
+ "chromium": "usbReserved"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458752,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "usbErrorRollOver": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Usb Error Roll Over",
+ "chromium": "usbErrorRollOver"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458753,
+ "linux": null,
+ "xkb": null,
+ "windows": 255,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "usbPostFail": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Usb Post Fail",
+ "chromium": "usbPostFail"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458754,
+ "linux": null,
+ "xkb": null,
+ "windows": 252,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "usbErrorUndefined": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Usb Error Undefined",
+ "chromium": "usbErrorUndefined"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458755,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "keyA": {
+ "names": {
+ "domkey": "KeyA",
+ "android": [
+ "A"
+ ],
+ "english": "Key A",
+ "chromium": "usA"
+ },
+ "scanCodes": {
+ "android": [
+ 30
+ ],
+ "usb": 458756,
+ "linux": 30,
+ "xkb": 38,
+ "windows": 30,
+ "macos": 0
+ },
+ "keyCodes": {
+ "android": [
+ 29
+ ]
+ }
+ },
+ "keyB": {
+ "names": {
+ "domkey": "KeyB",
+ "android": [
+ "B"
+ ],
+ "english": "Key B",
+ "chromium": "usB"
+ },
+ "scanCodes": {
+ "android": [
+ 48
+ ],
+ "usb": 458757,
+ "linux": 48,
+ "xkb": 56,
+ "windows": 48,
+ "macos": 11
+ },
+ "keyCodes": {
+ "android": [
+ 30
+ ]
+ }
+ },
+ "keyC": {
+ "names": {
+ "domkey": "KeyC",
+ "android": [
+ "C"
+ ],
+ "english": "Key C",
+ "chromium": "usC"
+ },
+ "scanCodes": {
+ "android": [
+ 46
+ ],
+ "usb": 458758,
+ "linux": 46,
+ "xkb": 54,
+ "windows": 46,
+ "macos": 8
+ },
+ "keyCodes": {
+ "android": [
+ 31
+ ]
+ }
+ },
+ "keyD": {
+ "names": {
+ "domkey": "KeyD",
+ "android": [
+ "D"
+ ],
+ "english": "Key D",
+ "chromium": "usD"
+ },
+ "scanCodes": {
+ "android": [
+ 32
+ ],
+ "usb": 458759,
+ "linux": 32,
+ "xkb": 40,
+ "windows": 32,
+ "macos": 2
+ },
+ "keyCodes": {
+ "android": [
+ 32
+ ]
+ }
+ },
+ "keyE": {
+ "names": {
+ "domkey": "KeyE",
+ "android": [
+ "E"
+ ],
+ "english": "Key E",
+ "chromium": "usE"
+ },
+ "scanCodes": {
+ "android": [
+ 18
+ ],
+ "usb": 458760,
+ "linux": 18,
+ "xkb": 26,
+ "windows": 18,
+ "macos": 14
+ },
+ "keyCodes": {
+ "android": [
+ 33
+ ]
+ }
+ },
+ "keyF": {
+ "names": {
+ "domkey": "KeyF",
+ "android": [
+ "F"
+ ],
+ "english": "Key F",
+ "chromium": "usF"
+ },
+ "scanCodes": {
+ "android": [
+ 33
+ ],
+ "usb": 458761,
+ "linux": 33,
+ "xkb": 41,
+ "windows": 33,
+ "macos": 3
+ },
+ "keyCodes": {
+ "android": [
+ 34
+ ]
+ }
+ },
+ "keyG": {
+ "names": {
+ "domkey": "KeyG",
+ "android": [
+ "G"
+ ],
+ "english": "Key G",
+ "chromium": "usG"
+ },
+ "scanCodes": {
+ "android": [
+ 34
+ ],
+ "usb": 458762,
+ "linux": 34,
+ "xkb": 42,
+ "windows": 34,
+ "macos": 5
+ },
+ "keyCodes": {
+ "android": [
+ 35
+ ]
+ }
+ },
+ "keyH": {
+ "names": {
+ "domkey": "KeyH",
+ "android": [
+ "H"
+ ],
+ "english": "Key H",
+ "chromium": "usH"
+ },
+ "scanCodes": {
+ "android": [
+ 35
+ ],
+ "usb": 458763,
+ "linux": 35,
+ "xkb": 43,
+ "windows": 35,
+ "macos": 4
+ },
+ "keyCodes": {
+ "android": [
+ 36
+ ]
+ }
+ },
+ "keyI": {
+ "names": {
+ "domkey": "KeyI",
+ "android": [
+ "I"
+ ],
+ "english": "Key I",
+ "chromium": "usI"
+ },
+ "scanCodes": {
+ "android": [
+ 23
+ ],
+ "usb": 458764,
+ "linux": 23,
+ "xkb": 31,
+ "windows": 23,
+ "macos": 34
+ },
+ "keyCodes": {
+ "android": [
+ 37
+ ]
+ }
+ },
+ "keyJ": {
+ "names": {
+ "domkey": "KeyJ",
+ "android": [
+ "J"
+ ],
+ "english": "Key J",
+ "chromium": "usJ"
+ },
+ "scanCodes": {
+ "android": [
+ 36
+ ],
+ "usb": 458765,
+ "linux": 36,
+ "xkb": 44,
+ "windows": 36,
+ "macos": 38
+ },
+ "keyCodes": {
+ "android": [
+ 38
+ ]
+ }
+ },
+ "keyK": {
+ "names": {
+ "domkey": "KeyK",
+ "android": [
+ "K"
+ ],
+ "english": "Key K",
+ "chromium": "usK"
+ },
+ "scanCodes": {
+ "android": [
+ 37
+ ],
+ "usb": 458766,
+ "linux": 37,
+ "xkb": 45,
+ "windows": 37,
+ "macos": 40
+ },
+ "keyCodes": {
+ "android": [
+ 39
+ ]
+ }
+ },
+ "keyL": {
+ "names": {
+ "domkey": "KeyL",
+ "android": [
+ "L"
+ ],
+ "english": "Key L",
+ "chromium": "usL"
+ },
+ "scanCodes": {
+ "android": [
+ 38
+ ],
+ "usb": 458767,
+ "linux": 38,
+ "xkb": 46,
+ "windows": 38,
+ "macos": 37
+ },
+ "keyCodes": {
+ "android": [
+ 40
+ ]
+ }
+ },
+ "keyM": {
+ "names": {
+ "domkey": "KeyM",
+ "android": [
+ "M"
+ ],
+ "english": "Key M",
+ "chromium": "usM"
+ },
+ "scanCodes": {
+ "android": [
+ 50
+ ],
+ "usb": 458768,
+ "linux": 50,
+ "xkb": 58,
+ "windows": 50,
+ "macos": 46
+ },
+ "keyCodes": {
+ "android": [
+ 41
+ ]
+ }
+ },
+ "keyN": {
+ "names": {
+ "domkey": "KeyN",
+ "android": [
+ "N"
+ ],
+ "english": "Key N",
+ "chromium": "usN"
+ },
+ "scanCodes": {
+ "android": [
+ 49
+ ],
+ "usb": 458769,
+ "linux": 49,
+ "xkb": 57,
+ "windows": 49,
+ "macos": 45
+ },
+ "keyCodes": {
+ "android": [
+ 42
+ ]
+ }
+ },
+ "keyO": {
+ "names": {
+ "domkey": "KeyO",
+ "android": [
+ "O"
+ ],
+ "english": "Key O",
+ "chromium": "usO"
+ },
+ "scanCodes": {
+ "android": [
+ 24
+ ],
+ "usb": 458770,
+ "linux": 24,
+ "xkb": 32,
+ "windows": 24,
+ "macos": 31
+ },
+ "keyCodes": {
+ "android": [
+ 43
+ ]
+ }
+ },
+ "keyP": {
+ "names": {
+ "domkey": "KeyP",
+ "android": [
+ "P"
+ ],
+ "english": "Key P",
+ "chromium": "usP"
+ },
+ "scanCodes": {
+ "android": [
+ 25
+ ],
+ "usb": 458771,
+ "linux": 25,
+ "xkb": 33,
+ "windows": 25,
+ "macos": 35
+ },
+ "keyCodes": {
+ "android": [
+ 44
+ ]
+ }
+ },
+ "keyQ": {
+ "names": {
+ "domkey": "KeyQ",
+ "android": [
+ "Q"
+ ],
+ "english": "Key Q",
+ "chromium": "usQ"
+ },
+ "scanCodes": {
+ "android": [
+ 16
+ ],
+ "usb": 458772,
+ "linux": 16,
+ "xkb": 24,
+ "windows": 16,
+ "macos": 12
+ },
+ "keyCodes": {
+ "android": [
+ 45
+ ]
+ }
+ },
+ "keyR": {
+ "names": {
+ "domkey": "KeyR",
+ "android": [
+ "R"
+ ],
+ "english": "Key R",
+ "chromium": "usR"
+ },
+ "scanCodes": {
+ "android": [
+ 19
+ ],
+ "usb": 458773,
+ "linux": 19,
+ "xkb": 27,
+ "windows": 19,
+ "macos": 15
+ },
+ "keyCodes": {
+ "android": [
+ 46
+ ]
+ }
+ },
+ "keyS": {
+ "names": {
+ "domkey": "KeyS",
+ "android": [
+ "S"
+ ],
+ "english": "Key S",
+ "chromium": "usS"
+ },
+ "scanCodes": {
+ "android": [
+ 31
+ ],
+ "usb": 458774,
+ "linux": 31,
+ "xkb": 39,
+ "windows": 31,
+ "macos": 1
+ },
+ "keyCodes": {
+ "android": [
+ 47
+ ]
+ }
+ },
+ "keyT": {
+ "names": {
+ "domkey": "KeyT",
+ "android": [
+ "T"
+ ],
+ "english": "Key T",
+ "chromium": "usT"
+ },
+ "scanCodes": {
+ "android": [
+ 20
+ ],
+ "usb": 458775,
+ "linux": 20,
+ "xkb": 28,
+ "windows": 20,
+ "macos": 17
+ },
+ "keyCodes": {
+ "android": [
+ 48
+ ]
+ }
+ },
+ "keyU": {
+ "names": {
+ "domkey": "KeyU",
+ "android": [
+ "U"
+ ],
+ "english": "Key U",
+ "chromium": "usU"
+ },
+ "scanCodes": {
+ "android": [
+ 22
+ ],
+ "usb": 458776,
+ "linux": 22,
+ "xkb": 30,
+ "windows": 22,
+ "macos": 32
+ },
+ "keyCodes": {
+ "android": [
+ 49
+ ]
+ }
+ },
+ "keyV": {
+ "names": {
+ "domkey": "KeyV",
+ "android": [
+ "V"
+ ],
+ "english": "Key V",
+ "chromium": "usV"
+ },
+ "scanCodes": {
+ "android": [
+ 47
+ ],
+ "usb": 458777,
+ "linux": 47,
+ "xkb": 55,
+ "windows": 47,
+ "macos": 9
+ },
+ "keyCodes": {
+ "android": [
+ 50
+ ]
+ }
+ },
+ "keyW": {
+ "names": {
+ "domkey": "KeyW",
+ "android": [
+ "W"
+ ],
+ "english": "Key W",
+ "chromium": "usW"
+ },
+ "scanCodes": {
+ "android": [
+ 17
+ ],
+ "usb": 458778,
+ "linux": 17,
+ "xkb": 25,
+ "windows": 17,
+ "macos": 13
+ },
+ "keyCodes": {
+ "android": [
+ 51
+ ]
+ }
+ },
+ "keyX": {
+ "names": {
+ "domkey": "KeyX",
+ "android": [
+ "X"
+ ],
+ "english": "Key X",
+ "chromium": "usX"
+ },
+ "scanCodes": {
+ "android": [
+ 45
+ ],
+ "usb": 458779,
+ "linux": 45,
+ "xkb": 53,
+ "windows": 45,
+ "macos": 7
+ },
+ "keyCodes": {
+ "android": [
+ 52
+ ]
+ }
+ },
+ "keyY": {
+ "names": {
+ "domkey": "KeyY",
+ "android": [
+ "Y"
+ ],
+ "english": "Key Y",
+ "chromium": "usY"
+ },
+ "scanCodes": {
+ "android": [
+ 21
+ ],
+ "usb": 458780,
+ "linux": 21,
+ "xkb": 29,
+ "windows": 21,
+ "macos": 16
+ },
+ "keyCodes": {
+ "android": [
+ 53
+ ]
+ }
+ },
+ "keyZ": {
+ "names": {
+ "domkey": "KeyZ",
+ "android": [
+ "Z"
+ ],
+ "english": "Key Z",
+ "chromium": "usZ"
+ },
+ "scanCodes": {
+ "android": [
+ 44
+ ],
+ "usb": 458781,
+ "linux": 44,
+ "xkb": 52,
+ "windows": 44,
+ "macos": 6
+ },
+ "keyCodes": {
+ "android": [
+ 54
+ ]
+ }
+ },
+ "digit1": {
+ "names": {
+ "domkey": "Digit1",
+ "android": [
+ "1"
+ ],
+ "english": "Digit 1",
+ "chromium": "digit1"
+ },
+ "scanCodes": {
+ "android": [
+ 2
+ ],
+ "usb": 458782,
+ "linux": 2,
+ "xkb": 10,
+ "windows": 2,
+ "macos": 18
+ },
+ "keyCodes": {
+ "android": [
+ 8
+ ]
+ }
+ },
+ "digit2": {
+ "names": {
+ "domkey": "Digit2",
+ "android": [
+ "2"
+ ],
+ "english": "Digit 2",
+ "chromium": "digit2"
+ },
+ "scanCodes": {
+ "android": [
+ 3
+ ],
+ "usb": 458783,
+ "linux": 3,
+ "xkb": 11,
+ "windows": 3,
+ "macos": 19
+ },
+ "keyCodes": {
+ "android": [
+ 9
+ ]
+ }
+ },
+ "digit3": {
+ "names": {
+ "domkey": "Digit3",
+ "android": [
+ "3"
+ ],
+ "english": "Digit 3",
+ "chromium": "digit3"
+ },
+ "scanCodes": {
+ "android": [
+ 4
+ ],
+ "usb": 458784,
+ "linux": 4,
+ "xkb": 12,
+ "windows": 4,
+ "macos": 20
+ },
+ "keyCodes": {
+ "android": [
+ 10
+ ]
+ }
+ },
+ "digit4": {
+ "names": {
+ "domkey": "Digit4",
+ "android": [
+ "4"
+ ],
+ "english": "Digit 4",
+ "chromium": "digit4"
+ },
+ "scanCodes": {
+ "android": [
+ 5
+ ],
+ "usb": 458785,
+ "linux": 5,
+ "xkb": 13,
+ "windows": 5,
+ "macos": 21
+ },
+ "keyCodes": {
+ "android": [
+ 11
+ ]
+ }
+ },
+ "digit5": {
+ "names": {
+ "domkey": "Digit5",
+ "android": [
+ "5"
+ ],
+ "english": "Digit 5",
+ "chromium": "digit5"
+ },
+ "scanCodes": {
+ "android": [
+ 6
+ ],
+ "usb": 458786,
+ "linux": 6,
+ "xkb": 14,
+ "windows": 6,
+ "macos": 23
+ },
+ "keyCodes": {
+ "android": [
+ 12
+ ]
+ }
+ },
+ "digit6": {
+ "names": {
+ "domkey": "Digit6",
+ "android": [
+ "6"
+ ],
+ "english": "Digit 6",
+ "chromium": "digit6"
+ },
+ "scanCodes": {
+ "android": [
+ 7
+ ],
+ "usb": 458787,
+ "linux": 7,
+ "xkb": 15,
+ "windows": 7,
+ "macos": 22
+ },
+ "keyCodes": {
+ "android": [
+ 13
+ ]
+ }
+ },
+ "digit7": {
+ "names": {
+ "domkey": "Digit7",
+ "android": [
+ "7"
+ ],
+ "english": "Digit 7",
+ "chromium": "digit7"
+ },
+ "scanCodes": {
+ "android": [
+ 8
+ ],
+ "usb": 458788,
+ "linux": 8,
+ "xkb": 16,
+ "windows": 8,
+ "macos": 26
+ },
+ "keyCodes": {
+ "android": [
+ 14
+ ]
+ }
+ },
+ "digit8": {
+ "names": {
+ "domkey": "Digit8",
+ "android": [
+ "8"
+ ],
+ "english": "Digit 8",
+ "chromium": "digit8"
+ },
+ "scanCodes": {
+ "android": [
+ 9
+ ],
+ "usb": 458789,
+ "linux": 9,
+ "xkb": 17,
+ "windows": 9,
+ "macos": 28
+ },
+ "keyCodes": {
+ "android": [
+ 15
+ ]
+ }
+ },
+ "digit9": {
+ "names": {
+ "domkey": "Digit9",
+ "android": [
+ "9"
+ ],
+ "english": "Digit 9",
+ "chromium": "digit9"
+ },
+ "scanCodes": {
+ "android": [
+ 10
+ ],
+ "usb": 458790,
+ "linux": 10,
+ "xkb": 18,
+ "windows": 10,
+ "macos": 25
+ },
+ "keyCodes": {
+ "android": [
+ 16
+ ]
+ }
+ },
+ "digit0": {
+ "names": {
+ "domkey": "Digit0",
+ "android": [
+ "0"
+ ],
+ "english": "Digit 0",
+ "chromium": "digit0"
+ },
+ "scanCodes": {
+ "android": [
+ 11
+ ],
+ "usb": 458791,
+ "linux": 11,
+ "xkb": 19,
+ "windows": 11,
+ "macos": 29
+ },
+ "keyCodes": {
+ "android": [
+ 7
+ ]
+ }
+ },
+ "enter": {
+ "names": {
+ "domkey": "Enter",
+ "android": [
+ "ENTER"
+ ],
+ "english": "Enter",
+ "chromium": "enter"
+ },
+ "scanCodes": {
+ "android": [
+ 28
+ ],
+ "usb": 458792,
+ "linux": 28,
+ "xkb": 36,
+ "windows": 28,
+ "macos": 36
+ },
+ "keyCodes": {
+ "android": [
+ 66
+ ]
+ }
+ },
+ "escape": {
+ "names": {
+ "domkey": "Escape",
+ "android": [
+ "ESCAPE"
+ ],
+ "english": "Escape",
+ "chromium": "escape"
+ },
+ "scanCodes": {
+ "android": [
+ 1
+ ],
+ "usb": 458793,
+ "linux": 1,
+ "xkb": 9,
+ "windows": 1,
+ "macos": 53
+ },
+ "keyCodes": {
+ "android": [
+ 111
+ ]
+ }
+ },
+ "backspace": {
+ "names": {
+ "domkey": "Backspace",
+ "android": [
+ "DEL"
+ ],
+ "english": "Backspace",
+ "chromium": "backspace"
+ },
+ "scanCodes": {
+ "android": [
+ 14
+ ],
+ "usb": 458794,
+ "linux": 14,
+ "xkb": 22,
+ "windows": 14,
+ "macos": 51
+ },
+ "keyCodes": {
+ "android": [
+ 67
+ ]
+ }
+ },
+ "tab": {
+ "names": {
+ "domkey": "Tab",
+ "android": [
+ "TAB"
+ ],
+ "english": "Tab",
+ "chromium": "tab"
+ },
+ "scanCodes": {
+ "android": [
+ 15
+ ],
+ "usb": 458795,
+ "linux": 15,
+ "xkb": 23,
+ "windows": 15,
+ "macos": 48
+ },
+ "keyCodes": {
+ "android": [
+ 61
+ ]
+ }
+ },
+ "space": {
+ "names": {
+ "domkey": "Space",
+ "android": [
+ "SPACE"
+ ],
+ "english": "Space",
+ "chromium": "space"
+ },
+ "scanCodes": {
+ "android": [
+ 57
+ ],
+ "usb": 458796,
+ "linux": 57,
+ "xkb": 65,
+ "windows": 57,
+ "macos": 49
+ },
+ "keyCodes": {
+ "android": [
+ 62
+ ]
+ }
+ },
+ "minus": {
+ "names": {
+ "domkey": "Minus",
+ "android": [
+ "MINUS"
+ ],
+ "english": "Minus",
+ "chromium": "minus"
+ },
+ "scanCodes": {
+ "android": [
+ 12
+ ],
+ "usb": 458797,
+ "linux": 12,
+ "xkb": 20,
+ "windows": 12,
+ "macos": 27
+ },
+ "keyCodes": {
+ "android": [
+ 69
+ ]
+ }
+ },
+ "equal": {
+ "names": {
+ "domkey": "Equal",
+ "android": [
+ "EQUALS"
+ ],
+ "english": "Equal",
+ "chromium": "equal"
+ },
+ "scanCodes": {
+ "android": [
+ 13
+ ],
+ "usb": 458798,
+ "linux": 13,
+ "xkb": 21,
+ "windows": 13,
+ "macos": 24
+ },
+ "keyCodes": {
+ "android": [
+ 70
+ ]
+ }
+ },
+ "bracketLeft": {
+ "names": {
+ "domkey": "BracketLeft",
+ "android": [
+ "LEFT_BRACKET"
+ ],
+ "english": "Bracket Left",
+ "chromium": "bracketLeft"
+ },
+ "scanCodes": {
+ "android": [
+ 26
+ ],
+ "usb": 458799,
+ "linux": 26,
+ "xkb": 34,
+ "windows": 26,
+ "macos": 33
+ },
+ "keyCodes": {
+ "android": [
+ 71
+ ]
+ }
+ },
+ "bracketRight": {
+ "names": {
+ "domkey": "BracketRight",
+ "android": [
+ "RIGHT_BRACKET"
+ ],
+ "english": "Bracket Right",
+ "chromium": "bracketRight"
+ },
+ "scanCodes": {
+ "android": [
+ 27
+ ],
+ "usb": 458800,
+ "linux": 27,
+ "xkb": 35,
+ "windows": 27,
+ "macos": 30
+ },
+ "keyCodes": {
+ "android": [
+ 72
+ ]
+ }
+ },
+ "backslash": {
+ "names": {
+ "domkey": "Backslash",
+ "android": [
+ "BACKSLASH"
+ ],
+ "english": "Backslash",
+ "chromium": "backslash"
+ },
+ "scanCodes": {
+ "android": [
+ 43,
+ 86
+ ],
+ "usb": 458801,
+ "linux": 43,
+ "xkb": 51,
+ "windows": 43,
+ "macos": 42
+ },
+ "keyCodes": {
+ "android": [
+ 73
+ ]
+ }
+ },
+ "semicolon": {
+ "names": {
+ "domkey": "Semicolon",
+ "android": [
+ "SEMICOLON"
+ ],
+ "english": "Semicolon",
+ "chromium": "semicolon"
+ },
+ "scanCodes": {
+ "android": [
+ 39
+ ],
+ "usb": 458803,
+ "linux": 39,
+ "xkb": 47,
+ "windows": 39,
+ "macos": 41
+ },
+ "keyCodes": {
+ "android": [
+ 74
+ ]
+ }
+ },
+ "quote": {
+ "names": {
+ "domkey": "Quote",
+ "android": [
+ "APOSTROPHE"
+ ],
+ "english": "Quote",
+ "chromium": "quote"
+ },
+ "scanCodes": {
+ "android": [
+ 40
+ ],
+ "usb": 458804,
+ "linux": 40,
+ "xkb": 48,
+ "windows": 40,
+ "macos": 39
+ },
+ "keyCodes": {
+ "android": [
+ 75
+ ]
+ }
+ },
+ "backquote": {
+ "names": {
+ "domkey": "Backquote",
+ "android": [
+ "GRAVE"
+ ],
+ "english": "Backquote",
+ "chromium": "backquote"
+ },
+ "scanCodes": {
+ "android": [
+ 41
+ ],
+ "usb": 458805,
+ "linux": 41,
+ "xkb": 49,
+ "windows": 41,
+ "macos": 50
+ },
+ "keyCodes": {
+ "android": [
+ 68
+ ]
+ }
+ },
+ "comma": {
+ "names": {
+ "domkey": "Comma",
+ "android": [
+ "COMMA"
+ ],
+ "english": "Comma",
+ "chromium": "comma"
+ },
+ "scanCodes": {
+ "android": [
+ 51
+ ],
+ "usb": 458806,
+ "linux": 51,
+ "xkb": 59,
+ "windows": 51,
+ "macos": 43
+ },
+ "keyCodes": {
+ "android": [
+ 55
+ ]
+ }
+ },
+ "period": {
+ "names": {
+ "domkey": "Period",
+ "android": [
+ "PERIOD"
+ ],
+ "english": "Period",
+ "chromium": "period"
+ },
+ "scanCodes": {
+ "android": [
+ 52
+ ],
+ "usb": 458807,
+ "linux": 52,
+ "xkb": 60,
+ "windows": 52,
+ "macos": 47
+ },
+ "keyCodes": {
+ "android": [
+ 56
+ ]
+ }
+ },
+ "slash": {
+ "names": {
+ "domkey": "Slash",
+ "android": [
+ "SLASH"
+ ],
+ "english": "Slash",
+ "chromium": "slash"
+ },
+ "scanCodes": {
+ "android": [
+ 53
+ ],
+ "usb": 458808,
+ "linux": 53,
+ "xkb": 61,
+ "windows": 53,
+ "macos": 44
+ },
+ "keyCodes": {
+ "android": [
+ 76
+ ]
+ }
+ },
+ "capsLock": {
+ "names": {
+ "domkey": "CapsLock",
+ "android": [
+ "CAPS_LOCK"
+ ],
+ "english": "Caps Lock",
+ "chromium": "capsLock"
+ },
+ "scanCodes": {
+ "android": [
+ 58
+ ],
+ "usb": 458809,
+ "linux": 58,
+ "xkb": 66,
+ "windows": 58,
+ "macos": 57
+ },
+ "keyCodes": {
+ "android": [
+ 115
+ ]
+ }
+ },
+ "f1": {
+ "names": {
+ "domkey": "F1",
+ "android": [
+ "F1"
+ ],
+ "english": "F1",
+ "chromium": "f1"
+ },
+ "scanCodes": {
+ "android": [
+ 59
+ ],
+ "usb": 458810,
+ "linux": 59,
+ "xkb": 67,
+ "windows": 59,
+ "macos": 122
+ },
+ "keyCodes": {
+ "android": [
+ 131
+ ]
+ }
+ },
+ "f2": {
+ "names": {
+ "domkey": "F2",
+ "android": [
+ "F2"
+ ],
+ "english": "F2",
+ "chromium": "f2"
+ },
+ "scanCodes": {
+ "android": [
+ 60
+ ],
+ "usb": 458811,
+ "linux": 60,
+ "xkb": 68,
+ "windows": 60,
+ "macos": 120
+ },
+ "keyCodes": {
+ "android": [
+ 132
+ ]
+ }
+ },
+ "f3": {
+ "names": {
+ "domkey": "F3",
+ "android": [
+ "F3"
+ ],
+ "english": "F3",
+ "chromium": "f3"
+ },
+ "scanCodes": {
+ "android": [
+ 61
+ ],
+ "usb": 458812,
+ "linux": 61,
+ "xkb": 69,
+ "windows": 61,
+ "macos": 99
+ },
+ "keyCodes": {
+ "android": [
+ 133
+ ]
+ }
+ },
+ "f4": {
+ "names": {
+ "domkey": "F4",
+ "android": [
+ "F4"
+ ],
+ "english": "F4",
+ "chromium": "f4"
+ },
+ "scanCodes": {
+ "android": [
+ 62
+ ],
+ "usb": 458813,
+ "linux": 62,
+ "xkb": 70,
+ "windows": 62,
+ "macos": 118
+ },
+ "keyCodes": {
+ "android": [
+ 134
+ ]
+ }
+ },
+ "f5": {
+ "names": {
+ "domkey": "F5",
+ "android": [
+ "F5"
+ ],
+ "english": "F5",
+ "chromium": "f5"
+ },
+ "scanCodes": {
+ "android": [
+ 63
+ ],
+ "usb": 458814,
+ "linux": 63,
+ "xkb": 71,
+ "windows": 63,
+ "macos": 96
+ },
+ "keyCodes": {
+ "android": [
+ 135
+ ]
+ }
+ },
+ "f6": {
+ "names": {
+ "domkey": "F6",
+ "android": [
+ "F6"
+ ],
+ "english": "F6",
+ "chromium": "f6"
+ },
+ "scanCodes": {
+ "android": [
+ 64
+ ],
+ "usb": 458815,
+ "linux": 64,
+ "xkb": 72,
+ "windows": 64,
+ "macos": 97
+ },
+ "keyCodes": {
+ "android": [
+ 136
+ ]
+ }
+ },
+ "f7": {
+ "names": {
+ "domkey": "F7",
+ "android": [
+ "F7"
+ ],
+ "english": "F7",
+ "chromium": "f7"
+ },
+ "scanCodes": {
+ "android": [
+ 65
+ ],
+ "usb": 458816,
+ "linux": 65,
+ "xkb": 73,
+ "windows": 65,
+ "macos": 98
+ },
+ "keyCodes": {
+ "android": [
+ 137
+ ]
+ }
+ },
+ "f8": {
+ "names": {
+ "domkey": "F8",
+ "android": [
+ "F8"
+ ],
+ "english": "F8",
+ "chromium": "f8"
+ },
+ "scanCodes": {
+ "android": [
+ 66
+ ],
+ "usb": 458817,
+ "linux": 66,
+ "xkb": 74,
+ "windows": 66,
+ "macos": 100
+ },
+ "keyCodes": {
+ "android": [
+ 138
+ ]
+ }
+ },
+ "f9": {
+ "names": {
+ "domkey": "F9",
+ "android": [
+ "F9"
+ ],
+ "english": "F9",
+ "chromium": "f9"
+ },
+ "scanCodes": {
+ "android": [
+ 67
+ ],
+ "usb": 458818,
+ "linux": 67,
+ "xkb": 75,
+ "windows": 67,
+ "macos": 101
+ },
+ "keyCodes": {
+ "android": [
+ 139
+ ]
+ }
+ },
+ "f10": {
+ "names": {
+ "domkey": "F10",
+ "android": [
+ "F10"
+ ],
+ "english": "F10",
+ "chromium": "f10"
+ },
+ "scanCodes": {
+ "android": [
+ 68
+ ],
+ "usb": 458819,
+ "linux": 68,
+ "xkb": 76,
+ "windows": 68,
+ "macos": 109
+ },
+ "keyCodes": {
+ "android": [
+ 140
+ ]
+ }
+ },
+ "f11": {
+ "names": {
+ "domkey": "F11",
+ "android": [
+ "F11"
+ ],
+ "english": "F11",
+ "chromium": "f11"
+ },
+ "scanCodes": {
+ "android": [
+ 87
+ ],
+ "usb": 458820,
+ "linux": 87,
+ "xkb": 95,
+ "windows": 87,
+ "macos": 103
+ },
+ "keyCodes": {
+ "android": [
+ 141
+ ]
+ }
+ },
+ "f12": {
+ "names": {
+ "domkey": "F12",
+ "android": [
+ "F12"
+ ],
+ "english": "F12",
+ "chromium": "f12"
+ },
+ "scanCodes": {
+ "android": [
+ 88
+ ],
+ "usb": 458821,
+ "linux": 88,
+ "xkb": 96,
+ "windows": 88,
+ "macos": 111
+ },
+ "keyCodes": {
+ "android": [
+ 142
+ ]
+ }
+ },
+ "printScreen": {
+ "names": {
+ "domkey": "PrintScreen",
+ "android": [
+ "SYSRQ"
+ ],
+ "english": "Print Screen",
+ "chromium": "printScreen"
+ },
+ "scanCodes": {
+ "android": [
+ 99
+ ],
+ "usb": 458822,
+ "linux": 99,
+ "xkb": 107,
+ "windows": 57399,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 120
+ ]
+ }
+ },
+ "scrollLock": {
+ "names": {
+ "domkey": "ScrollLock",
+ "android": [
+ "SCROLL_LOCK"
+ ],
+ "english": "Scroll Lock",
+ "chromium": "scrollLock"
+ },
+ "scanCodes": {
+ "android": [
+ 70
+ ],
+ "usb": 458823,
+ "linux": 70,
+ "xkb": 78,
+ "windows": 70,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 116
+ ]
+ }
+ },
+ "pause": {
+ "names": {
+ "domkey": "Pause",
+ "android": [
+ "BREAK"
+ ],
+ "english": "Pause",
+ "chromium": "pause"
+ },
+ "scanCodes": {
+ "android": [
+ 119,
+ 411
+ ],
+ "usb": 458824,
+ "linux": 119,
+ "xkb": 127,
+ "windows": 69,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 121
+ ]
+ }
+ },
+ "insert": {
+ "names": {
+ "domkey": "Insert",
+ "android": [
+ "INSERT"
+ ],
+ "english": "Insert",
+ "chromium": "insert"
+ },
+ "scanCodes": {
+ "android": [
+ 110
+ ],
+ "usb": 458825,
+ "linux": 110,
+ "xkb": 118,
+ "windows": 57426,
+ "macos": 114
+ },
+ "keyCodes": {
+ "android": [
+ 124
+ ]
+ }
+ },
+ "home": {
+ "names": {
+ "domkey": "Home",
+ "android": [
+ "MOVE_HOME"
+ ],
+ "english": "Home",
+ "chromium": "home"
+ },
+ "scanCodes": {
+ "android": [
+ 102
+ ],
+ "usb": 458826,
+ "linux": 102,
+ "xkb": 110,
+ "windows": 57415,
+ "macos": 115
+ },
+ "keyCodes": {
+ "android": [
+ 122
+ ]
+ }
+ },
+ "pageUp": {
+ "names": {
+ "domkey": "PageUp",
+ "android": [
+ "PAGE_UP"
+ ],
+ "english": "Page Up",
+ "chromium": "pageUp"
+ },
+ "scanCodes": {
+ "android": [
+ 104,
+ 177
+ ],
+ "usb": 458827,
+ "linux": 104,
+ "xkb": 112,
+ "windows": 57417,
+ "macos": 116
+ },
+ "keyCodes": {
+ "android": [
+ 92
+ ]
+ }
+ },
+ "delete": {
+ "names": {
+ "domkey": "Delete",
+ "android": [
+ "FORWARD_DEL"
+ ],
+ "english": "Delete",
+ "chromium": "del"
+ },
+ "scanCodes": {
+ "android": [
+ 111
+ ],
+ "usb": 458828,
+ "linux": 111,
+ "xkb": 119,
+ "windows": 57427,
+ "macos": 117
+ },
+ "keyCodes": {
+ "android": [
+ 112
+ ]
+ }
+ },
+ "end": {
+ "names": {
+ "domkey": "End",
+ "android": [
+ "MOVE_END"
+ ],
+ "english": "End",
+ "chromium": "end"
+ },
+ "scanCodes": {
+ "android": [
+ 107
+ ],
+ "usb": 458829,
+ "linux": 107,
+ "xkb": 115,
+ "windows": 57423,
+ "macos": 119
+ },
+ "keyCodes": {
+ "android": [
+ 123
+ ]
+ }
+ },
+ "pageDown": {
+ "names": {
+ "domkey": "PageDown",
+ "android": [
+ "PAGE_DOWN"
+ ],
+ "english": "Page Down",
+ "chromium": "pageDown"
+ },
+ "scanCodes": {
+ "android": [
+ 109,
+ 178
+ ],
+ "usb": 458830,
+ "linux": 109,
+ "xkb": 117,
+ "windows": 57425,
+ "macos": 121
+ },
+ "keyCodes": {
+ "android": [
+ 93
+ ]
+ }
+ },
+ "arrowRight": {
+ "names": {
+ "domkey": "ArrowRight",
+ "android": [
+ "DPAD_RIGHT"
+ ],
+ "english": "Arrow Right",
+ "chromium": "arrowRight"
+ },
+ "scanCodes": {
+ "android": [
+ 106
+ ],
+ "usb": 458831,
+ "linux": 106,
+ "xkb": 114,
+ "windows": 57421,
+ "macos": 124
+ },
+ "keyCodes": {
+ "android": [
+ 22
+ ]
+ }
+ },
+ "arrowLeft": {
+ "names": {
+ "domkey": "ArrowLeft",
+ "android": [
+ "DPAD_LEFT"
+ ],
+ "english": "Arrow Left",
+ "chromium": "arrowLeft"
+ },
+ "scanCodes": {
+ "android": [
+ 105
+ ],
+ "usb": 458832,
+ "linux": 105,
+ "xkb": 113,
+ "windows": 57419,
+ "macos": 123
+ },
+ "keyCodes": {
+ "android": [
+ 21
+ ]
+ }
+ },
+ "arrowDown": {
+ "names": {
+ "domkey": "ArrowDown",
+ "android": [
+ "DPAD_DOWN"
+ ],
+ "english": "Arrow Down",
+ "chromium": "arrowDown"
+ },
+ "scanCodes": {
+ "android": [
+ 108
+ ],
+ "usb": 458833,
+ "linux": 108,
+ "xkb": 116,
+ "windows": 57424,
+ "macos": 125
+ },
+ "keyCodes": {
+ "android": [
+ 20
+ ]
+ }
+ },
+ "arrowUp": {
+ "names": {
+ "domkey": "ArrowUp",
+ "android": [
+ "DPAD_UP"
+ ],
+ "english": "Arrow Up",
+ "chromium": "arrowUp"
+ },
+ "scanCodes": {
+ "android": [
+ 103
+ ],
+ "usb": 458834,
+ "linux": 103,
+ "xkb": 111,
+ "windows": 57416,
+ "macos": 126
+ },
+ "keyCodes": {
+ "android": [
+ 19
+ ]
+ }
+ },
+ "numLock": {
+ "names": {
+ "domkey": "NumLock",
+ "android": [
+ "NUM_LOCK"
+ ],
+ "english": "Num Lock",
+ "chromium": "numLock"
+ },
+ "scanCodes": {
+ "android": [
+ 69
+ ],
+ "usb": 458835,
+ "linux": 69,
+ "xkb": 77,
+ "windows": 57413,
+ "macos": 71
+ },
+ "keyCodes": {
+ "android": [
+ 143
+ ]
+ }
+ },
+ "numpadDivide": {
+ "names": {
+ "domkey": "NumpadDivide",
+ "android": [
+ "NUMPAD_DIVIDE"
+ ],
+ "english": "Numpad Divide",
+ "chromium": "numpadDivide"
+ },
+ "scanCodes": {
+ "android": [
+ 98
+ ],
+ "usb": 458836,
+ "linux": 98,
+ "xkb": 106,
+ "windows": 57397,
+ "macos": 75
+ },
+ "keyCodes": {
+ "android": [
+ 154
+ ]
+ }
+ },
+ "numpadMultiply": {
+ "names": {
+ "domkey": "NumpadMultiply",
+ "android": [
+ "NUMPAD_MULTIPLY"
+ ],
+ "english": "Numpad Multiply",
+ "chromium": "numpadMultiply"
+ },
+ "scanCodes": {
+ "android": [
+ 55
+ ],
+ "usb": 458837,
+ "linux": 55,
+ "xkb": 63,
+ "windows": 55,
+ "macos": 67
+ },
+ "keyCodes": {
+ "android": [
+ 155
+ ]
+ }
+ },
+ "numpadSubtract": {
+ "names": {
+ "domkey": "NumpadSubtract",
+ "android": [
+ "NUMPAD_SUBTRACT"
+ ],
+ "english": "Numpad Subtract",
+ "chromium": "numpadSubtract"
+ },
+ "scanCodes": {
+ "android": [
+ 74
+ ],
+ "usb": 458838,
+ "linux": 74,
+ "xkb": 82,
+ "windows": 74,
+ "macos": 78
+ },
+ "keyCodes": {
+ "android": [
+ 156
+ ]
+ }
+ },
+ "numpadAdd": {
+ "names": {
+ "domkey": "NumpadAdd",
+ "android": [
+ "NUMPAD_ADD"
+ ],
+ "english": "Numpad Add",
+ "chromium": "numpadAdd"
+ },
+ "scanCodes": {
+ "android": [
+ 78
+ ],
+ "usb": 458839,
+ "linux": 78,
+ "xkb": 86,
+ "windows": 78,
+ "macos": 69
+ },
+ "keyCodes": {
+ "android": [
+ 157
+ ]
+ }
+ },
+ "numpadEnter": {
+ "names": {
+ "domkey": "NumpadEnter",
+ "android": [
+ "NUMPAD_ENTER"
+ ],
+ "english": "Numpad Enter",
+ "chromium": "numpadEnter"
+ },
+ "scanCodes": {
+ "android": [
+ 96
+ ],
+ "usb": 458840,
+ "linux": 96,
+ "xkb": 104,
+ "windows": 57372,
+ "macos": 76
+ },
+ "keyCodes": {
+ "android": [
+ 160
+ ]
+ }
+ },
+ "numpad1": {
+ "names": {
+ "domkey": "Numpad1",
+ "android": [
+ "NUMPAD_1"
+ ],
+ "english": "Numpad 1",
+ "chromium": "numpad1"
+ },
+ "scanCodes": {
+ "android": [
+ 79
+ ],
+ "usb": 458841,
+ "linux": 79,
+ "xkb": 87,
+ "windows": 79,
+ "macos": 83
+ },
+ "keyCodes": {
+ "android": [
+ 145
+ ]
+ }
+ },
+ "numpad2": {
+ "names": {
+ "domkey": "Numpad2",
+ "android": [
+ "NUMPAD_2"
+ ],
+ "english": "Numpad 2",
+ "chromium": "numpad2"
+ },
+ "scanCodes": {
+ "android": [
+ 80
+ ],
+ "usb": 458842,
+ "linux": 80,
+ "xkb": 88,
+ "windows": 80,
+ "macos": 84
+ },
+ "keyCodes": {
+ "android": [
+ 146
+ ]
+ }
+ },
+ "numpad3": {
+ "names": {
+ "domkey": "Numpad3",
+ "android": [
+ "NUMPAD_3"
+ ],
+ "english": "Numpad 3",
+ "chromium": "numpad3"
+ },
+ "scanCodes": {
+ "android": [
+ 81
+ ],
+ "usb": 458843,
+ "linux": 81,
+ "xkb": 89,
+ "windows": 81,
+ "macos": 85
+ },
+ "keyCodes": {
+ "android": [
+ 147
+ ]
+ }
+ },
+ "numpad4": {
+ "names": {
+ "domkey": "Numpad4",
+ "android": [
+ "NUMPAD_4"
+ ],
+ "english": "Numpad 4",
+ "chromium": "numpad4"
+ },
+ "scanCodes": {
+ "android": [
+ 75
+ ],
+ "usb": 458844,
+ "linux": 75,
+ "xkb": 83,
+ "windows": 75,
+ "macos": 86
+ },
+ "keyCodes": {
+ "android": [
+ 148
+ ]
+ }
+ },
+ "numpad5": {
+ "names": {
+ "domkey": "Numpad5",
+ "android": [
+ "NUMPAD_5"
+ ],
+ "english": "Numpad 5",
+ "chromium": "numpad5"
+ },
+ "scanCodes": {
+ "android": [
+ 76
+ ],
+ "usb": 458845,
+ "linux": 76,
+ "xkb": 84,
+ "windows": 76,
+ "macos": 87
+ },
+ "keyCodes": {
+ "android": [
+ 149
+ ]
+ }
+ },
+ "numpad6": {
+ "names": {
+ "domkey": "Numpad6",
+ "android": [
+ "NUMPAD_6"
+ ],
+ "english": "Numpad 6",
+ "chromium": "numpad6"
+ },
+ "scanCodes": {
+ "android": [
+ 77
+ ],
+ "usb": 458846,
+ "linux": 77,
+ "xkb": 85,
+ "windows": 77,
+ "macos": 88
+ },
+ "keyCodes": {
+ "android": [
+ 150
+ ]
+ }
+ },
+ "numpad7": {
+ "names": {
+ "domkey": "Numpad7",
+ "android": [
+ "NUMPAD_7"
+ ],
+ "english": "Numpad 7",
+ "chromium": "numpad7"
+ },
+ "scanCodes": {
+ "android": [
+ 71
+ ],
+ "usb": 458847,
+ "linux": 71,
+ "xkb": 79,
+ "windows": 71,
+ "macos": 89
+ },
+ "keyCodes": {
+ "android": [
+ 151
+ ]
+ }
+ },
+ "numpad8": {
+ "names": {
+ "domkey": "Numpad8",
+ "android": [
+ "NUMPAD_8"
+ ],
+ "english": "Numpad 8",
+ "chromium": "numpad8"
+ },
+ "scanCodes": {
+ "android": [
+ 72
+ ],
+ "usb": 458848,
+ "linux": 72,
+ "xkb": 80,
+ "windows": 72,
+ "macos": 91
+ },
+ "keyCodes": {
+ "android": [
+ 152
+ ]
+ }
+ },
+ "numpad9": {
+ "names": {
+ "domkey": "Numpad9",
+ "android": [
+ "NUMPAD_9"
+ ],
+ "english": "Numpad 9",
+ "chromium": "numpad9"
+ },
+ "scanCodes": {
+ "android": [
+ 73
+ ],
+ "usb": 458849,
+ "linux": 73,
+ "xkb": 81,
+ "windows": 73,
+ "macos": 92
+ },
+ "keyCodes": {
+ "android": [
+ 153
+ ]
+ }
+ },
+ "numpad0": {
+ "names": {
+ "domkey": "Numpad0",
+ "android": [
+ "NUMPAD_0"
+ ],
+ "english": "Numpad 0",
+ "chromium": "numpad0"
+ },
+ "scanCodes": {
+ "android": [
+ 82
+ ],
+ "usb": 458850,
+ "linux": 82,
+ "xkb": 90,
+ "windows": 82,
+ "macos": 82
+ },
+ "keyCodes": {
+ "android": [
+ 144
+ ]
+ }
+ },
+ "numpadDecimal": {
+ "names": {
+ "domkey": "NumpadDecimal",
+ "android": [
+ "NUMPAD_DOT"
+ ],
+ "english": "Numpad Decimal",
+ "chromium": "numpadDecimal"
+ },
+ "scanCodes": {
+ "android": [
+ 83
+ ],
+ "usb": 458851,
+ "linux": 83,
+ "xkb": 91,
+ "windows": 83,
+ "macos": 65
+ },
+ "keyCodes": {
+ "android": [
+ 158
+ ]
+ }
+ },
+ "intlBackslash": {
+ "names": {
+ "domkey": "IntlBackslash",
+ "android": null,
+ "english": "Intl Backslash",
+ "chromium": "intlBackslash"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458852,
+ "linux": 86,
+ "xkb": 94,
+ "windows": 86,
+ "macos": 10
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "contextMenu": {
+ "names": {
+ "domkey": "ContextMenu",
+ "android": [
+ "MENU"
+ ],
+ "english": "Context Menu",
+ "chromium": "contextMenu"
+ },
+ "scanCodes": {
+ "android": [
+ 127,
+ 139
+ ],
+ "usb": 458853,
+ "linux": 127,
+ "xkb": 135,
+ "windows": 57437,
+ "macos": 110
+ },
+ "keyCodes": {
+ "android": [
+ 82
+ ]
+ }
+ },
+ "power": {
+ "names": {
+ "domkey": "Power",
+ "android": [
+ "POWER"
+ ],
+ "english": "Power",
+ "chromium": "power"
+ },
+ "scanCodes": {
+ "android": [
+ 116,
+ 152
+ ],
+ "usb": 458854,
+ "linux": 116,
+ "xkb": 124,
+ "windows": 57438,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 26
+ ]
+ }
+ },
+ "numpadEqual": {
+ "names": {
+ "domkey": "NumpadEqual",
+ "android": [
+ "NUMPAD_EQUALS"
+ ],
+ "english": "Numpad Equal",
+ "chromium": "numpadEqual"
+ },
+ "scanCodes": {
+ "android": [
+ 117
+ ],
+ "usb": 458855,
+ "linux": 117,
+ "xkb": 125,
+ "windows": 89,
+ "macos": 81
+ },
+ "keyCodes": {
+ "android": [
+ 161
+ ]
+ }
+ },
+ "f13": {
+ "names": {
+ "domkey": "F13",
+ "android": [
+ "F13"
+ ],
+ "english": "F13",
+ "chromium": "f13"
+ },
+ "scanCodes": {
+ "android": [
+ 183
+ ],
+ "usb": 458856,
+ "linux": 183,
+ "xkb": 191,
+ "windows": 100,
+ "macos": 105
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f14": {
+ "names": {
+ "domkey": "F14",
+ "android": [
+ "F14"
+ ],
+ "english": "F14",
+ "chromium": "f14"
+ },
+ "scanCodes": {
+ "android": [
+ 184
+ ],
+ "usb": 458857,
+ "linux": 184,
+ "xkb": 192,
+ "windows": 101,
+ "macos": 107
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f15": {
+ "names": {
+ "domkey": "F15",
+ "android": [
+ "F15"
+ ],
+ "english": "F15",
+ "chromium": "f15"
+ },
+ "scanCodes": {
+ "android": [
+ 185
+ ],
+ "usb": 458858,
+ "linux": 185,
+ "xkb": 193,
+ "windows": 102,
+ "macos": 113
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f16": {
+ "names": {
+ "domkey": "F16",
+ "android": [
+ "F16"
+ ],
+ "english": "F16",
+ "chromium": "f16"
+ },
+ "scanCodes": {
+ "android": [
+ 186
+ ],
+ "usb": 458859,
+ "linux": 186,
+ "xkb": 194,
+ "windows": 103,
+ "macos": 106
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f17": {
+ "names": {
+ "domkey": "F17",
+ "android": [
+ "F17"
+ ],
+ "english": "F17",
+ "chromium": "f17"
+ },
+ "scanCodes": {
+ "android": [
+ 187
+ ],
+ "usb": 458860,
+ "linux": 187,
+ "xkb": 195,
+ "windows": 104,
+ "macos": 64
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f18": {
+ "names": {
+ "domkey": "F18",
+ "android": [
+ "F18"
+ ],
+ "english": "F18",
+ "chromium": "f18"
+ },
+ "scanCodes": {
+ "android": [
+ 188
+ ],
+ "usb": 458861,
+ "linux": 188,
+ "xkb": 196,
+ "windows": 105,
+ "macos": 79
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f19": {
+ "names": {
+ "domkey": "F19",
+ "android": [
+ "F19"
+ ],
+ "english": "F19",
+ "chromium": "f19"
+ },
+ "scanCodes": {
+ "android": [
+ 189
+ ],
+ "usb": 458862,
+ "linux": 189,
+ "xkb": 197,
+ "windows": 106,
+ "macos": 80
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f20": {
+ "names": {
+ "domkey": "F20",
+ "android": [
+ "F20"
+ ],
+ "english": "F20",
+ "chromium": "f20"
+ },
+ "scanCodes": {
+ "android": [
+ 190
+ ],
+ "usb": 458863,
+ "linux": 190,
+ "xkb": 198,
+ "windows": 107,
+ "macos": 90
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f21": {
+ "names": {
+ "domkey": "F21",
+ "android": [
+ "F21"
+ ],
+ "english": "F21",
+ "chromium": "f21"
+ },
+ "scanCodes": {
+ "android": [
+ 191
+ ],
+ "usb": 458864,
+ "linux": 191,
+ "xkb": 199,
+ "windows": 108,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f22": {
+ "names": {
+ "domkey": "F22",
+ "android": [
+ "F22"
+ ],
+ "english": "F22",
+ "chromium": "f22"
+ },
+ "scanCodes": {
+ "android": [
+ 192
+ ],
+ "usb": 458865,
+ "linux": 192,
+ "xkb": 200,
+ "windows": 109,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f23": {
+ "names": {
+ "domkey": "F23",
+ "android": [
+ "F23"
+ ],
+ "english": "F23",
+ "chromium": "f23"
+ },
+ "scanCodes": {
+ "android": [
+ 193
+ ],
+ "usb": 458866,
+ "linux": 193,
+ "xkb": 201,
+ "windows": 110,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "f24": {
+ "names": {
+ "domkey": "F24",
+ "android": [
+ "F24"
+ ],
+ "english": "F24",
+ "chromium": "f24"
+ },
+ "scanCodes": {
+ "android": [
+ 194
+ ],
+ "usb": 458867,
+ "linux": 194,
+ "xkb": 202,
+ "windows": 118,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "open": {
+ "names": {
+ "domkey": "Open",
+ "android": [
+ "OPEN"
+ ],
+ "english": "Open",
+ "chromium": "open"
+ },
+ "scanCodes": {
+ "android": [
+ 134
+ ],
+ "usb": 458868,
+ "linux": 134,
+ "xkb": 142,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "help": {
+ "names": {
+ "domkey": "Help",
+ "android": [
+ "HELP"
+ ],
+ "english": "Help",
+ "chromium": "help"
+ },
+ "scanCodes": {
+ "android": [
+ 138
+ ],
+ "usb": 458869,
+ "linux": 138,
+ "xkb": 146,
+ "windows": 57403,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 259
+ ]
+ }
+ },
+ "select": {
+ "names": {
+ "domkey": "Select",
+ "android": null,
+ "english": "Select",
+ "chromium": "select"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458871,
+ "linux": 132,
+ "xkb": 140,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "again": {
+ "names": {
+ "domkey": "Again",
+ "android": [
+ "AGAIN"
+ ],
+ "english": "Again",
+ "chromium": "again"
+ },
+ "scanCodes": {
+ "android": [
+ 129
+ ],
+ "usb": 458873,
+ "linux": 129,
+ "xkb": 137,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "undo": {
+ "names": {
+ "domkey": "Undo",
+ "android": [
+ "UNDO"
+ ],
+ "english": "Undo",
+ "chromium": "undo"
+ },
+ "scanCodes": {
+ "android": [
+ 131
+ ],
+ "usb": 458874,
+ "linux": 131,
+ "xkb": 139,
+ "windows": 57352,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "cut": {
+ "names": {
+ "domkey": "Cut",
+ "android": [
+ "CUT"
+ ],
+ "english": "Cut",
+ "chromium": "cut"
+ },
+ "scanCodes": {
+ "android": [
+ 137
+ ],
+ "usb": 458875,
+ "linux": 137,
+ "xkb": 145,
+ "windows": 57367,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 277
+ ]
+ }
+ },
+ "copy": {
+ "names": {
+ "domkey": "Copy",
+ "android": [
+ "COPY"
+ ],
+ "english": "Copy",
+ "chromium": "copy"
+ },
+ "scanCodes": {
+ "android": [
+ 133
+ ],
+ "usb": 458876,
+ "linux": 133,
+ "xkb": 141,
+ "windows": 57368,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 278
+ ]
+ }
+ },
+ "paste": {
+ "names": {
+ "domkey": "Paste",
+ "android": [
+ "PASTE"
+ ],
+ "english": "Paste",
+ "chromium": "paste"
+ },
+ "scanCodes": {
+ "android": [
+ 135
+ ],
+ "usb": 458877,
+ "linux": 135,
+ "xkb": 143,
+ "windows": 57354,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 279
+ ]
+ }
+ },
+ "find": {
+ "names": {
+ "domkey": "Find",
+ "android": [
+ "FIND"
+ ],
+ "english": "Find",
+ "chromium": "find"
+ },
+ "scanCodes": {
+ "android": [
+ 136
+ ],
+ "usb": 458878,
+ "linux": 136,
+ "xkb": 144,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "audioVolumeMute": {
+ "names": {
+ "domkey": "AudioVolumeMute",
+ "android": [
+ "VOLUME_MUTE"
+ ],
+ "english": "Audio Volume Mute",
+ "chromium": "volumeMute"
+ },
+ "scanCodes": {
+ "android": [
+ 113
+ ],
+ "usb": 458879,
+ "linux": 113,
+ "xkb": 121,
+ "windows": 57376,
+ "macos": 74
+ },
+ "keyCodes": {
+ "android": [
+ 164
+ ]
+ }
+ },
+ "audioVolumeUp": {
+ "names": {
+ "domkey": "AudioVolumeUp",
+ "android": [
+ "VOLUME_UP"
+ ],
+ "english": "Audio Volume Up",
+ "chromium": "volumeUp"
+ },
+ "scanCodes": {
+ "android": [
+ 115
+ ],
+ "usb": 458880,
+ "linux": 115,
+ "xkb": 123,
+ "windows": 57392,
+ "macos": 72
+ },
+ "keyCodes": {
+ "android": [
+ 24
+ ]
+ }
+ },
+ "audioVolumeDown": {
+ "names": {
+ "domkey": "AudioVolumeDown",
+ "android": [
+ "VOLUME_DOWN"
+ ],
+ "english": "Audio Volume Down",
+ "chromium": "volumeDown"
+ },
+ "scanCodes": {
+ "android": [
+ 114
+ ],
+ "usb": 458881,
+ "linux": 114,
+ "xkb": 122,
+ "windows": 57390,
+ "macos": 73
+ },
+ "keyCodes": {
+ "android": [
+ 25
+ ]
+ }
+ },
+ "numpadComma": {
+ "names": {
+ "domkey": "NumpadComma",
+ "android": [
+ "NUMPAD_COMMA"
+ ],
+ "english": "Numpad Comma",
+ "chromium": "numpadComma"
+ },
+ "scanCodes": {
+ "android": [
+ 95,
+ 121
+ ],
+ "usb": 458885,
+ "linux": 121,
+ "xkb": 129,
+ "windows": 126,
+ "macos": 95
+ },
+ "keyCodes": {
+ "android": [
+ 159
+ ]
+ }
+ },
+ "intlRo": {
+ "names": {
+ "domkey": "IntlRo",
+ "android": null,
+ "english": "Intl Ro",
+ "chromium": "intlRo"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458887,
+ "linux": 89,
+ "xkb": 97,
+ "windows": 115,
+ "macos": 94
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "kanaMode": {
+ "names": {
+ "domkey": "KanaMode",
+ "android": null,
+ "english": "Kana Mode",
+ "chromium": "kanaMode"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458888,
+ "linux": 93,
+ "xkb": 101,
+ "windows": 112,
+ "macos": 104
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "intlYen": {
+ "names": {
+ "domkey": "IntlYen",
+ "android": null,
+ "english": "Intl Yen",
+ "chromium": "intlYen"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458889,
+ "linux": 124,
+ "xkb": 132,
+ "windows": 125,
+ "macos": 93
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "convert": {
+ "names": {
+ "domkey": "Convert",
+ "android": [
+ "HENKAN"
+ ],
+ "english": "Convert",
+ "chromium": "convert"
+ },
+ "scanCodes": {
+ "android": [
+ 92
+ ],
+ "usb": 458890,
+ "linux": 92,
+ "xkb": 100,
+ "windows": 121,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 214
+ ]
+ }
+ },
+ "nonConvert": {
+ "names": {
+ "domkey": "NonConvert",
+ "android": [
+ "MUHENKAN"
+ ],
+ "english": "Non Convert",
+ "chromium": "nonConvert"
+ },
+ "scanCodes": {
+ "android": [
+ 94
+ ],
+ "usb": 458891,
+ "linux": 94,
+ "xkb": 102,
+ "windows": 123,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 213
+ ]
+ }
+ },
+ "lang1": {
+ "names": {
+ "domkey": "Lang1",
+ "android": null,
+ "english": "Lang 1",
+ "chromium": "lang1"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458896,
+ "linux": 122,
+ "xkb": 130,
+ "windows": 114,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "lang2": {
+ "names": {
+ "domkey": "Lang2",
+ "android": null,
+ "english": "Lang 2",
+ "chromium": "lang2"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458897,
+ "linux": 123,
+ "xkb": 131,
+ "windows": 113,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "lang3": {
+ "names": {
+ "domkey": "Lang3",
+ "android": [
+ "KATAKANA"
+ ],
+ "english": "Lang 3",
+ "chromium": "lang3"
+ },
+ "scanCodes": {
+ "android": [
+ 90
+ ],
+ "usb": 458898,
+ "linux": 90,
+ "xkb": 98,
+ "windows": 120,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "lang4": {
+ "names": {
+ "domkey": "Lang4",
+ "android": [
+ "HIRAGANA"
+ ],
+ "english": "Lang 4",
+ "chromium": "lang4"
+ },
+ "scanCodes": {
+ "android": [
+ 91
+ ],
+ "usb": 458899,
+ "linux": 91,
+ "xkb": 99,
+ "windows": 119,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "lang5": {
+ "names": {
+ "domkey": "Lang5",
+ "android": null,
+ "english": "Lang 5",
+ "chromium": "lang5"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458900,
+ "linux": 85,
+ "xkb": 93,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "abort": {
+ "names": {
+ "domkey": "Abort",
+ "android": null,
+ "english": "Abort",
+ "chromium": "abort"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458907,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "props": {
+ "names": {
+ "domkey": "Props",
+ "android": [
+ "PROPS"
+ ],
+ "english": "Props",
+ "chromium": "props"
+ },
+ "scanCodes": {
+ "android": [
+ 130
+ ],
+ "usb": 458915,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadParenLeft": {
+ "names": {
+ "domkey": "NumpadParenLeft",
+ "android": [
+ "NUMPAD_LEFT_PAREN"
+ ],
+ "english": "Numpad Paren Left",
+ "chromium": "numpadParenLeft"
+ },
+ "scanCodes": {
+ "android": [
+ 179
+ ],
+ "usb": 458934,
+ "linux": 179,
+ "xkb": 187,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 162
+ ]
+ }
+ },
+ "numpadParenRight": {
+ "names": {
+ "domkey": "NumpadParenRight",
+ "android": [
+ "NUMPAD_RIGHT_PAREN"
+ ],
+ "english": "Numpad Paren Right",
+ "chromium": "numpadParenRight"
+ },
+ "scanCodes": {
+ "android": [
+ 180
+ ],
+ "usb": 458935,
+ "linux": 180,
+ "xkb": 188,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 163
+ ]
+ }
+ },
+ "numpadBackspace": {
+ "names": {
+ "domkey": "NumpadBackspace",
+ "android": null,
+ "english": "Numpad Backspace",
+ "chromium": "numpadBackspace"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458939,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadMemoryStore": {
+ "names": {
+ "domkey": "NumpadMemoryStore",
+ "android": null,
+ "english": "Numpad Memory Store",
+ "chromium": "numpadMemoryStore"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458960,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadMemoryRecall": {
+ "names": {
+ "domkey": "NumpadMemoryRecall",
+ "android": null,
+ "english": "Numpad Memory Recall",
+ "chromium": "numpadMemoryRecall"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458961,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadMemoryClear": {
+ "names": {
+ "domkey": "NumpadMemoryClear",
+ "android": null,
+ "english": "Numpad Memory Clear",
+ "chromium": "numpadMemoryClear"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458962,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadMemoryAdd": {
+ "names": {
+ "domkey": "NumpadMemoryAdd",
+ "android": null,
+ "english": "Numpad Memory Add",
+ "chromium": "numpadMemoryAdd"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458963,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadMemorySubtract": {
+ "names": {
+ "domkey": "NumpadMemorySubtract",
+ "android": null,
+ "english": "Numpad Memory Subtract",
+ "chromium": "numpadMemorySubtract"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458964,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadSignChange": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Numpad Sign Change",
+ "chromium": "numpadSignChange"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458967,
+ "linux": 118,
+ "xkb": 126,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadClear": {
+ "names": {
+ "domkey": "NumpadClear",
+ "android": null,
+ "english": "Numpad Clear",
+ "chromium": "numpadClear"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458968,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "numpadClearEntry": {
+ "names": {
+ "domkey": "NumpadClearEntry",
+ "android": null,
+ "english": "Numpad Clear Entry",
+ "chromium": "numpadClearEntry"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 458969,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "controlLeft": {
+ "names": {
+ "domkey": "ControlLeft",
+ "android": [
+ "CTRL_LEFT"
+ ],
+ "english": "Control Left",
+ "chromium": "controlLeft"
+ },
+ "scanCodes": {
+ "android": [
+ 29
+ ],
+ "usb": 458976,
+ "linux": 29,
+ "xkb": 37,
+ "windows": 29,
+ "macos": 59
+ },
+ "keyCodes": {
+ "android": [
+ 113
+ ]
+ }
+ },
+ "shiftLeft": {
+ "names": {
+ "domkey": "ShiftLeft",
+ "android": [
+ "SHIFT_LEFT"
+ ],
+ "english": "Shift Left",
+ "chromium": "shiftLeft"
+ },
+ "scanCodes": {
+ "android": [
+ 42
+ ],
+ "usb": 458977,
+ "linux": 42,
+ "xkb": 50,
+ "windows": 42,
+ "macos": 56
+ },
+ "keyCodes": {
+ "android": [
+ 59
+ ]
+ }
+ },
+ "altLeft": {
+ "names": {
+ "domkey": "AltLeft",
+ "android": [
+ "ALT_LEFT"
+ ],
+ "english": "Alt Left",
+ "chromium": "altLeft"
+ },
+ "scanCodes": {
+ "android": [
+ 56
+ ],
+ "usb": 458978,
+ "linux": 56,
+ "xkb": 64,
+ "windows": 56,
+ "macos": 58
+ },
+ "keyCodes": {
+ "android": [
+ 57
+ ]
+ }
+ },
+ "metaLeft": {
+ "names": {
+ "domkey": "MetaLeft",
+ "android": [
+ "META_LEFT"
+ ],
+ "english": "Meta Left",
+ "chromium": "metaLeft"
+ },
+ "scanCodes": {
+ "android": [
+ 125
+ ],
+ "usb": 458979,
+ "linux": 125,
+ "xkb": 133,
+ "windows": 57435,
+ "macos": 55
+ },
+ "keyCodes": {
+ "android": [
+ 117
+ ]
+ }
+ },
+ "controlRight": {
+ "names": {
+ "domkey": "ControlRight",
+ "android": [
+ "CTRL_RIGHT"
+ ],
+ "english": "Control Right",
+ "chromium": "controlRight"
+ },
+ "scanCodes": {
+ "android": [
+ 97
+ ],
+ "usb": 458980,
+ "linux": 97,
+ "xkb": 105,
+ "windows": 57373,
+ "macos": 62
+ },
+ "keyCodes": {
+ "android": [
+ 114
+ ]
+ }
+ },
+ "shiftRight": {
+ "names": {
+ "domkey": "ShiftRight",
+ "android": [
+ "SHIFT_RIGHT"
+ ],
+ "english": "Shift Right",
+ "chromium": "shiftRight"
+ },
+ "scanCodes": {
+ "android": [
+ 54
+ ],
+ "usb": 458981,
+ "linux": 54,
+ "xkb": 62,
+ "windows": 54,
+ "macos": 60
+ },
+ "keyCodes": {
+ "android": [
+ 60
+ ]
+ }
+ },
+ "altRight": {
+ "names": {
+ "domkey": "AltRight",
+ "android": [
+ "ALT_RIGHT"
+ ],
+ "english": "Alt Right",
+ "chromium": "altRight"
+ },
+ "scanCodes": {
+ "android": [
+ 100
+ ],
+ "usb": 458982,
+ "linux": 100,
+ "xkb": 108,
+ "windows": 57400,
+ "macos": 61
+ },
+ "keyCodes": {
+ "android": [
+ 58
+ ]
+ }
+ },
+ "metaRight": {
+ "names": {
+ "domkey": "MetaRight",
+ "android": [
+ "META_RIGHT"
+ ],
+ "english": "Meta Right",
+ "chromium": "metaRight"
+ },
+ "scanCodes": {
+ "android": [
+ 126
+ ],
+ "usb": 458983,
+ "linux": 126,
+ "xkb": 134,
+ "windows": 57436,
+ "macos": 54
+ },
+ "keyCodes": {
+ "android": [
+ 118
+ ]
+ }
+ },
+ "info": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "INFO"
+ ],
+ "english": "Info",
+ "chromium": "info"
+ },
+ "scanCodes": {
+ "android": [
+ 358
+ ],
+ "usb": 786528,
+ "linux": 358,
+ "xkb": 366,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 165
+ ]
+ }
+ },
+ "closedCaptionToggle": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "CAPTIONS"
+ ],
+ "english": "Closed Caption Toggle",
+ "chromium": "closedCaptionToggle"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786529,
+ "linux": 370,
+ "xkb": 378,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 175
+ ]
+ }
+ },
+ "brightnessUp": {
+ "names": {
+ "domkey": "BrightnessUp",
+ "android": [
+ "BRIGHTNESS_UP"
+ ],
+ "english": "Brightness Up",
+ "chromium": "brightnessUp"
+ },
+ "scanCodes": {
+ "android": [
+ 225
+ ],
+ "usb": 786543,
+ "linux": 225,
+ "xkb": 233,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 221
+ ]
+ }
+ },
+ "brightnessDown": {
+ "names": {
+ "domkey": "BrightnessDown",
+ "android": [
+ "BRIGHTNESS_DOWN"
+ ],
+ "english": "Brightness Down",
+ "chromium": "brightnessDown"
+ },
+ "scanCodes": {
+ "android": [
+ 224
+ ],
+ "usb": 786544,
+ "linux": 224,
+ "xkb": 232,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 220
+ ]
+ }
+ },
+ "brightnessToggle": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Brightness Toggle",
+ "chromium": "brightnessToggle"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786546,
+ "linux": 431,
+ "xkb": 439,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "brightnessMinimum": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Brightness Minimum",
+ "chromium": "brightnessMinimum"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786547,
+ "linux": 592,
+ "xkb": 600,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "brightnessMaximum": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Brightness Maximum",
+ "chromium": "brightnessMaximum"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786548,
+ "linux": 593,
+ "xkb": 601,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "brightnessAuto": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Brightness Auto",
+ "chromium": "brightnessAuto"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786549,
+ "linux": 244,
+ "xkb": 252,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "mediaLast": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "LAST_CHANNEL"
+ ],
+ "english": "Media Last",
+ "chromium": "mediaLast"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786563,
+ "linux": 405,
+ "xkb": 413,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 229
+ ]
+ }
+ },
+ "launchPhone": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Launch Phone",
+ "chromium": "launchPhone"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786572,
+ "linux": 169,
+ "xkb": 177,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "programGuide": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Program Guide",
+ "chromium": "programGuide"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786573,
+ "linux": 362,
+ "xkb": 370,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "exit": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "EXIT"
+ ],
+ "english": "Exit",
+ "chromium": "exit"
+ },
+ "scanCodes": {
+ "android": [
+ 174
+ ],
+ "usb": 786580,
+ "linux": 174,
+ "xkb": 182,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "channelUp": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "CHANNEL_UP"
+ ],
+ "english": "Channel Up",
+ "chromium": "channelUp"
+ },
+ "scanCodes": {
+ "android": [
+ 402
+ ],
+ "usb": 786588,
+ "linux": 410,
+ "xkb": 418,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 166
+ ]
+ }
+ },
+ "channelDown": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "CHANNEL_DOWN"
+ ],
+ "english": "Channel Down",
+ "chromium": "channelDown"
+ },
+ "scanCodes": {
+ "android": [
+ 403
+ ],
+ "usb": 786589,
+ "linux": 411,
+ "xkb": 419,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 167
+ ]
+ }
+ },
+ "mediaPlay": {
+ "names": {
+ "domkey": "MediaPlay",
+ "android": [
+ "MEDIA_PLAY"
+ ],
+ "english": "Media Play",
+ "chromium": "mediaPlay"
+ },
+ "scanCodes": {
+ "android": [
+ 200,
+ 207
+ ],
+ "usb": 786608,
+ "linux": 207,
+ "xkb": 215,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 126
+ ]
+ }
+ },
+ "mediaRecord": {
+ "names": {
+ "domkey": "MediaRecord",
+ "android": [
+ "MEDIA_RECORD"
+ ],
+ "english": "Media Record",
+ "chromium": "mediaRecord"
+ },
+ "scanCodes": {
+ "android": [
+ 167
+ ],
+ "usb": 786610,
+ "linux": 167,
+ "xkb": 175,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 130
+ ]
+ }
+ },
+ "mediaFastForward": {
+ "names": {
+ "domkey": "MediaFastForward",
+ "android": [
+ "MEDIA_FAST_FORWARD"
+ ],
+ "english": "Media Fast Forward",
+ "chromium": "mediaFastForward"
+ },
+ "scanCodes": {
+ "android": [
+ 208
+ ],
+ "usb": 786611,
+ "linux": 208,
+ "xkb": 216,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 90
+ ]
+ }
+ },
+ "mediaRewind": {
+ "names": {
+ "domkey": "MediaRewind",
+ "android": [
+ "MEDIA_REWIND"
+ ],
+ "english": "Media Rewind",
+ "chromium": "mediaRewind"
+ },
+ "scanCodes": {
+ "android": [
+ 168
+ ],
+ "usb": 786612,
+ "linux": 168,
+ "xkb": 176,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 89
+ ]
+ }
+ },
+ "mediaTrackNext": {
+ "names": {
+ "domkey": "MediaTrackNext",
+ "android": [
+ "MEDIA_NEXT"
+ ],
+ "english": "Media Track Next",
+ "chromium": "mediaTrackNext"
+ },
+ "scanCodes": {
+ "android": [
+ 163
+ ],
+ "usb": 786613,
+ "linux": 163,
+ "xkb": 171,
+ "windows": 57369,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 87
+ ]
+ }
+ },
+ "mediaTrackPrevious": {
+ "names": {
+ "domkey": "MediaTrackPrevious",
+ "android": [
+ "MEDIA_PREVIOUS"
+ ],
+ "english": "Media Track Previous",
+ "chromium": "mediaTrackPrevious"
+ },
+ "scanCodes": {
+ "android": [
+ 165
+ ],
+ "usb": 786614,
+ "linux": 165,
+ "xkb": 173,
+ "windows": 57360,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 88
+ ]
+ }
+ },
+ "mediaStop": {
+ "names": {
+ "domkey": "MediaStop",
+ "android": [
+ "MEDIA_STOP"
+ ],
+ "english": "Media Stop",
+ "chromium": "mediaStop"
+ },
+ "scanCodes": {
+ "android": [
+ 128,
+ 166
+ ],
+ "usb": 786615,
+ "linux": 166,
+ "xkb": 174,
+ "windows": 57380,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 86
+ ]
+ }
+ },
+ "eject": {
+ "names": {
+ "domkey": "Eject",
+ "android": [
+ "MEDIA_EJECT"
+ ],
+ "english": "Eject",
+ "chromium": "eject"
+ },
+ "scanCodes": {
+ "android": [
+ 161,
+ 162
+ ],
+ "usb": 786616,
+ "linux": 161,
+ "xkb": 169,
+ "windows": 57388,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 129
+ ]
+ }
+ },
+ "mediaPlayPause": {
+ "names": {
+ "domkey": "MediaPlayPause",
+ "android": [
+ "MEDIA_PLAY_PAUSE"
+ ],
+ "english": "Media Play Pause",
+ "chromium": "mediaPlayPause"
+ },
+ "scanCodes": {
+ "android": [
+ 164
+ ],
+ "usb": 786637,
+ "linux": 164,
+ "xkb": 172,
+ "windows": 57378,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 85
+ ]
+ }
+ },
+ "speechInputToggle": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Speech Input Toggle",
+ "chromium": "speechInputToggle"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786639,
+ "linux": 582,
+ "xkb": 590,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "bassBoost": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "BASSBOOST"
+ ],
+ "english": "Bass Boost",
+ "chromium": "bassBoost"
+ },
+ "scanCodes": {
+ "android": [
+ 209
+ ],
+ "usb": 786661,
+ "linux": 209,
+ "xkb": 217,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "mediaSelect": {
+ "names": {
+ "domkey": "MediaSelect",
+ "android": null,
+ "english": "Media Select",
+ "chromium": "mediaSelect"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786819,
+ "linux": 171,
+ "xkb": 179,
+ "windows": 57453,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchWordProcessor": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Launch Word Processor",
+ "chromium": "launchWordProcessor"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786820,
+ "linux": 421,
+ "xkb": 429,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchSpreadsheet": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Launch Spreadsheet",
+ "chromium": "launchSpreadsheet"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786822,
+ "linux": 423,
+ "xkb": 431,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchMail": {
+ "names": {
+ "domkey": "LaunchMail",
+ "android": [
+ "ENVELOPE"
+ ],
+ "english": "Launch Mail",
+ "chromium": "launchMail"
+ },
+ "scanCodes": {
+ "android": [
+ 155,
+ 215
+ ],
+ "usb": 786826,
+ "linux": 155,
+ "xkb": 163,
+ "windows": 57452,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 65
+ ]
+ }
+ },
+ "launchContacts": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "CONTACTS"
+ ],
+ "english": "Launch Contacts",
+ "chromium": "launchContacts"
+ },
+ "scanCodes": {
+ "android": [
+ 429
+ ],
+ "usb": 786829,
+ "linux": 429,
+ "xkb": 437,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 207
+ ]
+ }
+ },
+ "launchCalendar": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "CALENDAR"
+ ],
+ "english": "Launch Calendar",
+ "chromium": "launchCalendar"
+ },
+ "scanCodes": {
+ "android": [
+ 397
+ ],
+ "usb": 786830,
+ "linux": 397,
+ "xkb": 405,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 208
+ ]
+ }
+ },
+ "launchApp2": {
+ "names": {
+ "domkey": "LaunchApp2",
+ "android": null,
+ "english": "Launch App2",
+ "chromium": "launchApp2"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786834,
+ "linux": 140,
+ "xkb": 148,
+ "windows": 57377,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchApp1": {
+ "names": {
+ "domkey": "LaunchApp1",
+ "android": null,
+ "english": "Launch App1",
+ "chromium": "launchApp1"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786836,
+ "linux": 144,
+ "xkb": 152,
+ "windows": 57451,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchInternetBrowser": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Launch Internet Browser",
+ "chromium": "launchInternetBrowser"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786838,
+ "linux": 150,
+ "xkb": 158,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "logOff": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Log Off",
+ "chromium": "logOff"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786844,
+ "linux": 433,
+ "xkb": 441,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "lockScreen": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Lock Screen",
+ "chromium": "lockScreen"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786846,
+ "linux": 152,
+ "xkb": 160,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchControlPanel": {
+ "names": {
+ "domkey": "LaunchControlPanel",
+ "android": null,
+ "english": "Launch Control Panel",
+ "chromium": "launchControlPanel"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786847,
+ "linux": 579,
+ "xkb": 587,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "selectTask": {
+ "names": {
+ "domkey": "SelectTask",
+ "android": null,
+ "english": "Select Task",
+ "chromium": "selectTask"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786850,
+ "linux": 580,
+ "xkb": 588,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchDocuments": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Launch Documents",
+ "chromium": "launchDocuments"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786855,
+ "linux": 235,
+ "xkb": 243,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "spellCheck": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Spell Check",
+ "chromium": "spellCheck"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786859,
+ "linux": 432,
+ "xkb": 440,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchKeyboardLayout": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Launch Keyboard Layout",
+ "chromium": "launchKeyboardLayout"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786862,
+ "linux": 374,
+ "xkb": 382,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchScreenSaver": {
+ "names": {
+ "domkey": "LaunchScreenSaver",
+ "android": null,
+ "english": "Launch Screen Saver",
+ "chromium": "launchScreenSaver"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786865,
+ "linux": 581,
+ "xkb": 589,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "launchAudioBrowser": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Launch Audio Browser",
+ "chromium": "launchAudioBrowser"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786871,
+ "linux": 392,
+ "xkb": 400,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "newKey": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "NEW"
+ ],
+ "english": "New Key",
+ "chromium": "new"
+ },
+ "scanCodes": {
+ "android": [
+ 181
+ ],
+ "usb": 786945,
+ "linux": 181,
+ "xkb": 189,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "close": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "MEDIA_CLOSE",
+ "CLOSE"
+ ],
+ "english": "Close",
+ "chromium": "close"
+ },
+ "scanCodes": {
+ "android": [
+ 160,
+ 206
+ ],
+ "usb": 786947,
+ "linux": 206,
+ "xkb": 214,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 128
+ ]
+ }
+ },
+ "save": {
+ "names": {
+ "domkey": null,
+ "android": null,
+ "english": "Save",
+ "chromium": "save"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786951,
+ "linux": 234,
+ "xkb": 242,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "print": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "PRINT"
+ ],
+ "english": "Print",
+ "chromium": "print"
+ },
+ "scanCodes": {
+ "android": [
+ 210
+ ],
+ "usb": 786952,
+ "linux": 210,
+ "xkb": 218,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "browserSearch": {
+ "names": {
+ "domkey": "BrowserSearch",
+ "android": [
+ "SEARCH"
+ ],
+ "english": "Browser Search",
+ "chromium": "browserSearch"
+ },
+ "scanCodes": {
+ "android": [
+ 217
+ ],
+ "usb": 786977,
+ "linux": 217,
+ "xkb": 225,
+ "windows": 57445,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 84
+ ]
+ }
+ },
+ "browserHome": {
+ "names": {
+ "domkey": "BrowserHome",
+ "android": null,
+ "english": "Browser Home",
+ "chromium": "browserHome"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786979,
+ "linux": 172,
+ "xkb": 180,
+ "windows": 57394,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "browserBack": {
+ "names": {
+ "domkey": "BrowserBack",
+ "android": null,
+ "english": "Browser Back",
+ "chromium": "browserBack"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786980,
+ "linux": 158,
+ "xkb": 166,
+ "windows": 57450,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "browserForward": {
+ "names": {
+ "domkey": "BrowserForward",
+ "android": [
+ "FORWARD"
+ ],
+ "english": "Browser Forward",
+ "chromium": "browserForward"
+ },
+ "scanCodes": {
+ "android": [
+ 159
+ ],
+ "usb": 786981,
+ "linux": 159,
+ "xkb": 167,
+ "windows": 57449,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 125
+ ]
+ }
+ },
+ "browserStop": {
+ "names": {
+ "domkey": "BrowserStop",
+ "android": null,
+ "english": "Browser Stop",
+ "chromium": "browserStop"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786982,
+ "linux": 128,
+ "xkb": 136,
+ "windows": 57448,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "browserRefresh": {
+ "names": {
+ "domkey": "BrowserRefresh",
+ "android": null,
+ "english": "Browser Refresh",
+ "chromium": "browserRefresh"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786983,
+ "linux": 173,
+ "xkb": 181,
+ "windows": 57447,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "browserFavorites": {
+ "names": {
+ "domkey": "BrowserFavorites",
+ "android": [
+ "BOOKMARK"
+ ],
+ "english": "Browser Favorites",
+ "chromium": "browserFavorites"
+ },
+ "scanCodes": {
+ "android": [
+ 156
+ ],
+ "usb": 786986,
+ "linux": 156,
+ "xkb": 164,
+ "windows": 57446,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 174
+ ]
+ }
+ },
+ "zoomIn": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "ZOOM_IN"
+ ],
+ "english": "Zoom In",
+ "chromium": "zoomIn"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786989,
+ "linux": 418,
+ "xkb": 426,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 168
+ ]
+ }
+ },
+ "zoomOut": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "ZOOM_OUT"
+ ],
+ "english": "Zoom Out",
+ "chromium": "zoomOut"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786990,
+ "linux": 419,
+ "xkb": 427,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 169
+ ]
+ }
+ },
+ "zoomToggle": {
+ "names": {
+ "domkey": "ZoomToggle",
+ "android": [
+ "TV_ZOOM_MODE"
+ ],
+ "english": "Zoom Toggle",
+ "chromium": "zoomToggle"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 786994,
+ "linux": null,
+ "xkb": null,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": [
+ 255
+ ]
+ }
+ },
+ "redo": {
+ "names": {
+ "domkey": null,
+ "android": [
+ "REDO"
+ ],
+ "english": "Redo",
+ "chromium": "redo"
+ },
+ "scanCodes": {
+ "android": [
+ 182
+ ],
+ "usb": 787065,
+ "linux": 182,
+ "xkb": 190,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "mailReply": {
+ "names": {
+ "domkey": "MailReply",
+ "android": null,
+ "english": "Mail Reply",
+ "chromium": "mailReply"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 787081,
+ "linux": 232,
+ "xkb": 240,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "mailForward": {
+ "names": {
+ "domkey": "MailForward",
+ "android": null,
+ "english": "Mail Forward",
+ "chromium": "mailForward"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 787083,
+ "linux": 233,
+ "xkb": 241,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ },
+ "mailSend": {
+ "names": {
+ "domkey": "MailSend",
+ "android": null,
+ "english": "Mail Send",
+ "chromium": "mailSend"
+ },
+ "scanCodes": {
+ "android": null,
+ "usb": 787084,
+ "linux": 231,
+ "xkb": 239,
+ "windows": null,
+ "macos": null
+ },
+ "keyCodes": {
+ "android": null
+ }
+ }
+}
\ No newline at end of file
diff --git a/dev/tools/gen_keycodes/data/key_name_to_android_name.json b/dev/tools/gen_keycodes/data/key_name_to_android_name.json
new file mode 100644
index 00000000000..f51ec020058
--- /dev/null
+++ b/dev/tools/gen_keycodes/data/key_name_to_android_name.json
@@ -0,0 +1,252 @@
+{
+ "again": ["AGAIN"],
+ "altLeft": ["ALT_LEFT"],
+ "altRight": ["ALT_RIGHT"],
+ "appSwitch": ["APP_SWITCH"],
+ "arrowDown": ["DPAD_DOWN"],
+ "arrowLeft": ["DPAD_LEFT"],
+ "arrowRight": ["DPAD_RIGHT"],
+ "arrowUp": ["DPAD_UP"],
+ "audioVolumeDown": ["VOLUME_DOWN"],
+ "audioVolumeMute": ["VOLUME_MUTE"],
+ "audioVolumeUp": ["VOLUME_UP"],
+ "avrInput": ["AVR_INPUT"],
+ "avrPower": ["AVR_POWER"],
+ "bassBoost": ["BASSBOOST"],
+ "print": ["PRINT"],
+ "backquote": ["GRAVE"],
+ "backslash": ["BACKSLASH"],
+ "backspace": ["DEL"],
+ "bracketLeft": ["LEFT_BRACKET"],
+ "bracketRight": ["RIGHT_BRACKET"],
+ "brightnessDown": ["BRIGHTNESS_DOWN"],
+ "brightnessUp": ["BRIGHTNESS_UP"],
+ "browserFavorites": ["BOOKMARK"],
+ "browserForward": ["FORWARD"],
+ "browserSearch": ["SEARCH"],
+ "call": ["CALL"],
+ "camera": ["CAMERA"],
+ "cameraFocus": ["FOCUS"],
+ "capsLock": ["CAPS_LOCK"],
+ "channelDown": ["CHANNEL_DOWN"],
+ "channelUp": ["CHANNEL_UP"],
+ "clear": ["CLEAR"],
+ "close": ["MEDIA_CLOSE", "CLOSE"],
+ "closedCaptionToggle": ["CAPTIONS"],
+ "colorF0Red": ["PROG_RED"],
+ "colorF1Green": ["PROG_GREEN"],
+ "colorF2Yellow": ["PROG_YELLOW"],
+ "colorF3Blue": ["PROG_BLUE"],
+ "comma": ["COMMA"],
+ "contextMenu": ["MENU"],
+ "controlLeft": ["CTRL_LEFT"],
+ "controlRight": ["CTRL_RIGHT"],
+ "convert": ["HENKAN"],
+ "copy": ["COPY"],
+ "cut": ["CUT"],
+ "delete": ["FORWARD_DEL"],
+ "digit0": ["0"],
+ "digit1": ["1"],
+ "digit2": ["2"],
+ "digit3": ["3"],
+ "digit4": ["4"],
+ "digit5": ["5"],
+ "digit6": ["6"],
+ "digit7": ["7"],
+ "digit8": ["8"],
+ "digit9": ["9"],
+ "dvr": ["DVR"],
+ "eisu": ["EISU"],
+ "eject": ["MEDIA_EJECT"],
+ "end": ["MOVE_END"],
+ "endCall": ["ENDCALL"],
+ "enter": ["ENTER"],
+ "equal": ["EQUALS"],
+ "escape": ["ESCAPE"],
+ "exit": ["EXIT"],
+ "f1": ["F1"],
+ "f2": ["F2"],
+ "f3": ["F3"],
+ "f4": ["F4"],
+ "f5": ["F5"],
+ "f6": ["F6"],
+ "f7": ["F7"],
+ "f8": ["F8"],
+ "f9": ["F9"],
+ "f10": ["F10"],
+ "f11": ["F11"],
+ "f12": ["F12"],
+ "f13": ["F13"],
+ "f14": ["F14"],
+ "f15": ["F15"],
+ "f16": ["F16"],
+ "f17": ["F17"],
+ "f18": ["F18"],
+ "f19": ["F19"],
+ "f20": ["F20"],
+ "f21": ["F21"],
+ "f22": ["F22"],
+ "f23": ["F23"],
+ "f24": ["F24"],
+ "find": ["FIND"],
+ "fn": ["FUNCTION"],
+ "goBack": ["BACK"],
+ "goHome": ["HOME"],
+ "groupNext": ["LANGUAGE_SWITCH"],
+ "guide": ["GUIDE"],
+ "headsetHook": ["HEADSETHOOK"],
+ "help": ["HELP"],
+ "hiraganaKatakana": ["KATAKANA_HIRAGANA"],
+ "home": ["MOVE_HOME"],
+ "info": ["INFO"],
+ "insert": ["INSERT"],
+ "kanjiMode": ["KANA"],
+ "keyA": ["A"],
+ "keyB": ["B"],
+ "keyC": ["C"],
+ "keyD": ["D"],
+ "keyE": ["E"],
+ "keyF": ["F"],
+ "keyG": ["G"],
+ "keyH": ["H"],
+ "keyI": ["I"],
+ "keyJ": ["J"],
+ "keyK": ["K"],
+ "keyL": ["L"],
+ "keyM": ["M"],
+ "keyN": ["N"],
+ "keyO": ["O"],
+ "keyP": ["P"],
+ "keyQ": ["Q"],
+ "keyR": ["R"],
+ "keyS": ["S"],
+ "keyT": ["T"],
+ "keyU": ["U"],
+ "keyV": ["V"],
+ "keyW": ["W"],
+ "keyX": ["X"],
+ "keyY": ["Y"],
+ "keyZ": ["Z"],
+ "lang3": ["KATAKANA"],
+ "lang4": ["HIRAGANA"],
+ "launchAssistant": ["ASSIST"],
+ "launchCalculator": ["CALCULATOR"],
+ "launchCalendar": ["CALENDAR"],
+ "launchContacts": ["CONTACTS"],
+ "launchMail": ["ENVELOPE"],
+ "launchMusicPlayer": ["MUSIC"],
+ "launchWebBrowser": ["EXPLORER"],
+ "mannerMode": ["MANNER_MODE"],
+ "mediaAudioTrack": ["MEDIA_AUDIO_TRACK"],
+ "mediaFastForward": ["MEDIA_FAST_FORWARD"],
+ "mediaLast": ["LAST_CHANNEL"],
+ "mediaPause": ["MEDIA_PAUSE"],
+ "mediaPlay": ["MEDIA_PLAY"],
+ "mediaPlayPause": ["MEDIA_PLAY_PAUSE"],
+ "mediaRecord": ["MEDIA_RECORD"],
+ "mediaRewind": ["MEDIA_REWIND"],
+ "mediaSkipBackward": ["MEDIA_SKIP_BACKWARD"],
+ "mediaSkipForward": ["MEDIA_SKIP_FORWARD"],
+ "mediaStepBackward": ["MEDIA_STEP_BACKWARD"],
+ "mediaStepForward": ["MEDIA_STEP_FORWARD"],
+ "mediaStop": ["MEDIA_STOP"],
+ "mediaTopMenu": ["MEDIA_TOP_MENU"],
+ "mediaTrackNext": ["MEDIA_NEXT"],
+ "mediaTrackPrevious": ["MEDIA_PREVIOUS"],
+ "metaLeft": ["META_LEFT"],
+ "metaRight": ["META_RIGHT"],
+ "microphoneVolumeMute": ["MUTE"],
+ "minus": ["MINUS"],
+ "modeChange": ["SWITCH_CHARSET"],
+ "navigateIn": ["NAVIGATE_IN"],
+ "navigateNext": ["NAVIGATE_NEXT"],
+ "navigateOut": ["NAVIGATE_OUT"],
+ "navigatePrevious": ["NAVIGATE_PREVIOUS"],
+ "newKey": ["NEW"],
+ "nonConvert": ["MUHENKAN"],
+ "none": ["UNKNOWN"],
+ "notification": ["NOTIFICATION"],
+ "numLock": ["NUM_LOCK"],
+ "numpad0": ["NUMPAD_0"],
+ "numpad1": ["NUMPAD_1"],
+ "numpad2": ["NUMPAD_2"],
+ "numpad3": ["NUMPAD_3"],
+ "numpad4": ["NUMPAD_4"],
+ "numpad5": ["NUMPAD_5"],
+ "numpad6": ["NUMPAD_6"],
+ "numpad7": ["NUMPAD_7"],
+ "numpad8": ["NUMPAD_8"],
+ "numpad9": ["NUMPAD_9"],
+ "numpadAdd": ["NUMPAD_ADD"],
+ "numpadComma": ["NUMPAD_COMMA"],
+ "numpadDecimal": ["NUMPAD_DOT"],
+ "numpadDivide": ["NUMPAD_DIVIDE"],
+ "numpadEnter": ["NUMPAD_ENTER"],
+ "numpadEqual": ["NUMPAD_EQUALS"],
+ "numpadMultiply": ["NUMPAD_MULTIPLY"],
+ "numpadParenLeft": ["NUMPAD_LEFT_PAREN"],
+ "numpadParenRight": ["NUMPAD_RIGHT_PAREN"],
+ "numpadSubtract": ["NUMPAD_SUBTRACT"],
+ "open": ["OPEN"],
+ "pageDown": ["PAGE_DOWN"],
+ "pageUp": ["PAGE_UP"],
+ "pairing": ["PAIRING"],
+ "paste": ["PASTE"],
+ "pause": ["BREAK"],
+ "period": ["PERIOD"],
+ "power": ["POWER"],
+ "printScreen": ["SYSRQ"],
+ "props": ["PROPS"],
+ "quote": ["APOSTROPHE"],
+ "redo": ["REDO"],
+ "scrollLock": ["SCROLL_LOCK"],
+ "semicolon": ["SEMICOLON"],
+ "settings": ["SETTINGS"],
+ "shiftLeft": ["SHIFT_LEFT"],
+ "shiftRight": ["SHIFT_RIGHT"],
+ "slash": ["SLASH"],
+ "sleep": ["SLEEP"],
+ "space": ["SPACE"],
+ "standby": ["SLEEP"],
+ "stbInput": ["STB_INPUT"],
+ "stbPower": ["STB_POWER"],
+ "suspend": ["SUSPEND"],
+ "symbol": ["SYM"],
+ "tab": ["TAB"],
+ "teletext": ["TV_TELETEXT"],
+ "tv": ["TV"],
+ "tv3dMode": ["3D_MODE"],
+ "tvAntennaCable": ["TV_ANTENNA_CABLE"],
+ "tvAudioDescription": ["TV_AUDIO_DESCRIPTION"],
+ "tvAudioDescriptionMixDown": ["TV_AUDIO_DESCRIPTION_MIX_DOWN"],
+ "tvAudioDescriptionMixUp": ["TV_AUDIO_DESCRIPTION_MIX_UP"],
+ "tvContentsMenu": ["TV_CONTENTS_MENU"],
+ "tvDataService": ["TV_DATA_SERVICE"],
+ "tvInput": ["TV_INPUT"],
+ "tvInputComponent1": ["TV_INPUT_COMPONENT_1"],
+ "tvInputComponent2": ["TV_INPUT_COMPONENT_2"],
+ "tvInputComposite1": ["TV_INPUT_COMPOSITE_1"],
+ "tvInputComposite2": ["TV_INPUT_COMPOSITE_2"],
+ "tvInputHdmi1": ["TV_INPUT_HDMI_1"],
+ "tvInputHdmi2": ["TV_INPUT_HDMI_2"],
+ "tvInputHdmi3": ["TV_INPUT_HDMI_3"],
+ "tvInputHdmi4": ["TV_INPUT_HDMI_4"],
+ "tvInputVga1": ["TV_INPUT_VGA_1"],
+ "tvNetwork": ["TV_NETWORK"],
+ "tvNumberEntry": ["TV_NUMBER_ENTRY"],
+ "tvPower": ["TV_POWER"],
+ "tvRadioService": ["TV_RADIO_SERVICE"],
+ "tvSatellite": ["TV_SATELLITE"],
+ "tvSatelliteBs": ["TV_SATELLITE_BS"],
+ "tvSatelliteCs": ["TV_SATELLITE_CS"],
+ "tvSatelliteToggle": ["TV_SATELLITE_SERVICE"],
+ "tvTerrestrialAnalog": ["TV_TERRESTRIAL_ANALOG"],
+ "tvTerrestrialDigital": ["TV_TERRESTRIAL_DIGITAL"],
+ "tvTimer": ["TV_TIMER_PROGRAMMING"],
+ "undo": ["UNDO"],
+ "wakeUp": ["WAKEUP"],
+ "zenkakuHankaku": ["ZENKAKU_HANKAKU"],
+ "zoomIn": ["ZOOM_IN"],
+ "zoomOut": ["ZOOM_OUT"],
+ "zoomToggle": ["TV_ZOOM_MODE"]
+}
diff --git a/dev/tools/gen_keycodes/data/keyboard_key.tmpl b/dev/tools/gen_keycodes/data/keyboard_key.tmpl
new file mode 100644
index 00000000000..69590955bd6
--- /dev/null
+++ b/dev/tools/gen_keycodes/data/keyboard_key.tmpl
@@ -0,0 +1,238 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT
+// This file is generated by dev/tools/gen_keycodes/bin/gen_keycodes.dart and
+// should not be edited directly.
+//
+// Edit the template dev/tools/gen_keycodes/data/keyboard_key.tmpl instead.
+// See dev/tools/gen_keycodes/README.md for more information.
+
+import 'package:flutter/foundation.dart';
+
+/// A class with static values that describe the keys that are returned from
+/// [RawKeyEvent.logicalKey].
+///
+/// These represent *logical* keys, which are keys which are interpreted in the
+/// context of any modifiers, modes, or keyboard layouts which may be in effect.
+///
+/// This is contrast to [PhysicalKeyboardKey], which represents a physical key
+/// in a particular location on the keyboard, without regard for the modifier
+/// state, mode, or keyboard layout.
+///
+/// See also:
+///
+/// * [RawKeyEvent], the keyboard event object received by widgets that listen
+/// to keyboard events.
+/// * [RawKeyboardListener], a widget used to listen to and supply handlers for
+/// keyboard events.
+class LogicalKeyboardKey extends Diagnosticable {
+ /// Defines a KeyboardKey value and optional debug name.
+ ///
+ /// To save executable size, it is recommended that the [debugName] be null in
+ /// release mode. You can do this using the [kReleaseMode] constant:
+ ///
+ /// {@tool sample}
+ /// const LogicalKeyboardKey mySpecialKey = LogicalKeyboardKey(
+ /// 0x0010000000a,
+ /// debugName: kReleaseMode ? null : 'Special Key',
+ /// );
+ /// {@end-tool}
+ const LogicalKeyboardKey(this.keyId, {this.debugName, this.keyLabel});
+
+ /// A unique code representing this key.
+ ///
+ /// This is an opaque identifier, and should not be unpacked to derive
+ /// information from it, as the representation of the code could change at any
+ /// time.
+ final int keyId;
+
+ /// The debug string to print for this keyboard key, which will be null in
+ /// release mode.
+ final String debugName;
+
+ /// The Unicode string representing the character produced by a [RawKeyEvent].
+ ///
+ /// This value is useful for describing or matching mnemonic keyboard
+ /// shortcuts.
+ ///
+ /// On most platforms this is a single code point, but it could contain any
+ /// Unicode string. The `keyLabel` differs from [RawKeyEvent.character]
+ /// because `keyLabel` only takes into account the key being pressed, not any
+ /// combining keys pressed before it, so, for example, an “o” that follows a
+ /// combining dieresis (“¨”, COMBINING DIAERESIS (U+0308)) would just return
+ /// “o” for [keyLabel], but would return “ö” for [RawKeyEvent.character].
+ ///
+ /// {@macro flutter.services.RawKeyEventData.keyLabel}
+ final String keyLabel;
+
+ @override
+ int get hashCode => keyId.hashCode;
+
+ @override
+ bool operator ==(dynamic other) {
+ if (other.runtimeType != runtimeType) {
+ return false;
+ }
+ final LogicalKeyboardKey typedOther = other;
+ return keyId == typedOther.keyId;
+ }
+
+ /// Finds the [LogicalKeyboardKey] that matches the given ID.
+ static LogicalKeyboardKey findKeyByKeyId(int keyId) => _knownLogicalKeys[keyId];
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(StringProperty('keyId', '0x${keyId.toRadixString(16).padLeft(8, '0')}', showName: true));
+ properties.add(StringProperty('keyLabel', keyLabel, showName: true));
+ properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
+ }
+
+ /// Returns true if the given label represents a Unicode control character.
+ ///
+ /// Examples of control characters are characters like "U+000A LINE FEED (LF)"
+ /// or "U+001B ESCAPE (ESC)".
+ ///
+ /// See for more
+ /// information.
+ ///
+ /// Used by [RawKeyEvent] subclasses to help construct IDs.
+ static bool isControlCharacter(String label) {
+ if (label.length > 1) {
+ return false;
+ }
+ final int codeUnit = label.codeUnitAt(0);
+ return (codeUnit <= 0x1f && codeUnit >= 0x00) || (codeUnit >= 0x7f && codeUnit <= 0x9f);
+ }
+
+ /// Returns true if the [keyId] of this object is one that is autogenerated by
+ /// Flutter.
+ ///
+ /// Autogenerated key IDs are generated in response to platform key codes
+ /// which Flutter doesn't recognize, and their IDs shouldn't be used in a
+ /// persistent way.
+ ///
+ /// Autogenerated IDs should be a rare occurrence: Flutter supports most keys.
+ ///
+ /// Keys that generate Unicode characters (even if unknown to Flutter) will
+ /// not return true for `isAutogenerated`, since they will be assigned a
+ /// Unicode-based code that will remain stable.
+ ///
+ /// If Flutter adds support for a previously unsupported key code, the ID it
+ /// reports will change, but the ID will remain stable on the platform it is
+ /// produced on until Flutter adds support for recognizing it.
+ ///
+ /// So, hypothetically, if Android added a new key code of 0xffff,
+ /// representing a new "do what I mean" key, then the autogenerated code would
+ /// be 0x1020000ffff, but once Flutter added the "doWhatIMean" key to the
+ /// definitions below, the new code would be 0x0020000ffff for all platforms
+ /// that had a "do what I mean" key from then on.
+ bool get isAutogenerated => (keyId & autogeneratedMask) != 0;
+
+ /// Mask for the 32-bit value portion of the key code. This is used by
+ /// platform-specific code to generate Flutter key codes.
+ static const int valueMask = 0x000FFFFFFFF;
+
+ /// Mask for the platform prefix portion of the key code. This is used by
+ /// platform-specific code to generate Flutter key codes.
+ static const int platformMask = 0x0FF00000000;
+
+ /// Mask for the autogenerated bit portion of the key code. This is used by
+ /// platform-specific code to generate new Flutter key codes for keys which
+ /// are not recognized.
+ static const int autogeneratedMask = 0x10000000000;
+
+ /// The code prefix for keys which have a Unicode representation. This is used
+ /// by platform-specific code to generate Flutter key codes.
+ static const int unicodePlane = 0x00000000000;
+
+ /// The code prefix for keys which do not have a Unicode representation. This
+ /// is used by platform-specific code to generate Flutter key codes using HID
+ /// Usage codes.
+ static const int hidPlane = 0x00100000000;
+@@@LOGICAL_KEY_DEFINITIONS@@@
+ // A list of all predefined constant LogicalKeyboardKeys so they can be
+ // searched..
+ static const Map _knownLogicalKeys = {
+@@@LOGICAL_KEY_MAP@@@
+ };
+}
+
+/// A class with static values that describe the keys that are returned from
+/// [RawKeyEvent.physicalKey].
+///
+/// These represent *physical* keys, which are keys which represent a
+/// particular key location on the keyboard. It ignores any modifiers, modes,
+/// or keyboard layouts which may be in effect. This is contrast to
+/// [LogicalKeyboardKey], which represents a logical key interpreted in the
+/// context of modifiers, modes, and/or keyboard layouts.
+///
+/// See also:
+///
+/// * [RawKeyEvent], the keyboard event object received by widgets that listen
+/// to keyboard events.
+/// * [RawKeyboardListener], a widget used to listen to and supply handlers for
+/// keyboard events.
+class PhysicalKeyboardKey extends Diagnosticable {
+ /// Defines a KeyboardKey value and optional debug name. The debug name must
+ /// be null in release mode.
+ ///
+ /// To save executable size, it is recommended that the [debugName] be null in
+ /// release mode. You can do this using the [kReleaseMode] constant:
+ ///
+ /// {@tool sample}
+ /// const PhysicalKeyboardKey mySpecialPhysicalKey = PhysicalKeyboardKey(
+ /// 0x0010000000a,
+ /// debugName: kReleaseMode ? null : 'Special Key',
+ /// );
+ /// {@end-tool}
+ const PhysicalKeyboardKey(this.usbHidUsage, {this.debugName});
+
+ /// The unique USB HID usage ID of this physical key on the keyboard.
+ ///
+ /// Due to the variations in platform APIs, this may not be the actual HID
+ /// usage code from the hardware, but a value derived from available
+ /// information on the platform.
+ ///
+ /// See
+ /// for the HID usage values and their meanings.
+ final int usbHidUsage;
+
+ /// The debug string to print for this keyboard key, which will be null in
+ /// release mode.
+ final String debugName;
+
+ /// Finds a known [PhysicalKeyboardKey] that matches the given USB HID usage
+ /// code.
+ static PhysicalKeyboardKey findKeyByCode(int usageCode) => _knownPhysicalKeys[usageCode];
+
+ @override
+ int get hashCode => usbHidUsage.hashCode;
+
+ @override
+ bool operator ==(dynamic other) {
+ if (other.runtimeType != runtimeType) {
+ return false;
+ }
+ final PhysicalKeyboardKey typedOther = other;
+ return usbHidUsage == typedOther.usbHidUsage;
+ }
+
+ @override
+ void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+ super.debugFillProperties(properties);
+ properties.add(StringProperty('usbHidUsage', '0x${usbHidUsage.toRadixString(16).padLeft(8, '0')}', showName: true));
+ properties.add(StringProperty('debugName', debugName, showName: true, defaultValue: null));
+ }
+
+ // Key constants for all keyboard keys in the USB HID specification at the
+ // time Flutter was built.
+@@@PHYSICAL_KEY_DEFINITIONS@@@
+ // A list of all the predefined constant PhysicalKeyboardKeys so that they
+ // can be searched.
+ static const Map _knownPhysicalKeys = {
+@@@PHYSICAL_KEY_MAP@@@
+ };
+}
diff --git a/dev/tools/gen_keycodes/data/keyboard_maps.tmpl b/dev/tools/gen_keycodes/data/keyboard_maps.tmpl
new file mode 100644
index 00000000000..a52371b4280
--- /dev/null
+++ b/dev/tools/gen_keycodes/data/keyboard_maps.tmpl
@@ -0,0 +1,40 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT
+// This file is generated by dev/tools/gen_keycodes/bin/gen_keycodes.dart and
+// should not be edited directly.
+//
+// Edit dev/tools/gen_keycodes/data/keyboard_maps.tmpl instead.
+// See dev/tools/gen_keycodes/README.md for more information.
+
+import 'keyboard_key.dart';
+
+/// Maps Android-specific key codes to the matching [LogicalKeyboardKey].
+const Map kAndroidToLogicalKey = {
+@@@ANDROID_KEY_CODE_MAP@@@
+};
+
+/// Maps Android-specific scan codes to the matching [PhysicalKeyboardKey].
+const Map kAndroidToPhysicalKey = {
+@@@ANDROID_SCAN_CODE_MAP@@@
+};
+
+/// A map of Android key codes which have printable representations, but appear
+/// on the number pad. Used to provide different key objects for keys like
+/// KEY_EQUALS and NUMPAD_EQUALS.
+const Map kAndroidNumPadMap = {
+@@@ANDROID_NUMPAD_MAP@@@
+};
+
+/// Maps Fuchsia-specific IDs to the matching [LogicalKeyboardKey].
+const Map kFuchsiaToLogicalKey = {
+@@@FUCHSIA_KEY_CODE_MAP@@@
+};
+
+/// Maps Fuchsia-specific USB HID Usage IDs to the matching
+/// [PhysicalKeyboardKey].
+const Map kFuchsiaToPhysicalKey = {
+@@@FUCHSIA_SCAN_CODE_MAP@@@
+};
diff --git a/dev/tools/gen_keycodes/data/printable.json b/dev/tools/gen_keycodes/data/printable.json
new file mode 100644
index 00000000000..ae2ef3a8a0f
--- /dev/null
+++ b/dev/tools/gen_keycodes/data/printable.json
@@ -0,0 +1,69 @@
+{
+ "backquote": "`",
+ "backslash": "\\",
+ "bracketLeft": "[",
+ "bracketRight": "]",
+ "comma": ",",
+ "digit0": "0",
+ "digit1": "1",
+ "digit2": "2",
+ "digit3": "3",
+ "digit4": "4",
+ "digit5": "5",
+ "digit6": "6",
+ "digit7": "7",
+ "digit8": "8",
+ "digit9": "9",
+ "equal": "=",
+ "keyA": "A",
+ "keyB": "B",
+ "keyC": "C",
+ "keyD": "D",
+ "keyE": "E",
+ "keyF": "F",
+ "keyG": "G",
+ "keyH": "H",
+ "keyI": "I",
+ "keyJ": "J",
+ "keyK": "K",
+ "keyL": "L",
+ "keyM": "M",
+ "keyN": "N",
+ "keyO": "O",
+ "keyP": "P",
+ "keyQ": "Q",
+ "keyR": "R",
+ "keyS": "S",
+ "keyT": "T",
+ "keyU": "U",
+ "keyV": "V",
+ "keyW": "W",
+ "keyX": "X",
+ "keyY": "Y",
+ "keyZ": "Z",
+ "minus": "-",
+ "numpad0": "0",
+ "numpad1": "1",
+ "numpad2": "2",
+ "numpad3": "3",
+ "numpad4": "4",
+ "numpad5": "5",
+ "numpad6": "6",
+ "numpad7": "7",
+ "numpad8": "8",
+ "numpad9": "9",
+ "numpadAdd": "+",
+ "numpadComma": ",",
+ "numpadDecimal": ".",
+ "numpadDivide": "/",
+ "numpadEqual": "=",
+ "numpadMultiply": "*",
+ "numpadParenLeft": "(",
+ "numpadParenRight": ")",
+ "numpadSubtract": "-",
+ "period": ".",
+ "quote": "'",
+ "semicolon": ";",
+ "slash": "/",
+ "space": " "
+}
diff --git a/dev/tools/gen_keycodes/lib/code_gen.dart b/dev/tools/gen_keycodes/lib/code_gen.dart
new file mode 100644
index 00000000000..f2a86ceaeb8
--- /dev/null
+++ b/dev/tools/gen_keycodes/lib/code_gen.dart
@@ -0,0 +1,195 @@
+// Copyright 2019 The Chromium 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:io';
+import 'package:path/path.dart' as path;
+
+import 'package:gen_keycodes/key_data.dart';
+import 'package:gen_keycodes/utils.dart';
+
+/// Generates the keyboard_keys.dart and keyboard_maps.dart files, based on the
+/// information in the key data structure given to it.
+class CodeGenerator {
+ CodeGenerator(this.keyData);
+
+ /// Given an [input] string, wraps the text at 80 characters and prepends each
+ /// line with the [prefix] string. Use for generated comments.
+ String wrapString(String input, String prefix) {
+ final int wrapWidth = 80 - prefix.length;
+ final StringBuffer result = StringBuffer();
+ final List words = input.split(RegExp(r'\s+'));
+ String currentLine = words.removeAt(0);
+ for (String word in words) {
+ if ((currentLine.length + word.length) < wrapWidth) {
+ currentLine += ' $word';
+ } else {
+ result.writeln('$prefix$currentLine');
+ currentLine = '$word';
+ }
+ }
+ if (currentLine.isNotEmpty) {
+ result.writeln('$prefix$currentLine');
+ }
+ return result.toString();
+ }
+
+ /// Gets the generated definitions of PhysicalKeyboardKeys.
+ String get physicalDefinitions {
+ final StringBuffer definitions = StringBuffer();
+ for (Key entry in keyData.data) {
+ final String comment = wrapString('Represents the location of a '
+ '"${entry.commentName}" key on a generalized keyboard. See the function '
+ '[RawKeyEvent.physicalKey] for more information.', ' /// ');
+ definitions.write('''
+
+$comment static const PhysicalKeyboardKey ${entry.constantName} = PhysicalKeyboardKey(${toHex(entry.usbHidCode, digits: 8)}, debugName: kReleaseMode ? null : '${entry.commentName}');
+''');
+ }
+ return definitions.toString();
+ }
+
+ /// Gets the generated definitions of LogicalKeyboardKeys.
+ String get logicalDefinitions {
+ String escapeLabel(String label) => label.contains("'") ? 'r"$label"' : "r'$label'";
+ final StringBuffer definitions = StringBuffer();
+ for (Key entry in keyData.data) {
+ final String comment = wrapString('Represents a logical "${entry.commentName}" key on the '
+ 'keyboard. See the function [RawKeyEvent.logicalKey] for more information.', ' /// ');
+ if (entry.keyLabel == null) {
+ definitions.write('''
+
+$comment static const LogicalKeyboardKey ${entry.constantName} = LogicalKeyboardKey(${toHex(entry.flutterId, digits: 11)}, debugName: kReleaseMode ? null : '${entry.commentName}');
+''');
+ } else {
+ definitions.write('''
+
+$comment static const LogicalKeyboardKey ${entry.constantName} = LogicalKeyboardKey(${toHex(entry.flutterId, digits: 11)}, keyLabel: ${escapeLabel(entry.keyLabel)}, debugName: kReleaseMode ? null : '${entry.commentName}');
+''');
+ }
+ }
+ return definitions.toString();
+ }
+
+ /// This generates the map of USB HID codes to physical keys.
+ String get predefinedHidCodeMap {
+ final StringBuffer scanCodeMap = StringBuffer();
+ for (Key entry in keyData.data) {
+ scanCodeMap.writeln(' ${toHex(entry.usbHidCode)}: ${entry.constantName},');
+ }
+ return scanCodeMap.toString().trimRight();
+ }
+
+ /// THis generates the map of Flutter key codes to logical keys.
+ String get predefinedKeyCodeMap {
+ final StringBuffer keyCodeMap = StringBuffer();
+ for (Key entry in keyData.data) {
+ keyCodeMap.writeln(' ${toHex(entry.flutterId, digits: 10)}: ${entry.constantName},');
+ }
+ return keyCodeMap.toString().trimRight();
+ }
+
+ /// This generates the map of Android key codes to logical keys.
+ String get androidKeyCodeMap {
+ final StringBuffer androidKeyCodeMap = StringBuffer();
+ for (Key entry in keyData.data) {
+ if (entry.androidKeyCodes != null) {
+ for (int code in entry.androidKeyCodes.cast()) {
+ androidKeyCodeMap.writeln(' $code: LogicalKeyboardKey.${entry.constantName},');
+ }
+ }
+ }
+ return androidKeyCodeMap.toString().trimRight();
+ }
+
+ /// This generates the map of Android number pad key codes to logical keys.
+ String get androidNumpadMap {
+ final StringBuffer androidKeyCodeMap = StringBuffer();
+ final List onlyNumpads = keyData.data.where((Key entry) {
+ return entry.constantName.startsWith('numpad') && entry.keyLabel != null;
+ }).toList();
+ for (Key entry in onlyNumpads) {
+ if (entry.androidKeyCodes != null) {
+ for (int code in entry.androidKeyCodes.cast()) {
+ androidKeyCodeMap.writeln(' $code: LogicalKeyboardKey.${entry.constantName},');
+ }
+ }
+ }
+ return androidKeyCodeMap.toString().trimRight();
+ }
+
+ /// This generates the map of Android scan codes to physical keys.
+ String get androidScanCodeMap {
+ final StringBuffer androidScanCodeMap = StringBuffer();
+ for (Key entry in keyData.data) {
+ if (entry.androidScanCodes != null) {
+ for (int code in entry.androidScanCodes.cast()) {
+ androidScanCodeMap.writeln(' $code: PhysicalKeyboardKey.${entry.constantName},');
+ }
+ }
+ }
+ return androidScanCodeMap.toString().trimRight();
+ }
+
+ /// This generates the map of Fuchsia key codes to logical keys.
+ String get fuchsiaKeyCodeMap {
+ final StringBuffer fuchsiaKeyCodeMap = StringBuffer();
+ for (Key entry in keyData.data) {
+ if (entry.usbHidCode != null) {
+ fuchsiaKeyCodeMap.writeln(' ${toHex(entry.flutterId)}: LogicalKeyboardKey.${entry.constantName},');
+ }
+ }
+ return fuchsiaKeyCodeMap.toString().trimRight();
+ }
+
+ /// This generates the map of Fuchsia USB HID codes to physical keys.
+ String get fuchsiaHidCodeMap {
+ final StringBuffer fuchsiaScanCodeMap = StringBuffer();
+ for (Key entry in keyData.data) {
+ if (entry.usbHidCode != null) {
+ fuchsiaScanCodeMap.writeln(' ${toHex(entry.usbHidCode)}: PhysicalKeyboardKey.${entry.constantName},');
+ }
+ }
+ return fuchsiaScanCodeMap.toString().trimRight();
+ }
+
+ /// Substitutes the various maps and definitions into the template file for
+ /// keyboard_key.dart.
+ String generateKeyboardKeys() {
+ final Map mappings = {
+ 'PHYSICAL_KEY_MAP': predefinedHidCodeMap,
+ 'LOGICAL_KEY_MAP': predefinedKeyCodeMap,
+ 'LOGICAL_KEY_DEFINITIONS': logicalDefinitions,
+ 'PHYSICAL_KEY_DEFINITIONS': physicalDefinitions,
+ };
+
+ final String template = File(path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'keyboard_key.tmpl')).readAsStringSync();
+ return _injectDictionary(template, mappings);
+ }
+
+ /// Substitutes the various platform specific maps into the template file for
+ /// keyboard_maps.dart.
+ String generateKeyboardMaps() {
+ final Map mappings = {
+ 'ANDROID_SCAN_CODE_MAP': androidScanCodeMap,
+ 'ANDROID_KEY_CODE_MAP': androidKeyCodeMap,
+ 'ANDROID_NUMPAD_MAP': androidNumpadMap,
+ 'FUCHSIA_SCAN_CODE_MAP': fuchsiaHidCodeMap,
+ 'FUCHSIA_KEY_CODE_MAP': fuchsiaKeyCodeMap,
+ };
+
+ final String template = File(path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'keyboard_maps.tmpl')).readAsStringSync();
+ return _injectDictionary(template, mappings);
+ }
+
+ /// The database of keys loaded from disk.
+ final KeyData keyData;
+
+ static String _injectDictionary(String template, Map dictionary) {
+ String result = template;
+ for (String key in dictionary.keys) {
+ result = result.replaceAll('@@@$key@@@', dictionary[key]);
+ }
+ return result;
+ }
+}
diff --git a/dev/tools/gen_keycodes/lib/key_data.dart b/dev/tools/gen_keycodes/lib/key_data.dart
new file mode 100644
index 00000000000..d2e66fe0f54
--- /dev/null
+++ b/dev/tools/gen_keycodes/lib/key_data.dart
@@ -0,0 +1,365 @@
+// Copyright 2019 The Chromium 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 'package:meta/meta.dart';
+
+import 'package:gen_keycodes/utils.dart';
+
+/// 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
+/// [KeyData.fromJson] constructor and [toJson] method, respectively.
+class KeyData {
+ /// Parses the input data given in from the various data source files,
+ /// populating the data structure.
+ ///
+ /// None of the parameters may be null.
+ KeyData(
+ String chromiumHidCodes,
+ String androidKeyboardLayout,
+ String androidKeyCodeHeader,
+ String androidNameMap,
+ ) : assert(chromiumHidCodes != null),
+ assert(androidKeyboardLayout != null),
+ assert(androidKeyCodeHeader != null),
+ assert(androidNameMap != null) {
+ _nameToAndroidScanCodes = _readAndroidScanCodes(androidKeyboardLayout);
+ _nameToAndroidKeyCode = _readAndroidKeyCodes(androidKeyCodeHeader);
+ final Map> dynamicNames = json.decode(androidNameMap).cast>();
+ _nameToAndroidName = dynamicNames.map>((String key, List value) {
+ return MapEntry>(key, value.cast());
+ });
+ data = _readHidEntries(chromiumHidCodes);
+ }
+
+ /// Parses the given JSON data and populates the data structure from it.
+ KeyData.fromJson(Map contentMap) {
+ data = [];
+ for (String key in contentMap.keys) {
+ data.add(Key.fromJsonMapEntry(key, contentMap[key]));
+ }
+ }
+
+ /// Converts the data structure into a JSON structure that can be parsed by
+ /// [KeyData.fromJson].
+ Map toJson() {
+ for (Key entry in data) {
+ entry.androidKeyNames = _nameToAndroidName[entry.constantName]?.cast();
+ if (entry.androidKeyNames != null && entry.androidKeyNames.isNotEmpty) {
+ for (String androidKeyName in entry.androidKeyNames) {
+ if (_nameToAndroidKeyCode[androidKeyName] != null) {
+ entry.androidKeyCodes ??= [];
+ entry.androidKeyCodes.add(_nameToAndroidKeyCode[androidKeyName]);
+ }
+ if (_nameToAndroidScanCodes[androidKeyName] != null && _nameToAndroidScanCodes[androidKeyName].isNotEmpty) {
+ entry.androidScanCodes ??= [];
+ entry.androidScanCodes.addAll(_nameToAndroidScanCodes[androidKeyName]);
+ }
+ }
+ }
+ }
+
+ final Map outputMap = {};
+ for (Key entry in data) {
+ outputMap[entry.constantName] = entry.toJson();
+ }
+ return outputMap;
+ }
+
+ /// The list of keys.
+ List data;
+
+ /// The mapping from the Flutter name (e.g. "eject") to the Android name (e.g.
+ /// "MEDIA_EJECT").
+ ///
+ /// Only populated if data is parsed from the source files, not if parsed from
+ /// JSON.
+ Map> _nameToAndroidName;
+
+ /// The mapping from the Android name (e.g. "MEDIA_EJECT") to the integer scan
+ /// code (physical location) of the key.
+ ///
+ /// Only populated if data is parsed from the source files, not if parsed from
+ /// JSON.
+ Map> _nameToAndroidScanCodes;
+
+ /// The mapping from Android name (e.g. "MEDIA_EJECT") to the integer key code
+ /// (logical meaning) of the key.
+ ///
+ /// Only populated if data is parsed from the source files, not if parsed from
+ /// JSON.
+ Map _nameToAndroidKeyCode;
+
+ /// Parses entries from Androids Generic.kl scan code data file.
+ ///
+ /// Lines in this file look like this (without the ///):
+ /// key 100 ALT_RIGHT
+ /// # key 101 "KEY_LINEFEED"
+ ///
+ /// We parse the commented out lines as well as the non-commented lines, so so
+ /// that we can get names for all of the available scan codes, not just ones
+ /// defined for the generic profile.
+ ///
+ /// Also, note that some keys (notably MEDIA_EJECT) can be mapped to more than
+ /// one scan code, so the mapping can't just be 1:1, it has to be 1:many.
+ Map> _readAndroidScanCodes(String keyboardLayout) {
+ final RegExp keyEntry = RegExp(r'''#?\s*key\s+([0-9]+)\s*"?(?:KEY_)?([0-9A-Z_]+|\(undefined\))"?\s*(FUNCTION)?''');
+ final Map> result = >{};
+ keyboardLayout.replaceAllMapped(keyEntry, (Match match) {
+ if (match.group(3) == 'FUNCTION') {
+ // Skip odd duplicate Android FUNCTION keys (F1-F12 are already defined).
+ return '';
+ }
+ final String name = match.group(2);
+ if (name == '(undefined)') {
+ // Skip undefined scan codes.
+ return '';
+ }
+ final String androidName = match.group(2);
+ result[androidName] ??= [];
+ result[androidName].add(int.parse(match.group(1)));
+ });
+
+ return result;
+ }
+
+ /// 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,
+ Map _readAndroidKeyCodes(String headerFile) {
+ 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_([A-Z0-9_]+)\s*=\s*([0-9]+),?''');
+ final Map result = {};
+ headerFile.replaceAllMapped(enumEntry, (Match match) {
+ result[match.group(1)] = int.parse(match.group(2));
+ });
+ return result;
+ }
+
+ /// Parses entries from Chromium's HID code mapping header file.
+ ///
+ /// Lines in this file look like this (without the ///):
+ /// USB evdev XKB Win Mac Code Enum
+ /// USB_KEYMAP(0x000010, 0x0000, 0x0000, 0x0000, 0xffff, "Hyper", HYPER),
+ List _readHidEntries(String input) {
+ final List entries = [];
+ final RegExp usbMapRegExp = RegExp(
+ r'''USB_KEYMAP\s*\(\s*0x([a-fA-F0-9]+),\s*0x([a-fA-F0-9]+),'''
+ r'''\s*0x([a-fA-F0-9]+),\s*0x([a-fA-F0-9]+),\s*0x([a-fA-F0-9]+),\s*"?([^\s]+?)"?,\s*([^\s]+?)\s*\)''',
+ multiLine: true);
+ final RegExp commentRegExp = RegExp(r'//.*$', multiLine: true);
+ input = input.replaceAll(commentRegExp, '');
+ input.replaceAllMapped(usbMapRegExp, (Match match) {
+ if (match != null) {
+ final int macScanCode = getHex(match.group(5));
+ final int linuxScanCode = getHex(match.group(2));
+ final int xKbScanCode = getHex(match.group(3));
+ final int windowsScanCode = getHex(match.group(4));
+ final Key newEntry = Key(
+ usbHidCode: getHex(match.group(1)),
+ linuxScanCode: linuxScanCode == 0 ? null : linuxScanCode,
+ xKbScanCode: xKbScanCode == 0 ? null : xKbScanCode,
+ windowsScanCode: windowsScanCode == 0 ? null : windowsScanCode,
+ macOsScanCode: macScanCode == 0xffff ? null : macScanCode,
+ name: match.group(6) == 'NULL' ? null : match.group(6),
+ // The input data has a typo...
+ chromiumName: shoutingToLowerCamel(match.group(7)).replaceAll('Minimium', 'Minimum'),
+ );
+ if (newEntry.chromiumName == 'none') {
+ newEntry.name = 'None';
+ }
+ if (newEntry.name == 'IntlHash') {
+ // Skip key that is not actually generated by any keyboard.
+ return '';
+ }
+ entries.add(newEntry);
+ }
+ return match.group(0);
+ });
+ return entries;
+ }
+}
+
+/// A single entry in the key data structure.
+///
+/// Can be read from JSON with the [Key..fromJsonMapEntry] constructor, or
+/// written with the [toJson] method.
+class Key {
+ /// Creates a single key entry from available data.
+ ///
+ /// The [usbHidCode] and [chromiumName] parameters must not be null.
+ Key({
+ String enumName,
+ this.name,
+ @required this.usbHidCode,
+ this.linuxScanCode,
+ this.xKbScanCode,
+ this.windowsScanCode,
+ this.macOsScanCode,
+ @required this.chromiumName,
+ this.androidKeyNames,
+ this.androidScanCodes,
+ this.androidKeyCodes,
+ }) : assert(usbHidCode != null),
+ assert(chromiumName != null),
+ _constantName = enumName;
+
+ /// Populates the key from a JSON map.
+ factory Key.fromJsonMapEntry(String name, Map map) {
+ return Key(
+ enumName: name,
+ name: map['names']['domkey'],
+ chromiumName: map['names']['chromium'],
+ usbHidCode: map['scanCodes']['usb'],
+ androidKeyNames: map['names']['android']?.cast(),
+ androidScanCodes: map['scanCodes']['android']?.cast(),
+ androidKeyCodes: map['keyCodes']['android']?.cast(),
+ linuxScanCode: map['scanCodes']['linux'],
+ xKbScanCode: map['scanCodes']['xkb'],
+ windowsScanCode: map['scanCodes']['windows'],
+ macOsScanCode: map['scanCodes']['macos'],
+ );
+ }
+
+ /// The USB HID code of the key
+ int usbHidCode;
+
+ /// The Linux scan code of the key, from Chromium's header file.
+ int linuxScanCode;
+ /// The XKb scan code of the key from Chromium's header file.
+ int xKbScanCode;
+ /// The Windows scan code of the key from Chromium's header file.
+ int windowsScanCode;
+ /// The macOS scan code of the key from Chromium's header file.
+ int macOsScanCode;
+ /// 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.
+ String name;
+ /// The Chromium symbol name for the key.
+ String chromiumName;
+ /// The list of names that Android gives to this key (symbol names minus the
+ /// prefix).
+ List androidKeyNames;
+ /// 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.
+ List androidKeyCodes;
+ /// The list of Android scan codes matching this key, created by looking up
+ /// the Android name in the Chromium data, and substituting the Android scan
+ /// code value.
+ List androidScanCodes;
+
+ /// Creates a JSON map from the key data.
+ Map toJson() {
+ return {
+ 'names': {
+ 'domkey': name,
+ 'android': androidKeyNames,
+ 'english': commentName,
+ 'chromium': chromiumName,
+ },
+ 'scanCodes': {
+ 'android': androidScanCodes,
+ 'usb': usbHidCode,
+ 'linux': linuxScanCode,
+ 'xkb': xKbScanCode,
+ 'windows': windowsScanCode,
+ 'macos': macOsScanCode,
+ },
+ 'keyCodes': >{
+ 'android': androidKeyCodes,
+ },
+ };
+ }
+
+ /// Returns the printable representation of this key, if any.
+ ///
+ /// If there is no printable representation, returns null.
+ String get keyLabel => printable[constantName];
+
+ int get flutterId {
+ if (printable.containsKey(constantName) && !constantName.startsWith('numpad')) {
+ return unicodePlane | (keyLabel.codeUnitAt(0) & valueMask);
+ }
+ return hidPlane | (usbHidCode & valueMask);
+ }
+
+ /// Gets the name of the key suitable for placing in comments.
+ ///
+ /// Takes the [constantName] and converts it from lower camel case to capitalized
+ /// separate words (e.g. "wakeUp" converts to "Wake Up").
+ String get commentName {
+ String upperCamel = lowerCamelToUpperCamel(constantName);
+ upperCamel = upperCamel.replaceAllMapped(RegExp(r'(Digit|Numpad|Lang)([0-9]+)'), (Match match) => '${match.group(1)} ${match.group(2)}');
+ return upperCamel.replaceAllMapped(RegExp(r'([A-Z])'), (Match match) => ' ${match.group(1)}').trim();
+ }
+
+ /// Gets the named used for the key constant in the definitions in
+ /// keyboard_keys.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).
+ String get constantName {
+ if (_constantName == null) {
+ String result;
+ if (name == null || name.isEmpty) {
+ // If it doesn't have a DomKey name then use the Chromium symbol name.
+ result = chromiumName;
+ } else {
+ result = upperCamelToLowerCamel(name);
+ }
+ if (kDartReservedWords.contains(result)) {
+ return '${result}Key';
+ }
+ // Don't set enumName: we want it to regen each time if never set, but
+ // to stay set if set by the JSON loading.
+ return result;
+ }
+ return _constantName;
+ }
+ set constantName(String value) => _constantName = value;
+ String _constantName;
+
+ @override
+ String toString() {
+ return """'$constantName': (name: "$name", usbHidCode: ${toHex(usbHidCode)}, """
+ '''linuxKeyCode: ${toHex(linuxScanCode)}, xKbKeyCode: ${toHex(xKbScanCode)}, '''
+ '''windowsKeyCode: ${toHex(windowsScanCode)}, macOsKeyCode: ${toHex(macOsScanCode)}, '''
+ '''chromiumSymbolName: $chromiumName''';
+ }
+
+ /// Returns the static map of printable representations.
+ static Map get printable {
+ if (_printable == null) {
+ final String printableKeys = File(path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'printable.json',)).readAsStringSync();
+ final Map printable = json.decode(printableKeys);
+ _printable = printable.cast();
+ }
+ return _printable;
+ }
+ static Map _printable;
+
+ /// Mask for the 32-bit value portion of the code.
+ static const int valueMask = 0x000FFFFFFFF;
+
+ /// The code prefix for keys which have a Unicode representation.
+ static const int unicodePlane = 0x00000000000;
+
+ /// The code prefix for keys which do not have a Unicode representation, but
+ /// do have a USB HID ID.
+ static const int hidPlane = 0x00100000000;
+}
diff --git a/dev/tools/gen_keycodes/lib/utils.dart b/dev/tools/gen_keycodes/lib/utils.dart
new file mode 100644
index 00000000000..0d556cf71ea
--- /dev/null
+++ b/dev/tools/gen_keycodes/lib/utils.dart
@@ -0,0 +1,111 @@
+// Copyright 2019 The Chromium 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:io' hide Platform;
+
+import 'package:path/path.dart' as path;
+import 'package:platform/platform.dart' show LocalPlatform;
+
+/// The location of the Flutter root directory, based on the known location of
+/// this script.
+final Directory flutterRoot = Directory(path.dirname(const LocalPlatform().script.toFilePath())).parent.parent.parent.parent;
+
+/// Converts `FOO_BAR` to `fooBar`.
+String shoutingToLowerCamel(String shouting) {
+ final RegExp initialLetter = RegExp(r'_([^_])([^_]*)');
+ final String snake = shouting.toLowerCase();
+ final String result = snake.replaceAllMapped(initialLetter, (Match match) {
+ return match.group(1).toUpperCase() + match.group(2).toLowerCase();
+ });
+ return result;
+}
+
+/// Converts 'FooBar' to 'fooBar'.
+String upperCamelToLowerCamel(String upperCamel) {
+ return upperCamel.substring(0, 1).toLowerCase() + upperCamel.substring(1);
+}
+
+/// Converts 'fooBar' to 'FooBar'.
+String lowerCamelToUpperCamel(String lowerCamel) {
+ return lowerCamel.substring(0, 1).toUpperCase() + lowerCamel.substring(1);
+}
+
+/// A list of Dart reserved words.
+///
+/// Since these are Dart reserved words, we can't use them as-is for enum names.
+const List kDartReservedWords = [
+ 'abstract',
+ 'as',
+ 'assert',
+ 'async',
+ 'await',
+ 'break',
+ 'case',
+ 'catch',
+ 'class',
+ 'const',
+ 'continue',
+ 'covariant',
+ 'default',
+ 'deferred',
+ 'do',
+ 'dynamic',
+ 'else',
+ 'enum',
+ 'export',
+ 'extends',
+ 'external',
+ 'factory',
+ 'false',
+ 'final',
+ 'finally',
+ 'for',
+ 'Function',
+ 'get',
+ 'hide',
+ 'if',
+ 'implements',
+ 'import',
+ 'in',
+ 'interface',
+ 'is',
+ 'library',
+ 'mixin',
+ 'new',
+ 'null',
+ 'on',
+ 'operator',
+ 'part',
+ 'rethrow',
+ 'return',
+ 'set',
+ 'show',
+ 'static',
+ 'super',
+ 'switch',
+ 'sync',
+ 'this',
+ 'throw',
+ 'true',
+ 'try',
+ 'typedef',
+ 'var',
+ 'void',
+ 'while',
+ 'with',
+ 'yield',
+];
+
+/// Converts an integer into a hex string with the given number of digits.
+String toHex(int value, {int digits = 8}) {
+ if (value == null) {
+ return 'null';
+ }
+ return '0x${value.toRadixString(16).padLeft(digits, '0')}';
+}
+
+/// Parses an integer from a hex string.
+int getHex(String input) {
+ return int.parse(input, radix: 16);
+}
diff --git a/dev/tools/gen_keycodes/pubspec.yaml b/dev/tools/gen_keycodes/pubspec.yaml
new file mode 100644
index 00000000000..43f7a3ed196
--- /dev/null
+++ b/dev/tools/gen_keycodes/pubspec.yaml
@@ -0,0 +1,24 @@
+name: gen_keycodes
+description: Generates keycode source files from various resources.
+
+environment:
+ # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
+ sdk: ">=2.0.0-dev.68.0 <3.0.0"
+
+dependencies:
+ args: 1.5.1
+ http: 0.12.0+1
+ path: 1.6.2
+ platform: 2.2.0
+
+ async: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ http_parser: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ meta: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ source_span: 1.5.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ string_scanner: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+# PUBSPEC CHECKSUM: 433d
diff --git a/packages/flutter_build/pubspec.yaml b/packages/flutter_build/pubspec.yaml
index bd81dc578cb..e511e803cb2 100644
--- a/packages/flutter_build/pubspec.yaml
+++ b/packages/flutter_build/pubspec.yaml
@@ -33,7 +33,7 @@ dependencies:
pedantic: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
plugin: 0.2.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- protobuf: 0.13.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ protobuf: 0.13.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pubspec_parse: 0.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
scratch_space: 0.0.3+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -50,4 +50,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc: true
-# PUBSPEC CHECKSUM: 1f34
+# PUBSPEC CHECKSUM: 9f35
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index 5e2f0dbbbdd..d141bb91f08 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -81,7 +81,7 @@ dependencies:
pedantic: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
petitparser: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
- protobuf: 0.13.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+ protobuf: 0.13.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
pubspec_parse: 0.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
scratch_space: 0.0.3+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -100,7 +100,7 @@ dev_dependencies:
collection: 1.14.11
mockito: 4.0.0
file_testing: 2.0.3
- vm_service_lib: 0.3.10+2
+ vm_service_lib: 3.14.1
http_multi_server: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
@@ -117,4 +117,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc: true
-# PUBSPEC CHECKSUM: 7930
+# PUBSPEC CHECKSUM: 32d8