gen_keycodes.dart 11.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8
// 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' hide Platform;

import 'package:args/args.dart';
9 10 11
import 'package:gen_keycodes/android_code_gen.dart';
import 'package:gen_keycodes/base_code_gen.dart';
import 'package:gen_keycodes/gtk_code_gen.dart';
12
import 'package:gen_keycodes/ios_code_gen.dart';
13 14 15
import 'package:gen_keycodes/keyboard_keys_code_gen.dart';
import 'package:gen_keycodes/keyboard_maps_code_gen.dart';
import 'package:gen_keycodes/logical_key_data.dart';
16 17
import 'package:gen_keycodes/macos_code_gen.dart';
import 'package:gen_keycodes/physical_key_data.dart';
18
import 'package:gen_keycodes/testing_key_codes_gen.dart';
19
import 'package:gen_keycodes/utils.dart';
20 21
import 'package:gen_keycodes/web_code_gen.dart';
import 'package:gen_keycodes/windows_code_gen.dart';
22 23
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
24

25
/// Get contents of the file that contains the physical key mapping in Chromium
26
/// source.
27
Future<String> getChromiumCodes() async {
28 29
  final Uri keyCodesUri = Uri.parse('https://chromium.googlesource.com/codesearch/chromium/src/+/refs/heads/master/ui/events/keycodes/dom/dom_code_data.inc?format=TEXT');
  return utf8.decode(base64.decode(await http.read(keyCodesUri)));
30 31
}

32 33 34 35 36 37 38
/// Get contents of the file that contains the logical key mapping in Chromium
/// source.
Future<String> getChromiumKeys() async {
  final Uri keyCodesUri = Uri.parse('https://chromium.googlesource.com/codesearch/chromium/src/+/refs/heads/master/ui/events/keycodes/dom/dom_key_data.inc?format=TEXT');
  return utf8.decode(base64.decode(await http.read(keyCodesUri)));
}

39 40 41 42 43 44
/// 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)));
}

45 46
Future<String> getWindowsKeyCodes() async {
  final Uri keyCodesUri = Uri.parse('https://raw.githubusercontent.com/tpn/winsdk-10/master/Include/10.0.10240.0/um/WinUser.h');
47
  return http.read(keyCodesUri);
48 49
}

50 51 52 53 54 55 56 57 58 59 60
/// 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)));
}

61 62
Future<String> getGlfwKeyCodes() async {
  final Uri keyCodesUri = Uri.parse('https://raw.githubusercontent.com/glfw/glfw/master/include/GLFW/glfw3.h');
63
  return http.read(keyCodesUri);
64 65
}

66 67
Future<String> getGtkKeyCodes() async {
  final Uri keyCodesUri = Uri.parse('https://gitlab.gnome.org/GNOME/gtk/-/raw/master/gdk/gdkkeysyms.h');
68
  return http.read(keyCodesUri);
69 70
}

71 72 73 74
String readDataFile(String fileName) {
  return File(path.join(dataRoot, fileName)).readAsStringSync();
}

75 76 77 78 79 80 81 82 83
bool _assertsEnabled() {
  bool enabledAsserts = false;
  assert(() {
    enabledAsserts = true;
    return true;
  }());
  return enabledAsserts;
}

84
Future<void> main(List<String> rawArguments) async {
85 86 87 88
  if (!_assertsEnabled()) {
    print('The gen_keycodes script must be run with --enable-asserts.');
    return;
  }
89 90
  final ArgParser argParser = ArgParser();
  argParser.addOption(
91 92 93 94 95 96 97
    'engine-root',
    defaultsTo: path.join(flutterRoot.path, '..', 'engine', 'src', 'flutter'),
    help: 'The path to the root of the flutter/engine repository. This is used '
        'to place the generated engine mapping files. If --engine-root is not '
        r'specified, it will default to $flutterRoot/../engine/src/flutter, '
        'assuming the engine gclient folder is placed at the same folder as '
        'the flutter/flutter repository.',
98
  );
99
  argParser.addOption(
100 101 102 103 104 105 106
    'physical-data',
    defaultsTo: path.join(dataRoot, 'physical_key_data.json'),
    help: 'The path to where the physical key data file should be written when '
        'collected, and read from when generating output code. If --physical-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.',
107
  );
108
  argParser.addOption(
109 110 111 112
    'logical-data',
    defaultsTo: path.join(dataRoot, 'logical_key_data.json'),
    help: 'The path to where the logical key data file should be written when '
        'collected, and read from when generating output code. If --logical-data is '
113 114 115 116 117 118 119
        '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'),
120
    help: 'The path to where the output "keyboard_key.dart" file should be '
121 122 123 124 125 126 127
        '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'),
128
    help: 'The path to where the output "keyboard_maps.dart" file should be '
129 130 131 132 133 134
      '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',
135
    defaultsTo: false,
136 137 138
    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 '
139 140
        '"physical_key_data.json" and "logical_key_data.json", and then '
        'update these files with the fresh data.',
141 142 143
  );
  argParser.addFlag(
    'help',
144
    defaultsTo: false,
145 146 147 148 149 150
    negatable: false,
    help: 'Print help for this command.',
  );

  final ArgResults parsedArguments = argParser.parse(rawArguments);

151
  if (parsedArguments['help'] as bool) {
152 153 154 155
    print(argParser.usage);
    exit(0);
  }

156
  PlatformCodeGenerator.engineRoot = parsedArguments['engine-root'] as String;
