gen_keycodes.dart 12.1 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 9 10 11
// 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';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;

12 13
import 'package:gen_keycodes/android_code_gen.dart';
import 'package:gen_keycodes/base_code_gen.dart';
14
import 'package:gen_keycodes/ios_code_gen.dart';
15 16 17 18 19 20 21 22
import 'package:gen_keycodes/macos_code_gen.dart';
import 'package:gen_keycodes/fuchsia_code_gen.dart';
import 'package:gen_keycodes/glfw_code_gen.dart';
import 'package:gen_keycodes/gtk_code_gen.dart';
import 'package:gen_keycodes/windows_code_gen.dart';
import 'package:gen_keycodes/web_code_gen.dart';
import 'package:gen_keycodes/keyboard_keys_code_gen.dart';
import 'package:gen_keycodes/keyboard_maps_code_gen.dart';
23 24 25 26 27 28
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 {
29 30
  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)));
31 32 33 34 35 36 37 38
}

/// 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)));
}

39 40
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');
41
  return http.read(keyCodesUri);
42 43
}

44 45 46 47 48 49 50 51 52 53 54
/// 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)));
}

55 56
Future<String> getGlfwKeyCodes() async {
  final Uri keyCodesUri = Uri.parse('https://raw.githubusercontent.com/glfw/glfw/master/include/GLFW/glfw3.h');
57
  return http.read(keyCodesUri);
58 59
}

60 61
Future<String> getGtkKeyCodes() async {
  final Uri keyCodesUri = Uri.parse('https://gitlab.gnome.org/GNOME/gtk/-/raw/master/gdk/gdkkeysyms.h');
62
  return http.read(keyCodesUri);
63 64
}

65 66 67 68 69 70 71 72 73
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.',
  );
74 75 76 77 78 79
  argParser.addOption(
    'supplemental-hid-codes',
    defaultsTo: path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'supplemental_hid_codes.inc'),
    help: "The path to where the supplemental HID codes that don't appear in the "
        'Chromium map should be read.',
  );
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
  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.',
  );
99 100 101 102 103 104 105
  argParser.addOption(
    'glfw-keycodes',
    defaultsTo: null,
    help: 'The path to where the GLFW keycodes header file should be read. '
        'If --glfw-keycodes is not specified, the input will be read from the '
        'correct file in the GLFW github repository.',
  );
106 107 108 109 110 111 112
  argParser.addOption(
    'gtk-keycodes',
    defaultsTo: null,
    help: 'The path to where the GTK keycodes header file should be read. '
        'If --gtk-keycodes is not specified, the input will be read from the '
        'correct file in the GTK repository.',
  );
113 114 115 116 117 118 119 120 121 122 123 124 125
  argParser.addOption(
    'windows-keycodes',
    defaultsTo: null,
    help: 'The path to where the Windows keycodes header file should be read. '
        'If --windows-keycodes is not specified, the input will be read from the '
        'correct file in the Windows github repository.',
  );
  argParser.addOption(
    'windows-domkey',
    defaultsTo: path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'key_name_to_windows_name.json'),
    help: 'The path to where the Windows keycode to DomKey mapping is.',
  );
  argParser.addOption(
126 127 128 129
    'glfw-domkey',
    defaultsTo: path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'key_name_to_glfw_name.json'),
    help: 'The path to where the GLFW keycode to DomKey mapping is.',
  );
130 131 132 133 134
  argParser.addOption(
    'gtk-domkey',
    defaultsTo: path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'key_name_to_gtk_name.json'),
    help: 'The path to where the GTK keycode to DomKey mapping is.',
  );
135 136 137 138 139 140 141 142 143 144 145 146
  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'),
147
    help: 'The path to where the output "keyboard_keys.dart" file should be '
148 149 150 151 152 153 154
        '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'),
155
    help: 'The path to where the output "keyboard_maps.dart" file should be '
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
      '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);

177
  if (parsedArguments['help'] as bool) {
178 179 180 181 182
    print(argParser.usage);
    exit(0);
  }

  KeyData data;
