gen_keycodes.dart 12 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 12
// 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;

13 14 15 16 17 18 19 20 21 22
import 'package:gen_keycodes/android_code_gen.dart';
import 'package:gen_keycodes/base_code_gen.dart';
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 41 42 43
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');
  return await http.read(keyCodesUri);
}

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

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

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

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