Adds the keyboard mapping for Linux (#29993)
This commit is contained in:
parent
a1712dcfbe
commit
b70d260b3c
@ -97,6 +97,11 @@ class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
|
||||
dataText.add(Text('characters: ${data.characters}'));
|
||||
dataText.add(Text('charactersIgnoringModifiers: ${data.charactersIgnoringModifiers}'));
|
||||
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
|
||||
} else if (data is RawKeyEventDataLinux) {
|
||||
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
|
||||
dataText.add(Text('scanCode: ${data.scanCode}'));
|
||||
dataText.add(Text('codePoint: ${data.codePoint}'));
|
||||
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
|
||||
}
|
||||
dataText.add(Text('logical: ${_event.logicalKey}'));
|
||||
dataText.add(Text('physical: ${_event.physicalKey}'));
|
||||
|
@ -38,6 +38,11 @@ Future<String> getAndroidScanCodes() async {
|
||||
return utf8.decode(base64.decode(await http.read(scanCodesUri)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Future<void> main(List<String> rawArguments) async {
|
||||
final ArgParser argParser = ArgParser();
|
||||
argParser.addOption(
|
||||
@ -66,6 +71,19 @@ Future<void> main(List<String> rawArguments) async {
|
||||
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.',
|
||||
);
|
||||
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.',
|
||||
);
|
||||
argParser.addOption(
|
||||
'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.',
|
||||
);
|
||||
|
||||
argParser.addOption(
|
||||
'data',
|
||||
defaultsTo: path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'key_data.json'),
|
||||
@ -136,8 +154,17 @@ Future<void> main(List<String> rawArguments) async {
|
||||
androidScanCodes = File(parsedArguments['android-scancodes']).readAsStringSync();
|
||||
}
|
||||
|
||||
String glfwKeyCodes;
|
||||
if (parsedArguments['glfw-keycodes'] == null) {
|
||||
glfwKeyCodes = await getGlfwKeyCodes();
|
||||
} else {
|
||||
glfwKeyCodes = File(parsedArguments['glfw-keycodes']).readAsStringSync();
|
||||
}
|
||||
|
||||
final String glfwToDomKey = File(parsedArguments['glfw-domkey']).readAsStringSync();
|
||||
final String androidToDomKey = File(parsedArguments['android-domkey']).readAsStringSync();
|
||||
data = KeyData(hidCodes, androidScanCodes, androidKeyCodes, androidToDomKey);
|
||||
|
||||
data = KeyData(hidCodes, androidScanCodes, androidKeyCodes, androidToDomKey, glfwKeyCodes, glfwToDomKey);
|
||||
|
||||
const JsonEncoder encoder = JsonEncoder.withIndent(' ');
|
||||
File(parsedArguments['data']).writeAsStringSync(encoder.convert(data.toJson()));
|
||||
|
File diff suppressed because it is too large
Load Diff
116
dev/tools/gen_keycodes/data/key_name_to_glfw_name.json
Normal file
116
dev/tools/gen_keycodes/data/key_name_to_glfw_name.json
Normal file
@ -0,0 +1,116 @@
|
||||
{
|
||||
"altLeft": ["LEFT_ALT"],
|
||||
"altRight": ["RIGHT_ALT"],
|
||||
"arrowDown": ["DOWN"],
|
||||
"arrowLeft": ["LEFT"],
|
||||
"arrowRight": ["RIGHT"],
|
||||
"arrowUp": ["UP"],
|
||||
"backquote": ["GRAVE_ACCENT"],
|
||||
"backslash": ["BACKSLASH"],
|
||||
"backspace": ["BACKSPACE"],
|
||||
"bracketLeft": ["LEFT_BRACKET"],
|
||||
"bracketRight": ["RIGHT_BRACKET"],
|
||||
"capsLock": ["CAPS_LOCK"],
|
||||
"controlLeft": ["LEFT_CONTROL"],
|
||||
"controlRight": ["RIGHT_CONTROL"],
|
||||
"contextMenu": ["MENU"],
|
||||
"comma": ["COMMA"],
|
||||
"delete": ["DELETE"],
|
||||
"digit0": ["0"],
|
||||
"digit1": ["1"],
|
||||
"digit2": ["2"],
|
||||
"digit3": ["3"],
|
||||
"digit4": ["4"],
|
||||
"digit5": ["5"],
|
||||
"digit6": ["6"],
|
||||
"digit7": ["7"],
|
||||
"digit8": ["8"],
|
||||
"digit9": ["9"],
|
||||
"end": ["END"],
|
||||
"enter": ["ENTER"],
|
||||
"equal": ["EQUAL"],
|
||||
"escape": ["ESCAPE"],
|
||||
"f1": ["F1"],
|
||||
"f2": ["F2"],
|
||||
"f3": ["F3"],
|
||||
"f4": ["F4"],
|
||||
"f5": ["F5"],
|
||||
"f6": ["F6"],
|
||||
"f7": ["F7"],
|
||||
"f8": ["F8"],
|
||||
"f9": ["F9"],
|
||||
"f10": ["F10"],
|
||||
"f11": ["F11"],
|
||||
"f12": ["F12"],
|
||||
"f13": ["F13"],
|
||||
"f14": ["F14"],
|
||||
"f15": ["F15"],
|
||||
"f16": ["F16"],
|
||||
"f17": ["F17"],
|
||||
"f18": ["F18"],
|
||||
"f19": ["F19"],
|
||||
"f20": ["F20"],
|
||||
"f21": ["F21"],
|
||||
"f22": ["F22"],
|
||||
"f23": ["F23"],
|
||||
"f25": ["F25"],
|
||||
"home": ["HOME"],
|
||||
"insert": ["INSERT"],
|
||||
"keyA": ["A"],
|
||||
"keyB": ["B"],
|
||||
"keyC": ["C"],
|
||||
"keyD": ["D"],
|
||||
"keyE": ["E"],
|
||||
"keyF": ["F"],
|
||||
"keyG": ["G"],
|
||||
"keyH": ["H"],
|
||||
"keyI": ["I"],
|
||||
"keyJ": ["J"],
|
||||
"keyK": ["K"],
|
||||
"keyL": ["L"],
|
||||
"keyM": ["M"],
|
||||
"keyN": ["N"],
|
||||
"keyO": ["O"],
|
||||
"keyP": ["P"],
|
||||
"keyQ": ["Q"],
|
||||
"keyR": ["R"],
|
||||
"keyS": ["S"],
|
||||
"keyT": ["T"],
|
||||
"keyU": ["U"],
|
||||
"keyV": ["V"],
|
||||
"keyW": ["W"],
|
||||
"keyX": ["X"],
|
||||
"keyY": ["Y"],
|
||||
"keyZ": ["Z"],
|
||||
"minus": ["MINUS"],
|
||||
"numLock": ["NUM_LOCK"],
|
||||
"numpad0": ["KP_0"],
|
||||
"numpad1": ["KP_1"],
|
||||
"numpad2": ["KP_2"],
|
||||
"numpad3": ["KP_3"],
|
||||
"numpad4": ["KP_4"],
|
||||
"numpad5": ["KP_5"],
|
||||
"numpad6": ["KP_6"],
|
||||
"numpad7": ["KP_7"],
|
||||
"numpad8": ["KP_8"],
|
||||
"numpad9": ["KP_9"],
|
||||
"numpadDecimal": ["KP_DECIMAL"],
|
||||
"numpadDivide": ["KP_DIVIDE"],
|
||||
"numpadMultiply": ["KP_MULTIPLY"],
|
||||
"numpadSubtract": ["NUMPAD_SUBTRACT"],
|
||||
"numpadAdd": ["KP_ADD"],
|
||||
"numpadEnter": ["KP_ENTER"],
|
||||
"numpadEqual": ["KP_EQUAL"],
|
||||
"pageDown": ["PAGE_DOWN"],
|
||||
"pageUp": ["PAGE_UP"],
|
||||
"pause": ["PAUSE"],
|
||||
"printScreen": ["PRINT_SCREEN"],
|
||||
"quote": ["APOSTROPHE"],
|
||||
"period": ["PERIOD"],
|
||||
"semicolon": ["SEMICOLON"],
|
||||
"slash": ["SLASH"],
|
||||
"space": ["SPACE"],
|
||||
"shiftLeft": ["LEFT_SHIFT"],
|
||||
"shiftRight": ["RIGHT_SHIFT"],
|
||||
"tab": ["TAB"]
|
||||
}
|
@ -50,3 +50,21 @@ const Map<int, PhysicalKeyboardKey> kMacOsToPhysicalKey = <int, PhysicalKeyboard
|
||||
const Map<int, LogicalKeyboardKey> kMacOsNumPadMap = <int, LogicalKeyboardKey>{
|
||||
@@@MACOS_NUMPAD_MAP@@@
|
||||
};
|
||||
|
||||
/// Maps GLFW-specific key codes to the matching [LogicalKeyboardKey].
|
||||
const Map<int, LogicalKeyboardKey> kGlfwToLogicalKey = <int, LogicalKeyboardKey>{
|
||||
@@@GLFW_KEY_CODE_MAP@@@
|
||||
};
|
||||
|
||||
/// A map of GLFW key codes which have printable representations, but appear
|
||||
/// on the number pad. Used to provide different key objects for keys like
|
||||
/// KEY_EQUALS and NUMPAD_EQUALS.
|
||||
const Map<int, LogicalKeyboardKey> kGlfwNumpadMap = <int, LogicalKeyboardKey>{
|
||||
@@@GLFW_NUMPAD_MAP@@@
|
||||
};
|
||||
|
||||
/// Maps XKB specific key code values representing [PhysicalKeyboardKey].
|
||||
const Map<int, PhysicalKeyboardKey> kLinuxToPhysicalKey = <int, PhysicalKeyboardKey>{
|
||||
@@@XKB_SCAN_CODE_MAP@@@
|
||||
};
|
||||
|
||||
|
@ -93,6 +93,46 @@ $otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalK
|
||||
return keyCodeMap.toString().trimRight();
|
||||
}
|
||||
|
||||
/// This generates the map of GLFW number pad key codes to logical keys.
|
||||
String get glfwNumpadMap {
|
||||
final StringBuffer glfwNumpadMap = StringBuffer();
|
||||
final List<Key> onlyNumpads = keyData.data.where((Key entry) {
|
||||
return entry.constantName.startsWith('numpad') && entry.keyLabel != null;
|
||||
}).toList();
|
||||
for (Key entry in onlyNumpads) {
|
||||
if (entry.glfwKeyCodes != null) {
|
||||
for (int code in entry.glfwKeyCodes.cast<int>()) {
|
||||
glfwNumpadMap.writeln(' $code: LogicalKeyboardKey.${entry.constantName},');
|
||||
}
|
||||
}
|
||||
}
|
||||
return glfwNumpadMap.toString().trimRight();
|
||||
}
|
||||
|
||||
/// This generates the map of GLFW key codes to logical keys.
|
||||
String get glfwKeyCodeMap {
|
||||
final StringBuffer glfwKeyCodeMap = StringBuffer();
|
||||
for (Key entry in keyData.data) {
|
||||
if (entry.glfwKeyCodes != null) {
|
||||
for (int code in entry.glfwKeyCodes.cast<int>()) {
|
||||
glfwKeyCodeMap.writeln(' $code: LogicalKeyboardKey.${entry.constantName},');
|
||||
}
|
||||
}
|
||||
}
|
||||
return glfwKeyCodeMap.toString().trimRight();
|
||||
}
|
||||
|
||||
/// This generates the map of XKB USB HID codes to physical keys.
|
||||
String get xkbScanCodeMap {
|
||||
final StringBuffer xkbScanCodeMap = StringBuffer();
|
||||
for (Key entry in keyData.data) {
|
||||
if (entry.xKbScanCode != null) {
|
||||
xkbScanCodeMap.writeln(' ${toHex(entry.xKbScanCode)}: PhysicalKeyboardKey.${entry.constantName},');
|
||||
}
|
||||
}
|
||||
return xkbScanCodeMap.toString().trimRight();
|
||||
}
|
||||
|
||||
/// This generates the map of Android key codes to logical keys.
|
||||
String get androidKeyCodeMap {
|
||||
final StringBuffer androidKeyCodeMap = StringBuffer();
|
||||
@ -210,6 +250,9 @@ $otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalK
|
||||
'FUCHSIA_KEY_CODE_MAP': fuchsiaKeyCodeMap,
|
||||
'MACOS_SCAN_CODE_MAP': macOsScanCodeMap,
|
||||
'MACOS_NUMPAD_MAP': macOsNumpadMap,
|
||||
'GLFW_KEY_CODE_MAP': glfwKeyCodeMap,
|
||||
'GLFW_NUMPAD_MAP': glfwNumpadMap,
|
||||
'XKB_SCAN_CODE_MAP': xkbScanCodeMap,
|
||||
};
|
||||
|
||||
final String template = File(path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'keyboard_maps.tmpl')).readAsStringSync();
|
||||
|
@ -26,14 +26,25 @@ class KeyData {
|
||||
String androidKeyboardLayout,
|
||||
String androidKeyCodeHeader,
|
||||
String androidNameMap,
|
||||
String glfwKeyCodeHeader,
|
||||
String glfwNameMap
|
||||
) : assert(chromiumHidCodes != null),
|
||||
assert(androidKeyboardLayout != null),
|
||||
assert(androidKeyCodeHeader != null),
|
||||
assert(androidNameMap != null) {
|
||||
assert(androidNameMap != null),
|
||||
assert(glfwKeyCodeHeader != null),
|
||||
assert(glfwNameMap != null) {
|
||||
_nameToAndroidScanCodes = _readAndroidScanCodes(androidKeyboardLayout);
|
||||
_nameToAndroidKeyCode = _readAndroidKeyCodes(androidKeyCodeHeader);
|
||||
final Map<String, List<dynamic>> dynamicNames = json.decode(androidNameMap).cast<String, List<dynamic>>();
|
||||
_nameToAndroidName = dynamicNames.map<String, List<String>>((String key, List<dynamic> value) {
|
||||
_nameToGlfwKeyCode = _readGlfwKeyCodes(glfwKeyCodeHeader);
|
||||
// Cast Android dom map
|
||||
final Map<String, List<dynamic>> dynamicAndroidNames = json.decode(androidNameMap).cast<String, List<dynamic>>();
|
||||
_nameToAndroidName = dynamicAndroidNames.map<String, List<String>>((String key, List<dynamic> value) {
|
||||
return MapEntry<String, List<String>>(key, value.cast<String>());
|
||||
});
|
||||
// Cast GLFW dom map
|
||||
final Map<String, List<dynamic>> dynamicGlfwNames = json.decode(glfwNameMap).cast<String, List<dynamic>>();
|
||||
_nameToGlfwName = dynamicGlfwNames.map<String, List<String>>((String key, List<dynamic> value) {
|
||||
return MapEntry<String, List<String>>(key, value.cast<String>());
|
||||
});
|
||||
data = _readHidEntries(chromiumHidCodes);
|
||||
@ -51,6 +62,7 @@ class KeyData {
|
||||
/// [KeyData.fromJson].
|
||||
Map<String, dynamic> toJson() {
|
||||
for (Key entry in data) {
|
||||
// Android Key names
|
||||
entry.androidKeyNames = _nameToAndroidName[entry.constantName]?.cast<String>();
|
||||
if (entry.androidKeyNames != null && entry.androidKeyNames.isNotEmpty) {
|
||||
for (String androidKeyName in entry.androidKeyNames) {
|
||||
@ -64,6 +76,17 @@ class KeyData {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GLFW key names
|
||||
entry.glfwKeyNames = _nameToGlfwName[entry.constantName]?.cast<String>();
|
||||
if (entry.glfwKeyNames != null && entry.glfwKeyNames.isNotEmpty) {
|
||||
for (String glfwKeyName in entry.glfwKeyNames) {
|
||||
if (_nameToGlfwKeyCode[glfwKeyName] != null) {
|
||||
entry.glfwKeyCodes ??= <int>[];
|
||||
entry.glfwKeyCodes.add(_nameToGlfwKeyCode[glfwKeyName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, dynamic> outputMap = <String, dynamic>{};
|
||||
@ -83,6 +106,13 @@ class KeyData {
|
||||
/// JSON.
|
||||
Map<String, List<String>> _nameToAndroidName;
|
||||
|
||||
/// The mapping from the Flutter name (e.g. "eject") to the GLFW name (e.g.
|
||||
/// "GLFW_MEDIA_EJECT").
|
||||
///
|
||||
/// Only populated if data is parsed from the source files, not if parsed from
|
||||
/// JSON.
|
||||
Map<String, List<String>> _nameToGlfwName;
|
||||
|
||||
/// The mapping from the Android name (e.g. "MEDIA_EJECT") to the integer scan
|
||||
/// code (physical location) of the key.
|
||||
///
|
||||
@ -97,6 +127,13 @@ class KeyData {
|
||||
/// JSON.
|
||||
Map<String, int> _nameToAndroidKeyCode;
|
||||
|
||||
/// The mapping from GLFW name (e.g. "GLFW_KEY_COMMA") to the integer key code
|
||||
/// (logical meaning) of the key.
|
||||
///
|
||||
/// Only populated if data is parsed from the source files, not if parsed from
|
||||
/// JSON.
|
||||
Map<String, int> _nameToGlfwKeyCode;
|
||||
|
||||
/// Parses entries from Androids Generic.kl scan code data file.
|
||||
///
|
||||
/// Lines in this file look like this (without the ///):
|
||||
@ -147,6 +184,30 @@ class KeyData {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Parses entries from GLFW's keycodes.h key code data file.
|
||||
///
|
||||
/// Lines in this file look like this (without the ///):
|
||||
/// /** Space key. */
|
||||
/// #define GLFW_KEY_SPACE 32,
|
||||
Map<String, int> _readGlfwKeyCodes(String headerFile) {
|
||||
// Only get the KEY definitions, ignore the rest (mouse, joystick, etc).
|
||||
final RegExp enumEntry = RegExp(r'''define GLFW_KEY_([A-Z0-9_]+)\s*([A-Z0-9_]+),?''');
|
||||
final Map<String, dynamic> replaced = <String, dynamic>{};
|
||||
headerFile.replaceAllMapped(enumEntry, (Match match) {
|
||||
replaced[match.group(1)] = int.tryParse(match.group(2)) ?? match.group(2).replaceAll('GLFW_KEY_', '');
|
||||
});
|
||||
final Map<String, int> result = <String, int>{};
|
||||
replaced.forEach((String key, dynamic value) {
|
||||
// Some definition values point to other definitions (e.g #define GLFW_KEY_LAST GLFW_KEY_MENU).
|
||||
if (value is String) {
|
||||
result[key] = replaced[value];
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Parses entries from Chromium's HID code mapping header file.
|
||||
///
|
||||
/// Lines in this file look like this (without the ///):
|
||||
@ -211,6 +272,8 @@ class Key {
|
||||
this.androidKeyNames,
|
||||
this.androidScanCodes,
|
||||
this.androidKeyCodes,
|
||||
this.glfwKeyNames,
|
||||
this.glfwKeyCodes,
|
||||
}) : assert(usbHidCode != null),
|
||||
assert(chromiumName != null),
|
||||
_constantName = enumName;
|
||||
@ -261,6 +324,15 @@ class Key {
|
||||
/// code value.
|
||||
List<int> androidScanCodes;
|
||||
|
||||
/// The list of names that GFLW gives to this key (symbol names minus the
|
||||
/// prefix).
|
||||
List<String> glfwKeyNames;
|
||||
|
||||
/// The list of GLFW key codes matching this key, created by looking up the
|
||||
/// Linux name in the Chromium data, and substituting the GLFW key code
|
||||
/// value.
|
||||
List<int> glfwKeyCodes;
|
||||
|
||||
/// Creates a JSON map from the key data.
|
||||
Map<String, dynamic> toJson() {
|
||||
return <String, dynamic>{
|
||||
@ -269,6 +341,7 @@ class Key {
|
||||
'android': androidKeyNames,
|
||||
'english': commentName,
|
||||
'chromium': chromiumName,
|
||||
'glfw': glfwKeyNames,
|
||||
},
|
||||
'scanCodes': <String, dynamic>{
|
||||
'android': androidScanCodes,
|
||||
@ -280,6 +353,7 @@ class Key {
|
||||
},
|
||||
'keyCodes': <String, List<int>>{
|
||||
'android': androidKeyCodes,
|
||||
'glfw': glfwKeyCodes,
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -337,8 +411,8 @@ class Key {
|
||||
@override
|
||||
String toString() {
|
||||
return """'$constantName': (name: "$name", usbHidCode: ${toHex(usbHidCode)}, """
|
||||
'''linuxKeyCode: ${toHex(linuxScanCode)}, xKbKeyCode: ${toHex(xKbScanCode)}, '''
|
||||
'''windowsKeyCode: ${toHex(windowsScanCode)}, macOsKeyCode: ${toHex(macOsScanCode)}, '''
|
||||
'''linuxScanCode: ${toHex(linuxScanCode)}, xKbScanCode: ${toHex(xKbScanCode)}, '''
|
||||
'''windowsKeyCode: ${toHex(windowsScanCode)}, macOsScanCode: ${toHex(macOsScanCode)}, '''
|
||||
'''chromiumSymbolName: $chromiumName''';
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ export 'src/services/platform_views.dart';
|
||||
export 'src/services/raw_keyboard.dart';
|
||||
export 'src/services/raw_keyboard_android.dart';
|
||||
export 'src/services/raw_keyboard_fuchsia.dart';
|
||||
export 'src/services/raw_keyboard_linux.dart';
|
||||
export 'src/services/raw_keyboard_macos.dart';
|
||||
export 'src/services/system_channels.dart';
|
||||
export 'src/services/system_chrome.dart';
|
||||
|
@ -843,9 +843,7 @@ const Map<int, PhysicalKeyboardKey> kFuchsiaToPhysicalKey = <int, PhysicalKeyboa
|
||||
0x000c028c: PhysicalKeyboardKey.mailSend,
|
||||
};
|
||||
|
||||
|
||||
/// Maps macOS-specific key code values representing [PhysicalKeyboardKey].
|
||||
/// MacOS doesn't provide a scan code, but a virtual keycode to represent a physical key.
|
||||
const Map<int, PhysicalKeyboardKey> kMacOsToPhysicalKey = <int, PhysicalKeyboardKey>{
|
||||
0x00000000: PhysicalKeyboardKey.keyA,
|
||||
0x0000000b: PhysicalKeyboardKey.keyB,
|
||||
@ -989,3 +987,353 @@ const Map<int, LogicalKeyboardKey> kMacOsNumPadMap = <int, LogicalKeyboardKey>{
|
||||
0x00000051: LogicalKeyboardKey.numpadEqual,
|
||||
0x0000005f: LogicalKeyboardKey.numpadComma,
|
||||
};
|
||||
|
||||
/// Maps GLFW-specific key codes to the matching [LogicalKeyboardKey].
|
||||
const Map<int, LogicalKeyboardKey> kGlfwToLogicalKey = <int, LogicalKeyboardKey>{
|
||||
65: LogicalKeyboardKey.keyA,
|
||||
66: LogicalKeyboardKey.keyB,
|
||||
67: LogicalKeyboardKey.keyC,
|
||||
68: LogicalKeyboardKey.keyD,
|
||||
69: LogicalKeyboardKey.keyE,
|
||||
70: LogicalKeyboardKey.keyF,
|
||||
71: LogicalKeyboardKey.keyG,
|
||||
72: LogicalKeyboardKey.keyH,
|
||||
73: LogicalKeyboardKey.keyI,
|
||||
74: LogicalKeyboardKey.keyJ,
|
||||
75: LogicalKeyboardKey.keyK,
|
||||
76: LogicalKeyboardKey.keyL,
|
||||
77: LogicalKeyboardKey.keyM,
|
||||
78: LogicalKeyboardKey.keyN,
|
||||
79: LogicalKeyboardKey.keyO,
|
||||
80: LogicalKeyboardKey.keyP,
|
||||
81: LogicalKeyboardKey.keyQ,
|
||||
82: LogicalKeyboardKey.keyR,
|
||||
83: LogicalKeyboardKey.keyS,
|
||||
84: LogicalKeyboardKey.keyT,
|
||||
85: LogicalKeyboardKey.keyU,
|
||||
86: LogicalKeyboardKey.keyV,
|
||||
87: LogicalKeyboardKey.keyW,
|
||||
88: LogicalKeyboardKey.keyX,
|
||||
89: LogicalKeyboardKey.keyY,
|
||||
90: LogicalKeyboardKey.keyZ,
|
||||
49: LogicalKeyboardKey.digit1,
|
||||
50: LogicalKeyboardKey.digit2,
|
||||
51: LogicalKeyboardKey.digit3,
|
||||
52: LogicalKeyboardKey.digit4,
|
||||
53: LogicalKeyboardKey.digit5,
|
||||
54: LogicalKeyboardKey.digit6,
|
||||
55: LogicalKeyboardKey.digit7,
|
||||
56: LogicalKeyboardKey.digit8,
|
||||
57: LogicalKeyboardKey.digit9,
|
||||
48: LogicalKeyboardKey.digit0,
|
||||
257: LogicalKeyboardKey.enter,
|
||||
256: LogicalKeyboardKey.escape,
|
||||
259: LogicalKeyboardKey.backspace,
|
||||
258: LogicalKeyboardKey.tab,
|
||||
32: LogicalKeyboardKey.space,
|
||||
45: LogicalKeyboardKey.minus,
|
||||
61: LogicalKeyboardKey.equal,
|
||||
91: LogicalKeyboardKey.bracketLeft,
|
||||
93: LogicalKeyboardKey.bracketRight,
|
||||
92: LogicalKeyboardKey.backslash,
|
||||
59: LogicalKeyboardKey.semicolon,
|
||||
39: LogicalKeyboardKey.quote,
|
||||
96: LogicalKeyboardKey.backquote,
|
||||
44: LogicalKeyboardKey.comma,
|
||||
46: LogicalKeyboardKey.period,
|
||||
47: LogicalKeyboardKey.slash,
|
||||
280: LogicalKeyboardKey.capsLock,
|
||||
290: LogicalKeyboardKey.f1,
|
||||
291: LogicalKeyboardKey.f2,
|
||||
292: LogicalKeyboardKey.f3,
|
||||
293: LogicalKeyboardKey.f4,
|
||||
294: LogicalKeyboardKey.f5,
|
||||
295: LogicalKeyboardKey.f6,
|
||||
296: LogicalKeyboardKey.f7,
|
||||
297: LogicalKeyboardKey.f8,
|
||||
298: LogicalKeyboardKey.f9,
|
||||
299: LogicalKeyboardKey.f10,
|
||||
300: LogicalKeyboardKey.f11,
|
||||
301: LogicalKeyboardKey.f12,
|
||||
283: LogicalKeyboardKey.printScreen,
|
||||
284: LogicalKeyboardKey.pause,
|
||||
260: LogicalKeyboardKey.insert,
|
||||
268: LogicalKeyboardKey.home,
|
||||
266: LogicalKeyboardKey.pageUp,
|
||||
261: LogicalKeyboardKey.delete,
|
||||
269: LogicalKeyboardKey.end,
|
||||
267: LogicalKeyboardKey.pageDown,
|
||||
262: LogicalKeyboardKey.arrowRight,
|
||||
263: LogicalKeyboardKey.arrowLeft,
|
||||
264: LogicalKeyboardKey.arrowDown,
|
||||
265: LogicalKeyboardKey.arrowUp,
|
||||
282: LogicalKeyboardKey.numLock,
|
||||
331: LogicalKeyboardKey.numpadDivide,
|
||||
332: LogicalKeyboardKey.numpadMultiply,
|
||||
334: LogicalKeyboardKey.numpadAdd,
|
||||
335: LogicalKeyboardKey.numpadEnter,
|
||||
321: LogicalKeyboardKey.numpad1,
|
||||
322: LogicalKeyboardKey.numpad2,
|
||||
323: LogicalKeyboardKey.numpad3,
|
||||
324: LogicalKeyboardKey.numpad4,
|
||||
325: LogicalKeyboardKey.numpad5,
|
||||
326: LogicalKeyboardKey.numpad6,
|
||||
327: LogicalKeyboardKey.numpad7,
|
||||
328: LogicalKeyboardKey.numpad8,
|
||||
329: LogicalKeyboardKey.numpad9,
|
||||
320: LogicalKeyboardKey.numpad0,
|
||||
330: LogicalKeyboardKey.numpadDecimal,
|
||||
348: LogicalKeyboardKey.contextMenu,
|
||||
336: LogicalKeyboardKey.numpadEqual,
|
||||
302: LogicalKeyboardKey.f13,
|
||||
303: LogicalKeyboardKey.f14,
|
||||
304: LogicalKeyboardKey.f15,
|
||||
305: LogicalKeyboardKey.f16,
|
||||
306: LogicalKeyboardKey.f17,
|
||||
307: LogicalKeyboardKey.f18,
|
||||
308: LogicalKeyboardKey.f19,
|
||||
309: LogicalKeyboardKey.f20,
|
||||
310: LogicalKeyboardKey.f21,
|
||||
311: LogicalKeyboardKey.f22,
|
||||
312: LogicalKeyboardKey.f23,
|
||||
341: LogicalKeyboardKey.controlLeft,
|
||||
340: LogicalKeyboardKey.shiftLeft,
|
||||
342: LogicalKeyboardKey.altLeft,
|
||||
345: LogicalKeyboardKey.controlRight,
|
||||
344: LogicalKeyboardKey.shiftRight,
|
||||
346: LogicalKeyboardKey.altRight,
|
||||
};
|
||||
|
||||
/// A map of GLFW key codes which have printable representations, but appear
|
||||
/// on the number pad. Used to provide different key objects for keys like
|
||||
/// KEY_EQUALS and NUMPAD_EQUALS.
|
||||
const Map<int, LogicalKeyboardKey> kGlfwNumpadMap = <int, LogicalKeyboardKey>{
|
||||
331: LogicalKeyboardKey.numpadDivide,
|
||||
332: LogicalKeyboardKey.numpadMultiply,
|
||||
334: LogicalKeyboardKey.numpadAdd,
|
||||
321: LogicalKeyboardKey.numpad1,
|
||||
322: LogicalKeyboardKey.numpad2,
|
||||
323: LogicalKeyboardKey.numpad3,
|
||||
324: LogicalKeyboardKey.numpad4,
|
||||
325: LogicalKeyboardKey.numpad5,
|
||||
326: LogicalKeyboardKey.numpad6,
|
||||
327: LogicalKeyboardKey.numpad7,
|
||||
328: LogicalKeyboardKey.numpad8,
|
||||
329: LogicalKeyboardKey.numpad9,
|
||||
320: LogicalKeyboardKey.numpad0,
|
||||
330: LogicalKeyboardKey.numpadDecimal,
|
||||
336: LogicalKeyboardKey.numpadEqual,
|
||||
};
|
||||
|
||||
/// Maps XKB specific key code values representing [PhysicalKeyboardKey].
|
||||
const Map<int, PhysicalKeyboardKey> kLinuxToPhysicalKey = <int, PhysicalKeyboardKey>{
|
||||
0x0000024f: PhysicalKeyboardKey.launchAssistant,
|
||||
0x00000096: PhysicalKeyboardKey.sleep,
|
||||
0x00000097: PhysicalKeyboardKey.wakeUp,
|
||||
0x00000026: PhysicalKeyboardKey.keyA,
|
||||
0x00000038: PhysicalKeyboardKey.keyB,
|
||||
0x00000036: PhysicalKeyboardKey.keyC,
|
||||
0x00000028: PhysicalKeyboardKey.keyD,
|
||||
0x0000001a: PhysicalKeyboardKey.keyE,
|
||||
0x00000029: PhysicalKeyboardKey.keyF,
|
||||
0x0000002a: PhysicalKeyboardKey.keyG,
|
||||
0x0000002b: PhysicalKeyboardKey.keyH,
|
||||
0x0000001f: PhysicalKeyboardKey.keyI,
|
||||
0x0000002c: PhysicalKeyboardKey.keyJ,
|
||||
0x0000002d: PhysicalKeyboardKey.keyK,
|
||||
0x0000002e: PhysicalKeyboardKey.keyL,
|
||||
0x0000003a: PhysicalKeyboardKey.keyM,
|
||||
0x00000039: PhysicalKeyboardKey.keyN,
|
||||
0x00000020: PhysicalKeyboardKey.keyO,
|
||||
0x00000021: PhysicalKeyboardKey.keyP,
|
||||
0x00000018: PhysicalKeyboardKey.keyQ,
|
||||
0x0000001b: PhysicalKeyboardKey.keyR,
|
||||
0x00000027: PhysicalKeyboardKey.keyS,
|
||||
0x0000001c: PhysicalKeyboardKey.keyT,
|
||||
0x0000001e: PhysicalKeyboardKey.keyU,
|
||||
0x00000037: PhysicalKeyboardKey.keyV,
|
||||
0x00000019: PhysicalKeyboardKey.keyW,
|
||||
0x00000035: PhysicalKeyboardKey.keyX,
|
||||
0x0000001d: PhysicalKeyboardKey.keyY,
|
||||
0x00000034: PhysicalKeyboardKey.keyZ,
|
||||
0x0000000a: PhysicalKeyboardKey.digit1,
|
||||
0x0000000b: PhysicalKeyboardKey.digit2,
|
||||
0x0000000c: PhysicalKeyboardKey.digit3,
|
||||
0x0000000d: PhysicalKeyboardKey.digit4,
|
||||
0x0000000e: PhysicalKeyboardKey.digit5,
|
||||
0x0000000f: PhysicalKeyboardKey.digit6,
|
||||
0x00000010: PhysicalKeyboardKey.digit7,
|
||||
0x00000011: PhysicalKeyboardKey.digit8,
|
||||
0x00000012: PhysicalKeyboardKey.digit9,
|
||||
0x00000013: PhysicalKeyboardKey.digit0,
|
||||
0x00000024: PhysicalKeyboardKey.enter,
|
||||
0x00000009: PhysicalKeyboardKey.escape,
|
||||
0x00000016: PhysicalKeyboardKey.backspace,
|
||||
0x00000017: PhysicalKeyboardKey.tab,
|
||||
0x00000041: PhysicalKeyboardKey.space,
|
||||
0x00000014: PhysicalKeyboardKey.minus,
|
||||
0x00000015: PhysicalKeyboardKey.equal,
|
||||
0x00000022: PhysicalKeyboardKey.bracketLeft,
|
||||
0x00000023: PhysicalKeyboardKey.bracketRight,
|
||||
0x00000033: PhysicalKeyboardKey.backslash,
|
||||
0x0000002f: PhysicalKeyboardKey.semicolon,
|
||||
0x00000030: PhysicalKeyboardKey.quote,
|
||||
0x00000031: PhysicalKeyboardKey.backquote,
|
||||
0x0000003b: PhysicalKeyboardKey.comma,
|
||||
0x0000003c: PhysicalKeyboardKey.period,
|
||||
0x0000003d: PhysicalKeyboardKey.slash,
|
||||
0x00000042: PhysicalKeyboardKey.capsLock,
|
||||
0x00000043: PhysicalKeyboardKey.f1,
|
||||
0x00000044: PhysicalKeyboardKey.f2,
|
||||
0x00000045: PhysicalKeyboardKey.f3,
|
||||
0x00000046: PhysicalKeyboardKey.f4,
|
||||
0x00000047: PhysicalKeyboardKey.f5,
|
||||
0x00000048: PhysicalKeyboardKey.f6,
|
||||
0x00000049: PhysicalKeyboardKey.f7,
|
||||
0x0000004a: PhysicalKeyboardKey.f8,
|
||||
0x0000004b: PhysicalKeyboardKey.f9,
|
||||
0x0000004c: PhysicalKeyboardKey.f10,
|
||||
0x0000005f: PhysicalKeyboardKey.f11,
|
||||
0x00000060: PhysicalKeyboardKey.f12,
|
||||
0x0000006b: PhysicalKeyboardKey.printScreen,
|
||||
0x0000004e: PhysicalKeyboardKey.scrollLock,
|
||||
0x0000007f: PhysicalKeyboardKey.pause,
|
||||
0x00000076: PhysicalKeyboardKey.insert,
|
||||
0x0000006e: PhysicalKeyboardKey.home,
|
||||
0x00000070: PhysicalKeyboardKey.pageUp,
|
||||
0x00000077: PhysicalKeyboardKey.delete,
|
||||
0x00000073: PhysicalKeyboardKey.end,
|
||||
0x00000075: PhysicalKeyboardKey.pageDown,
|
||||
0x00000072: PhysicalKeyboardKey.arrowRight,
|
||||
0x00000071: PhysicalKeyboardKey.arrowLeft,
|
||||
0x00000074: PhysicalKeyboardKey.arrowDown,
|
||||
0x0000006f: PhysicalKeyboardKey.arrowUp,
|
||||
0x0000004d: PhysicalKeyboardKey.numLock,
|
||||
0x0000006a: PhysicalKeyboardKey.numpadDivide,
|
||||
0x0000003f: PhysicalKeyboardKey.numpadMultiply,
|
||||
0x00000052: PhysicalKeyboardKey.numpadSubtract,
|
||||
0x00000056: PhysicalKeyboardKey.numpadAdd,
|
||||
0x00000068: PhysicalKeyboardKey.numpadEnter,
|
||||
0x00000057: PhysicalKeyboardKey.numpad1,
|
||||
0x00000058: PhysicalKeyboardKey.numpad2,
|
||||
0x00000059: PhysicalKeyboardKey.numpad3,
|
||||
0x00000053: PhysicalKeyboardKey.numpad4,
|
||||
0x00000054: PhysicalKeyboardKey.numpad5,
|
||||
0x00000055: PhysicalKeyboardKey.numpad6,
|
||||
0x0000004f: PhysicalKeyboardKey.numpad7,
|
||||
0x00000050: PhysicalKeyboardKey.numpad8,
|
||||
0x00000051: PhysicalKeyboardKey.numpad9,
|
||||
0x0000005a: PhysicalKeyboardKey.numpad0,
|
||||
0x0000005b: PhysicalKeyboardKey.numpadDecimal,
|
||||
0x0000005e: PhysicalKeyboardKey.intlBackslash,
|
||||
0x00000087: PhysicalKeyboardKey.contextMenu,
|
||||
0x0000007c: PhysicalKeyboardKey.power,
|
||||
0x0000007d: PhysicalKeyboardKey.numpadEqual,
|
||||
0x000000bf: PhysicalKeyboardKey.f13,
|
||||
0x000000c0: PhysicalKeyboardKey.f14,
|
||||
0x000000c1: PhysicalKeyboardKey.f15,
|
||||
0x000000c2: PhysicalKeyboardKey.f16,
|
||||
0x000000c3: PhysicalKeyboardKey.f17,
|
||||
0x000000c4: PhysicalKeyboardKey.f18,
|
||||
0x000000c5: PhysicalKeyboardKey.f19,
|
||||
0x000000c6: PhysicalKeyboardKey.f20,
|
||||
0x000000c7: PhysicalKeyboardKey.f21,
|
||||
0x000000c8: PhysicalKeyboardKey.f22,
|
||||
0x000000c9: PhysicalKeyboardKey.f23,
|
||||
0x000000ca: PhysicalKeyboardKey.f24,
|
||||
0x0000008e: PhysicalKeyboardKey.open,
|
||||
0x00000092: PhysicalKeyboardKey.help,
|
||||
0x0000008c: PhysicalKeyboardKey.select,
|
||||
0x00000089: PhysicalKeyboardKey.again,
|
||||
0x0000008b: PhysicalKeyboardKey.undo,
|
||||
0x00000091: PhysicalKeyboardKey.cut,
|
||||
0x0000008d: PhysicalKeyboardKey.copy,
|
||||
0x0000008f: PhysicalKeyboardKey.paste,
|
||||
0x00000090: PhysicalKeyboardKey.find,
|
||||
0x00000079: PhysicalKeyboardKey.audioVolumeMute,
|
||||
0x0000007b: PhysicalKeyboardKey.audioVolumeUp,
|
||||
0x0000007a: PhysicalKeyboardKey.audioVolumeDown,
|
||||
0x00000081: PhysicalKeyboardKey.numpadComma,
|
||||
0x00000061: PhysicalKeyboardKey.intlRo,
|
||||
0x00000065: PhysicalKeyboardKey.kanaMode,
|
||||
0x00000084: PhysicalKeyboardKey.intlYen,
|
||||
0x00000064: PhysicalKeyboardKey.convert,
|
||||
0x00000066: PhysicalKeyboardKey.nonConvert,
|
||||
0x00000082: PhysicalKeyboardKey.lang1,
|
||||
0x00000083: PhysicalKeyboardKey.lang2,
|
||||
0x00000062: PhysicalKeyboardKey.lang3,
|
||||
0x00000063: PhysicalKeyboardKey.lang4,
|
||||
0x0000005d: PhysicalKeyboardKey.lang5,
|
||||
0x000000bb: PhysicalKeyboardKey.numpadParenLeft,
|
||||
0x000000bc: PhysicalKeyboardKey.numpadParenRight,
|
||||
0x0000007e: PhysicalKeyboardKey.numpadSignChange,
|
||||
0x00000025: PhysicalKeyboardKey.controlLeft,
|
||||
0x00000032: PhysicalKeyboardKey.shiftLeft,
|
||||
0x00000040: PhysicalKeyboardKey.altLeft,
|
||||
0x00000085: PhysicalKeyboardKey.metaLeft,
|
||||
0x00000069: PhysicalKeyboardKey.controlRight,
|
||||
0x0000003e: PhysicalKeyboardKey.shiftRight,
|
||||
0x0000006c: PhysicalKeyboardKey.altRight,
|
||||
0x00000086: PhysicalKeyboardKey.metaRight,
|
||||
0x0000016e: PhysicalKeyboardKey.info,
|
||||
0x0000017a: PhysicalKeyboardKey.closedCaptionToggle,
|
||||
0x000000e9: PhysicalKeyboardKey.brightnessUp,
|
||||
0x000000e8: PhysicalKeyboardKey.brightnessDown,
|
||||
0x000001b7: PhysicalKeyboardKey.brightnessToggle,
|
||||
0x00000258: PhysicalKeyboardKey.brightnessMinimum,
|
||||
0x00000259: PhysicalKeyboardKey.brightnessMaximum,
|
||||
0x000000fc: PhysicalKeyboardKey.brightnessAuto,
|
||||
0x0000019d: PhysicalKeyboardKey.mediaLast,
|
||||
0x000000b1: PhysicalKeyboardKey.launchPhone,
|
||||
0x00000172: PhysicalKeyboardKey.programGuide,
|
||||
0x000000b6: PhysicalKeyboardKey.exit,
|
||||
0x000001a2: PhysicalKeyboardKey.channelUp,
|
||||
0x000001a3: PhysicalKeyboardKey.channelDown,
|
||||
0x000000d7: PhysicalKeyboardKey.mediaPlay,
|
||||
0x000000af: PhysicalKeyboardKey.mediaRecord,
|
||||
0x000000d8: PhysicalKeyboardKey.mediaFastForward,
|
||||
0x000000b0: PhysicalKeyboardKey.mediaRewind,
|
||||
0x000000ab: PhysicalKeyboardKey.mediaTrackNext,
|
||||
0x000000ad: PhysicalKeyboardKey.mediaTrackPrevious,
|
||||
0x000000ae: PhysicalKeyboardKey.mediaStop,
|
||||
0x000000a9: PhysicalKeyboardKey.eject,
|
||||
0x000000ac: PhysicalKeyboardKey.mediaPlayPause,
|
||||
0x0000024e: PhysicalKeyboardKey.speechInputToggle,
|
||||
0x000000d9: PhysicalKeyboardKey.bassBoost,
|
||||
0x000000b3: PhysicalKeyboardKey.mediaSelect,
|
||||
0x000001ad: PhysicalKeyboardKey.launchWordProcessor,
|
||||
0x000001af: PhysicalKeyboardKey.launchSpreadsheet,
|
||||
0x000000a3: PhysicalKeyboardKey.launchMail,
|
||||
0x000001b5: PhysicalKeyboardKey.launchContacts,
|
||||
0x00000195: PhysicalKeyboardKey.launchCalendar,
|
||||
0x00000094: PhysicalKeyboardKey.launchApp2,
|
||||
0x00000098: PhysicalKeyboardKey.launchApp1,
|
||||
0x0000009e: PhysicalKeyboardKey.launchInternetBrowser,
|
||||
0x000001b9: PhysicalKeyboardKey.logOff,
|
||||
0x000000a0: PhysicalKeyboardKey.lockScreen,
|
||||
0x0000024b: PhysicalKeyboardKey.launchControlPanel,
|
||||
0x0000024c: PhysicalKeyboardKey.selectTask,
|
||||
0x000000f3: PhysicalKeyboardKey.launchDocuments,
|
||||
0x000001b8: PhysicalKeyboardKey.spellCheck,
|
||||
0x0000017e: PhysicalKeyboardKey.launchKeyboardLayout,
|
||||
0x0000024d: PhysicalKeyboardKey.launchScreenSaver,
|
||||
0x00000190: PhysicalKeyboardKey.launchAudioBrowser,
|
||||
0x000000bd: PhysicalKeyboardKey.newKey,
|
||||
0x000000d6: PhysicalKeyboardKey.close,
|
||||
0x000000f2: PhysicalKeyboardKey.save,
|
||||
0x000000da: PhysicalKeyboardKey.print,
|
||||
0x000000e1: PhysicalKeyboardKey.browserSearch,
|
||||
0x000000b4: PhysicalKeyboardKey.browserHome,
|
||||
0x000000a6: PhysicalKeyboardKey.browserBack,
|
||||
0x000000a7: PhysicalKeyboardKey.browserForward,
|
||||
0x00000088: PhysicalKeyboardKey.browserStop,
|
||||
0x000000b5: PhysicalKeyboardKey.browserRefresh,
|
||||
0x000000a4: PhysicalKeyboardKey.browserFavorites,
|
||||
0x000001aa: PhysicalKeyboardKey.zoomIn,
|
||||
0x000001ab: PhysicalKeyboardKey.zoomOut,
|
||||
0x000000be: PhysicalKeyboardKey.redo,
|
||||
0x000000f0: PhysicalKeyboardKey.mailReply,
|
||||
0x000000f1: PhysicalKeyboardKey.mailForward,
|
||||
0x000000ef: PhysicalKeyboardKey.mailSend,
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'keyboard_key.dart';
|
||||
import 'raw_keyboard_android.dart';
|
||||
import 'raw_keyboard_fuchsia.dart';
|
||||
import 'raw_keyboard_linux.dart';
|
||||
import 'raw_keyboard_macos.dart';
|
||||
import 'system_channels.dart';
|
||||
|
||||
@ -272,6 +273,14 @@ abstract class RawKeyEvent {
|
||||
keyCode: message['keyCode'] ?? 0,
|
||||
modifiers: message['modifiers'] ?? 0);
|
||||
break;
|
||||
case 'linux':
|
||||
data = RawKeyEventDataLinux(
|
||||
keyHelper: KeyHelper(message['toolkit'] ?? ''),
|
||||
codePoint: message['codePoint'] ?? 0,
|
||||
keyCode: message['keyCode'] ?? 0,
|
||||
scanCode: message['scanCode'] ?? 0,
|
||||
modifiers: message['modifiers'] ?? 0);
|
||||
break;
|
||||
default:
|
||||
// We don't yet implement raw key events on iOS or other platforms, but
|
||||
// we don't hit this exception because the engine never sends us these
|
||||
|
258
packages/flutter/lib/src/services/raw_keyboard_linux.dart
Normal file
258
packages/flutter/lib/src/services/raw_keyboard_linux.dart
Normal file
@ -0,0 +1,258 @@
|
||||
// Copyright 2019 The Chromium 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:flutter/foundation.dart';
|
||||
|
||||
import 'keyboard_key.dart';
|
||||
import 'keyboard_maps.dart';
|
||||
import 'raw_keyboard.dart';
|
||||
|
||||
/// Platform-specific key event data for Linux.
|
||||
///
|
||||
/// Different window toolkit implementations can map to different key codes. This class
|
||||
/// will use the correct mapping depending on the [toolkit] provided.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RawKeyboard], which uses this interface to expose key data.
|
||||
class RawKeyEventDataLinux extends RawKeyEventData {
|
||||
/// Creates a key event data structure specific for macOS.
|
||||
///
|
||||
/// The [toolkit], [scanCode], [codePoint], [keyCode], and [modifiers], arguments
|
||||
/// must not be null.
|
||||
const RawKeyEventDataLinux({
|
||||
@required this.keyHelper,
|
||||
this.scanCode = 0,
|
||||
this.codePoint = 0,
|
||||
this.keyCode = 0,
|
||||
this.modifiers = 0,
|
||||
}) : assert(scanCode != null),
|
||||
assert(codePoint != null),
|
||||
assert(keyCode != null),
|
||||
assert(modifiers != null),
|
||||
assert(keyHelper != null);
|
||||
|
||||
/// A helper class that abstracts the fetching of the toolkit-specific mappings.
|
||||
///
|
||||
/// There is no real concept of a "native" window toolkit on Linux, and each implementation
|
||||
/// (GLFW, GTK, QT, etc) may have a different key code mapping.
|
||||
final KeyHelper keyHelper;
|
||||
|
||||
/// The hardware scan code id corresponding to this key event.
|
||||
///
|
||||
/// These values are not reliable and vary from device to device, so this
|
||||
/// information is mainly useful for debugging.
|
||||
final int scanCode;
|
||||
|
||||
/// The Unicode code point represented by the key event, if any.
|
||||
///
|
||||
/// If there is no Unicode code point, this value is zero.
|
||||
///
|
||||
/// Dead keys are represented as Unicode combining characters.
|
||||
final int codePoint;
|
||||
|
||||
/// The hardware key code corresponding to this key event.
|
||||
///
|
||||
/// This is the physical key that was pressed, not the Unicode character.
|
||||
/// See [codePoint] for the Unicode character. This value may be different depending
|
||||
/// on the window toolkit used (See [toolkit]).
|
||||
final int keyCode;
|
||||
|
||||
/// A mask of the current modifiers using the values in Modifier Flags.
|
||||
/// This value may be different depending on the window toolkit used (See [toolkit]).
|
||||
final int modifiers;
|
||||
|
||||
@override
|
||||
String get keyLabel => codePoint == 0 ? null : String.fromCharCode(codePoint);
|
||||
|
||||
@override
|
||||
PhysicalKeyboardKey get physicalKey => kLinuxToPhysicalKey[scanCode] ?? PhysicalKeyboardKey.none;
|
||||
|
||||
@override
|
||||
LogicalKeyboardKey get logicalKey {
|
||||
// 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.
|
||||
final LogicalKeyboardKey numPadKey = keyHelper.numpadKey(keyCode);
|
||||
if (numPadKey != null) {
|
||||
return numPadKey;
|
||||
}
|
||||
|
||||
// If it has a non-control-character label, then either return the existing
|
||||
// constant, or construct a new Unicode-based key from it. Don't mark it as
|
||||
// autogenerated, since the label uniquely identifies an ID from the Unicode
|
||||
// plane.
|
||||
if (keyLabel != null &&
|
||||
!LogicalKeyboardKey.isControlCharacter(keyLabel)) {
|
||||
final int keyId = LogicalKeyboardKey.unicodePlane | (codePoint & LogicalKeyboardKey.valueMask);
|
||||
return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
|
||||
keyId,
|
||||
keyLabel: keyLabel,
|
||||
debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}',
|
||||
);
|
||||
}
|
||||
|
||||
// Look to see if the keyCode is one we know about and have a mapping for.
|
||||
LogicalKeyboardKey newKey = keyHelper.logicalKey(keyCode);
|
||||
if (newKey != null) {
|
||||
return newKey;
|
||||
}
|
||||
|
||||
const int linuxKeyIdPlane = 0x00600000000;
|
||||
|
||||
// This is a non-printable key that we don't know about, so we mint a new
|
||||
// code with the autogenerated bit set.
|
||||
newKey ??= LogicalKeyboardKey(
|
||||
linuxKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask,
|
||||
debugName: kReleaseMode ? null : 'Unknown key code $keyCode',
|
||||
);
|
||||
return newKey;
|
||||
}
|
||||
|
||||
@override
|
||||
bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
|
||||
return keyHelper.isModifierPressed(key, modifiers, side: side);
|
||||
}
|
||||
|
||||
@override
|
||||
KeyboardSide getModifierSide(ModifierKey key) {
|
||||
return keyHelper.getModifierSide(key);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType(keyLabel: $keyLabel, keyCode: $keyCode, scanCode: $scanCode,'
|
||||
' codePoint: $codePoint, modifiers: $modifiers, '
|
||||
'modifiers down: $modifiersPressed)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Abstract class for window-specific key mappings.
|
||||
///
|
||||
/// Given that there might be multiple window toolkit implementations (GLFW,
|
||||
/// GTK, QT, etc), this creates a common interface for each of the
|
||||
/// different toolkits.
|
||||
abstract class KeyHelper {
|
||||
/// Create a KeyHelper implementation depending on the given toolkit.
|
||||
factory KeyHelper(String toolkit) {
|
||||
if (toolkit == 'glfw') {
|
||||
return GLFWKeyHelper();
|
||||
} else {
|
||||
throw FlutterError('Window toolkit not recognized: $toolkit');
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a [KeyboardSide] enum value that describes which side or sides of
|
||||
/// the given keyboard modifier key were pressed at the time of this event.
|
||||
KeyboardSide getModifierSide(ModifierKey key);
|
||||
|
||||
/// Returns true if the given [ModifierKey] was pressed at the time of this
|
||||
/// event.
|
||||
bool isModifierPressed(ModifierKey key, int modifiers, {KeyboardSide side = KeyboardSide.any});
|
||||
|
||||
/// The numpad key from the specific key code mapping.
|
||||
LogicalKeyboardKey numpadKey(int keyCode);
|
||||
|
||||
/// The logical key key from the specific key code mapping.
|
||||
LogicalKeyboardKey logicalKey(int keyCode);
|
||||
}
|
||||
|
||||
/// Helper class that uses GLFW-specific key mappings.
|
||||
class GLFWKeyHelper with KeyHelper {
|
||||
/// This mask is used to check the [modifiers] field to test whether the CAPS
|
||||
/// LOCK modifier key is on.
|
||||
///
|
||||
/// {@template flutter.services.logicalKeyboardKey.modifiers}
|
||||
/// Use this value if you need to decode the [modifiers] field yourself, but
|
||||
/// it's much easier to use [isModifierPressed] if you just want to know if
|
||||
/// a modifier is pressed.
|
||||
/// {@endtemplate}
|
||||
static const int modifierCapsLock = 0x0010;
|
||||
|
||||
/// This mask is used to check the [modifiers] field to test whether one of the
|
||||
/// SHIFT modifier keys is pressed.
|
||||
///
|
||||
/// {@macro flutter.services.logicalKeyboardKey.modifiers}
|
||||
static const int modifierShift = 0x0001;
|
||||
|
||||
/// This mask is used to check the [modifiers] field to test whether one of the
|
||||
/// CTRL modifier keys is pressed.
|
||||
///
|
||||
/// {@macro flutter.services.logicalKeyboardKey.modifiers}
|
||||
static const int modifierControl = 0x0002;
|
||||
|
||||
/// This mask is used to check the [modifiers] field to test whether one of the
|
||||
/// ALT modifier keys is pressed.
|
||||
///
|
||||
/// {@macro flutter.services.logicalKeyboardKey.modifiers}
|
||||
static const int modifierAlt = 0x0004;
|
||||
|
||||
/// This mask is used to check the [modifiers] field to test whether one of the
|
||||
/// Meta(SUPER) modifier keys is pressed.
|
||||
///
|
||||
/// {@macro flutter.services.logicalKeyboardKey.modifiers}
|
||||
static const int modifierMeta = 0x0008;
|
||||
|
||||
|
||||
/// This mask is used to check the [modifiers] field to test whether any key in
|
||||
/// the numeric keypad is pressed.
|
||||
///
|
||||
/// {@macro flutter.services.logicalKeyboardKey.modifiers}
|
||||
static const int modifierNumericPad = 0x0020;
|
||||
|
||||
@override
|
||||
bool isModifierPressed(ModifierKey key, int modifiers, {KeyboardSide side = KeyboardSide.any}) {
|
||||
switch (key) {
|
||||
case ModifierKey.controlModifier:
|
||||
return modifiers & modifierControl != 0;
|
||||
case ModifierKey.shiftModifier:
|
||||
return modifiers & modifierShift != 0;
|
||||
case ModifierKey.altModifier:
|
||||
return modifiers & modifierAlt != 0;
|
||||
case ModifierKey.metaModifier:
|
||||
return modifiers & modifierMeta != 0;
|
||||
case ModifierKey.capsLockModifier:
|
||||
return modifiers & modifierCapsLock != 0;
|
||||
case ModifierKey.numLockModifier:
|
||||
return modifiers & modifierNumericPad != 0;
|
||||
case ModifierKey.functionModifier:
|
||||
case ModifierKey.symbolModifier:
|
||||
case ModifierKey.scrollLockModifier:
|
||||
// These are not used in GLFW keyboards.
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
KeyboardSide getModifierSide(ModifierKey key) {
|
||||
switch (key) {
|
||||
case ModifierKey.controlModifier:
|
||||
case ModifierKey.shiftModifier:
|
||||
case ModifierKey.altModifier:
|
||||
case ModifierKey.metaModifier:
|
||||
// Neither GLFW or X11 provide a distiction between left and right modifiers, so defaults to KeyboardSide.any.
|
||||
// https://code.woboq.org/qt5/include/X11/X.h.html#_M/ShiftMask
|
||||
return KeyboardSide.any;
|
||||
case ModifierKey.capsLockModifier:
|
||||
case ModifierKey.numLockModifier:
|
||||
case ModifierKey.functionModifier:
|
||||
case ModifierKey.symbolModifier:
|
||||
case ModifierKey.scrollLockModifier:
|
||||
return KeyboardSide.all;
|
||||
}
|
||||
assert(false, 'Not handling $key type properly.');
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
LogicalKeyboardKey numpadKey(int keyCode) {
|
||||
return kGlfwNumpadMap[keyCode];
|
||||
}
|
||||
|
||||
@override
|
||||
LogicalKeyboardKey logicalKey(int keyCode) {
|
||||
return kGlfwToLogicalKey[keyCode];
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ void main() {
|
||||
expect(
|
||||
data.isModifierPressed(key, side: modifierTests[modifier].side),
|
||||
isFalse,
|
||||
reason: '$key should not be pressed with metaState $modifier with metaState $modifier '
|
||||
reason: '$key should not be pressed with metaState $modifier '
|
||||
'and additional key ${RawKeyEventDataAndroid.modifierFunction}.',
|
||||
);
|
||||
}
|
||||
@ -264,7 +264,7 @@ void main() {
|
||||
expect(data.keyLabel, isNull);
|
||||
});
|
||||
});
|
||||
group('RawKeyEventDataMacOs', () {
|
||||
group('RawKeyEventDataMacOs', () {
|
||||
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
|
||||
RawKeyEventDataMacOs.modifierOption | RawKeyEventDataMacOs.modifierLeftOption: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left),
|
||||
RawKeyEventDataMacOs.modifierOption | RawKeyEventDataMacOs.modifierRightOption: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right),
|
||||
@ -340,7 +340,7 @@ void main() {
|
||||
expect(
|
||||
data.isModifierPressed(key, side: modifierTests[modifier].side),
|
||||
isFalse,
|
||||
reason: '$key should not be pressed with metaState $modifier with metaState $modifier '
|
||||
reason: '$key should not be pressed with metaState $modifier '
|
||||
'and additional key ${RawKeyEventDataMacOs.modifierFunction}.',
|
||||
);
|
||||
}
|
||||
@ -393,4 +393,129 @@ void main() {
|
||||
expect(data.keyLabel, isNull);
|
||||
});
|
||||
});
|
||||
group('RawKeyEventDataLinux-GFLW', () {
|
||||
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
|
||||
GLFWKeyHelper.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any),
|
||||
GLFWKeyHelper.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.any),
|
||||
GLFWKeyHelper.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.any),
|
||||
GLFWKeyHelper.modifierMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.any),
|
||||
GLFWKeyHelper.modifierNumericPad: _ModifierCheck(ModifierKey.numLockModifier, KeyboardSide.all),
|
||||
GLFWKeyHelper.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all),
|
||||
};
|
||||
|
||||
test('modifier keys are recognized individually', () {
|
||||
for (int modifier in modifierTests.keys) {
|
||||
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'linux',
|
||||
'toolkit': 'glfw',
|
||||
'keyCode': 0x04,
|
||||
'scanCode': 0x01,
|
||||
'codePoint': 0x10,
|
||||
'modifiers': modifier,
|
||||
});
|
||||
final RawKeyEventDataLinux data = event.data;
|
||||
for (ModifierKey key in ModifierKey.values) {
|
||||
if (modifierTests[modifier].key == key) {
|
||||
expect(
|
||||
data.isModifierPressed(key, side: modifierTests[modifier].side),
|
||||
isTrue,
|
||||
reason: "$key should be pressed with metaState $modifier, but isn't.",
|
||||
);
|
||||
expect(data.getModifierSide(key), equals(modifierTests[modifier].side));
|
||||
} else {
|
||||
expect(
|
||||
data.isModifierPressed(key, side: modifierTests[modifier].side),
|
||||
isFalse,
|
||||
reason: '$key should not be pressed with metaState $modifier.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
test('modifier keys are recognized when combined', () {
|
||||
for (int modifier in modifierTests.keys) {
|
||||
if (modifier == GLFWKeyHelper.modifierControl) {
|
||||
// No need to combine CTRL key with itself.
|
||||
continue;
|
||||
}
|
||||
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'linux',
|
||||
'toolkit': 'glfw',
|
||||
'keyCode': 0x04,
|
||||
'scanCode': 0x64,
|
||||
'codePoint': 0x1,
|
||||
'modifiers': modifier | GLFWKeyHelper.modifierControl,
|
||||
});
|
||||
final RawKeyEventDataLinux data = event.data;
|
||||
for (ModifierKey key in ModifierKey.values) {
|
||||
if (modifierTests[modifier].key == key || key == ModifierKey.controlModifier) {
|
||||
expect(
|
||||
data.isModifierPressed(key, side: modifierTests[modifier].side),
|
||||
isTrue,
|
||||
reason: '$key should be pressed with metaState $modifier '
|
||||
"and additional key ${GLFWKeyHelper.modifierControl}, but isn't.",
|
||||
);
|
||||
if (key != ModifierKey.controlModifier) {
|
||||
expect(data.getModifierSide(key), equals(modifierTests[modifier].side));
|
||||
} else {
|
||||
expect(data.getModifierSide(key), equals(KeyboardSide.any));
|
||||
}
|
||||
} else {
|
||||
expect(
|
||||
data.isModifierPressed(key, side: modifierTests[modifier].side),
|
||||
isFalse,
|
||||
reason: '$key should not be pressed with metaState $modifier '
|
||||
'and additional key ${GLFWKeyHelper.modifierControl}.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
test('Printable keyboard keys are correctly translated', () {
|
||||
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'linux',
|
||||
'toolkit': 'glfw',
|
||||
'keyCode': 65,
|
||||
'scanCode': 0x00000026,
|
||||
'codePoint': 97,
|
||||
'modifiers': 0x0,
|
||||
});
|
||||
final RawKeyEventDataLinux data = keyAEvent.data;
|
||||
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
|
||||
expect(data.logicalKey, equals(LogicalKeyboardKey.keyA));
|
||||
expect(data.keyLabel, equals('a'));
|
||||
});
|
||||
test('Control keyboard keys are correctly translated', () {
|
||||
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'linux',
|
||||
'toolkit': 'glfw',
|
||||
'keyCode': 256,
|
||||
'scanCode': 0x00000009,
|
||||
'codePoint': 0,
|
||||
'modifiers': 0x0,
|
||||
});
|
||||
final RawKeyEventDataLinux data = escapeKeyEvent.data;
|
||||
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
|
||||
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
|
||||
expect(data.keyLabel, isNull);
|
||||
});
|
||||
test('Modifier keyboard keys are correctly translated', () {
|
||||
final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
|
||||
'type': 'keydown',
|
||||
'keymap': 'linux',
|
||||
'toolkit': 'glfw',
|
||||
'keyCode': 340,
|
||||
'scanCode': 0x00000032,
|
||||
'codePoint': 0,
|
||||
});
|
||||
final RawKeyEventDataLinux data = shiftLeftKeyEvent.data;
|
||||
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
|
||||
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
|
||||
expect(data.keyLabel, isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user