183
  if (parsedArguments['collect'] as bool) {
184 185 186 187
    String hidCodes;
    if (parsedArguments['chromium-hid-codes'] == null) {
      hidCodes = await getChromiumConversions();
    } else {
188
      hidCodes = File(parsedArguments['chromium-hid-codes'] as String).readAsStringSync();
189 190
    }

191
    final String supplementalHidCodes = File(parsedArguments['supplemental-hid-codes'] as String).readAsStringSync();
192 193
    hidCodes = '$hidCodes\n$supplementalHidCodes';

194 195 196 197
    String androidKeyCodes;
    if (parsedArguments['android-keycodes'] == null) {
      androidKeyCodes = await getAndroidKeyCodes();
    } else {
198
      androidKeyCodes = File(parsedArguments['android-keycodes'] as String).readAsStringSync();
199 200 201 202 203 204
    }

    String androidScanCodes;
    if (parsedArguments['android-scancodes'] == null) {
      androidScanCodes = await getAndroidScanCodes();
    } else {
205
      androidScanCodes = File(parsedArguments['android-scancodes'] as String).readAsStringSync();
206 207
    }

208 209 210 211
    String glfwKeyCodes;
    if (parsedArguments['glfw-keycodes'] == null) {
      glfwKeyCodes = await getGlfwKeyCodes();
    } else {
212
      glfwKeyCodes = File(parsedArguments['glfw-keycodes'] as String).readAsStringSync();
213 214
    }

215 216 217 218 219 220 221
    String gtkKeyCodes;
    if (parsedArguments['gtk-keycodes'] == null) {
      gtkKeyCodes = await getGtkKeyCodes();
    } else {
      gtkKeyCodes = File(parsedArguments['gtk-keycodes'] as String).readAsStringSync();
    }

222 223 224 225 226 227 228 229
    String windowsKeyCodes;
    if (parsedArguments['windows-keycodes'] == null) {
      windowsKeyCodes = await getWindowsKeyCodes();
    } else {
      windowsKeyCodes = File(parsedArguments['windows-keycodes'] as String).readAsStringSync();
    }

    final String windowsToDomKey = File(parsedArguments['windows-domkey'] as String).readAsStringSync();
230
    final String glfwToDomKey = File(parsedArguments['glfw-domkey'] as String).readAsStringSync();
231
    final String gtkToDomKey = File(parsedArguments['gtk-domkey'] as String).readAsStringSync();
232
    final String androidToDomKey = File(parsedArguments['android-domkey'] as String).readAsStringSync();
233

234
    data = KeyData(hidCodes, androidScanCodes, androidKeyCodes, androidToDomKey, glfwKeyCodes, glfwToDomKey, gtkKeyCodes, gtkToDomKey, windowsKeyCodes, windowsToDomKey);
235 236

    const JsonEncoder encoder = JsonEncoder.withIndent('  ');
237
    File(parsedArguments['data'] as String).writeAsStringSync(encoder.convert(data.toJson()));
238
  } else {
239
    data = KeyData.fromJson(json.decode(await File(parsedArguments['data'] as String).readAsString()) as Map<String, dynamic>);
240 241
  }

242
  final File codeFile = File(parsedArguments['code'] as String);
243 244 245
  if (!codeFile.existsSync()) {
    codeFile.createSync(recursive: true);
  }
246 247
  print('Writing ${'key codes'.padRight(15)}${codeFile.absolute}');
  await codeFile.writeAsString(KeyboardKeysCodeGenerator(data).generate());
248

249
  final File mapsFile = File(parsedArguments['maps'] as String);
250 251 252
  if (!mapsFile.existsSync()) {
    mapsFile.createSync(recursive: true);
  }
253 254
  print('Writing ${'key maps'.padRight(15)}${mapsFile.absolute}');
  await mapsFile.writeAsString(KeyboardMapsCodeGenerator(data).generate());
255

256
  for (final String platform in <String>['android', 'macos', 'ios', 'glfw', 'fuchsia', 'linux', 'windows', 'web']) {
257 258 259 260 261 262 263 264 265 266 267
    PlatformCodeGenerator codeGenerator;
    switch (platform) {
      case 'glfw':
        codeGenerator = GlfwCodeGenerator(data);
        break;
      case 'fuchsia':
        codeGenerator = FuchsiaCodeGenerator(data);
        break;
      case 'android':
        codeGenerator = AndroidCodeGenerator(data);
        break;
268
      case 'macos':
269 270
        codeGenerator = MacOsCodeGenerator(data);
        break;
271 272 273
      case 'ios':
        codeGenerator = IosCodeGenerator(data);
        break;
274 275 276 277 278 279 280 281 282 283 284 285
      case 'windows':
        codeGenerator = WindowsCodeGenerator(data);
        break;
      case 'linux':
        codeGenerator = GtkCodeGenerator(data);
        break;
      case 'web':
        codeGenerator = WebCodeGenerator(data);
        break;
      default:
        assert(false);
    }
286

287
    final File platformFile = File(codeGenerator.outputPath(platform));
288 289 290
    if (!platformFile.existsSync()) {
      platformFile.createSync(recursive: true);
    }
291 292
    print('Writing ${'$platform map'.padRight(15)}${platformFile.absolute}');
    await platformFile.writeAsString(codeGenerator.generate());
293
  }
294
}