157

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
  PhysicalKeyData physicalData;
  LogicalKeyData logicalData;
  if (parsedArguments['collect'] as bool) {
    // Physical
    final String baseHidCodes = await getChromiumCodes();
    final String supplementalHidCodes = readDataFile('supplemental_hid_codes.inc');
    final String androidScanCodes = await getAndroidScanCodes();
    final String androidToDomKey = readDataFile('android_key_name_to_name.json');
    physicalData = PhysicalKeyData(
      <String>[baseHidCodes, supplementalHidCodes].join('\n'),
      androidScanCodes,
      androidToDomKey,
    );

    // Logical
    final String gtkKeyCodes = await getGtkKeyCodes();
    final String webLogicalKeys = await getChromiumKeys();
    final String supplementalKeyData = readDataFile('supplemental_key_data.inc');
    final String gtkToDomKey = readDataFile('gtk_logical_name_mapping.json');
    final String windowsKeyCodes = await getWindowsKeyCodes();
    final String windowsToDomKey = readDataFile('windows_logical_to_window_vk.json');
    final String macosLogicalToPhysical = readDataFile('macos_logical_to_physical.json');
    final String iosLogicalToPhysical = readDataFile('ios_logical_to_physical.json');
    final String androidKeyCodes = await getAndroidKeyCodes();
182 183
    final String glfwKeyCodes = await getGlfwKeyCodes();
    final String glfwToDomKey = readDataFile('glfw_key_name_to_name.json');
184 185 186 187 188 189 190 191 192 193 194

    logicalData = LogicalKeyData(
      <String>[webLogicalKeys, supplementalKeyData].join('\n'),
      gtkKeyCodes,
      gtkToDomKey,
      windowsKeyCodes,
      windowsToDomKey,
      androidKeyCodes,
      androidToDomKey,
      macosLogicalToPhysical,
      iosLogicalToPhysical,
195 196
      glfwKeyCodes,
      glfwToDomKey,
197 198 199 200
      physicalData,
    );

    // Write data files
201
    const JsonEncoder encoder = JsonEncoder.withIndent('  ');
202 203 204 205
    final String physicalJson = encoder.convert(physicalData.toJson());
    File(parsedArguments['physical-data'] as String).writeAsStringSync('$physicalJson\n');
    final String logicalJson = encoder.convert(logicalData.toJson());
    File(parsedArguments['logical-data'] as String).writeAsStringSync('$logicalJson\n');
206
  } else {
207 208
    physicalData = PhysicalKeyData.fromJson(json.decode(await File(parsedArguments['physical-data'] as String).readAsString()) as Map<String, dynamic>);
    logicalData = LogicalKeyData.fromJson(json.decode(await File(parsedArguments['logical-data'] as String).readAsString()) as Map<String, dynamic>);
209 210
  }

211
  final File codeFile = File(parsedArguments['code'] as String);
212 213 214
  if (!codeFile.existsSync()) {
    codeFile.createSync(recursive: true);
  }
215
  print('Writing ${'key codes'.padRight(15)}${codeFile.absolute}');
216
  await codeFile.writeAsString(KeyboardKeysCodeGenerator(physicalData, logicalData).generate());
217

218
  final File mapsFile = File(parsedArguments['maps'] as String);
219 220 221
  if (!mapsFile.existsSync()) {
    mapsFile.createSync(recursive: true);
  }
222
  print('Writing ${'key maps'.padRight(15)}${mapsFile.absolute}');
223 224
  await mapsFile.writeAsString(KeyboardMapsCodeGenerator(physicalData, logicalData).generate());

225 226 227 228 229 230 231 232
  final File keyCodesFile = File(path.join(PlatformCodeGenerator.engineRoot,
      'shell', 'platform', 'embedder', 'test_utils', 'key_codes.h'));
  if (!mapsFile.existsSync()) {
    mapsFile.createSync(recursive: true);
  }
  print('Writing ${'engine key codes'.padRight(15)}${mapsFile.absolute}');
  await keyCodesFile.writeAsString(KeyCodesCcGenerator(physicalData, logicalData).generate());

233 234 235 236 237
  final Map<String, PlatformCodeGenerator> platforms = <String, PlatformCodeGenerator>{
    'android': AndroidCodeGenerator(
      physicalData,
      logicalData,
    ),
238 239 240 241 242
    'macos': MacOSCodeGenerator(
      physicalData,
      logicalData,
    ),
    'ios': IOSCodeGenerator(
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
      physicalData,
      logicalData,
    ),
    'windows': WindowsCodeGenerator(
      physicalData,
      logicalData,
      readDataFile('windows_scancode_logical_map.json')
    ),
    'linux': GtkCodeGenerator(
      physicalData,
      logicalData,
      readDataFile('gtk_modifier_bit_mapping.json'),
      readDataFile('gtk_lock_bit_mapping.json'),
    ),
    'web': WebCodeGenerator(
      physicalData,
      logicalData,
      readDataFile('web_logical_location_mapping.json'),
    ),
  };
  await Future.wait(platforms.entries.map((MapEntry<String, PlatformCodeGenerator> entry) {
    final String platform = entry.key;
    final PlatformCodeGenerator codeGenerator = entry.value;
266
    final File platformFile = File(codeGenerator.outputPath(platform));
267 268 269
    if (!platformFile.existsSync()) {
      platformFile.createSync(recursive: true);
    }
270
    print('Writing ${'$platform map'.padRight(15)}${platformFile.absolute}');
271 272
    return platformFile.writeAsString(codeGenerator.generate());
  }));
273
}