Skip to content

Commit a92f0ef

Browse files
authored
[Keyboard, Android] Generate keyboard codes (#104032)
1 parent 23bc3d6 commit a92f0ef

7 files changed

+255
-33
lines changed

dev/tools/gen_keycodes/bin/gen_keycodes.dart

+28-28
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import 'package:gen_keycodes/keyboard_maps_code_gen.dart';
1515
import 'package:gen_keycodes/logical_key_data.dart';
1616
import 'package:gen_keycodes/macos_code_gen.dart';
1717
import 'package:gen_keycodes/physical_key_data.dart';
18-
import 'package:gen_keycodes/testing_key_codes_gen.dart';
18+
import 'package:gen_keycodes/testing_key_codes_cc_gen.dart';
19+
import 'package:gen_keycodes/testing_key_codes_java_gen.dart';
1920
import 'package:gen_keycodes/utils.dart';
2021
import 'package:gen_keycodes/web_code_gen.dart';
2122
import 'package:gen_keycodes/windows_code_gen.dart';
@@ -81,6 +82,15 @@ bool _assertsEnabled() {
8182
return enabledAsserts;
8283
}
8384

85+
Future<void> generate(String name, String outDir, BaseCodeGenerator generator) {
86+
final File codeFile = File(outDir);
87+
if (!codeFile.existsSync()) {
88+
codeFile.createSync(recursive: true);
89+
}
90+
print('Writing ${name.padRight(15)}${codeFile.absolute}');
91+
return codeFile.writeAsString(generator.generate());
92+
}
93+
8494
Future<void> main(List<String> rawArguments) async {
8595
if (!_assertsEnabled()) {
8696
print('The gen_keycodes script must be run with --enable-asserts.');
@@ -208,27 +218,20 @@ Future<void> main(List<String> rawArguments) async {
208218

209219
final Map<String, bool> layoutGoals = parseMapOfBool(readDataFile('layout_goals.json'));
210220

211-
final File codeFile = File(parsedArguments['code'] as String);
212-
if (!codeFile.existsSync()) {
213-
codeFile.createSync(recursive: true);
214-
}
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-
if (!mapsFile.existsSync()) {
220-
mapsFile.createSync(recursive: true);
221-
}
222-
print('Writing ${'key maps'.padRight(15)}${mapsFile.absolute}');
223-
await mapsFile.writeAsString(KeyboardMapsCodeGenerator(physicalData, logicalData).generate());
224-
225-
final File keyCodesFile = File(path.join(PlatformCodeGenerator.engineRoot,
226-
'shell', 'platform', 'embedder', 'test_utils', 'key_codes.h'));
227-
if (!mapsFile.existsSync()) {
228-
mapsFile.createSync(recursive: true);
229-
}
230-
print('Writing ${'engine key codes'.padRight(15)}${mapsFile.absolute}');
231-
await keyCodesFile.writeAsString(KeyCodesCcGenerator(physicalData, logicalData).generate());
221+
await generate('key codes',
222+
parsedArguments['code'] as String,
223+
KeyboardKeysCodeGenerator(physicalData, logicalData));
224+
await generate('key maps',
225+
parsedArguments['maps'] as String,
226+
KeyboardMapsCodeGenerator(physicalData, logicalData));
227+
await generate('engine utils',
228+
path.join(PlatformCodeGenerator.engineRoot,
229+
'shell', 'platform', 'embedder', 'test_utils', 'key_codes.h'),
230+
KeyCodesCcGenerator(physicalData, logicalData));
231+
await generate('android utils',
232+
path.join(PlatformCodeGenerator.engineRoot, 'shell', 'platform',
233+
path.join('android', 'test', 'io', 'flutter', 'util', 'KeyCodes.java')),
234+
KeyCodesJavaGenerator(physicalData, logicalData));
232235

233236
final Map<String, PlatformCodeGenerator> platforms = <String, PlatformCodeGenerator>{
234237
'android': AndroidCodeGenerator(
@@ -265,11 +268,8 @@ Future<void> main(List<String> rawArguments) async {
265268
await Future.wait(platforms.entries.map((MapEntry<String, PlatformCodeGenerator> entry) {
266269
final String platform = entry.key;
267270
final PlatformCodeGenerator codeGenerator = entry.value;
268-
final File platformFile = File(codeGenerator.outputPath(platform));
269-
if (!platformFile.existsSync()) {
270-
platformFile.createSync(recursive: true);
271-
}
272-
print('Writing ${'$platform map'.padRight(15)}${platformFile.absolute}');
273-
return platformFile.writeAsString(codeGenerator.generate());
271+
return generate('$platform map',
272+
codeGenerator.outputPath(platform),
273+
codeGenerator);
274274
}));
275275
}

dev/tools/gen_keycodes/data/android_keyboard_map_java.tmpl

+75
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,64 @@ package io.flutter.embedding.android;
1111
// Edit the template dev/tools/gen_keycodes/data/android_keyboard_map_java.tmpl instead.
1212
// See dev/tools/gen_keycodes/README.md for more information.
1313

14+
import android.view.KeyEvent;
1415
import java.util.HashMap;
1516

17+
/** Static information used by {@link KeyEmbedderResponder}. */
1618
public class KeyboardMap {
19+
/** A physicalKey-logicalKey pair used to define mappings. */
20+
public static class KeyPair {
21+
public KeyPair(long physicalKey, long logicalKey) {
22+
this.physicalKey = physicalKey;
23+
this.logicalKey = logicalKey;
24+
}
25+
26+
public long physicalKey;
27+
public long logicalKey;
28+
}
29+
30+
/**
31+
* An immutable configuration item that defines how to synchronize pressing modifiers (such as
32+
* Shift or Ctrl), so that the {@link KeyEmbedderResponder} must synthesize events until the
33+
* combined pressing state of {@link keys} matches the true meta state masked by {@link mask}.
34+
*/
35+
public static class PressingGoal {
36+
public PressingGoal(int mask, KeyPair[] keys) {
37+
this.mask = mask;
38+
this.keys = keys;
39+
}
40+
41+
public final int mask;
42+
public final KeyPair[] keys;
43+
}
44+
45+
/**
46+
* A configuration item that defines how to synchronize toggling modifiers (such as CapsLock), so
47+
* that the {@link KeyEmbedderResponder} must synthesize events until the enabling state of the
48+
* key matches the true meta state masked by {@link #mask}.
49+
*
50+
* <p>The objects of this class are mutable. The {@link #enabled} field will be used to store the
51+
* current enabling state.
52+
*/
53+
public static class TogglingGoal {
54+
public TogglingGoal(int mask, long physicalKey, long logicalKey) {
55+
this.mask = mask;
56+
this.physicalKey = physicalKey;
57+
this.logicalKey = logicalKey;
58+
}
59+
60+
public final int mask;
61+
public final long physicalKey;
62+
public final long logicalKey;
63+
/**
64+
* Used by {@link KeyEmbedderResponder} to store the current enabling state of this modifier.
65+
*
66+
* <p>Initialized as false.
67+
*/
68+
public boolean enabled = false;
69+
}
70+
71+
/** Maps from Android scan codes {@link KeyEvent#getScanCode()} to Flutter physical keys. */
1772
public static final HashMap<Long, Long> scanCodeToPhysical =
1873
new HashMap<Long, Long>() {
1974
private static final long serialVersionUID = 1L;
@@ -23,6 +78,7 @@ public class KeyboardMap {
2378
}
2479
};
2580

81+
/** Maps from Android key codes {@link KeyEvent#getKeyCode()} to Flutter logical keys. */
2682
public static final HashMap<Long, Long> keyCodeToLogical =
2783
new HashMap<Long, Long>() {
2884
private static final long serialVersionUID = 1L;
@@ -31,4 +87,23 @@ public class KeyboardMap {
3187
@@@ANDROID_KEY_CODE_MAP@@@
3288
}
3389
};
90+
91+
public static final PressingGoal[] pressingGoals =
92+
new PressingGoal[] {
93+
@@@PRESSING_GOALS@@@
94+
};
95+
96+
/**
97+
* A list of toggling modifiers that must be synchronized on each key event.
98+
*
99+
* <p>The list is not a static variable but constructed by a function, because {@link
100+
* TogglingGoal} is mutable.
101+
*/
102+
public static TogglingGoal[] getTogglingGoals() {
103+
return new TogglingGoal[] {
104+
@@@TOGGLING_GOALS@@@
105+
};
106+
}
107+
108+
@@@MASK_CONSTANTS@@@
34109
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.flutter.util;
2+
3+
// DO NOT EDIT -- DO NOT EDIT -- DO NOT EDIT
4+
// This file is generated by
5+
// flutter/flutter:dev/tools/gen_keycodes/bin/gen_keycodes.dart and should not
6+
// be edited directly.
7+
//
8+
// Edit the template
9+
// flutter/flutter:dev/tools/gen_keycodes/data/key_codes_java.tmpl
10+
// instead.
11+
//
12+
// See flutter/flutter:dev/tools/gen_keycodes/README.md for more information.
13+
14+
/**
15+
* This class contains keyboard constants to be used in unit tests. They should not be used in
16+
* production code.
17+
*/
18+
public class KeyCodes {
19+
@@@PHYSICAL_KEY_DEFINITIONS@@@
20+
21+
@@@LOGICAL_KEY_DEFINITIONS@@@
22+
}

dev/tools/gen_keycodes/data/keyboard_key.tmpl

+7-5
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,17 @@ abstract class KeyboardKey with Diagnosticable {
4848
///
4949
/// {@tool dartpad}
5050
/// This example shows how to detect if the user has selected the logical "Q"
51-
/// key.
51+
/// key and handle the key if they have.
5252
///
5353
/// ** See code in examples/api/lib/services/keyboard_key/logical_keyboard_key.0.dart **
5454
/// {@end-tool}
5555
/// See also:
5656
///
5757
/// * [RawKeyEvent], the keyboard event object received by widgets that listen
5858
/// to keyboard events.
59-
/// * [RawKeyboardListener], a widget used to listen to and supply handlers for
60-
/// keyboard events.
59+
/// * [Focus.onKey], the handler on a widget that lets you handle key events.
60+
/// * [RawKeyboardListener], a widget used to listen to keyboard events (but
61+
/// not handle them).
6162
@immutable
6263
class LogicalKeyboardKey extends KeyboardKey {
6364
/// Creates a new LogicalKeyboardKey object for a key ID.
@@ -312,8 +313,9 @@ class LogicalKeyboardKey extends KeyboardKey {
312313
///
313314
/// * [RawKeyEvent], the keyboard event object received by widgets that listen
314315
/// to keyboard events.
315-
/// * [RawKeyboardListener], a widget used to listen to and supply handlers for
316-
/// keyboard events.
316+
/// * [Focus.onKey], the handler on a widget that lets you handle key events.
317+
/// * [RawKeyboardListener], a widget used to listen to keyboard events (but
318+
/// not handle them).
317319
@immutable
318320
class PhysicalKeyboardKey extends KeyboardKey {
319321
/// Creates a new PhysicalKeyboardKey object for a USB HID usage.

dev/tools/gen_keycodes/lib/android_code_gen.dart

+60
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'package:path/path.dart' as path;
66

77
import 'base_code_gen.dart';
8+
import 'constants.dart';
89
import 'logical_key_data.dart';
910
import 'physical_key_data.dart';
1011
import 'utils.dart';
@@ -39,6 +40,62 @@ class AndroidCodeGenerator extends PlatformCodeGenerator {
3940
return androidScanCodeMap.toString().trimRight();
4041
}
4142

43+
String get _pressingGoals {
44+
final OutputLines<int> lines = OutputLines<int>('Android pressing goals');
45+
const Map<String, List<String>> goalsSource = <String, List<String>>{
46+
'SHIFT': <String>['ShiftLeft', 'ShiftRight'],
47+
'CTRL': <String>['ControlLeft', 'ControlRight'],
48+
'ALT': <String>['AltLeft', 'AltRight'],
49+
};
50+
goalsSource.forEach((String flagName, List<String> keys) {
51+
int? lineId;
52+
final List<String> keysString = keys.map((String keyName) {
53+
final PhysicalKeyEntry physicalKey = keyData.entryByName(keyName);
54+
final LogicalKeyEntry logicalKey = logicalData.entryByName(keyName);
55+
lineId ??= physicalKey.usbHidCode;
56+
return ' new KeyPair(${toHex(physicalKey.usbHidCode)}L, '
57+
'${toHex(logicalKey.value, digits: 10)}L), // ${physicalKey.name}';
58+
}).toList();
59+
lines.add(lineId!,
60+
' new PressingGoal(\n'
61+
' KeyEvent.META_${flagName}_ON,\n'
62+
' new KeyPair[] {\n'
63+
'${keysString.join('\n')}\n'
64+
' }),');
65+
});
66+
return lines.sortedJoin().trimRight();
67+
}
68+
69+
String get _togglingGoals {
70+
final OutputLines<int> lines = OutputLines<int>('Android toggling goals');
71+
const Map<String, String> goalsSource = <String, String>{
72+
'CAPS_LOCK': 'CapsLock',
73+
};
74+
goalsSource.forEach((String flagName, String keyName) {
75+
final PhysicalKeyEntry physicalKey = keyData.entryByName(keyName);
76+
final LogicalKeyEntry logicalKey = logicalData.entryByName(keyName);
77+
lines.add(physicalKey.usbHidCode,
78+
' new TogglingGoal(KeyEvent.META_${flagName}_ON, '
79+
'${toHex(physicalKey.usbHidCode)}L, '
80+
'${toHex(logicalKey.value, digits: 10)}L),');
81+
});
82+
return lines.sortedJoin().trimRight();
83+
}
84+
85+
/// This generates the mask values for the part of a key code that defines its plane.
86+
String get _maskConstants {
87+
final StringBuffer buffer = StringBuffer();
88+
const List<MaskConstant> maskConstants = <MaskConstant>[
89+
kValueMask,
90+
kUnicodePlane,
91+
kAndroidPlane,
92+
];
93+
for (final MaskConstant constant in maskConstants) {
94+
buffer.writeln(' public static final long k${constant.upperCamelName} = ${toHex(constant.value, digits: 11)}L;');
95+
}
96+
return buffer.toString().trimRight();
97+
}
98+
4299
@override
43100
String get templatePath => path.join(dataRoot, 'android_keyboard_map_java.tmpl');
44101

@@ -51,6 +108,9 @@ class AndroidCodeGenerator extends PlatformCodeGenerator {
51108
return <String, String>{
52109
'ANDROID_SCAN_CODE_MAP': _androidScanCodeMap,
53110
'ANDROID_KEY_CODE_MAP': _androidKeyCodeMap,
111+
'PRESSING_GOALS': _pressingGoals,
112+
'TOGGLING_GOALS': _togglingGoals,
113+
'MASK_CONSTANTS': _maskConstants,
54114
};
55115
}
56116
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:path/path.dart' as path;
6+
7+
import 'base_code_gen.dart';
8+
import 'logical_key_data.dart';
9+
import 'physical_key_data.dart';
10+
import 'utils.dart';
11+
12+
String _toUpperSnake(String lowerCammel) {
13+
// Converts 'myTVFoo' to 'myTvFoo'.
14+
final String trueUpperCammel = lowerCammel.replaceAllMapped(
15+
RegExp(r'([A-Z]{3,})'),
16+
(Match match) {
17+
final String matched = match.group(1)!;
18+
return matched.substring(0, 1)
19+
+ matched.substring(1, matched.length - 2).toLowerCase()
20+
+ matched.substring(matched.length - 2, matched.length - 1);
21+
});
22+
// Converts 'myTvFoo' to 'MY_TV_FOO'.
23+
return trueUpperCammel.replaceAllMapped(
24+
RegExp(r'([A-Z])'),
25+
(Match match) => '_${match.group(1)!}').toUpperCase();
26+
}
27+
28+
/// Generates the common/testing/key_codes.h based on the information in the key
29+
/// data structure given to it.
30+
class KeyCodesJavaGenerator extends BaseCodeGenerator {
31+
KeyCodesJavaGenerator(super.keyData, super.logicalData);
32+
33+
/// Gets the generated definitions of PhysicalKeyboardKeys.
34+
String get _physicalDefinitions {
35+
final OutputLines<int> lines = OutputLines<int>('Physical Key list');
36+
for (final PhysicalKeyEntry entry in keyData.entries) {
37+
lines.add(entry.usbHidCode, '''
38+
public static final long PHYSICAL_${_toUpperSnake(entry.constantName)} = ${toHex(entry.usbHidCode)}l;''');
39+
}
40+
return lines.sortedJoin().trimRight();
41+
}
42+
43+
/// Gets the generated definitions of PhysicalKeyboardKeys.
44+
String get _logicalDefinitions {
45+
final OutputLines<int> lines = OutputLines<int>('Logical Key list');
46+
for (final LogicalKeyEntry entry in logicalData.entries) {
47+
lines.add(entry.value, '''
48+
public static final long LOGICAL_${_toUpperSnake(entry.constantName)} = ${toHex(entry.value, digits: 11)}l;''');
49+
}
50+
return lines.sortedJoin().trimRight();
51+
}
52+
53+
@override
54+
String get templatePath => path.join(dataRoot, 'key_codes_java.tmpl');
55+
56+
@override
57+
Map<String, String> mappings() {
58+
return <String, String>{
59+
'LOGICAL_KEY_DEFINITIONS': _logicalDefinitions,
60+
'PHYSICAL_KEY_DEFINITIONS': _physicalDefinitions,
61+
};
62+
}
63+
}

0 commit comments

Comments
 (0)