Adds macOS raw keyboard mapping (#29231)
diff --git a/dev/manual_tests/lib/raw_keyboard.dart b/dev/manual_tests/lib/raw_keyboard.dart
index 06ba889..a2474ee 100644
--- a/dev/manual_tests/lib/raw_keyboard.dart
+++ b/dev/manual_tests/lib/raw_keyboard.dart
@@ -92,6 +92,11 @@
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}'));
diff --git a/dev/tools/gen_keycodes/data/keyboard_maps.tmpl b/dev/tools/gen_keycodes/data/keyboard_maps.tmpl
index cdd18a7..06e6352 100644
--- a/dev/tools/gen_keycodes/data/keyboard_maps.tmpl
+++ b/dev/tools/gen_keycodes/data/keyboard_maps.tmpl
@@ -38,3 +38,15 @@
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@@@
+};
diff --git a/dev/tools/gen_keycodes/lib/code_gen.dart b/dev/tools/gen_keycodes/lib/code_gen.dart
index 3a8be0f..0ba5308 100644
--- a/dev/tools/gen_keycodes/lib/code_gen.dart
+++ b/dev/tools/gen_keycodes/lib/code_gen.dart
@@ -135,6 +135,31 @@
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 @@
/// 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();
diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart
index ff56d86..3a916fb 100644
--- a/packages/flutter/lib/services.dart
+++ b/packages/flutter/lib/services.dart
@@ -24,6 +24,7 @@
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';
diff --git a/packages/flutter/lib/src/services/keyboard_maps.dart b/packages/flutter/lib/src/services/keyboard_maps.dart
index a718a3a..0e03e08 100644
--- a/packages/flutter/lib/src/services/keyboard_maps.dart
+++ b/packages/flutter/lib/src/services/keyboard_maps.dart
@@ -842,3 +842,150 @@
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,
+};
diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart
index 02c1441..6c46d12 100644
--- a/packages/flutter/lib/src/services/raw_keyboard.dart
+++ b/packages/flutter/lib/src/services/raw_keyboard.dart
@@ -9,6 +9,7 @@
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 @@
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 @@
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.
diff --git a/packages/flutter/lib/src/services/raw_keyboard_macos.dart b/packages/flutter/lib/src/services/raw_keyboard_macos.dart
new file mode 100644
index 0000000..2c48059
--- /dev/null
+++ b/packages/flutter/lib/src/services/raw_keyboard_macos.dart
@@ -0,0 +1,315 @@
+// 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 macOS.
+///
+/// This object contains information about key events obtained from macOS's
+/// `NSEvent` interface.
+///
+/// See also:
+///
+/// * [RawKeyboard], which uses this interface to expose key data.
+class RawKeyEventDataMacOs extends RawKeyEventData {
+ /// Creates a key event data structure specific for macOS.
+ ///
+ /// The [characters], [charactersIgnoringModifiers], and [modifiers], arguments
+ /// must not be null.
+ const RawKeyEventDataMacOs({
+ this.characters = '',
+ this.charactersIgnoringModifiers = '',
+ this.keyCode = 0,
+ this.modifiers = 0,
+ }) : assert(characters != null),
+ assert(charactersIgnoringModifiers != null),
+ assert(keyCode != null),
+ assert(modifiers != null);
+
+ /// The Unicode characters associated with a key-up or key-down event.
+ ///
+ /// See also:
+ ///
+ /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1534183-characters?language=objc)
+ final String characters;
+
+ /// The characters generated by a key event as if no modifier key (except for
+ /// Shift) applies.
+ ///
+ /// See also:
+ ///
+ /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1524605-charactersignoringmodifiers?language=objc)
+ final String charactersIgnoringModifiers;
+
+ /// The virtual key code for the keyboard key associated with a key event.
+ ///
+ /// See also:
+ ///
+ /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1534513-keycode?language=objc)
+ final int keyCode;
+
+ /// A mask of the current modifiers using the values in Modifier Flags.
+ ///
+ /// See also:
+ ///
+ /// * [Apple's NSEvent documentation](https://developer.apple.com/documentation/appkit/nsevent/1535211-modifierflags?language=objc)
+ final int modifiers;
+
+ @override
+ String get keyLabel => charactersIgnoringModifiers.isEmpty ? null : charactersIgnoringModifiers;
+
+ @override
+ PhysicalKeyboardKey get physicalKey => kMacOsToPhysicalKey[keyCode] ?? 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 = kMacOsNumPadMap[keyCode];
+ if (numPadKey != null) {
+ return numPadKey;
+ }
+
+ // Look to see if the keyCode is one we know about and have a mapping for.
+ if (keyLabel != null &&
+ !LogicalKeyboardKey.isControlCharacter(keyLabel)) {
+ assert(charactersIgnoringModifiers.length <= 2);
+ int codeUnit = charactersIgnoringModifiers.codeUnitAt(0);
+ if (charactersIgnoringModifiers.length == 2) {
+ // Not covering length > 2 case since > 1 is already unlikely.
+ final int secondCode = charactersIgnoringModifiers.codeUnitAt(1);
+ codeUnit = (codeUnit << 16) | secondCode;
+ }
+
+ final int keyId = LogicalKeyboardKey.unicodePlane | (codeUnit & LogicalKeyboardKey.valueMask);
+ return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+ keyId,
+ keyLabel: keyLabel,
+ debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}',
+ );
+ }
+
+ // This is a non-printable key that we don't know about, so we mint a new
+ // code with the autogenerated bit set.
+ const int macOsKeyIdPlane = 0x00500000000;
+
+ // Keys like "backspace" won't have a character, but it's known by the physical keyboard.
+ // Since there is no logical keycode map for macOS (macOS uses the keycode to reference
+ // physical keys), a LogicalKeyboardKey is created with the physical key's HID usage and
+ // debugName. This avoids the need for duplicating the physical key map.
+ if (physicalKey != PhysicalKeyboardKey.none) {
+ final int keyId = physicalKey.usbHidUsage | LogicalKeyboardKey.hidPlane;
+ return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey(
+ keyId,
+ keyLabel: physicalKey.debugName,
+ debugName: physicalKey.debugName,
+ );
+ }
+
+ return LogicalKeyboardKey(
+ macOsKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask,
+ debugName: kReleaseMode ? null : 'Unknown macOS key code $keyCode',
+ );
+ }
+
+ bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) {
+ if (modifiers & anyMask == 0) {
+ return false;
+ }
+ switch (side) {
+ case KeyboardSide.any:
+ return true;
+ case KeyboardSide.all:
+ return modifiers & leftMask != 0 && modifiers & rightMask != 0;
+ case KeyboardSide.left:
+ return modifiers & leftMask != 0;
+ case KeyboardSide.right:
+ return modifiers & rightMask != 0;
+ }
+ return false;
+ }
+
+ @override
+ bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) {
+ final int independentModifier = modifiers & deviceIndependentMask;
+ switch (key) {
+ case ModifierKey.controlModifier:
+ return _isLeftRightModifierPressed(side, independentModifier & modifierControl, modifierLeftControl, modifierRightControl);
+ case ModifierKey.shiftModifier:
+ return _isLeftRightModifierPressed(side, independentModifier & modifierShift, modifierLeftShift, modifierRightShift);
+ case ModifierKey.altModifier:
+ return _isLeftRightModifierPressed(side, independentModifier & modifierOption, modifierLeftOption, modifierRightOption);
+ case ModifierKey.metaModifier:
+ return _isLeftRightModifierPressed(side, independentModifier & modifierCommand, modifierLeftCommand, modifierRightCommand);
+ case ModifierKey.capsLockModifier:
+ return independentModifier & modifierCapsLock != 0;
+ case ModifierKey.numLockModifier:
+ return independentModifier & modifierNumericPad != 0;
+ case ModifierKey.functionModifier:
+ return independentModifier & modifierFunction != 0;
+ case ModifierKey.symbolModifier:
+ case ModifierKey.scrollLockModifier:
+ // These are not used in macOS keyboards.
+ return false;
+ }
+ return false;
+ }
+
+ @override
+ KeyboardSide getModifierSide(ModifierKey key) {
+ KeyboardSide findSide(int leftMask, int rightMask) {
+ final int combinedMask = leftMask | rightMask;
+ final int combined = modifiers & combinedMask;
+ if (combined == leftMask) {
+ return KeyboardSide.left;
+ } else if (combined == rightMask) {
+ return KeyboardSide.right;
+ } else if (combined == combinedMask) {
+ return KeyboardSide.all;
+ }
+ return null;
+ }
+
+ switch (key) {
+ case ModifierKey.controlModifier:
+ return findSide(modifierLeftControl, modifierRightControl);
+ case ModifierKey.shiftModifier:
+ return findSide(modifierLeftShift, modifierRightShift);
+ case ModifierKey.altModifier:
+ return findSide(modifierLeftOption, modifierRightOption);
+ case ModifierKey.metaModifier:
+ return findSide(modifierLeftCommand, modifierRightCommand);
+ case ModifierKey.capsLockModifier:
+ case ModifierKey.numLockModifier:
+ case ModifierKey.scrollLockModifier:
+ case ModifierKey.functionModifier:
+ case ModifierKey.symbolModifier:
+ return KeyboardSide.all;
+ }
+
+ assert(false, 'Not handling $key type properly.');
+ return null;
+ }
+
+ // Modifier key masks. See Apple's NSEvent documentation
+ // https://developer.apple.com/documentation/appkit/nseventmodifierflags?language=objc
+ // https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-86/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h.auto.html
+
+ /// 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 = 0x10000;
+
+ /// 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 = 0x20000;
+
+ /// This mask is used to check the [modifiers] field to test whether the left
+ /// SHIFT modifier key is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierLeftShift = 0x02;
+
+ /// This mask is used to check the [modifiers] field to test whether the right
+ /// SHIFT modifier key is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierRightShift = 0x04;
+
+ /// 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 = 0x40000;
+
+ /// This mask is used to check the [modifiers] field to test whether the left
+ /// CTRL modifier key is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierLeftControl = 0x01;
+
+ /// This mask is used to check the [modifiers] field to test whether the right
+ /// CTRL modifier key is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierRightControl = 0x2000;
+
+ /// 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 modifierOption = 0x80000;
+
+ /// This mask is used to check the [modifiers] field to test whether the left
+ /// ALT modifier key is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierLeftOption = 0x20;
+
+ /// This mask is used to check the [modifiers] field to test whether the right
+ /// ALT modifier key is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierRightOption = 0x40;
+
+ /// This mask is used to check the [modifiers] field to test whether one of the
+ /// CMD modifier keys is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierCommand = 0x100000;
+
+ /// This mask is used to check the [modifiers] field to test whether the left
+ /// CMD modifier keys is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierLeftCommand = 0x08;
+
+ /// This mask is used to check the [modifiers] field to test whether the right
+ /// CMD modifier keys is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierRightCommand = 0x10;
+
+ /// 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 = 0x200000;
+
+ /// This mask is used to check the [modifiers] field to test whether the
+ /// HELP modifier key is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierHelp = 0x400000;
+
+ /// This mask is used to check the [modifiers] field to test whether one of the
+ /// FUNCTION modifier keys is pressed.
+ ///
+ /// {@macro flutter.services.logicalKeyboardKey.modifiers}
+ static const int modifierFunction = 0x800000;
+
+ /// Used to retrieve only the device-independent modifier flags, allowing
+ /// applications to mask off the device-dependent modifier flags, including
+ /// event coalescing information.
+ static const int deviceIndependentMask = 0xffff0000;
+
+ @override
+ String toString() {
+ return '$runtimeType(keyLabel: $keyLabel, keyCode: $keyCode, characters: $characters,'
+ ' unmodifiedCharacters: $charactersIgnoringModifiers, modifiers: $modifiers, '
+ 'modifiers down: $modifiersPressed)';
+ }
+}
diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart
index 73ce54d..b5bd89d 100644
--- a/packages/flutter/test/services/raw_keyboard_test.dart
+++ b/packages/flutter/test/services/raw_keyboard_test.dart
@@ -264,4 +264,133 @@
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);
+ });
+ });
}