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.
This commit is contained in:
Greg Spencer 2019-02-06 16:53:16 -08:00 committed by GitHub
parent a13fdbcf32
commit 2aad59314f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 7016 additions and 9 deletions

View file

@ -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

View file

@ -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

View file

@ -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 wasnt 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 doesnt 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)<br>
UP, code 0x00070039. (CAPS LOCK UP)<br>
DOWN, code 0x000700E1 (SHIFT DOWN)<br>
DOWN, code 0x0007001D, string U+00059 (Y DOWN, the code is for the "Z" key, but
string is the character, "Y")<br>
UP, code 0x0007001D (Y UP)<br>
UP, code 0x000700E1 (SHIFT UP)<br>
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.<br>
UP, code 0x00070035 (GRAVE UP)<br>
DOWN, code 0x00070008, string U+000EA (Unicode for ê‬) (E DOWN).<br>
UP, code 0x00070008. (E UP).<br>
It is an important point that even though were 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 dont 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.

View file

@ -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<String> 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<String> 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<String> 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<void> main(List<String> 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());
}

File diff suppressed because it is too large Load diff

View file

@ -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"]
}

View file

@ -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 <https://en.wikipedia.org/wiki/Unicode_control_characters> 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<int, LogicalKeyboardKey> _knownLogicalKeys = <int, LogicalKeyboardKey>{
@@@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 <https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf>
/// 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<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
@@@PHYSICAL_KEY_MAP@@@
};
}

View file

@ -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<int, LogicalKeyboardKey> kAndroidToLogicalKey = <int, LogicalKeyboardKey>{
@@@ANDROID_KEY_CODE_MAP@@@
};
/// Maps Android-specific scan codes to the matching [PhysicalKeyboardKey].
const Map<int, PhysicalKeyboardKey> kAndroidToPhysicalKey = <int, PhysicalKeyboardKey>{
@@@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<int, LogicalKeyboardKey> kAndroidNumPadMap = <int, LogicalKeyboardKey>{
@@@ANDROID_NUMPAD_MAP@@@
};
/// Maps Fuchsia-specific IDs to the matching [LogicalKeyboardKey].
const Map<int, LogicalKeyboardKey> kFuchsiaToLogicalKey = <int, LogicalKeyboardKey>{
@@@FUCHSIA_KEY_CODE_MAP@@@
};
/// Maps Fuchsia-specific USB HID Usage IDs to the matching
/// [PhysicalKeyboardKey].
const Map<int, PhysicalKeyboardKey> kFuchsiaToPhysicalKey = <int, PhysicalKeyboardKey>{
@@@FUCHSIA_SCAN_CODE_MAP@@@
};

View file

@ -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": " "
}

View file

@ -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<String> 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<int>()) {
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<Key> 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<int>()) {
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<int>()) {
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<String, String> mappings = <String, String>{
'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<String, String> mappings = <String, String>{
'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<String, String> dictionary) {
String result = template;
for (String key in dictionary.keys) {
result = result.replaceAll('@@@$key@@@', dictionary[key]);
}
return result;
}
}

View file

@ -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<String, List<dynamic>> dynamicNames = json.decode(androidNameMap).cast<String, List<dynamic>>();
_nameToAndroidName = dynamicNames.map<String, List<String>>((String key, List<dynamic> value) {
return MapEntry<String, List<String>>(key, value.cast<String>());
});
data = _readHidEntries(chromiumHidCodes);
}
/// Parses the given JSON data and populates the data structure from it.
KeyData.fromJson(Map<String, dynamic> contentMap) {
data = <Key>[];
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<String, dynamic> toJson() {
for (Key entry in data) {
entry.androidKeyNames = _nameToAndroidName[entry.constantName]?.cast<String>();
if (entry.androidKeyNames != null && entry.androidKeyNames.isNotEmpty) {
for (String androidKeyName in entry.androidKeyNames) {
if (_nameToAndroidKeyCode[androidKeyName] != null) {
entry.androidKeyCodes ??= <int>[];
entry.androidKeyCodes.add(_nameToAndroidKeyCode[androidKeyName]);
}
if (_nameToAndroidScanCodes[androidKeyName] != null && _nameToAndroidScanCodes[androidKeyName].isNotEmpty) {
entry.androidScanCodes ??= <int>[];
entry.androidScanCodes.addAll(_nameToAndroidScanCodes[androidKeyName]);
}
}
}
}
final Map<String, dynamic> outputMap = <String, dynamic>{};
for (Key entry in data) {
outputMap[entry.constantName] = entry.toJson();
}
return outputMap;
}
/// The list of keys.
List<Key> 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<String, List<String>> _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<String, List<int>> _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<String, int> _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<String, List<int>> _readAndroidScanCodes(String keyboardLayout) {
final RegExp keyEntry = RegExp(r'''#?\s*key\s+([0-9]+)\s*"?(?:KEY_)?([0-9A-Z_]+|\(undefined\))"?\s*(FUNCTION)?''');
final Map<String, List<int>> result = <String, List<int>>{};
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] ??= <int>[];
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<String, int> _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<String, int> result = <String, int>{};
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<Key> _readHidEntries(String input) {
final List<Key> entries = <Key>[];
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<String, dynamic> map) {
return Key(
enumName: name,
name: map['names']['domkey'],
chromiumName: map['names']['chromium'],
usbHidCode: map['scanCodes']['usb'],
androidKeyNames: map['names']['android']?.cast<String>(),
androidScanCodes: map['scanCodes']['android']?.cast<int>(),
androidKeyCodes: map['keyCodes']['android']?.cast<int>(),
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<String> 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<int> 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<int> androidScanCodes;
/// Creates a JSON map from the key data.
Map<String, dynamic> toJson() {
return <String, dynamic>{
'names': <String, dynamic>{
'domkey': name,
'android': androidKeyNames,
'english': commentName,
'chromium': chromiumName,
},
'scanCodes': <String, dynamic>{
'android': androidScanCodes,
'usb': usbHidCode,
'linux': linuxScanCode,
'xkb': xKbScanCode,
'windows': windowsScanCode,
'macos': macOsScanCode,
},
'keyCodes': <String, List<int>>{
'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<String, String> get printable {
if (_printable == null) {
final String printableKeys = File(path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'printable.json',)).readAsStringSync();
final Map<String, dynamic> printable = json.decode(printableKeys);
_printable = printable.cast<String, String>();
}
return _printable;
}
static Map<String, String> _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;
}

View file

@ -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<String> kDartReservedWords = <String>[
'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);
}

View file

@ -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

View file

@ -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

View file

@ -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