Skip to content

Commit

Permalink
[gen_keycode, RawKeyboard] Apply derived keyboard layout from Linux (…
Browse files Browse the repository at this point in the history
…#102709)
  • Loading branch information
dkwingsmt authored May 12, 2022
1 parent 5288ff8 commit 65ea767
Show file tree
Hide file tree
Showing 15 changed files with 146 additions and 30 deletions.
4 changes: 4 additions & 0 deletions dev/tools/gen_keycodes/bin/gen_keycodes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ Future<void> main(List<String> rawArguments) async {
logicalData = LogicalKeyData.fromJson(json.decode(await File(parsedArguments['logical-data'] as String).readAsString()) as Map<String, dynamic>);
}

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);
Expand Down Expand Up @@ -236,6 +238,7 @@ Future<void> main(List<String> rawArguments) async {
'macos': MacOSCodeGenerator(
physicalData,
logicalData,
layoutGoals,
),
'ios': IOSCodeGenerator(
physicalData,
Expand All @@ -251,6 +254,7 @@ Future<void> main(List<String> rawArguments) async {
logicalData,
readDataFile('gtk_modifier_bit_mapping.json'),
readDataFile('gtk_lock_bit_mapping.json'),
layoutGoals,
),
'web': WebCodeGenerator(
physicalData,
Expand Down
1 change: 1 addition & 0 deletions dev/tools/gen_keycodes/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
| [`chromium_modifiers.json`](chromium_modifiers.json) | Maps the web's `key` for modifier keys to the names of the logical keys for these keys' left and right variations.This is used when generating logical keys to provide independent values for sided logical keys. Web uses the same `key` for modifier keys of different sides, but Flutter's logical key model treats them as different keys.|
| [`printable.json`](printable.json) | Maps Flutter key name to its printable character. This character is used as the key label.|
| [`synonyms.json`](synonyms.json) | Maps pseudo-keys that represent other keys to the sets of keys they represent. For example, this contains the "shift" key that represents either a "shiftLeft" or "shiftRight" key.|
| [`layout_goals.json`](layout_goals.json) | A list of layout goals, keys that the platform keyboard manager should find mappings for. Each key in this file is the key name of the goal, both logical and physical simultaneously, while its value represents whether the goal is mandatory. A mandatory goal must be fulfilled, and the manager will use the default value from this file if a mapping can not be found. A non-mandatory goal is suggestive, only used if the key mapping information is malformed (e.g. contains no ASCII characters.) |

### Framework

Expand Down
4 changes: 4 additions & 0 deletions dev/tools/gen_keycodes/data/gtk_key_mapping_cc.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ void initialize_lock_bit_to_checked_keys(GHashTable* table) {
@@@GTK_MODE_BIT_MAP@@@
}

const std::vector<LayoutGoal> layout_goals = {
@@@LAYOUT_GOALS@@@
};

@@@MASK_CONSTANTS@@@
50 changes: 50 additions & 0 deletions dev/tools/gen_keycodes/data/layout_goals.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"KeyA": true,
"KeyB": true,
"KeyC": true,
"KeyD": true,
"KeyE": true,
"KeyF": true,
"KeyG": true,
"KeyH": true,
"KeyI": true,
"KeyJ": true,
"KeyK": true,
"KeyL": true,
"KeyM": true,
"KeyN": true,
"KeyO": true,
"KeyP": true,
"KeyQ": true,
"KeyR": true,
"KeyS": true,
"KeyT": true,
"KeyU": true,
"KeyV": true,
"KeyW": true,
"KeyX": true,
"KeyY": true,
"KeyZ": true,
"Digit1": true,
"Digit2": true,
"Digit3": true,
"Digit4": true,
"Digit5": true,
"Digit6": true,
"Digit7": true,
"Digit8": true,
"Digit9": true,
"Digit0": true,
"Quote": false,
"Comma": false,
"Minus": false,
"Period": false,
"Slash": false,
"Semicolon": false,
"Equal": false,
"BracketLeft": false,
"Backslash": false,
"BracketRight": false,
"Backquote": false,
"IntlBackslash": false
}
20 changes: 20 additions & 0 deletions dev/tools/gen_keycodes/lib/gtk_code_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class GtkCodeGenerator extends PlatformCodeGenerator {
super.logicalData,
String modifierBitMapping,
String lockBitMapping,
this._layoutGoals,
) : _modifierBitMapping = parseMapOfListOfString(modifierBitMapping),
_lockBitMapping = parseMapOfListOfString(lockBitMapping);

Expand Down Expand Up @@ -91,6 +92,24 @@ class GtkCodeGenerator extends PlatformCodeGenerator {
}
final Map<String, List<String>> _lockBitMapping;

final Map<String, bool> _layoutGoals;
String get _layoutGoalsString {
final OutputLines<int> lines = OutputLines<int>('GTK layout goals');
_layoutGoals.forEach((String name, bool mandatory) {
final PhysicalKeyEntry physicalEntry = keyData.entryByName(name);
final LogicalKeyEntry logicalEntry = logicalData.entryByName(name);
final String line = 'LayoutGoal{'
'${toHex(physicalEntry.xKbScanCode, digits: 2)}, '
'${toHex(logicalEntry.value, digits: 2)}, '
'${mandatory ? 'true' : 'false'}'
'},';
lines.add(logicalEntry.value,
' ${line.padRight(39)}'
'// ${logicalEntry.name}');
});
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();
Expand Down Expand Up @@ -120,6 +139,7 @@ class GtkCodeGenerator extends PlatformCodeGenerator {
'GTK_MODIFIER_BIT_MAP': _gtkModifierBitMap,
'GTK_MODE_BIT_MAP': _gtkModeBitMap,
'MASK_CONSTANTS': _maskConstants,
'LAYOUT_GOALS': _layoutGoalsString,
};
}
}
4 changes: 2 additions & 2 deletions dev/tools/gen_keycodes/lib/ios_code_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class IOSCodeGenerator extends PlatformCodeGenerator {
String get _keyToModifierFlagMap {
final StringBuffer modifierKeyMap = StringBuffer();
for (final String name in kModifiersOfInterest) {
final String line =' {${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}, kModifierFlag${lowerCamelToUpperCamel(name)}},';
final String line = '{${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}, kModifierFlag${lowerCamelToUpperCamel(name)}},';
modifierKeyMap.writeln(' ${line.padRight(42)}// $name');
}
return modifierKeyMap.toString().trimRight();
Expand All @@ -99,7 +99,7 @@ class IOSCodeGenerator extends PlatformCodeGenerator {
String get _modifierFlagToKeyMap {
final StringBuffer modifierKeyMap = StringBuffer();
for (final String name in kModifiersOfInterest) {
final String line =' {kModifierFlag${lowerCamelToUpperCamel(name)}, ${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}},';
final String line = '{kModifierFlag${lowerCamelToUpperCamel(name)}, ${toHex(logicalData.entryByName(name).iOSKeyCodeValues[0])}},';
modifierKeyMap.writeln(' ${line.padRight(42)}// $name');
}
return modifierKeyMap.toString().trimRight();
Expand Down
35 changes: 16 additions & 19 deletions dev/tools/gen_keycodes/lib/macos_code_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const List<String> kSpecialLogicalKeys = <String>['CapsLock'];
/// Generates the key mapping for macOS, based on the information in the key
/// data structure given to it.
class MacOSCodeGenerator extends PlatformCodeGenerator {
MacOSCodeGenerator(super.keyData, super.logicalData);
MacOSCodeGenerator(super.keyData, super.logicalData, this._layoutGoals);

/// This generates the map of macOS key codes to physical keys.
String get _scanCodeMap {
Expand Down Expand Up @@ -96,24 +96,21 @@ class MacOSCodeGenerator extends PlatformCodeGenerator {
return specialKeyConstants.toString().trimRight();
}

String get _layoutGoals {
final Map<String, bool> _layoutGoals;
String get _layoutGoalsString {
final OutputLines<int> lines = OutputLines<int>('macOS layout goals');
final Iterable<LogicalKeyEntry> asciiEntries = logicalData.entries.where(
(LogicalKeyEntry entry) => entry.value <= 128);
for (final LogicalKeyEntry logicalEntry in asciiEntries) {
final int value = logicalEntry.value;
final PhysicalKeyEntry? physicalEntry = keyData.tryEntryByName(logicalEntry.name);
if (physicalEntry == null) {
continue;
}
final bool mandatory = (value >= '0'.codeUnitAt(0) && value <= '9'.codeUnitAt(0))
|| (value >= 'a'.codeUnitAt(0) && value <= 'z'.codeUnitAt(0));
lines.add(value,
' LayoutGoal{${toHex(physicalEntry.macOSScanCode, digits: 2)}, '
'${toHex(value, digits: 2)}, '
'${mandatory ? 'true}, ' : 'false},'}'
' // ${logicalEntry.name}');
}
_layoutGoals.forEach((String name, bool mandatory) {
final PhysicalKeyEntry physicalEntry = keyData.entryByName(name);
final LogicalKeyEntry logicalEntry = logicalData.entryByName(name);
final String line = 'LayoutGoal{'
'${toHex(physicalEntry.macOSScanCode, digits: 2)}, '
'${toHex(logicalEntry.value, digits: 2)}, '
'${mandatory ? 'true' : 'false'}'
'},';
lines.add(logicalEntry.value,
' ${line.padRight(39)}'
'// ${logicalEntry.name}');
});
return lines.sortedJoin().trimRight();
}

Expand All @@ -136,7 +133,7 @@ class MacOSCodeGenerator extends PlatformCodeGenerator {
'KEYCODE_TO_MODIFIER_FLAG_MAP': _keyToModifierFlagMap,
'MODIFIER_FLAG_TO_KEYCODE_MAP': _modifierFlagToKeyMap,
'SPECIAL_KEY_CONSTANTS': _specialKeyConstants,
'LAYOUT_GOALS': _layoutGoals,
'LAYOUT_GOALS': _layoutGoalsString,
};
}
}
16 changes: 8 additions & 8 deletions dev/tools/gen_keycodes/lib/physical_key_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class PhysicalKeyData {
input = input.replaceAll(commentRegExp, '');
for (final RegExpMatch match in usbMapRegExp.allMatches(input)) {
final int usbHidCode = getHex(match.namedGroup('usb')!);
final int linuxScanCode = getHex(match.namedGroup('evdev')!);
final int evdevCode = getHex(match.namedGroup('evdev')!);
final int xKbScanCode = getHex(match.namedGroup('xkb')!);
final int windowsScanCode = getHex(match.namedGroup('win')!);
final int macScanCode = getHex(match.namedGroup('mac')!);
Expand All @@ -174,7 +174,7 @@ class PhysicalKeyData {
final PhysicalKeyEntry newEntry = PhysicalKeyEntry(
usbHidCode: usbHidCode,
androidScanCodes: nameToAndroidScanCodes[name] ?? <int>[],
linuxScanCode: linuxScanCode == 0 ? null : linuxScanCode,
evdevCode: evdevCode == 0 ? null : evdevCode,
xKbScanCode: xKbScanCode == 0 ? null : xKbScanCode,
windowsScanCode: windowsScanCode == 0 ? null : windowsScanCode,
macOSScanCode: macScanCode == 0xffff ? null : macScanCode,
Expand Down Expand Up @@ -210,7 +210,7 @@ class PhysicalKeyEntry {
required this.usbHidCode,
required this.name,
required this.androidScanCodes,
required this.linuxScanCode,
required this.evdevCode,
required this.xKbScanCode,
required this.windowsScanCode,
required this.macOSScanCode,
Expand All @@ -227,7 +227,7 @@ class PhysicalKeyEntry {
chromiumCode: names['chromium'] as String?,
usbHidCode: scanCodes['usb'] as int,
androidScanCodes: (scanCodes['android'] as List<dynamic>?)?.cast<int>() ?? <int>[],
linuxScanCode: scanCodes['linux'] as int?,
evdevCode: scanCodes['linux'] as int?,
xKbScanCode: scanCodes['xkb'] as int?,
windowsScanCode: scanCodes['windows'] as int?,
macOSScanCode: scanCodes['macos'] as int?,
Expand All @@ -238,8 +238,8 @@ class PhysicalKeyEntry {
/// The USB HID code of the key
final int usbHidCode;

/// The Linux scan code of the key, from Chromium's header file.
final int? linuxScanCode;
/// The Evdev scan code of the key, from Chromium's header file.
final int? evdevCode;
/// The XKb scan code of the key from Chromium's header file.
final int? xKbScanCode;
/// The Windows scan code of the key from Chromium's header file.
Expand Down Expand Up @@ -269,7 +269,7 @@ class PhysicalKeyEntry {
'scanCodes': <String, dynamic>{
'android': androidScanCodes,
'usb': usbHidCode,
'linux': linuxScanCode,
'linux': evdevCode,
'xkb': xKbScanCode,
'windows': windowsScanCode,
'macos': macOSScanCode,
Expand Down Expand Up @@ -318,7 +318,7 @@ class PhysicalKeyEntry {
@override
String toString() {
return """'$constantName': (name: "$name", usbHidCode: ${toHex(usbHidCode)}, """
'linuxScanCode: ${toHex(linuxScanCode)}, xKbScanCode: ${toHex(xKbScanCode)}, '
'linuxScanCode: ${toHex(evdevCode)}, xKbScanCode: ${toHex(xKbScanCode)}, '
'windowsKeyCode: ${toHex(windowsScanCode)}, macOSScanCode: ${toHex(macOSScanCode)}, '
'windowsScanCode: ${toHex(windowsScanCode)}, chromiumSymbolName: $chromiumCode '
'iOSScanCode: ${toHex(iOSScanCode)})';
Expand Down
4 changes: 4 additions & 0 deletions dev/tools/gen_keycodes/lib/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ Map<String, List<String?>> parseMapOfListOfNullableString(String jsonString) {
});
}

Map<String, bool> parseMapOfBool(String jsonString) {
return (json.decode(jsonString) as Map<String, dynamic>).cast<String, bool>();
}

/// Reverse the map of { fromValue -> list of toValue } to { toValue -> fromValue } and return.
Map<String, String> reverseMapOfListOfString(Map<String, List<String>> inMap, void Function(String fromValue, String newToValue) onDuplicate) {
final Map<String, String> result = <String, String>{};
Expand Down
2 changes: 1 addition & 1 deletion dev/tools/gen_keycodes/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: gen_keycodes
description: Generates keycode source files from various resources.

environment:
sdk: ">=2.17.0-0 <3.0.0"
sdk: ">=2.18.0-0 <3.0.0"

dependencies:
args: 2.3.0
Expand Down
4 changes: 4 additions & 0 deletions dev/tools/gen_keycodes/test/gen_keycodes_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ final PhysicalKeyData physicalData = PhysicalKeyData.fromJson(
json.decode(readDataFile('physical_key_data.json')) as Map<String, dynamic>);
final LogicalKeyData logicalData = LogicalKeyData.fromJson(
json.decode(readDataFile('logical_key_data.json')) as Map<String, dynamic>);
final Map<String, bool> keyGoals = parseMapOfBool(
readDataFile('layout_goals.json'));

void main() {
setUp(() {
Expand Down Expand Up @@ -65,6 +67,7 @@ void main() {
final PlatformCodeGenerator codeGenerator = MacOSCodeGenerator(
physicalData,
logicalData,
keyGoals,
);
final String output = codeGenerator.generate();

Expand Down Expand Up @@ -119,6 +122,7 @@ void main() {
logicalData,
readDataFile(path.join(dataRoot, 'gtk_modifier_bit_mapping.json')),
readDataFile(path.join(dataRoot, 'gtk_lock_bit_mapping.json')),
keyGoals,
);
final String output = codeGenerator.generate();

Expand Down
1 change: 1 addition & 0 deletions packages/flutter/lib/src/services/raw_keyboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ abstract class RawKeyEvent with Diagnosticable {
scanCode: message['scanCode'] as int? ?? 0,
modifiers: message['modifiers'] as int? ?? 0,
isDown: message['type'] == 'keydown',
specifiedLogicalKey: message['specifiedLogicalKey'] as int?,
);
if (unicodeScalarValues != 0) {
character = String.fromCharCode(unicodeScalarValues);
Expand Down
15 changes: 15 additions & 0 deletions packages/flutter/lib/src/services/raw_keyboard_linux.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class RawKeyEventDataLinux extends RawKeyEventData {
this.keyCode = 0,
this.modifiers = 0,
required this.isDown,
this.specifiedLogicalKey,
}) : assert(scanCode != null),
assert(unicodeScalarValues != null),
assert((unicodeScalarValues & ~LogicalKeyboardKey.valueMask) == 0),
Expand Down Expand Up @@ -68,6 +69,15 @@ class RawKeyEventDataLinux extends RawKeyEventData {
/// Whether or not this key event is a key down (true) or key up (false).
final bool isDown;

/// A logical key specified by the embedding that should be used instead of
/// deriving from raw data.
///
/// The GTK embedding detects the keyboard layout and maps some keys to
/// logical keys in a way that can not be derived from per-key information.
///
/// This is not part of the native GTK key event.
final int? specifiedLogicalKey;

@override
String get keyLabel => unicodeScalarValues == 0 ? '' : String.fromCharCode(unicodeScalarValues);

Expand All @@ -76,6 +86,10 @@ class RawKeyEventDataLinux extends RawKeyEventData {

@override
LogicalKeyboardKey get logicalKey {
if (specifiedLogicalKey != null) {
final int key = specifiedLogicalKey!;
return LogicalKeyboardKey.findKeyByKeyId(key) ?? LogicalKeyboardKey(key);
}
// Look to see if the keyCode is a printable number pad key, so that a
// difference between regular keys (e.g. "=") and the number pad version
// (e.g. the "=" on the number pad) can be determined.
Expand Down Expand Up @@ -124,6 +138,7 @@ class RawKeyEventDataLinux extends RawKeyEventData {
properties.add(DiagnosticsProperty<int>('keyCode', keyCode));
properties.add(DiagnosticsProperty<int>('modifiers', modifiers));
properties.add(DiagnosticsProperty<bool>('isDown', isDown));
properties.add(DiagnosticsProperty<int?>('specifiedLogicalKey', specifiedLogicalKey, defaultValue: null));
}

@override
Expand Down
1 change: 1 addition & 0 deletions packages/flutter/lib/src/services/raw_keyboard_macos.dart
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class RawKeyEventDataMacOs extends RawKeyEventData {
properties.add(DiagnosticsProperty<String>('charactersIgnoringModifiers', charactersIgnoringModifiers));
properties.add(DiagnosticsProperty<int>('keyCode', keyCode));
properties.add(DiagnosticsProperty<int>('modifiers', modifiers));
properties.add(DiagnosticsProperty<int?>('specifiedLogicalKey', specifiedLogicalKey, defaultValue: null));
}

@override
Expand Down
15 changes: 15 additions & 0 deletions packages/flutter/test/services/raw_keyboard_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2421,6 +2421,21 @@ void main() {
expect(data.keyLabel, isEmpty);
});

test('Prioritize logical key from specifiedLogicalKey', () {
final RawKeyEvent digit1FromFrench = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': 0x6c6,
'scanCode': 0x26,
'unicodeScalarValues': 0x424,
'specifiedLogicalKey': 0x61,
});
final RawKeyEventDataLinux data = digit1FromFrench.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyA));
});

test('data.toString', () {
expect(RawKeyEvent.fromMessage(const <String, Object?>{
'type': 'keydown',
Expand Down

0 comments on commit 65ea767

Please sign in to comment.