From 2aad59314fb98f29a4dcdcacda77c0926418ea95 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Wed, 6 Feb 2019 16:53:16 -0800 Subject: [PATCH] Add a keyboard key code generator. (#27620) This adds a keycode generator that incorporates input from the Chromium and Android source trees, as well as some local tables, to generate static constants for the LogicalKeyboardKey and PhysicalKeyboardKey classes, as well as mappings from each of the platforms we support so far (currently only Android and Fuchsia). This code generator parses the input files, generates an intermediate data structure (`key_data.json`) that is checked in, and then generates the Dart sources for these classes and some static maps that will also be checked in (but are not included in this PR). The idea is that these codes don't change often, and so we don't need to generate them on every build, but we would like to be able to update them easily in the future if new data becomes available. If the existing data disappears or becomes unusable, we can maintain the checked-in data structure by hand if necessary, and still be able to generate the code. This PR only contains the code generator, not the classes themselves. In another follow-on PR, I'll run the generator and check in the output of the generator. --- dev/devicelab/pubspec.yaml | 4 +- dev/integration_tests/ui/pubspec.yaml | 4 +- dev/tools/gen_keycodes/README.md | 168 + dev/tools/gen_keycodes/bin/gen_keycodes.dart | 161 + dev/tools/gen_keycodes/data/key_data.json | 5384 +++++++++++++++++ .../data/key_name_to_android_name.json | 252 + dev/tools/gen_keycodes/data/keyboard_key.tmpl | 238 + .../gen_keycodes/data/keyboard_maps.tmpl | 40 + dev/tools/gen_keycodes/data/printable.json | 69 + dev/tools/gen_keycodes/lib/code_gen.dart | 195 + dev/tools/gen_keycodes/lib/key_data.dart | 365 ++ dev/tools/gen_keycodes/lib/utils.dart | 111 + dev/tools/gen_keycodes/pubspec.yaml | 24 + packages/flutter_build/pubspec.yaml | 4 +- packages/flutter_tools/pubspec.yaml | 6 +- 15 files changed, 7016 insertions(+), 9 deletions(-) create mode 100644 dev/tools/gen_keycodes/README.md create mode 100644 dev/tools/gen_keycodes/bin/gen_keycodes.dart create mode 100644 dev/tools/gen_keycodes/data/key_data.json create mode 100644 dev/tools/gen_keycodes/data/key_name_to_android_name.json create mode 100644 dev/tools/gen_keycodes/data/keyboard_key.tmpl create mode 100644 dev/tools/gen_keycodes/data/keyboard_maps.tmpl create mode 100644 dev/tools/gen_keycodes/data/printable.json create mode 100644 dev/tools/gen_keycodes/lib/code_gen.dart create mode 100644 dev/tools/gen_keycodes/lib/key_data.dart create mode 100644 dev/tools/gen_keycodes/lib/utils.dart create mode 100644 dev/tools/gen_keycodes/pubspec.yaml 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