From deb155e89a61c1c45679b59ba928f4e7eefece0c Mon Sep 17 00:00:00 2001 From: Francisco Magdaleno Date: Tue, 3 Sep 2019 09:13:01 -0700 Subject: [PATCH] [macos] Check for special keys before creating a logical key (#37901) --- .../lib/src/services/raw_keyboard_macos.dart | 46 ++++++++++++++----- .../test/services/raw_keyboard_test.dart | 16 +++++++ 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/packages/flutter/lib/src/services/raw_keyboard_macos.dart b/packages/flutter/lib/src/services/raw_keyboard_macos.dart index 2c48059102..b7474a732c 100644 --- a/packages/flutter/lib/src/services/raw_keyboard_macos.dart +++ b/packages/flutter/lib/src/services/raw_keyboard_macos.dart @@ -75,14 +75,18 @@ class RawKeyEventDataMacOs extends RawKeyEventData { if (numPadKey != null) { return numPadKey; } - - // Look to see if the keyCode is one we know about and have a mapping for. + // If this key is printable, generate the LogicalKeyboardKey from its Unicode value. + // Control keys such as ESC, CRTL, and SHIFT are not printable. HOME, DEL, arrow keys, and function + // keys are considered modifier function keys, which generate invalid Unicode scalar values. if (keyLabel != null && - !LogicalKeyboardKey.isControlCharacter(keyLabel)) { + !LogicalKeyboardKey.isControlCharacter(keyLabel) && + !_isUnprintableKey(keyLabel)) { + // Given that charactersIgnoringModifiers can contain a String of arbitrary length, + // limit to a maximum of two Unicode scalar values. It is unlikely that a keyboard would produce a code point + // bigger than 32 bits, but it is still worth defending against this case. 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; } @@ -95,14 +99,11 @@ class RawKeyEventDataMacOs extends RawKeyEventData { ); } - // 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. + // Control keys like "backspace" and movement keys like arrow keys don't have a printable representation, + // but are present on 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 duplicating the physical + // key map. if (physicalKey != PhysicalKeyboardKey.none) { final int keyId = physicalKey.usbHidUsage | LogicalKeyboardKey.hidPlane; return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey( @@ -112,6 +113,10 @@ class RawKeyEventDataMacOs extends RawKeyEventData { ); } + // 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; + return LogicalKeyboardKey( macOsKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask, debugName: kReleaseMode ? null : 'Unknown macOS key code $keyCode', @@ -197,6 +202,23 @@ class RawKeyEventDataMacOs extends RawKeyEventData { return null; } + /// Returns true if the given label represents an unprintable key. + /// + /// Examples of unprintable keys are "NSUpArrowFunctionKey = 0xF700" + /// or "NSHomeFunctionKey = 0xF729". + /// + /// See for more + /// information. + /// + /// Used by [RawKeyEvent] subclasses to help construct IDs. + static bool _isUnprintableKey(String label) { + if (label.length > 1) { + return false; + } + final int codeUnit = label.codeUnitAt(0); + return codeUnit >= 0xF700 && codeUnit <= 0xF8FF; + } + // 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 diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart index 16439575dd..c5438f5e5d 100644 --- a/packages/flutter/test/services/raw_keyboard_test.dart +++ b/packages/flutter/test/services/raw_keyboard_test.dart @@ -450,6 +450,22 @@ void main() { expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); expect(data.keyLabel, isNull); }); + test('Unprintable keyboard keys are correctly translated', () { + final RawKeyEvent leftArrowKey = RawKeyEvent.fromMessage(const { + 'type': 'keydown', + 'keymap': 'macos', + 'keyCode': 0x0000007B, + 'characters': '', + 'charactersIgnoringModifiers': '', // NSLeftArrowFunctionKey = 0xF702 + 'character': null, + 'modifiers': RawKeyEventDataMacOs.modifierFunction, + }); + final RawKeyEventDataMacOs data = leftArrowKey.data; + expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowLeft)); + expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft)); + expect(data.logicalKey.keyLabel, isNull); + }); + }); group('RawKeyEventDataLinux-GFLW', () { const Map modifierTests = {