Unverified Commit c163ed96 authored by Francisco Magdaleno's avatar Francisco Magdaleno Committed by GitHub

Adds macOS raw keyboard mapping (#29231)

parent 75c50da5
......@@ -92,6 +92,11 @@ class _HardwareKeyDemoState extends State<RawKeyboardDemo> {
dataText.add(Text('codePoint: ${data.codePoint} (${_asHex(data.codePoint)})'));
dataText.add(Text('hidUsage: ${data.hidUsage} (${_asHex(data.hidUsage)})'));
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
} else if (data is RawKeyEventDataMacOs) {
dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})'));
dataText.add(Text('characters: ${data.characters}'));
dataText.add(Text('charactersIgnoringModifiers: ${data.charactersIgnoringModifiers}'));
dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})'));
}
dataText.add(Text('logical: ${_event.logicalKey}'));
dataText.add(Text('physical: ${_event.physicalKey}'));
......
......@@ -38,3 +38,15 @@ const Map<int, LogicalKeyboardKey> kFuchsiaToLogicalKey = <int, LogicalKeyboardK
const Map<int, PhysicalKeyboardKey> kFuchsiaToPhysicalKey = <int, PhysicalKeyboardKey>{
@@@FUCHSIA_SCAN_CODE_MAP@@@
};
/// Maps macOS-specific key code values representing [PhysicalKeyboardKey].
const Map<int, PhysicalKeyboardKey> kMacOsToPhysicalKey = <int, PhysicalKeyboardKey>{
@@@MACOS_SCAN_CODE_MAP@@@
};
/// A map of macOS 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> kMacOsNumPadMap = <int, LogicalKeyboardKey>{
@@@MACOS_NUMPAD_MAP@@@
};
......@@ -135,6 +135,31 @@ $otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalK
return androidScanCodeMap.toString().trimRight();
}
/// This generates the map of macOS key codes to physical keys.
String get macOsScanCodeMap {
final StringBuffer macOsScanCodeMap = StringBuffer();
for (Key entry in keyData.data) {
if (entry.macOsScanCode != null) {
macOsScanCodeMap.writeln(' ${toHex(entry.macOsScanCode)}: PhysicalKeyboardKey.${entry.constantName},');
}
}
return macOsScanCodeMap.toString().trimRight();
}
/// This generates the map of macOS number pad key codes to logical keys.
String get macOsNumpadMap {
final StringBuffer macOsNumPadMap = 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.macOsScanCode != null) {
macOsNumPadMap.writeln(' ${toHex(entry.macOsScanCode)}: LogicalKeyboardKey.${entry.constantName},');
}
}
return macOsNumPadMap.toString().trimRight();
}
/// This generates the map of Fuchsia key codes to logical keys.
String get fuchsiaKeyCodeMap {
final StringBuffer fuchsiaKeyCodeMap = StringBuffer();
......@@ -174,12 +199,17 @@ $otherComments static const LogicalKeyboardKey ${entry.constantName} = LogicalK
/// Substitutes the various platform specific maps into the template file for
/// keyboard_maps.dart.
String generateKeyboardMaps() {
// There is no macOS keycode map since macOS uses keycode to represent a physical key.
// The LogicalKeyboardKey is generated by raw_keyboard_macos.dart from the unmodified characters
// from NSEvent.
final Map<String, String> mappings = <String, String>{
'ANDROID_SCAN_CODE_MAP': androidScanCodeMap,
'ANDROID_KEY_CODE_MAP': androidKeyCodeMap,
'ANDROID_NUMPAD_MAP': androidNumpadMap,
'FUCHSIA_SCAN_CODE_MAP': fuchsiaHidCodeMap,
'FUCHSIA_KEY_CODE_MAP': fuchsiaKeyCodeMap,
'MACOS_SCAN_CODE_MAP': macOsScanCodeMap,
'MACOS_NUMPAD_MAP': macOsNumpadMap,
};
final String template = File(path.join(flutterRoot.path, 'dev', 'tools', 'gen_keycodes', 'data', 'keyboard_maps.tmpl')).readAsStringSync();
......
......@@ -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_macos.dart';
export 'src/services/system_channels.dart';
export 'src/services/system_chrome.dart';
export 'src/services/system_navigator.dart';
......
......@@ -842,3 +842,150 @@ const Map<int, PhysicalKeyboardKey> kFuchsiaToPhysicalKey = <int, PhysicalKeyboa
0x000c028b: PhysicalKeyboardKey.mailForward,
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,
0x00000008: PhysicalKeyboardKey.keyC,
0x00000002: PhysicalKeyboardKey.keyD,
0x0000000e: PhysicalKeyboardKey.keyE,
0x00000003: PhysicalKeyboardKey.keyF,
0x00000005: PhysicalKeyboardKey.keyG,
0x00000004: PhysicalKeyboardKey.keyH,
0x00000022: PhysicalKeyboardKey.keyI,
0x00000026: PhysicalKeyboardKey.keyJ,
0x00000028: PhysicalKeyboardKey.keyK,
0x00000025: PhysicalKeyboardKey.keyL,
0x0000002e: PhysicalKeyboardKey.keyM,
0x0000002d: PhysicalKeyboardKey.keyN,
0x0000001f: PhysicalKeyboardKey.keyO,
0x00000023: PhysicalKeyboardKey.keyP,
0x0000000c: PhysicalKeyboardKey.keyQ,
0x0000000f: PhysicalKeyboardKey.keyR,
0x00000001: PhysicalKeyboardKey.keyS,
0x00000011: PhysicalKeyboardKey.keyT,
0x00000020: PhysicalKeyboardKey.keyU,
0x00000009: PhysicalKeyboardKey.keyV,
0x0000000d: PhysicalKeyboardKey.keyW,
0x00000007: PhysicalKeyboardKey.keyX,
0x00000010: PhysicalKeyboardKey.keyY,
0x00000006: PhysicalKeyboardKey.keyZ,
0x00000012: PhysicalKeyboardKey.digit1,
0x00000013: PhysicalKeyboardKey.digit2,
0x00000014: PhysicalKeyboardKey.digit3,
0x00000015: PhysicalKeyboardKey.digit4,
0x00000017: PhysicalKeyboardKey.digit5,
0x00000016: PhysicalKeyboardKey.digit6,
0x0000001a: PhysicalKeyboardKey.digit7,
0x0000001c: PhysicalKeyboardKey.digit8,
0x00000019: PhysicalKeyboardKey.digit9,
0x0000001d: PhysicalKeyboardKey.digit0,
0x00000024: PhysicalKeyboardKey.enter,
0x00000035: PhysicalKeyboardKey.escape,
0x00000033: PhysicalKeyboardKey.backspace,
0x00000030: PhysicalKeyboardKey.tab,
0x00000031: PhysicalKeyboardKey.space,
0x0000001b: PhysicalKeyboardKey.minus,
0x00000018: PhysicalKeyboardKey.equal,
0x00000021: PhysicalKeyboardKey.bracketLeft,
0x0000001e: PhysicalKeyboardKey.bracketRight,
0x0000002a: PhysicalKeyboardKey.backslash,
0x00000029: PhysicalKeyboardKey.semicolon,
0x00000027: PhysicalKeyboardKey.quote,
0x00000032: PhysicalKeyboardKey.backquote,
0x0000002b: PhysicalKeyboardKey.comma,
0x0000002f: PhysicalKeyboardKey.period,
0x0000002c: PhysicalKeyboardKey.slash,
0x00000039: PhysicalKeyboardKey.capsLock,
0x0000007a: PhysicalKeyboardKey.f1,
0x00000078: PhysicalKeyboardKey.f2,
0x00000063: PhysicalKeyboardKey.f3,
0x00000076: PhysicalKeyboardKey.f4,
0x00000060: PhysicalKeyboardKey.f5,
0x00000061: PhysicalKeyboardKey.f6,
0x00000062: PhysicalKeyboardKey.f7,
0x00000064: PhysicalKeyboardKey.f8,
0x00000065: PhysicalKeyboardKey.f9,
0x0000006d: PhysicalKeyboardKey.f10,
0x00000067: PhysicalKeyboardKey.f11,
0x0000006f: PhysicalKeyboardKey.f12,
0x00000072: PhysicalKeyboardKey.insert,
0x00000073: PhysicalKeyboardKey.home,
0x00000074: PhysicalKeyboardKey.pageUp,
0x00000075: PhysicalKeyboardKey.delete,
0x00000077: PhysicalKeyboardKey.end,
0x00000079: PhysicalKeyboardKey.pageDown,
0x0000007c: PhysicalKeyboardKey.arrowRight,
0x0000007b: PhysicalKeyboardKey.arrowLeft,
0x0000007d: PhysicalKeyboardKey.arrowDown,
0x0000007e: PhysicalKeyboardKey.arrowUp,
0x00000047: PhysicalKeyboardKey.numLock,
0x0000004b: PhysicalKeyboardKey.numpadDivide,
0x00000043: PhysicalKeyboardKey.numpadMultiply,
0x0000004e: PhysicalKeyboardKey.numpadSubtract,
0x00000045: PhysicalKeyboardKey.numpadAdd,
0x0000004c: PhysicalKeyboardKey.numpadEnter,
0x00000053: PhysicalKeyboardKey.numpad1,
0x00000054: PhysicalKeyboardKey.numpad2,
0x00000055: PhysicalKeyboardKey.numpad3,
0x00000056: PhysicalKeyboardKey.numpad4,
0x00000057: PhysicalKeyboardKey.numpad5,
0x00000058: PhysicalKeyboardKey.numpad6,
0x00000059: PhysicalKeyboardKey.numpad7,
0x0000005b: PhysicalKeyboardKey.numpad8,
0x0000005c: PhysicalKeyboardKey.numpad9,
0x00000052: PhysicalKeyboardKey.numpad0,
0x00000041: PhysicalKeyboardKey.numpadDecimal,
0x0000000a: PhysicalKeyboardKey.intlBackslash,
0x0000006e: PhysicalKeyboardKey.contextMenu,
0x00000051: PhysicalKeyboardKey.numpadEqual,
0x00000069: PhysicalKeyboardKey.f13,
0x0000006b: PhysicalKeyboardKey.f14,
0x00000071: PhysicalKeyboardKey.f15,
0x0000006a: PhysicalKeyboardKey.f16,
0x00000040: PhysicalKeyboardKey.f17,
0x0000004f: PhysicalKeyboardKey.f18,
0x00000050: PhysicalKeyboardKey.f19,
0x0000005a: PhysicalKeyboardKey.f20,
0x0000004a: PhysicalKeyboardKey.audioVolumeMute,
0x00000048: PhysicalKeyboardKey.audioVolumeUp,
0x00000049: PhysicalKeyboardKey.audioVolumeDown,
0x0000005f: PhysicalKeyboardKey.numpadComma,
0x0000005e: PhysicalKeyboardKey.intlRo,
0x00000068: PhysicalKeyboardKey.kanaMode,
0x0000005d: PhysicalKeyboardKey.intlYen,
0x0000003b: PhysicalKeyboardKey.controlLeft,
0x00000038: PhysicalKeyboardKey.shiftLeft,
0x0000003a: PhysicalKeyboardKey.altLeft,
0x00000037: PhysicalKeyboardKey.metaLeft,
0x0000003e: PhysicalKeyboardKey.controlRight,
0x0000003c: PhysicalKeyboardKey.shiftRight,
0x0000003d: PhysicalKeyboardKey.altRight,
0x00000036: PhysicalKeyboardKey.metaRight,
};
/// A map of macOS 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> kMacOsNumPadMap = <int, LogicalKeyboardKey>{
0x0000004b: LogicalKeyboardKey.numpadDivide,
0x00000043: LogicalKeyboardKey.numpadMultiply,
0x0000004e: LogicalKeyboardKey.numpadSubtract,
0x00000045: LogicalKeyboardKey.numpadAdd,
0x00000053: LogicalKeyboardKey.numpad1,
0x00000054: LogicalKeyboardKey.numpad2,
0x00000055: LogicalKeyboardKey.numpad3,
0x00000056: LogicalKeyboardKey.numpad4,
0x00000057: LogicalKeyboardKey.numpad5,
0x00000058: LogicalKeyboardKey.numpad6,
0x00000059: LogicalKeyboardKey.numpad7,
0x0000005b: LogicalKeyboardKey.numpad8,
0x0000005c: LogicalKeyboardKey.numpad9,
0x00000052: LogicalKeyboardKey.numpad0,
0x00000041: LogicalKeyboardKey.numpadDecimal,
0x00000051: LogicalKeyboardKey.numpadEqual,
0x0000005f: LogicalKeyboardKey.numpadComma,
};
......@@ -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_macos.dart';
import 'system_channels.dart';
/// An enum describing the side of the keyboard that a key is on, to allow
......@@ -263,6 +264,14 @@ abstract class RawKeyEvent {
modifiers: message['modifiers'] ?? 0,
);
break;
case 'macos':
data = RawKeyEventDataMacOs(
characters: message['characters'] ?? '',
charactersIgnoringModifiers:
message['charactersIgnoringModifiers'] ?? '',
keyCode: message['keyCode'] ?? 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
......@@ -400,7 +409,7 @@ class RawKeyDownEvent extends RawKeyEvent {
const RawKeyDownEvent({
@required RawKeyEventData data,
String character,
}) : super(data: data, character: character);
}) : super(data: data, character: character);
}
/// The user has released a key on the keyboard.
......
This diff is collapsed.
......@@ -264,4 +264,133 @@ void main() {
expect(data.keyLabel, isNull);
});
});
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),
RawKeyEventDataMacOs.modifierShift | RawKeyEventDataMacOs.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left),
RawKeyEventDataMacOs.modifierShift | RawKeyEventDataMacOs.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right),
RawKeyEventDataMacOs.modifierFunction: _ModifierCheck(ModifierKey.functionModifier, KeyboardSide.all),
RawKeyEventDataMacOs.modifierControl | RawKeyEventDataMacOs.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left),
RawKeyEventDataMacOs.modifierControl | RawKeyEventDataMacOs.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right),
RawKeyEventDataMacOs.modifierCommand | RawKeyEventDataMacOs.modifierLeftCommand: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left),
RawKeyEventDataMacOs.modifierCommand | RawKeyEventDataMacOs.modifierRightCommand: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right),
RawKeyEventDataMacOs.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': 'macos',
'keyCode': 0x04,
'characters': 'a',
'charactersIgnoringModifiers': 'a',
'modifiers': modifier,
});
final RawKeyEventDataMacOs 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 == RawKeyEventDataMacOs.modifierFunction) {
// No need to combine function key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x04,
'plainCodePoint': 0x64,
'characters': 'a',
'charactersIgnoringModifiers': 'a',
'modifiers': modifier | RawKeyEventDataMacOs.modifierFunction,
});
final RawKeyEventDataMacOs data = event.data;
for (ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier].key == key || key == ModifierKey.functionModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataMacOs.modifierFunction}, but isn't.",
);
if (key != ModifierKey.functionModifier) {
expect(data.getModifierSide(key), equals(modifierTests[modifier].side));
} else {
expect(data.getModifierSide(key), equals(KeyboardSide.all));
}
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier].side),
isFalse,
reason: '$key should not be pressed with metaState $modifier with metaState $modifier '
'and additional key ${RawKeyEventDataMacOs.modifierFunction}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
const String unmodifiedCharacter = 'a';
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x00000000,
'characters': 'a',
'charactersIgnoringModifiers': unmodifiedCharacter,
'modifiers': 0x0,
});
final RawKeyEventDataMacOs 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': 'macos',
'keyCode': 0x00000035,
'characters': '',
'charactersIgnoringModifiers': '',
'character': null,
'modifiers': 0x0,
});
final RawKeyEventDataMacOs 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': 'macos',
'keyCode': 0x00000038,
'characters': '',
'charactersIgnoringModifiers': '',
'character': null,
'modifiers': RawKeyEventDataMacOs.modifierLeftShift,
});
final RawKeyEventDataMacOs data = shiftLeftKeyEvent.data;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isNull);
});
});
}
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