Unverified Commit a92f0ef1 authored by Tong Mu's avatar Tong Mu Committed by GitHub

[Keyboard, Android] Generate keyboard codes (#104032)

parent 23bc3d64
......@@ -15,7 +15,8 @@ import 'package:gen_keycodes/keyboard_maps_code_gen.dart';
import 'package:gen_keycodes/logical_key_data.dart';
import 'package:gen_keycodes/macos_code_gen.dart';
import 'package:gen_keycodes/physical_key_data.dart';
import 'package:gen_keycodes/testing_key_codes_gen.dart';
import 'package:gen_keycodes/testing_key_codes_cc_gen.dart';
import 'package:gen_keycodes/testing_key_codes_java_gen.dart';
import 'package:gen_keycodes/utils.dart';
import 'package:gen_keycodes/web_code_gen.dart';
import 'package:gen_keycodes/windows_code_gen.dart';
......@@ -81,6 +82,15 @@ bool _assertsEnabled() {
return enabledAsserts;
}
Future<void> generate(String name, String outDir, BaseCodeGenerator generator) {
final File codeFile = File(outDir);
if (!codeFile.existsSync()) {
codeFile.createSync(recursive: true);
}
print('Writing ${name.padRight(15)}${codeFile.absolute}');
return codeFile.writeAsString(generator.generate());
}
Future<void> main(List<String> rawArguments) async {
if (!_assertsEnabled()) {
print('The gen_keycodes script must be run with --enable-asserts.');
......@@ -208,27 +218,20 @@ Future<void> main(List<String> rawArguments) async {
final Map<String, bool> layoutGoals = parseMapOfBool(readDataFile('layout_goals.json'));
final File codeFile = File(parsedArguments['code'] as String);
if (!codeFile.existsSync()) {
codeFile.createSync(recursive: true);
}
print('Writing ${'key codes'.padRight(15)}${codeFile.absolute}');
await codeFile.writeAsString(KeyboardKeysCodeGenerator(physicalData, logicalData).generate());
final File mapsFile = File(parsedArguments['maps'] as String);
if (!mapsFile.existsSync()) {
mapsFile.createSync(recursive: true);
}
print('Writing ${'key maps'.padRight(15)}${mapsFile.absolute}');
await mapsFile.writeAsString(KeyboardMapsCodeGenerator(physicalData, logicalData).generate());
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());
await generate('key codes',
parsedArguments['code'] as String,
KeyboardKeysCodeGenerator(physicalData, logicalData));
await generate('key maps',
parsedArguments['maps'] as String,
KeyboardMapsCodeGenerator(physicalData, logicalData));
await generate('engine utils',
path.join(PlatformCodeGenerator.engineRoot,
'shell', 'platform', 'embedder', 'test_utils', 'key_codes.h'),
KeyCodesCcGenerator(physicalData, logicalData));
await generate('android utils',
path.join(PlatformCodeGenerator.engineRoot, 'shell', 'platform',
path.join('android', 'test', 'io', 'flutter', 'util', 'KeyCodes.java')),
KeyCodesJavaGenerator(physicalData, logicalData));
final Map<String, PlatformCodeGenerator> platforms = <String, PlatformCodeGenerator>{
'android': AndroidCodeGenerator(
......@@ -265,11 +268,8 @@ Future<void> main(List<String> rawArguments) async {
await Future.wait(platforms.entries.map((MapEntry<String, PlatformCodeGenerator> entry) {
final String platform = entry.key;
final PlatformCodeGenerator codeGenerator = entry.value;
final File platformFile = File(codeGenerator.outputPath(platform));
if (!platformFile.existsSync()) {
platformFile.createSync(recursive: true);
}
print('Writing ${'$platform map'.padRight(15)}${platformFile.absolute}');
return platformFile.writeAsString(codeGenerator.generate());
return generate('$platform map',
codeGenerator.outputPath(platform),
codeGenerator);
}));
}
......@@ -11,9 +11,64 @@ package io.flutter.embedding.android;
// Edit the template dev/tools/gen_keycodes/data/android_keyboard_map_java.tmpl instead.
// See dev/tools/gen_keycodes/README.md for more information.
import android.view.KeyEvent;
import java.util.HashMap;
/** Static information used by {@link KeyEmbedderResponder}. */
public class KeyboardMap {
/** A physicalKey-logicalKey pair used to define mappings. */
public static class KeyPair {
public KeyPair(long physicalKey, long logicalKey) {
this.physicalKey = physicalKey;
this.logicalKey = logicalKey;
}
public long physicalKey;
public long logicalKey;
}
/**
* An immutable configuration item that defines how to synchronize pressing modifiers (such as
* Shift or Ctrl), so that the {@link KeyEmbedderResponder} must synthesize events until the
* combined pressing state of {@link keys} matches the true meta state masked by {@link mask}.
*/
public static class PressingGoal {
public PressingGoal(int mask, KeyPair[] keys) {
this.mask = mask;
this.keys = keys;
}
public final int mask;
public final KeyPair[] keys;
}
/**
* A configuration item that defines how to synchronize toggling modifiers (such as CapsLock), so
* that the {@link KeyEmbedderResponder} must synthesize events until the enabling state of the
* key matches the true meta state masked by {@link #mask}.
*
* <p>The objects of this class are mutable. The {@link #enabled} field will be used to store the
* current enabling state.
*/
public static class TogglingGoal {
public TogglingGoal(int mask, long physicalKey, long logicalKey) {
this.mask = mask;
this.physicalKey = physicalKey;
this.logicalKey = logicalKey;
}
public final int mask;
public final long physicalKey;
public final long logicalKey;
/**
* Used by {@link KeyEmbedderResponder} to store the current enabling state of this modifier.
*
* <p>Initialized as false.
*/
public boolean enabled = false;
}
/** Maps from Android scan codes {@link KeyEvent#getScanCode()} to Flutter physical keys. */
public static final HashMap<Long, Long> scanCodeToPhysical =
new HashMap<Long, Long>() {
private static final long serialVersionUID = 1L;
......@@ -23,6 +78,7 @@ public class KeyboardMap {
}
};
/** Maps from Android key codes {@link KeyEvent#getKeyCode()} to Flutter logical keys. */
public static final HashMap<Long, Long> keyCodeToLogical =
new HashMap<Long, Long>() {
private static final long serialVersionUID = 1L;
......@@ -31,4 +87,23 @@ public class KeyboardMap {
@@@ANDROID_KEY_CODE_MAP@@@
}
};
public static final PressingGoal[] pressingGoals =
new PressingGoal[] {
@@@PRESSING_GOALS@@@
};
/**
* A list of toggling modifiers that must be synchronized on each key event.
*
* <p>The list is not a static variable but constructed by a function, because {@link
* TogglingGoal} is mutable.
*/
public static TogglingGoal[] getTogglingGoals() {
return new TogglingGoal[] {
@@@TOGGLING_GOALS@@@
};
}
@@@MASK_CONSTANTS@@@
}
package io.flutter.util;
// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT
// This file is generated by
// flutter/flutter:dev/tools/gen_keycodes/bin/gen_keycodes.dart and should not
// be edited directly.
//
// Edit the template
// flutter/flutter:dev/tools/gen_keycodes/data/key_codes_java.tmpl
// instead.
//
// See flutter/flutter:dev/tools/gen_keycodes/README.md for more information.
/**
* This class contains keyboard constants to be used in unit tests. They should not be used in
* production code.
*/
public class KeyCodes {
@@@PHYSICAL_KEY_DEFINITIONS@@@
@@@LOGICAL_KEY_DEFINITIONS@@@
}
......@@ -48,7 +48,7 @@ abstract class KeyboardKey with Diagnosticable {
///
/// {@tool dartpad}
/// This example shows how to detect if the user has selected the logical "Q"
/// key.
/// key and handle the key if they have.
///
/// ** See code in examples/api/lib/services/keyboard_key/logical_keyboard_key.0.dart **
/// {@end-tool}
......@@ -56,8 +56,9 @@ abstract class KeyboardKey with Diagnosticable {
///
/// * [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.
/// * [Focus.onKey], the handler on a widget that lets you handle key events.
/// * [RawKeyboardListener], a widget used to listen to keyboard events (but
/// not handle them).
@immutable
class LogicalKeyboardKey extends KeyboardKey {
/// Creates a new LogicalKeyboardKey object for a key ID.
......@@ -312,8 +313,9 @@ class LogicalKeyboardKey extends KeyboardKey {
///
/// * [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.
/// * [Focus.onKey], the handler on a widget that lets you handle key events.
/// * [RawKeyboardListener], a widget used to listen to keyboard events (but
/// not handle them).
@immutable
class PhysicalKeyboardKey extends KeyboardKey {
/// Creates a new PhysicalKeyboardKey object for a USB HID usage.
......
......@@ -5,6 +5,7 @@
import 'package:path/path.dart' as path;
import 'base_code_gen.dart';
import 'constants.dart';
import 'logical_key_data.dart';
import 'physical_key_data.dart';
import 'utils.dart';
......@@ -39,6 +40,62 @@ class AndroidCodeGenerator extends PlatformCodeGenerator {
return androidScanCodeMap.toString().trimRight();
}
String get _pressingGoals {
final OutputLines<int> lines = OutputLines<int>('Android pressing goals');
const Map<String, List<String>> goalsSource = <String, List<String>>{
'SHIFT': <String>['ShiftLeft', 'ShiftRight'],
'CTRL': <String>['ControlLeft', 'ControlRight'],
'ALT': <String>['AltLeft', 'AltRight'],
};
goalsSource.forEach((String flagName, List<String> keys) {
int? lineId;
final List<String> keysString = keys.map((String keyName) {
final PhysicalKeyEntry physicalKey = keyData.entryByName(keyName);
final LogicalKeyEntry logicalKey = logicalData.entryByName(keyName);
lineId ??= physicalKey.usbHidCode;
return ' new KeyPair(${toHex(physicalKey.usbHidCode)}L, '
'${toHex(logicalKey.value, digits: 10)}L), // ${physicalKey.name}';
}).toList();
lines.add(lineId!,
' new PressingGoal(\n'
' KeyEvent.META_${flagName}_ON,\n'
' new KeyPair[] {\n'
'${keysString.join('\n')}\n'
' }),');
});
return lines.sortedJoin().trimRight();
}
String get _togglingGoals {
final OutputLines<int> lines = OutputLines<int>('Android toggling goals');
const Map<String, String> goalsSource = <String, String>{
'CAPS_LOCK': 'CapsLock',
};
goalsSource.forEach((String flagName, String keyName) {
final PhysicalKeyEntry physicalKey = keyData.entryByName(keyName);
final LogicalKeyEntry logicalKey = logicalData.entryByName(keyName);
lines.add(physicalKey.usbHidCode,
' new TogglingGoal(KeyEvent.META_${flagName}_ON, '
'${toHex(physicalKey.usbHidCode)}L, '
'${toHex(logicalKey.value, digits: 10)}L),');
});
return lines.sortedJoin().trimRight();
}
/// This generates the mask values for the part of a key code that defines its plane.
String get _maskConstants {
final StringBuffer buffer = StringBuffer();
const List<MaskConstant> maskConstants = <MaskConstant>[
kValueMask,
kUnicodePlane,
kAndroidPlane,
];
for (final MaskConstant constant in maskConstants) {
buffer.writeln(' public static final long k${constant.upperCamelName} = ${toHex(constant.value, digits: 11)}L;');
}
return buffer.toString().trimRight();
}
@override
String get templatePath => path.join(dataRoot, 'android_keyboard_map_java.tmpl');
......@@ -51,6 +108,9 @@ class AndroidCodeGenerator extends PlatformCodeGenerator {
return <String, String>{
'ANDROID_SCAN_CODE_MAP': _androidScanCodeMap,
'ANDROID_KEY_CODE_MAP': _androidKeyCodeMap,
'PRESSING_GOALS': _pressingGoals,
'TOGGLING_GOALS': _togglingGoals,
'MASK_CONSTANTS': _maskConstants,
};
}
}
// Copyright 2014 The Flutter 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 'package:path/path.dart' as path;
import 'base_code_gen.dart';
import 'logical_key_data.dart';
import 'physical_key_data.dart';
import 'utils.dart';
String _toUpperSnake(String lowerCammel) {
// Converts 'myTVFoo' to 'myTvFoo'.
final String trueUpperCammel = lowerCammel.replaceAllMapped(
RegExp(r'([A-Z]{3,})'),
(Match match) {
final String matched = match.group(1)!;
return matched.substring(0, 1)
+ matched.substring(1, matched.length - 2).toLowerCase()
+ matched.substring(matched.length - 2, matched.length - 1);
});
// Converts 'myTvFoo' to 'MY_TV_FOO'.
return trueUpperCammel.replaceAllMapped(
RegExp(r'([A-Z])'),
(Match match) => '_${match.group(1)!}').toUpperCase();
}
/// Generates the common/testing/key_codes.h based on the information in the key
/// data structure given to it.
class KeyCodesJavaGenerator extends BaseCodeGenerator {
KeyCodesJavaGenerator(super.keyData, super.logicalData);
/// Gets the generated definitions of PhysicalKeyboardKeys.
String get _physicalDefinitions {
final OutputLines<int> lines = OutputLines<int>('Physical Key list');
for (final PhysicalKeyEntry entry in keyData.entries) {
lines.add(entry.usbHidCode, '''
public static final long PHYSICAL_${_toUpperSnake(entry.constantName)} = ${toHex(entry.usbHidCode)}l;''');
}
return lines.sortedJoin().trimRight();
}
/// Gets the generated definitions of PhysicalKeyboardKeys.
String get _logicalDefinitions {
final OutputLines<int> lines = OutputLines<int>('Logical Key list');
for (final LogicalKeyEntry entry in logicalData.entries) {
lines.add(entry.value, '''
public static final long LOGICAL_${_toUpperSnake(entry.constantName)} = ${toHex(entry.value, digits: 11)}l;''');
}
return lines.sortedJoin().trimRight();
}
@override
String get templatePath => path.join(dataRoot, 'key_codes_java.tmpl');
@override
Map<String, String> mappings() {
return <String, String>{
'LOGICAL_KEY_DEFINITIONS': _logicalDefinitions,
'PHYSICAL_KEY_DEFINITIONS': _physicalDefinitions,
};
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment