gen_keycodes.dart 10.7 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/utils.dart';
19 20
import 'package:gen_keycodes/web_code_gen.dart';
import 'package:gen_keycodes/windows_code_gen.dart';
21 22
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as path;
23

24
/// Get contents of the file that contains the physical key mapping in Chromium
25
/// source.
26
Future<String> getChromiumCodes() async {
27 28
  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)));
29 30
}

31 32 33 34 35 36 37
/// 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)));
}

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

44 45
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');
46
  return http.read(keyCodesUri);
47 48
}

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

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

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

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

74 75 76
Future<void> main(List<String> rawArguments) async {
  final ArgParser argParser = ArgParser();
  argParser.addOption(
77 78 79 80 81 82 83
    '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.',
84
  );
85
  argParser.addOption(
86 87 88 89 90 91 92
    '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.',
93
  );
94
  argParser.addOption(
95 96 97 98
    '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 '
99 100 101 102 103 104 105
        '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'),
106
    help: 'The path to where the output "keyboard_key.dart" file should be '
107 108 109 110 111 112 113
        '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'),
114
    help: 'The path to where the output "keyboard_maps.dart" file should be '
115 116 117 118 119 120 121 122 123 124
      '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 '
125 126
        '"physical_key_data.json" and "logical_key_data.json", and then '
        'update these files with the fresh data.',
127 128 129 130 131 132 133 134 135 136
  );
  argParser.addFlag(
    'help',
    defaultsTo: false,
    negatable: false,
    help: 'Print help for this command.',
  );

  final ArgResults parsedArguments = argParser.parse(rawArguments);

137
  if (parsedArguments['help'] as bool) {
138 139 140 141
    print(argParser.usage);
    exit(0);
  }

142
  PlatformCodeGenerator.engineRoot = parsedArguments['engine-root'] as String;
143

144 145 146 147 148 149 150 151 152 153 154 155 156 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 182 183 184 185 186
  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');
    final String glfwKeyCodes = await getGlfwKeyCodes();
    final String glfwToDomKey = readDataFile('glfw_key_name_to_name.json');
    physicalData = PhysicalKeyData(
      <String>[baseHidCodes, supplementalHidCodes].join('\n'),
      androidScanCodes,
      androidToDomKey,
      glfwKeyCodes,
      glfwToDomKey,
    );

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

    logicalData = LogicalKeyData(
      <String>[webLogicalKeys, supplementalKeyData].join('\n'),
      gtkKeyCodes,
      gtkToDomKey,
      windowsKeyCodes,
      windowsToDomKey,
      androidKeyCodes,
      androidToDomKey,
      macosLogicalToPhysical,
      iosLogicalToPhysical,
      physicalData,
    );

    // Write data files
187
    const JsonEncoder encoder = JsonEncoder.withIndent('  ');
188 189 190 191
    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');
192
  } else {
193 194
    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>);
195 196
  }

197
  final File codeFile = File(parsedArguments['code'] as String);
198 199 200
  if (!codeFile.existsSync()) {
    codeFile.createSync(recursive: true);
  }
201
  print('Writing ${'key codes'.padRight(15)}${codeFile.absolute}');
202
  await codeFile.writeAsString(KeyboardKeysCodeGenerator(physicalData, logicalData).generate());
203

204
  final File mapsFile = File(parsedArguments['maps'] as String);
205 206 207
  if (!mapsFile.existsSync()) {
    mapsFile.createSync(recursive: true);
  }
208
  print('Writing ${'key maps'.padRight(15)}${mapsFile.absolute}');
209 210 211 212 213 214 215
  await mapsFile.writeAsString(KeyboardMapsCodeGenerator(physicalData, logicalData).generate());

  final Map<String, PlatformCodeGenerator> platforms = <String, PlatformCodeGenerator>{
    'android': AndroidCodeGenerator(
      physicalData,
      logicalData,
    ),
216 217 218 219 220
    'macos': MacOSCodeGenerator(
      physicalData,
      logicalData,
    ),
    'ios': IOSCodeGenerator(
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
      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;
244
    final File platformFile = File(codeGenerator.outputPath(platform));
245 246 247
    if (!platformFile.existsSync()) {
      platformFile.createSync(recursive: true);
    }
248
    print('Writing ${'$platform map'.padRight(15)}${platformFile.absolute}');
249 250
    return platformFile.writeAsString(codeGenerator.generate());
  }));
251
}