Unverified Commit c921c5ae authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add macOS fn key support. (#44410)

This adds support for the fn key on macOS. It adds it to the key mappings as a supplemental mapping that overwrites the one from the Chrome headers, since the chrome headers have a TODO, but no implementation of the key.

Also, ignore the fn key entirely on macOS. This is because On macOS laptop keyboards, the fn key is used to generate home/end and f1-f12, but it ALSO generates a separate down/up event for the fn key itself. Other platforms hide the fn key, and just produce the key that it is combined with, so to keep it possible to write cross platform code that looks at which keys are pressed, the fn key is ignored.
parent 9fa0f3b2
......@@ -66,33 +66,6 @@
"glfw": null
}
},
"fn": {
"names": {
"domkey": "Fn",
"android": [
"FUNCTION"
],
"english": "Fn",
"chromium": "fn",
"glfw": null
},
"scanCodes": {
"android": [
464
],
"usb": 18,
"linux": null,
"xkb": null,
"windows": null,
"macos": null
},
"keyCodes": {
"android": [
119
],
"glfw": null
}
},
"fnLock": {
"names": {
"domkey": "FnLock",
......@@ -7222,5 +7195,32 @@
],
"glfw": null
}
},
"fn": {
"names": {
"domkey": "Fn",
"android": [
"FUNCTION"
],
"english": "Fn",
"chromium": "fn",
"glfw": null
},
"scanCodes": {
"android": [
464
],
"usb": 18,
"linux": null,
"xkb": null,
"windows": null,
"macos": 63
},
"keyCodes": {
"android": [
119
],
"glfw": null
}
}
}
\ No newline at end of file
......@@ -238,6 +238,21 @@ class LogicalKeyboardKey extends KeyboardKey {
return result == null ? <LogicalKeyboardKey>{} : <LogicalKeyboardKey>{result};
}
/// Takes a set of keys, and returns the same set, but with any keys that have
/// synonyms replaced.
///
/// It is used, for example, to make sets of keys with members like
/// [controlRight] and [controlLeft] and convert that set to contain just
/// [control], so that the question "is any control key down?" can be asked.
static Set<LogicalKeyboardKey> collapseSynonyms(Set<LogicalKeyboardKey> input) {
final Set<LogicalKeyboardKey> result = <LogicalKeyboardKey>{};
for (LogicalKeyboardKey key in input) {
final LogicalKeyboardKey synonym = _synonyms[key];
result.add(synonym ?? key);
}
return result;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......
......@@ -53,6 +53,12 @@ const Map<int, LogicalKeyboardKey> kMacOsNumPadMap = <int, LogicalKeyboardKey>{
@@@MACOS_NUMPAD_MAP@@@
};
/// A map of macOS key codes which are numbered function keys, so that they
/// can be excluded when asking "is the Fn modifier down?".
const Map<int, LogicalKeyboardKey> kMacOsFunctionKeyMap = <int, LogicalKeyboardKey>{
@@@MACOS_FUNCTION_KEY_MAP@@@
};
/// Maps GLFW-specific key codes to the matching [LogicalKeyboardKey].
const Map<int, LogicalKeyboardKey> kGlfwToLogicalKey = <int, LogicalKeyboardKey>{
@@@GLFW_KEY_CODE_MAP@@@
......
......@@ -43,3 +43,10 @@
USB_KEYMAP(0x05ff1d, 0x0000, 0x0000, 0x0000, 0xffff, "GameButtonX", BUTTON_X),
USB_KEYMAP(0x05ff1e, 0x0000, 0x0000, 0x0000, 0xffff, "GameButtonY", BUTTON_Y),
USB_KEYMAP(0x05ff1f, 0x0000, 0x0000, 0x0000, 0xffff, "GameButtonZ", BUTTON_Z),
// The Mac defines a key code for the Fn key on Mac keyboards, but it's not
// defined on other platforms. Chromium does define an "Fn" row, but doesn't
// give it a Mac keycode. This overrides their definition.
// USB HID evdev XKB Win Mac DOMKey Code
USB_KEYMAP(0x000012, 0x0000, 0x0000, 0x0000, 0x003f, "Fn", FN),
......@@ -114,6 +114,13 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
}).toList();
}
List<Key> get functionKeyData {
final RegExp functionKeyRe = RegExp(r'^f[0-9]+$');
return keyData.data.where((Key entry) {
return functionKeyRe.hasMatch(entry.constantName);
}).toList();
}
/// This generates the map of USB HID codes to physical keys.
String get predefinedHidCodeMap {
final StringBuffer scanCodeMap = StringBuffer();
......@@ -240,6 +247,16 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
return macOsNumPadMap.toString().trimRight();
}
String get macOsFunctionKeyMap {
final StringBuffer macOsFunctionKeyMap = StringBuffer();
for (Key entry in functionKeyData) {
if (entry.macOsScanCode != null) {
macOsFunctionKeyMap.writeln(' ${toHex(entry.macOsScanCode)}: LogicalKeyboardKey.${entry.constantName},');
}
}
return macOsFunctionKeyMap.toString().trimRight();
}
/// This generates the map of Fuchsia key codes to logical keys.
String get fuchsiaKeyCodeMap {
final StringBuffer fuchsiaKeyCodeMap = StringBuffer();
......@@ -324,6 +341,7 @@ $otherComments static const LogicalKeyboardKey $constantName = LogicalKeyboardK
'FUCHSIA_KEY_CODE_MAP': fuchsiaKeyCodeMap,
'MACOS_SCAN_CODE_MAP': macOsScanCodeMap,
'MACOS_NUMPAD_MAP': macOsNumpadMap,
'MACOS_FUNCTION_KEY_MAP': macOsFunctionKeyMap,
'GLFW_KEY_CODE_MAP': glfwKeyCodeMap,
'GLFW_NUMPAD_MAP': glfwNumpadMap,
'XKB_SCAN_CODE_MAP': xkbScanCodeMap,
......
......@@ -244,6 +244,9 @@ class KeyData {
// Skip key that is not actually generated by any keyboard.
return '';
}
// Remove duplicates: last one wins, so that supplemental codes
// override.
entries.removeWhere((Key entry) => entry.usbHidCode == newEntry.usbHidCode);
entries.add(newEntry);
}
return match.group(0);
......
......@@ -315,11 +315,6 @@ class LogicalKeyboardKey extends KeyboardKey {
/// See the function [RawKeyEvent.logicalKey] for more information.
static const LogicalKeyboardKey superKey = LogicalKeyboardKey(0x00100000011, debugName: kReleaseMode ? null : 'Super Key');
/// Represents the logical "Fn" key on the keyboard.
///
/// See the function [RawKeyEvent.logicalKey] for more information.
static const LogicalKeyboardKey fn = LogicalKeyboardKey(0x00100000012, debugName: kReleaseMode ? null : 'Fn');
/// Represents the logical "Fn Lock" key on the keyboard.
///
/// See the function [RawKeyEvent.logicalKey] for more information.
......@@ -1625,6 +1620,11 @@ class LogicalKeyboardKey extends KeyboardKey {
/// See the function [RawKeyEvent.logicalKey] for more information.
static const LogicalKeyboardKey gameButtonZ = LogicalKeyboardKey(0x0010005ff1f, debugName: kReleaseMode ? null : 'Game Button Z');
/// Represents the logical "Fn" key on the keyboard.
///
/// See the function [RawKeyEvent.logicalKey] for more information.
static const LogicalKeyboardKey fn = LogicalKeyboardKey(0x00100000012, debugName: kReleaseMode ? null : 'Fn');
/// Represents the logical "Shift" key on the keyboard.
///
/// This key represents the union of the keys {shiftLeft, shiftRight} when
......@@ -1659,7 +1659,6 @@ class LogicalKeyboardKey extends KeyboardKey {
0x0100000000: none,
0x0100000010: hyper,
0x0100000011: superKey,
0x0100000012: fn,
0x0100000013: fnLock,
0x0100000014: suspend,
0x0100000015: resume,
......@@ -1921,6 +1920,7 @@ class LogicalKeyboardKey extends KeyboardKey {
0x010005ff1d: gameButtonX,
0x010005ff1e: gameButtonY,
0x010005ff1f: gameButtonZ,
0x0100000012: fn,
0x201000700e1: shift,
0x201000700e3: meta,
0x201000700e2: alt,
......@@ -2102,11 +2102,6 @@ class PhysicalKeyboardKey extends KeyboardKey {
/// See the function [RawKeyEvent.physicalKey] for more information.
static const PhysicalKeyboardKey superKey = PhysicalKeyboardKey(0x00000011, debugName: kReleaseMode ? null : 'Super Key');
/// Represents the location of the "Fn" key on a generalized keyboard.
///
/// See the function [RawKeyEvent.physicalKey] for more information.
static const PhysicalKeyboardKey fn = PhysicalKeyboardKey(0x00000012, debugName: kReleaseMode ? null : 'Fn');
/// Represents the location of the "Fn Lock" key on a generalized keyboard.
///
/// See the function [RawKeyEvent.physicalKey] for more information.
......@@ -3526,13 +3521,17 @@ class PhysicalKeyboardKey extends KeyboardKey {
/// See the function [RawKeyEvent.physicalKey] for more information.
static const PhysicalKeyboardKey gameButtonZ = PhysicalKeyboardKey(0x0005ff1f, debugName: kReleaseMode ? null : 'Game Button Z');
/// Represents the location of the "Fn" key on a generalized keyboard.
///
/// See the function [RawKeyEvent.physicalKey] for more information.
static const PhysicalKeyboardKey fn = PhysicalKeyboardKey(0x00000012, debugName: kReleaseMode ? null : 'Fn');
// A list of all the predefined constant PhysicalKeyboardKeys so that they
// can be searched.
static const Map<int, PhysicalKeyboardKey> _knownPhysicalKeys = <int, PhysicalKeyboardKey>{
0x00000000: none,
0x00000010: hyper,
0x00000011: superKey,
0x00000012: fn,
0x00000013: fnLock,
0x00000014: suspend,
0x00000015: resume,
......@@ -3794,5 +3793,6 @@ class PhysicalKeyboardKey extends KeyboardKey {
0x0005ff1d: gameButtonX,
0x0005ff1e: gameButtonY,
0x0005ff1f: gameButtonZ,
0x00000012: fn,
};
}
......@@ -14,7 +14,6 @@ import 'keyboard_key.dart';
/// Maps Android-specific key codes to the matching [LogicalKeyboardKey].
const Map<int, LogicalKeyboardKey> kAndroidToLogicalKey = <int, LogicalKeyboardKey>{
0: LogicalKeyboardKey.none,
119: LogicalKeyboardKey.fn,
223: LogicalKeyboardKey.sleep,
224: LogicalKeyboardKey.wakeUp,
29: LogicalKeyboardKey.keyA,
......@@ -194,11 +193,11 @@ const Map<int, LogicalKeyboardKey> kAndroidToLogicalKey = <int, LogicalKeyboardK
99: LogicalKeyboardKey.gameButtonX,
100: LogicalKeyboardKey.gameButtonY,
101: LogicalKeyboardKey.gameButtonZ,
119: LogicalKeyboardKey.fn,
};
/// Maps Android-specific scan codes to the matching [PhysicalKeyboardKey].
const Map<int, PhysicalKeyboardKey> kAndroidToPhysicalKey = <int, PhysicalKeyboardKey>{
464: PhysicalKeyboardKey.fn,
205: PhysicalKeyboardKey.suspend,
142: PhysicalKeyboardKey.sleep,
143: PhysicalKeyboardKey.wakeUp,
......@@ -425,6 +424,7 @@ const Map<int, PhysicalKeyboardKey> kAndroidToPhysicalKey = <int, PhysicalKeyboa
307: PhysicalKeyboardKey.gameButtonX,
308: PhysicalKeyboardKey.gameButtonY,
309: PhysicalKeyboardKey.gameButtonZ,
464: PhysicalKeyboardKey.fn,
};
/// A map of Android key codes which have printable representations, but appear
......@@ -457,7 +457,6 @@ const Map<int, LogicalKeyboardKey> kFuchsiaToLogicalKey = <int, LogicalKeyboardK
0x100000000: LogicalKeyboardKey.none,
0x100000010: LogicalKeyboardKey.hyper,
0x100000011: LogicalKeyboardKey.superKey,
0x100000012: LogicalKeyboardKey.fn,
0x100000013: LogicalKeyboardKey.fnLock,
0x100000014: LogicalKeyboardKey.suspend,
0x100000015: LogicalKeyboardKey.resume,
......@@ -719,6 +718,7 @@ const Map<int, LogicalKeyboardKey> kFuchsiaToLogicalKey = <int, LogicalKeyboardK
0x10005ff1d: LogicalKeyboardKey.gameButtonX,
0x10005ff1e: LogicalKeyboardKey.gameButtonY,
0x10005ff1f: LogicalKeyboardKey.gameButtonZ,
0x100000012: LogicalKeyboardKey.fn,
};
/// Maps Fuchsia-specific USB HID Usage IDs to the matching
......@@ -727,7 +727,6 @@ const Map<int, PhysicalKeyboardKey> kFuchsiaToPhysicalKey = <int, PhysicalKeyboa
0x00000000: PhysicalKeyboardKey.none,
0x00000010: PhysicalKeyboardKey.hyper,
0x00000011: PhysicalKeyboardKey.superKey,
0x00000012: PhysicalKeyboardKey.fn,
0x00000013: PhysicalKeyboardKey.fnLock,
0x00000014: PhysicalKeyboardKey.suspend,
0x00000015: PhysicalKeyboardKey.resume,
......@@ -989,6 +988,7 @@ const Map<int, PhysicalKeyboardKey> kFuchsiaToPhysicalKey = <int, PhysicalKeyboa
0x0005ff1d: PhysicalKeyboardKey.gameButtonX,
0x0005ff1e: PhysicalKeyboardKey.gameButtonY,
0x0005ff1f: PhysicalKeyboardKey.gameButtonZ,
0x00000012: PhysicalKeyboardKey.fn,
};
/// Maps macOS-specific key code values representing [PhysicalKeyboardKey].
......@@ -1113,6 +1113,7 @@ const Map<int, PhysicalKeyboardKey> kMacOsToPhysicalKey = <int, PhysicalKeyboard
0x0000003c: PhysicalKeyboardKey.shiftRight,
0x0000003d: PhysicalKeyboardKey.altRight,
0x00000036: PhysicalKeyboardKey.metaRight,
0x0000003f: PhysicalKeyboardKey.fn,
};
/// A map of macOS key codes which have printable representations, but appear
......@@ -1138,6 +1139,31 @@ const Map<int, LogicalKeyboardKey> kMacOsNumPadMap = <int, LogicalKeyboardKey>{
0x0000005f: LogicalKeyboardKey.numpadComma,
};
/// A map of macOS key codes which are numbered function keys, so that they
/// can be excluded when asking "is the Fn modifier down?".
const Map<int, LogicalKeyboardKey> kMacOsFunctionKeyMap = <int, LogicalKeyboardKey>{
0x0000007a: LogicalKeyboardKey.f1,
0x00000078: LogicalKeyboardKey.f2,
0x00000063: LogicalKeyboardKey.f3,
0x00000076: LogicalKeyboardKey.f4,
0x00000060: LogicalKeyboardKey.f5,
0x00000061: LogicalKeyboardKey.f6,
0x00000062: LogicalKeyboardKey.f7,
0x00000064: LogicalKeyboardKey.f8,
0x00000065: LogicalKeyboardKey.f9,
0x0000006d: LogicalKeyboardKey.f10,
0x00000067: LogicalKeyboardKey.f11,
0x0000006f: LogicalKeyboardKey.f12,
0x00000069: LogicalKeyboardKey.f13,
0x0000006b: LogicalKeyboardKey.f14,
0x00000071: LogicalKeyboardKey.f15,
0x0000006a: LogicalKeyboardKey.f16,
0x00000040: LogicalKeyboardKey.f17,
0x0000004f: LogicalKeyboardKey.f18,
0x00000050: LogicalKeyboardKey.f19,
0x0000005a: LogicalKeyboardKey.f20,
};
/// Maps GLFW-specific key codes to the matching [LogicalKeyboardKey].
const Map<int, LogicalKeyboardKey> kGlfwToLogicalKey = <int, LogicalKeyboardKey>{
65: LogicalKeyboardKey.keyA,
......@@ -1498,7 +1524,6 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'None': LogicalKeyboardKey.none,
'Hyper': LogicalKeyboardKey.hyper,
'Super': LogicalKeyboardKey.superKey,
'Fn': LogicalKeyboardKey.fn,
'FnLock': LogicalKeyboardKey.fnLock,
'Suspend': LogicalKeyboardKey.suspend,
'Resume': LogicalKeyboardKey.resume,
......@@ -1723,6 +1748,7 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'GameButtonX': LogicalKeyboardKey.gameButtonX,
'GameButtonY': LogicalKeyboardKey.gameButtonY,
'GameButtonZ': LogicalKeyboardKey.gameButtonZ,
'Fn': LogicalKeyboardKey.fn,
};
/// Maps Web KeyboardEvent codes to the matching [PhysicalKeyboardKey].
......@@ -1730,7 +1756,6 @@ const Map<String, PhysicalKeyboardKey> kWebToPhysicalKey = <String, PhysicalKeyb
'None': PhysicalKeyboardKey.none,
'Hyper': PhysicalKeyboardKey.hyper,
'Super': PhysicalKeyboardKey.superKey,
'Fn': PhysicalKeyboardKey.fn,
'FnLock': PhysicalKeyboardKey.fnLock,
'Suspend': PhysicalKeyboardKey.suspend,
'Resume': PhysicalKeyboardKey.resume,
......@@ -1955,6 +1980,7 @@ const Map<String, PhysicalKeyboardKey> kWebToPhysicalKey = <String, PhysicalKeyb
'GameButtonX': PhysicalKeyboardKey.gameButtonX,
'GameButtonY': PhysicalKeyboardKey.gameButtonY,
'GameButtonZ': PhysicalKeyboardKey.gameButtonZ,
'Fn': PhysicalKeyboardKey.fn,
};
/// A map of Web KeyboardEvent codes which have printable representations, but appear
......
......@@ -502,6 +502,15 @@ class RawKeyboard {
if (event == null) {
return;
}
if (event.data is RawKeyEventDataMacOs && event.logicalKey == LogicalKeyboardKey.fn) {
// On macOS laptop keyboards, the fn key is used to generate home/end and
// f1-f12, but it ALSO generates a separate down/up event for the fn key
// itself. Other platforms hide the fn key, and just produce the key that
// it is combined with, so to keep it possible to write cross platform
// code that looks at which keys are pressed, the fn key is ignored on
// macOS.
return;
}
if (event is RawKeyDownEvent) {
_keysPressed.add(event.logicalKey);
}
......@@ -561,7 +570,6 @@ class RawKeyboard {
LogicalKeyboardKey.capsLock,
LogicalKeyboardKey.numLock,
LogicalKeyboardKey.scrollLock,
LogicalKeyboardKey.fn,
};
void _synchronizeModifiers(RawKeyEvent event) {
......@@ -580,6 +588,10 @@ class RawKeyboard {
// pressed/released while the app doesn't have focus, to make sure that
// _keysPressed reflects reality at all times.
_keysPressed.removeAll(_allModifiers);
if (event.data is! RawKeyEventDataFuchsia && event.data is! RawKeyEventDataMacOs) {
// On Fuchsia and macOS, the Fn key is not considered a modifier key.
_keysPressed.remove(LogicalKeyboardKey.fn);
}
_keysPressed.addAll(modifierKeys);
}
......
......@@ -22,7 +22,7 @@ void main() {
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft},
<LogicalKeyboardKey>{ LogicalKeyboardKey.shiftLeft },
),
reason: 'on $platform',
);
......@@ -64,12 +64,44 @@ void main() {
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft},
<LogicalKeyboardKey>{ LogicalKeyboardKey.shiftLeft },
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.shiftLeft, platform: platform);
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
// The Fn key isn't mapped on linux.
if (platform != 'linux') {
await simulateKeyDownEvent(LogicalKeyboardKey.fn, platform: platform);
expect(RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
if (platform != 'macos') LogicalKeyboardKey.fn,
},
),
reason: 'on $platform');
await simulateKeyDownEvent(LogicalKeyboardKey.f12, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
if (platform != 'macos') LogicalKeyboardKey.fn,
LogicalKeyboardKey.f12,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.fn, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{ LogicalKeyboardKey.f12 },
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.f12, platform: platform);
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
}
}
}, skip: kIsWeb);
......
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