[gen_keycodes] Remove nonexistent Web keys and improve their emulation (#87098)

This commit is contained in:
Tong Mu 2021-07-29 16:44:06 -07:00 committed by GitHub
parent c451b6b0d7
commit a7899c1961
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 175 additions and 738 deletions

File diff suppressed because it is too large Load Diff

View File

@ -11,11 +11,7 @@ import 'package:path/path.dart' as path;
import 'constants.dart';
import 'physical_key_data.dart';
bool _isControlCharacter(String label) {
if (label.length != 1) {
return false;
}
final int codeUnit = label.codeUnitAt(0);
bool _isControlCharacter(int codeUnit) {
return (codeUnit <= 0x1f && codeUnit >= 0x00) || (codeUnit >= 0x7f && codeUnit <= 0x9f);
}
@ -27,8 +23,11 @@ class _ModifierPair {
final String right;
}
List<T> _toNonEmptyArray<T>(dynamic source) {
final List<dynamic>? dynamicNullableList = source as List<dynamic>?;
// Return map[key1][key2] as a non-nullable List<T>, where both map[key1] or
// map[key1][key2] might be null.
List<T> _getGrandchildList<T>(Map<String, dynamic> map, String key1, String key2) {
final dynamic value = (map[key1] as Map<String, dynamic>?)?[key2];
final List<dynamic>? dynamicNullableList = value as List<dynamic>?;
final List<dynamic> dynamicList = dynamicNullableList ?? <dynamic>[];
return dynamicList.cast<T>();
}
@ -155,20 +154,23 @@ class LogicalKeyData {
final int value = match.namedGroup('unicode') != null ?
getHex(match.namedGroup('unicode')!) :
match.namedGroup('char')!.codeUnitAt(0);
final String? keyLabel = match.namedGroup('kind')! == 'UNI' ? String.fromCharCode(value) : null;
final String? keyLabel = (match.namedGroup('kind')! == 'UNI' && !_isControlCharacter(value)) ?
String.fromCharCode(value) : null;
// Skip modifier keys from DOM. They will be added with supplemental data.
if (_chromeModifiers.containsKey(name) && source == 'DOM') {
continue;
}
final bool isPrintable = (keyLabel != null && !_isControlCharacter(keyLabel))
|| printable.containsKey(name);
final bool isPrintable = keyLabel != null;
data.putIfAbsent(name, () {
return LogicalKeyEntry.fromName(
final LogicalKeyEntry entry = LogicalKeyEntry.fromName(
value: toPlane(value, _sourceToPlane(source, isPrintable)),
name: name,
keyLabel: keyLabel,
)..webNames.add(webName);
);
if (source == 'DOM' && !isPrintable)
entry.webNames.add(webName);
return entry;
});
}
}
@ -418,9 +420,11 @@ class LogicalKeyData {
})();
static int _sourceToPlane(String source, bool isPrintable) {
if (isPrintable)
return kUnicodePlane.value;
switch (source) {
case 'DOM':
return isPrintable ? kUnicodePlane.value : kUnprintablePlane.value;
return kUnprintablePlane.value;
case 'FLUTTER':
return kFlutterPlane.value;
default:
@ -470,20 +474,20 @@ class LogicalKeyEntry {
LogicalKeyEntry.fromJsonMapEntry(Map<String, dynamic> map)
: value = map['value'] as int,
name = map['name'] as String,
webNames = _toNonEmptyArray<String>((map['names'] as Map<String, dynamic>)['web']),
macOSKeyCodeNames = _toNonEmptyArray<String>((map['names'] as Map<String, dynamic>)['macos']),
macOSKeyCodeValues = _toNonEmptyArray<int>((map['values'] as Map<String, dynamic>?)?['macos']),
iOSKeyCodeNames = _toNonEmptyArray<String>((map['names'] as Map<String, dynamic>)['ios']),
iOSKeyCodeValues = _toNonEmptyArray<int>((map['values'] as Map<String, dynamic>?)?['ios']),
gtkNames = _toNonEmptyArray<String>((map['names'] as Map<String, dynamic>)['gtk']),
gtkValues = _toNonEmptyArray<int>((map['values'] as Map<String, dynamic>?)?['gtk']),
windowsNames = _toNonEmptyArray<String>((map['names'] as Map<String, dynamic>)['windows']),
windowsValues = _toNonEmptyArray<int>((map['values'] as Map<String, dynamic>?)?['windows']),
androidNames = _toNonEmptyArray<String>((map['names'] as Map<String, dynamic>)['android']),
androidValues = _toNonEmptyArray<int>((map['values'] as Map<String, dynamic>?)?['android']),
fuchsiaValues = _toNonEmptyArray<int>((map['values'] as Map<String, dynamic>?)?['fuchsia']),
glfwNames = _toNonEmptyArray<String>((map['names'] as Map<String, dynamic>)['glfw']),
glfwValues = _toNonEmptyArray<int>((map['values'] as Map<String, dynamic>?)?['glfw']),
webNames = _getGrandchildList<String>(map, 'names', 'web'),
macOSKeyCodeNames = _getGrandchildList<String>(map, 'names', 'macos'),
macOSKeyCodeValues = _getGrandchildList<int>(map, 'values', 'macos'),
iOSKeyCodeNames = _getGrandchildList<String>(map, 'names', 'ios'),
iOSKeyCodeValues = _getGrandchildList<int>(map, 'values', 'ios'),
gtkNames = _getGrandchildList<String>(map, 'names', 'gtk'),
gtkValues = _getGrandchildList<int>(map, 'values', 'gtk'),
windowsNames = _getGrandchildList<String>(map, 'names', 'windows'),
windowsValues = _getGrandchildList<int>(map, 'values', 'windows'),
androidNames = _getGrandchildList<String>(map, 'names', 'android'),
androidValues = _getGrandchildList<int>(map, 'values', 'android'),
fuchsiaValues = _getGrandchildList<int>(map, 'values', 'fuchsia'),
glfwNames = _getGrandchildList<String>(map, 'names', 'glfw'),
glfwValues = _getGrandchildList<int>(map, 'values', 'glfw'),
keyLabel = map['keyLabel'] as String?;
final int value;

View File

@ -22,8 +22,10 @@ String readDataFile(String fileName) {
return File(path.join(dataRoot, fileName)).readAsStringSync();
}
final String testPhysicalData = path.join(dataRoot, 'physical_key_data.json');
final String testLogicalData = path.join(dataRoot,'logical_key_data.json');
final PhysicalKeyData physicalData = PhysicalKeyData.fromJson(
json.decode(readDataFile('physical_key_data.json')) as Map<String, dynamic>);
final LogicalKeyData logicalData = LogicalKeyData.fromJson(
json.decode(readDataFile('logical_key_data.json')) as Map<String, dynamic>);
void main() {
setUp(() {
@ -45,14 +47,6 @@ void main() {
}
test('Generate Keycodes for Android', () {
PhysicalKeyData physicalData;
LogicalKeyData logicalData;
physicalData = PhysicalKeyData.fromJson(
json.decode(File(testPhysicalData).readAsStringSync()) as Map<String, dynamic>);
logicalData = LogicalKeyData.fromJson(
json.decode(File(testLogicalData).readAsStringSync()) as Map<String, dynamic>);
const String platform = 'android';
final PlatformCodeGenerator codeGenerator = AndroidCodeGenerator(
physicalData,
@ -67,14 +61,6 @@ void main() {
checkCommonOutput(output);
});
test('Generate Keycodes for macOS', () {
PhysicalKeyData physicalData;
LogicalKeyData logicalData;
physicalData = PhysicalKeyData.fromJson(
json.decode(File(testPhysicalData).readAsStringSync()) as Map<String, dynamic>);
logicalData = LogicalKeyData.fromJson(
json.decode(File(testLogicalData).readAsStringSync()) as Map<String, dynamic>);
const String platform = 'macos';
final PlatformCodeGenerator codeGenerator = MacOSCodeGenerator(
physicalData,
@ -93,14 +79,6 @@ void main() {
checkCommonOutput(output);
});
test('Generate Keycodes for iOS', () {
PhysicalKeyData physicalData;
LogicalKeyData logicalData;
physicalData = PhysicalKeyData.fromJson(
json.decode(File(testPhysicalData).readAsStringSync()) as Map<String, dynamic>);
logicalData = LogicalKeyData.fromJson(
json.decode(File(testLogicalData).readAsStringSync()) as Map<String, dynamic>);
const String platform = 'ios';
final PlatformCodeGenerator codeGenerator = IOSCodeGenerator(
physicalData,
@ -120,14 +98,6 @@ void main() {
checkCommonOutput(output);
});
test('Generate Keycodes for Windows', () {
PhysicalKeyData physicalData;
LogicalKeyData logicalData;
physicalData = PhysicalKeyData.fromJson(
json.decode(File(testPhysicalData).readAsStringSync()) as Map<String, dynamic>);
logicalData = LogicalKeyData.fromJson(
json.decode(File(testLogicalData).readAsStringSync()) as Map<String, dynamic>);
const String platform = 'windows';
final PlatformCodeGenerator codeGenerator = WindowsCodeGenerator(
physicalData,
@ -143,14 +113,6 @@ void main() {
checkCommonOutput(output);
});
test('Generate Keycodes for Linux', () {
PhysicalKeyData physicalData;
LogicalKeyData logicalData;
physicalData = PhysicalKeyData.fromJson(
json.decode(File(testPhysicalData).readAsStringSync()) as Map<String, dynamic>);
logicalData = LogicalKeyData.fromJson(
json.decode(File(testLogicalData).readAsStringSync()) as Map<String, dynamic>);
const String platform = 'gtk';
final PlatformCodeGenerator codeGenerator = GtkCodeGenerator(
physicalData,
@ -166,14 +128,6 @@ void main() {
checkCommonOutput(output);
});
test('Generate Keycodes for Web', () {
PhysicalKeyData physicalData;
LogicalKeyData logicalData;
physicalData = PhysicalKeyData.fromJson(
json.decode(File(testPhysicalData).readAsStringSync()) as Map<String, dynamic>);
logicalData = LogicalKeyData.fromJson(
json.decode(File(testLogicalData).readAsStringSync()) as Map<String, dynamic>);
const String platform = 'web';
final PlatformCodeGenerator codeGenerator = WebCodeGenerator(
physicalData,
@ -188,4 +142,25 @@ void main() {
expect(output, contains('kWebLogicalLocationMap'));
checkCommonOutput(output);
});
test('LogicalKeyData', () async {
final List<LogicalKeyEntry> entries = logicalData.entries.toList();
// Regression tests for https://github.com/flutter/flutter/pull/87098
expect(
entries.indexWhere((LogicalKeyEntry entry) => entry.name == 'ShiftLeft'),
isNot(-1));
expect(
entries.indexWhere((LogicalKeyEntry entry) => entry.webNames.contains('ShiftLeft')),
-1);
// 'Shift' maps to both 'ShiftLeft' and 'ShiftRight', and should be resolved
// by other ways.
expect(
entries.indexWhere((LogicalKeyEntry entry) => entry.webNames.contains('Shift')),
-1);
// Printable keys must not be added with Web key of their names.
expect(
entries.indexWhere((LogicalKeyEntry entry) => entry.webNames.contains('Slash')),
-1);
});
}

View File

@ -2141,25 +2141,17 @@ const Map<int, PhysicalKeyboardKey> kLinuxToPhysicalKey = <int, PhysicalKeyboard
const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboardKey>{
'AVRInput': LogicalKeyboardKey.avrInput,
'AVRPower': LogicalKeyboardKey.avrPower,
'Abort': LogicalKeyboardKey.abort,
'Accel': LogicalKeyboardKey.accel,
'Accept': LogicalKeyboardKey.accept,
'Add': LogicalKeyboardKey.add,
'Again': LogicalKeyboardKey.again,
'AllCandidates': LogicalKeyboardKey.allCandidates,
'Alphanumeric': LogicalKeyboardKey.alphanumeric,
'Alt': LogicalKeyboardKey.alt,
'AltGraph': LogicalKeyboardKey.altGraph,
'AltLeft': LogicalKeyboardKey.altLeft,
'AltRight': LogicalKeyboardKey.altRight,
'Ampersand': LogicalKeyboardKey.ampersand,
'AppSwitch': LogicalKeyboardKey.appSwitch,
'ArrowDown': LogicalKeyboardKey.arrowDown,
'ArrowLeft': LogicalKeyboardKey.arrowLeft,
'ArrowRight': LogicalKeyboardKey.arrowRight,
'ArrowUp': LogicalKeyboardKey.arrowUp,
'Asterisk': LogicalKeyboardKey.asterisk,
'At': LogicalKeyboardKey.at,
'Attn': LogicalKeyboardKey.attn,
'AudioBalanceLeft': LogicalKeyboardKey.audioBalanceLeft,
'AudioBalanceRight': LogicalKeyboardKey.audioBalanceRight,
@ -2174,14 +2166,7 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'AudioVolumeDown': LogicalKeyboardKey.audioVolumeDown,
'AudioVolumeMute': LogicalKeyboardKey.audioVolumeMute,
'AudioVolumeUp': LogicalKeyboardKey.audioVolumeUp,
'Backquote': LogicalKeyboardKey.backquote,
'Backslash': LogicalKeyboardKey.backslash,
'Backspace': LogicalKeyboardKey.backspace,
'Bar': LogicalKeyboardKey.bar,
'BraceLeft': LogicalKeyboardKey.braceLeft,
'BraceRight': LogicalKeyboardKey.braceRight,
'BracketLeft': LogicalKeyboardKey.bracketLeft,
'BracketRight': LogicalKeyboardKey.bracketRight,
'BrightnessDown': LogicalKeyboardKey.brightnessDown,
'BrightnessUp': LogicalKeyboardKey.brightnessUp,
'BrowserBack': LogicalKeyboardKey.browserBack,
@ -2196,55 +2181,36 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'CameraFocus': LogicalKeyboardKey.cameraFocus,
'Cancel': LogicalKeyboardKey.cancel,
'CapsLock': LogicalKeyboardKey.capsLock,
'Caret': LogicalKeyboardKey.caret,
'ChannelDown': LogicalKeyboardKey.channelDown,
'ChannelUp': LogicalKeyboardKey.channelUp,
'Clear': LogicalKeyboardKey.clear,
'Close': LogicalKeyboardKey.close,
'ClosedCaptionToggle': LogicalKeyboardKey.closedCaptionToggle,
'CodeInput': LogicalKeyboardKey.codeInput,
'Colon': LogicalKeyboardKey.colon,
'ColorF0Red': LogicalKeyboardKey.colorF0Red,
'ColorF1Green': LogicalKeyboardKey.colorF1Green,
'ColorF2Yellow': LogicalKeyboardKey.colorF2Yellow,
'ColorF3Blue': LogicalKeyboardKey.colorF3Blue,
'ColorF4Grey': LogicalKeyboardKey.colorF4Grey,
'ColorF5Brown': LogicalKeyboardKey.colorF5Brown,
'Comma': LogicalKeyboardKey.comma,
'Compose': LogicalKeyboardKey.compose,
'ContextMenu': LogicalKeyboardKey.contextMenu,
'Control': LogicalKeyboardKey.control,
'ControlLeft': LogicalKeyboardKey.controlLeft,
'ControlRight': LogicalKeyboardKey.controlRight,
'Convert': LogicalKeyboardKey.convert,
'Copy': LogicalKeyboardKey.copy,
'CrSel': LogicalKeyboardKey.crSel,
'Cut': LogicalKeyboardKey.cut,
'DVR': LogicalKeyboardKey.dvr,
'Delete': LogicalKeyboardKey.delete,
'Digit0': LogicalKeyboardKey.digit0,
'Digit1': LogicalKeyboardKey.digit1,
'Digit2': LogicalKeyboardKey.digit2,
'Digit3': LogicalKeyboardKey.digit3,
'Digit4': LogicalKeyboardKey.digit4,
'Digit5': LogicalKeyboardKey.digit5,
'Digit6': LogicalKeyboardKey.digit6,
'Digit7': LogicalKeyboardKey.digit7,
'Digit8': LogicalKeyboardKey.digit8,
'Digit9': LogicalKeyboardKey.digit9,
'Dimmer': LogicalKeyboardKey.dimmer,
'DisplaySwap': LogicalKeyboardKey.displaySwap,
'Dollar': LogicalKeyboardKey.dollar,
'Eisu': LogicalKeyboardKey.eisu,
'Eject': LogicalKeyboardKey.eject,
'End': LogicalKeyboardKey.end,
'EndCall': LogicalKeyboardKey.endCall,
'Enter': LogicalKeyboardKey.enter,
'Equal': LogicalKeyboardKey.equal,
'EraseEof': LogicalKeyboardKey.eraseEof,
'Escape': LogicalKeyboardKey.escape,
'ExSel': LogicalKeyboardKey.exSel,
'Exclamation': LogicalKeyboardKey.exclamation,
'Execute': LogicalKeyboardKey.execute,
'Exit': LogicalKeyboardKey.exit,
'F1': LogicalKeyboardKey.f1,
@ -2287,40 +2253,8 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'Find': LogicalKeyboardKey.find,
'Fn': LogicalKeyboardKey.fn,
'FnLock': LogicalKeyboardKey.fnLock,
'GameButton1': LogicalKeyboardKey.gameButton1,
'GameButton10': LogicalKeyboardKey.gameButton10,
'GameButton11': LogicalKeyboardKey.gameButton11,
'GameButton12': LogicalKeyboardKey.gameButton12,
'GameButton13': LogicalKeyboardKey.gameButton13,
'GameButton14': LogicalKeyboardKey.gameButton14,
'GameButton15': LogicalKeyboardKey.gameButton15,
'GameButton16': LogicalKeyboardKey.gameButton16,
'GameButton2': LogicalKeyboardKey.gameButton2,
'GameButton3': LogicalKeyboardKey.gameButton3,
'GameButton4': LogicalKeyboardKey.gameButton4,
'GameButton5': LogicalKeyboardKey.gameButton5,
'GameButton6': LogicalKeyboardKey.gameButton6,
'GameButton7': LogicalKeyboardKey.gameButton7,
'GameButton8': LogicalKeyboardKey.gameButton8,
'GameButton9': LogicalKeyboardKey.gameButton9,
'GameButtonA': LogicalKeyboardKey.gameButtonA,
'GameButtonB': LogicalKeyboardKey.gameButtonB,
'GameButtonC': LogicalKeyboardKey.gameButtonC,
'GameButtonLeft1': LogicalKeyboardKey.gameButtonLeft1,
'GameButtonLeft2': LogicalKeyboardKey.gameButtonLeft2,
'GameButtonMode': LogicalKeyboardKey.gameButtonMode,
'GameButtonRight1': LogicalKeyboardKey.gameButtonRight1,
'GameButtonRight2': LogicalKeyboardKey.gameButtonRight2,
'GameButtonSelect': LogicalKeyboardKey.gameButtonSelect,
'GameButtonStart': LogicalKeyboardKey.gameButtonStart,
'GameButtonThumbLeft': LogicalKeyboardKey.gameButtonThumbLeft,
'GameButtonThumbRight': LogicalKeyboardKey.gameButtonThumbRight,
'GameButtonX': LogicalKeyboardKey.gameButtonX,
'GameButtonY': LogicalKeyboardKey.gameButtonY,
'GameButtonZ': LogicalKeyboardKey.gameButtonZ,
'GoBack': LogicalKeyboardKey.goBack,
'GoHome': LogicalKeyboardKey.goHome,
'Greater': LogicalKeyboardKey.greater,
'GroupFirst': LogicalKeyboardKey.groupFirst,
'GroupLast': LogicalKeyboardKey.groupLast,
'GroupNext': LogicalKeyboardKey.groupNext,
@ -2341,46 +2275,12 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'Info': LogicalKeyboardKey.info,
'Insert': LogicalKeyboardKey.insert,
'InstantReplay': LogicalKeyboardKey.instantReplay,
'IntlBackslash': LogicalKeyboardKey.intlBackslash,
'IntlRo': LogicalKeyboardKey.intlRo,
'IntlYen': LogicalKeyboardKey.intlYen,
'JunjaMode': LogicalKeyboardKey.junjaMode,
'KanaMode': LogicalKeyboardKey.kanaMode,
'KanjiMode': LogicalKeyboardKey.kanjiMode,
'Katakana': LogicalKeyboardKey.katakana,
'Key11': LogicalKeyboardKey.key11,
'Key12': LogicalKeyboardKey.key12,
'KeyA': LogicalKeyboardKey.keyA,
'KeyB': LogicalKeyboardKey.keyB,
'KeyC': LogicalKeyboardKey.keyC,
'KeyD': LogicalKeyboardKey.keyD,
'KeyE': LogicalKeyboardKey.keyE,
'KeyF': LogicalKeyboardKey.keyF,
'KeyG': LogicalKeyboardKey.keyG,
'KeyH': LogicalKeyboardKey.keyH,
'KeyI': LogicalKeyboardKey.keyI,
'KeyJ': LogicalKeyboardKey.keyJ,
'KeyK': LogicalKeyboardKey.keyK,
'KeyL': LogicalKeyboardKey.keyL,
'KeyM': LogicalKeyboardKey.keyM,
'KeyN': LogicalKeyboardKey.keyN,
'KeyO': LogicalKeyboardKey.keyO,
'KeyP': LogicalKeyboardKey.keyP,
'KeyQ': LogicalKeyboardKey.keyQ,
'KeyR': LogicalKeyboardKey.keyR,
'KeyS': LogicalKeyboardKey.keyS,
'KeyT': LogicalKeyboardKey.keyT,
'KeyU': LogicalKeyboardKey.keyU,
'KeyV': LogicalKeyboardKey.keyV,
'KeyW': LogicalKeyboardKey.keyW,
'KeyX': LogicalKeyboardKey.keyX,
'KeyY': LogicalKeyboardKey.keyY,
'KeyZ': LogicalKeyboardKey.keyZ,
'Lang1': LogicalKeyboardKey.lang1,
'Lang2': LogicalKeyboardKey.lang2,
'Lang3': LogicalKeyboardKey.lang3,
'Lang4': LogicalKeyboardKey.lang4,
'Lang5': LogicalKeyboardKey.lang5,
'LastNumberRedial': LogicalKeyboardKey.lastNumberRedial,
'LaunchApplication1': LogicalKeyboardKey.launchApplication1,
'LaunchApplication2': LogicalKeyboardKey.launchApplication2,
@ -2397,7 +2297,6 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'LaunchWebBrowser': LogicalKeyboardKey.launchWebBrowser,
'LaunchWebCam': LogicalKeyboardKey.launchWebCam,
'LaunchWordProcessor': LogicalKeyboardKey.launchWordProcessor,
'Less': LogicalKeyboardKey.less,
'Link': LogicalKeyboardKey.link,
'ListProgram': LogicalKeyboardKey.listProgram,
'LiveContent': LogicalKeyboardKey.liveContent,
@ -2426,14 +2325,10 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'MediaTopMenu': LogicalKeyboardKey.mediaTopMenu,
'MediaTrackNext': LogicalKeyboardKey.mediaTrackNext,
'MediaTrackPrevious': LogicalKeyboardKey.mediaTrackPrevious,
'Meta': LogicalKeyboardKey.meta,
'MetaLeft': LogicalKeyboardKey.metaLeft,
'MetaRight': LogicalKeyboardKey.metaRight,
'MicrophoneToggle': LogicalKeyboardKey.microphoneToggle,
'MicrophoneVolumeDown': LogicalKeyboardKey.microphoneVolumeDown,
'MicrophoneVolumeMute': LogicalKeyboardKey.microphoneVolumeMute,
'MicrophoneVolumeUp': LogicalKeyboardKey.microphoneVolumeUp,
'Minus': LogicalKeyboardKey.minus,
'ModeChange': LogicalKeyboardKey.modeChange,
'NavigateIn': LogicalKeyboardKey.navigateIn,
'NavigateNext': LogicalKeyboardKey.navigateNext,
@ -2446,38 +2341,13 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'NonConvert': LogicalKeyboardKey.nonConvert,
'Notification': LogicalKeyboardKey.notification,
'NumLock': LogicalKeyboardKey.numLock,
'NumberSign': LogicalKeyboardKey.numberSign,
'Numpad0': LogicalKeyboardKey.numpad0,
'Numpad1': LogicalKeyboardKey.numpad1,
'Numpad2': LogicalKeyboardKey.numpad2,
'Numpad3': LogicalKeyboardKey.numpad3,
'Numpad4': LogicalKeyboardKey.numpad4,
'Numpad5': LogicalKeyboardKey.numpad5,
'Numpad6': LogicalKeyboardKey.numpad6,
'Numpad7': LogicalKeyboardKey.numpad7,
'Numpad8': LogicalKeyboardKey.numpad8,
'Numpad9': LogicalKeyboardKey.numpad9,
'NumpadAdd': LogicalKeyboardKey.numpadAdd,
'NumpadComma': LogicalKeyboardKey.numpadComma,
'NumpadDecimal': LogicalKeyboardKey.numpadDecimal,
'NumpadDivide': LogicalKeyboardKey.numpadDivide,
'NumpadEnter': LogicalKeyboardKey.numpadEnter,
'NumpadEqual': LogicalKeyboardKey.numpadEqual,
'NumpadMultiply': LogicalKeyboardKey.numpadMultiply,
'NumpadParenLeft': LogicalKeyboardKey.numpadParenLeft,
'NumpadParenRight': LogicalKeyboardKey.numpadParenRight,
'NumpadSubtract': LogicalKeyboardKey.numpadSubtract,
'OnDemand': LogicalKeyboardKey.onDemand,
'Open': LogicalKeyboardKey.open,
'PageDown': LogicalKeyboardKey.pageDown,
'PageUp': LogicalKeyboardKey.pageUp,
'Pairing': LogicalKeyboardKey.pairing,
'ParenthesisLeft': LogicalKeyboardKey.parenthesisLeft,
'ParenthesisRight': LogicalKeyboardKey.parenthesisRight,
'Paste': LogicalKeyboardKey.paste,
'Pause': LogicalKeyboardKey.pause,
'Percent': LogicalKeyboardKey.percent,
'Period': LogicalKeyboardKey.period,
'PinPDown': LogicalKeyboardKey.pInPDown,
'PinPMove': LogicalKeyboardKey.pInPMove,
'PinPToggle': LogicalKeyboardKey.pInPToggle,
@ -2493,14 +2363,10 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'PrintScreen': LogicalKeyboardKey.printScreen,
'Process': LogicalKeyboardKey.process,
'Props': LogicalKeyboardKey.props,
'Question': LogicalKeyboardKey.question,
'Quote': LogicalKeyboardKey.quote,
'QuoteSingle': LogicalKeyboardKey.quoteSingle,
'RandomToggle': LogicalKeyboardKey.randomToggle,
'RcLowBattery': LogicalKeyboardKey.rcLowBattery,
'RecordSpeedNext': LogicalKeyboardKey.recordSpeedNext,
'Redo': LogicalKeyboardKey.redo,
'Resume': LogicalKeyboardKey.resume,
'RfBypass': LogicalKeyboardKey.rfBypass,
'Romaji': LogicalKeyboardKey.romaji,
'STBInput': LogicalKeyboardKey.stbInput,
@ -2510,15 +2376,9 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'ScreenModeNext': LogicalKeyboardKey.screenModeNext,
'ScrollLock': LogicalKeyboardKey.scrollLock,
'Select': LogicalKeyboardKey.select,
'Semicolon': LogicalKeyboardKey.semicolon,
'Settings': LogicalKeyboardKey.settings,
'Shift': LogicalKeyboardKey.shift,
'ShiftLeft': LogicalKeyboardKey.shiftLeft,
'ShiftLevel5': LogicalKeyboardKey.shiftLevel5,
'ShiftRight': LogicalKeyboardKey.shiftRight,
'SingleCandidate': LogicalKeyboardKey.singleCandidate,
'Slash': LogicalKeyboardKey.slash,
'Sleep': LogicalKeyboardKey.sleep,
'Soft1': LogicalKeyboardKey.soft1,
'Soft2': LogicalKeyboardKey.soft2,
'Soft3': LogicalKeyboardKey.soft3,
@ -2527,7 +2387,6 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'Soft6': LogicalKeyboardKey.soft6,
'Soft7': LogicalKeyboardKey.soft7,
'Soft8': LogicalKeyboardKey.soft8,
'Space': LogicalKeyboardKey.space,
'SpeechCorrectionList': LogicalKeyboardKey.speechCorrectionList,
'SpeechInputToggle': LogicalKeyboardKey.speechInputToggle,
'SpellCheck': LogicalKeyboardKey.spellCheck,
@ -2535,7 +2394,6 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'Standby': LogicalKeyboardKey.standby,
'Subtitle': LogicalKeyboardKey.subtitle,
'Super': LogicalKeyboardKey.superKey,
'Suspend': LogicalKeyboardKey.suspend,
'Symbol': LogicalKeyboardKey.symbol,
'SymbolLock': LogicalKeyboardKey.symbolLock,
'TV': LogicalKeyboardKey.tv,
@ -2570,8 +2428,6 @@ const Map<String, LogicalKeyboardKey> kWebToLogicalKey = <String, LogicalKeyboar
'TVTimer': LogicalKeyboardKey.tvTimer,
'Tab': LogicalKeyboardKey.tab,
'Teletext': LogicalKeyboardKey.teletext,
'Tilde': LogicalKeyboardKey.tilde,
'Underscore': LogicalKeyboardKey.underscore,
'Undo': LogicalKeyboardKey.undo,
'Unidentified': LogicalKeyboardKey.unidentified,
'VideoModeNext': LogicalKeyboardKey.videoModeNext,

View File

@ -285,12 +285,10 @@ abstract class RawKeyEvent with Diagnosticable {
/// Creates a concrete [RawKeyEvent] class from a message in the form received
/// on the [SystemChannels.keyEvent] channel.
factory RawKeyEvent.fromMessage(Map<String, dynamic> message) {
final RawKeyEventData data;
String? character;
RawKeyEventData _dataFromWeb() {
final String? key = message['key'] as String?;
if (key != null && key.isNotEmpty) {
if (key != null && key.isNotEmpty && key.length == 1) {
character = key;
}
return RawKeyEventDataWeb(
@ -300,6 +298,8 @@ abstract class RawKeyEvent with Diagnosticable {
metaState: message['metaState'] as int? ?? 0,
);
}
final RawKeyEventData data;
if (kIsWeb) {
data = _dataFromWeb();
} else {

View File

@ -10,6 +10,13 @@ import 'keyboard_key.dart';
import 'keyboard_maps.dart';
import 'raw_keyboard.dart';
String? _unicodeChar(String key) {
if (key.length == 1) {
return key.substring(0, 1);
}
return null;
}
/// Platform-specific key event data for Web.
///
/// See also:
@ -74,7 +81,7 @@ class RawKeyEventDataWeb extends RawKeyEventData {
final int metaState;
@override
String get keyLabel => key == 'Unidentified' ? '' : key;
String get keyLabel => key == 'Unidentified' ? '' : _unicodeChar(key) ?? '';
@override
PhysicalKeyboardKey get physicalKey {
@ -95,9 +102,14 @@ class RawKeyEventDataWeb extends RawKeyEventData {
return newKey;
}
final bool isPrintable = key.length == 1;
if (isPrintable)
return LogicalKeyboardKey(key.codeUnitAt(0));
// This is a non-printable key that we don't know about, so we mint a new
// code.
return LogicalKeyboardKey(code.hashCode | LogicalKeyboardKey.webPlane);
// key from `code`. Don't mint with `key`, because the `key` will always be
// "Unidentified" .
return LogicalKeyboardKey(code.hashCode + LogicalKeyboardKey.webPlane);
}
@override

View File

@ -2400,6 +2400,7 @@ void main() {
'keymap': 'web',
'code': 'KeyA',
'key': 'a',
'location': 0,
'metaState': 0x0,
});
final RawKeyEventDataWeb data = keyAEvent.data as RawKeyEventDataWeb;
@ -2413,6 +2414,8 @@ void main() {
'type': 'keydown',
'keymap': 'web',
'code': 'Escape',
'key': 'Escape',
'location': 0,
'metaState': 0x0,
});
final RawKeyEventDataWeb data = escapeKeyEvent.data as RawKeyEventDataWeb;
@ -2426,6 +2429,8 @@ void main() {
'type': 'keydown',
'keymap': 'web',
'code': 'ShiftLeft',
'key': 'Shift',
'location': 1,
'metaState': RawKeyEventDataWeb.modifierShift,
});
final RawKeyEventDataWeb data = shiftKeyEvent.data as RawKeyEventDataWeb;
@ -2439,6 +2444,8 @@ void main() {
'type': 'keydown',
'keymap': 'web',
'code': 'ArrowDown',
'key': 'ArrowDown',
'location': 0,
'metaState': 0x0,
});
final RawKeyEventDataWeb data = arrowKeyDown.data as RawKeyEventDataWeb;
@ -2447,6 +2454,25 @@ void main() {
expect(data.keyLabel, isEmpty);
});
test('Unrecognized keys are mapped to Web plane', () {
final RawKeyEvent arrowKeyDown = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'Unrecog1',
'key': 'Unrecog2',
'location': 0,
'metaState': 0x0,
});
final RawKeyEventDataWeb data = arrowKeyDown.data as RawKeyEventDataWeb;
// This might be easily broken on Web if the code fails to acknowledge
// that JavaScript doesn't handle 64-bit bit-wise operation.
expect(data.physicalKey.usbHidUsage, greaterThan(0x01700000000));
expect(data.physicalKey.usbHidUsage, lessThan(0x01800000000));
expect(data.logicalKey.keyId, greaterThan(0x01700000000));
expect(data.logicalKey.keyId, lessThan(0x01800000000));
expect(data.keyLabel, isEmpty);
});
test('data.toString', () {
expect(RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',

View File

@ -14,6 +14,16 @@ import 'package:flutter_test/flutter_test.dart';
import 'binding.dart';
import 'test_async_utils.dart';
// A tuple of `key` and `location` from Web's `KeyboardEvent` class.
//
// See [RawKeyEventDataWeb]'s `key` and `location` fields for details.
@immutable
class _WebKeyLocationPair {
const _WebKeyLocationPair(this.key, this.location);
final String key;
final int location;
}
// TODO(gspencergoog): Replace this with more robust key simulation code once
// the new key event code is in.
// https://github.com/flutter/flutter/issues/33521
@ -145,8 +155,32 @@ class KeyEventSimulator {
}
}
static String _getWebKeyCode(LogicalKeyboardKey key) {
static PhysicalKeyboardKey _inferPhysicalKey(LogicalKeyboardKey key) {
PhysicalKeyboardKey? result;
for (final PhysicalKeyboardKey physicalKey in PhysicalKeyboardKey.knownPhysicalKeys) {
if (physicalKey.debugName == key.debugName) {
result = physicalKey;
break;
}
}
assert(result != null, 'Unable to infer physical key for $key');
return result!;
}
static _WebKeyLocationPair _getWebKeyLocation(LogicalKeyboardKey key, String keyLabel) {
String? result;
for (final MapEntry<String, List<LogicalKeyboardKey?>> entry in kWebLocationMap.entries) {
final int foundIndex = entry.value.indexOf(key);
// If foundIndex is -1, then the key is not defined in kWebLocationMap.
// If foundIndex is 0, then the key is in the standard part of the keyboard,
// but we have to check `keyLabel` to see if it's remapped or modified.
if (foundIndex != -1 && foundIndex != 0) {
return _WebKeyLocationPair(entry.key, foundIndex);
}
}
if (keyLabel.isNotEmpty) {
return _WebKeyLocationPair(keyLabel, 0);
}
for (final String code in kWebToLogicalKey.keys) {
if (key.keyId == kWebToLogicalKey[code]!.keyId) {
result = code;
@ -154,6 +188,18 @@ class KeyEventSimulator {
}
}
assert(result != null, 'Key $key not found in web keyCode map');
return _WebKeyLocationPair(result!, 0);
}
static String _getWebCode(PhysicalKeyboardKey key) {
String? result;
for (final MapEntry<String, PhysicalKeyboardKey> entry in kWebToPhysicalKey.entries) {
if (entry.value.usbHidUsage == key.usbHidUsage) {
result = entry.key;
break;
}
}
assert(result != null, 'Key $key not found in web code map');
return result!;
}
@ -215,8 +261,6 @@ class KeyEventSimulator {
physicalKey ??= _findPhysicalKeyByPlatform(key, platform);
assert(key.debugName != null);
final int keyCode = _getKeyCode(key, platform);
final int scanCode = _getScanCode(physicalKey, platform);
final Map<String, dynamic> result = <String, dynamic>{
'type': isDown ? 'keydown' : 'keyup',
@ -225,14 +269,19 @@ class KeyEventSimulator {
final String resultCharacter = character ?? _keyLabel(key) ?? '';
void assignWeb() {
result['code'] = _getWebKeyCode(key);
result['key'] = resultCharacter;
final _WebKeyLocationPair keyLocation = _getWebKeyLocation(key, resultCharacter);
final PhysicalKeyboardKey actualPhysicalKey = physicalKey ?? _inferPhysicalKey(key);
result['code'] = _getWebCode(actualPhysicalKey);
result['key'] = keyLocation.key;
result['location'] = keyLocation.location;
result['metaState'] = _getWebModifierFlags(key, isDown);
}
if (kIsWeb) {
assignWeb();
return result;
}
final int keyCode = _getKeyCode(key, platform);
final int scanCode = _getScanCode(physicalKey, platform);
switch (platform) {
case 'android':