[windows] Adds support for keyboard mapping. (#54227)
diff --git a/dev/manual_tests/lib/raw_keyboard.dart b/dev/manual_tests/lib/raw_keyboard.dart index c329d60..5c50210 100644 --- a/dev/manual_tests/lib/raw_keyboard.dart +++ b/dev/manual_tests/lib/raw_keyboard.dart
@@ -109,6 +109,11 @@ dataText.add(Text('scanCode: ${data.scanCode}')); dataText.add(Text('unicodeScalarValues: ${data.unicodeScalarValues}')); dataText.add(Text('modifiers: ${data.modifiers} (${_asHex(data.modifiers)})')); + } else if (data is RawKeyEventDataWindows) { + dataText.add(Text('keyCode: ${data.keyCode} (${_asHex(data.keyCode)})')); + dataText.add(Text('scanCode: ${data.scanCode}')); + dataText.add(Text('characterCodePoint: ${data.characterCodePoint}')); + 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/bin/gen_keycodes.dart b/dev/tools/gen_keycodes/bin/gen_keycodes.dart index dc4241e..c8fdf13 100644 --- a/dev/tools/gen_keycodes/bin/gen_keycodes.dart +++ b/dev/tools/gen_keycodes/bin/gen_keycodes.dart
@@ -18,8 +18,8 @@ /// Get contents of the file that contains the key code mapping in Chromium /// source. Future<String> getChromiumConversions() async { - final Uri keyCodeMapUri = Uri.parse('https://cs.chromium.org/codesearch/f/chromium/src/ui/events/keycodes/dom/dom_code_data.inc'); - return await http.read(keyCodeMapUri); + final Uri keyCodesUri = Uri.parse('https://chromium.googlesource.com/codesearch/chromium/src/+/refs/heads/master/ui/events/keycodes/dom/dom_code_data.inc?format=TEXT'); + return utf8.decode(base64.decode(await http.read(keyCodesUri))); } /// Get contents of the file that contains the key codes in Android source.
diff --git a/dev/tools/gen_keycodes/data/key_data.json b/dev/tools/gen_keycodes/data/key_data.json index b4c56f7..5179151 100644 --- a/dev/tools/gen_keycodes/data/key_data.json +++ b/dev/tools/gen_keycodes/data/key_data.json
@@ -191,29 +191,6 @@ "windows": null } }, - "privacyScreenToggle": { - "names": { - "domkey": "PrivacyScreenToggle", - "android": null, - "english": "Privacy Screen Toggle", - "chromium": "privacyScreenToggle", - "glfw": null, - "windows": null - }, - "scanCodes": { - "android": null, - "usb": 23, - "linux": 633, - "xkb": 641, - "windows": null, - "macos": null - }, - "keyCodes": { - "android": null, - "glfw": null, - "windows": null - } - }, "sleep": { "names": { "domkey": "Sleep", @@ -3080,7 +3057,9 @@ "glfw": [ "RIGHT" ], - "windows": null + "windows": [ + "RIGHT" + ] }, "scanCodes": { "android": [ @@ -3099,7 +3078,9 @@ "glfw": [ 262 ], - "windows": null + "windows": [ + 39 + ] } }, "arrowLeft": { @@ -3113,7 +3094,9 @@ "glfw": [ "LEFT" ], - "windows": null + "windows": [ + "LEFT" + ] }, "scanCodes": { "android": [ @@ -3132,7 +3115,9 @@ "glfw": [ 263 ], - "windows": null + "windows": [ + 37 + ] } }, "arrowDown": { @@ -3146,7 +3131,9 @@ "glfw": [ "DOWN" ], - "windows": null + "windows": [ + "DOWN" + ] }, "scanCodes": { "android": [ @@ -3165,7 +3152,9 @@ "glfw": [ 264 ], - "windows": null + "windows": [ + 40 + ] } }, "arrowUp": { @@ -3179,7 +3168,9 @@ "glfw": [ "UP" ], - "windows": null + "windows": [ + "UP" + ] }, "scanCodes": { "android": [ @@ -3198,7 +3189,9 @@ "glfw": [ 265 ], - "windows": null + "windows": [ + 38 + ] } }, "numLock": { @@ -8276,4 +8269,4 @@ "windows": null } } -} +} \ No newline at end of file
diff --git a/dev/tools/gen_keycodes/data/key_name_to_windows_name.json b/dev/tools/gen_keycodes/data/key_name_to_windows_name.json index d5abd23..2513e1d 100644 --- a/dev/tools/gen_keycodes/data/key_name_to_windows_name.json +++ b/dev/tools/gen_keycodes/data/key_name_to_windows_name.json
@@ -31,10 +31,10 @@ "next": ["NEXT"], "end": ["END"], "home": ["HOME"], - "leftArrow": ["LEFT"], - "upArrow": ["UP"], - "rightArrow": ["RIGHT"], - "downArrow": ["DOWN"], + "arrowLeft": ["LEFT"], + "arrowUp": ["UP"], + "arrowRight": ["RIGHT"], + "arrowDown": ["DOWN"], "select": ["SELECT"], "print": ["PRINT"], "execute": ["EXECUTE"],
diff --git a/dev/tools/gen_keycodes/data/keyboard_key.tmpl b/dev/tools/gen_keycodes/data/keyboard_key.tmpl index ca6c54c..a20cc98 100644 --- a/dev/tools/gen_keycodes/data/keyboard_key.tmpl +++ b/dev/tools/gen_keycodes/data/keyboard_key.tmpl
@@ -19,7 +19,7 @@ /// that are returned from [RawKeyEvent.physicalKey]. /// * [LogicalKeyboardKey], a class with static values that describe the keys /// that are returned from [RawKeyEvent.logicalKey]. -abstract class KeyboardKey extends Diagnosticable { +abstract class KeyboardKey with Diagnosticable { /// A const constructor so that subclasses may be const. const KeyboardKey(); }
diff --git a/dev/tools/gen_keycodes/data/keyboard_maps.tmpl b/dev/tools/gen_keycodes/data/keyboard_maps.tmpl index 919cd20..3065879 100644 --- a/dev/tools/gen_keycodes/data/keyboard_maps.tmpl +++ b/dev/tools/gen_keycodes/data/keyboard_maps.tmpl
@@ -92,3 +92,20 @@ const Map<String, LogicalKeyboardKey> kWebNumPadMap = <String, LogicalKeyboardKey>{ @@@WEB_NUMPAD_MAP@@@ }; + +/// Maps Windows KeyboardEvent codes to the matching [LogicalKeyboardKey]. +const Map<int, LogicalKeyboardKey> kWindowsToLogicalKey = <int, LogicalKeyboardKey>{ +@@@WINDOWS_LOGICAL_KEY_MAP@@@ +}; + +/// Maps Windows KeyboardEvent codes to the matching [PhysicalKeyboardKey]. +const Map<int, PhysicalKeyboardKey> kWindowsToPhysicalKey = <int, PhysicalKeyboardKey>{ +@@@WINDOWS_PHYSICAL_KEY_MAP@@@ +}; + +/// A map of Windows KeyboardEvent 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> kWindowsNumPadMap = <int, LogicalKeyboardKey>{ +@@@WINDOWS_NUMPAD_MAP@@@ +};
diff --git a/dev/tools/gen_keycodes/lib/dart_code_gen.dart b/dev/tools/gen_keycodes/lib/dart_code_gen.dart index 07ddb99..983fc56 100644 --- a/dev/tools/gen_keycodes/lib/dart_code_gen.dart +++ b/dev/tools/gen_keycodes/lib/dart_code_gen.dart
@@ -225,6 +225,43 @@ return androidScanCodeMap.toString().trimRight(); } + /// This generates the map of Windows scan codes to physical keys. + String get windowsScanCodeMap { + final StringBuffer windowsScanCodeMap = StringBuffer(); + for (final Key entry in keyData.data) { + if (entry.windowsScanCode != null) { + windowsScanCodeMap.writeln(' ${toHex(entry.windowsScanCode)}: PhysicalKeyboardKey.${entry.constantName},'); + } + } + return windowsScanCodeMap.toString().trimRight(); + } + + /// This generates the map of Windows number pad key codes to logical keys. + String get windowsNumpadMap { + final StringBuffer windowsNumPadMap = StringBuffer(); + for (final Key entry in numpadKeyData) { + if (entry.windowsKeyCodes != null){ + for (final int code in entry.windowsKeyCodes) { + windowsNumPadMap.writeln(' $code: LogicalKeyboardKey.${entry.constantName},'); + } + } + } + return windowsNumPadMap.toString().trimRight(); + } + + /// This generates the map of Windows key codes to logical keys. + String get windowsKeyCodeMap { + final StringBuffer windowsKeyCodeMap = StringBuffer(); + for (final Key entry in keyData.data) { + if (entry.windowsKeyCodes != null) { + for (final int code in entry.windowsKeyCodes) { + windowsKeyCodeMap.writeln(' $code: LogicalKeyboardKey.${entry.constantName},'); + } + } + } + return windowsKeyCodeMap.toString().trimRight(); + } + /// This generates the map of macOS key codes to physical keys. String get macOsScanCodeMap { final StringBuffer macOsScanCodeMap = StringBuffer(); @@ -381,6 +418,9 @@ 'WEB_LOGICAL_KEY_MAP': webLogicalKeyMap, 'WEB_PHYSICAL_KEY_MAP': webPhysicalKeyMap, 'WEB_NUMPAD_MAP': webNumpadMap, + 'WINDOWS_LOGICAL_KEY_MAP': windowsKeyCodeMap, + 'WINDOWS_PHYSICAL_KEY_MAP': windowsScanCodeMap, + 'WINDOWS_NUMPAD_MAP': windowsNumpadMap, }; 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 d3d1e12..d52c58c 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart
@@ -29,6 +29,7 @@ export 'src/services/raw_keyboard_linux.dart'; export 'src/services/raw_keyboard_macos.dart'; export 'src/services/raw_keyboard_web.dart'; +export 'src/services/raw_keyboard_windows.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 8e2ee57..edb1425 100644 --- a/packages/flutter/lib/src/services/keyboard_maps.dart +++ b/packages/flutter/lib/src/services/keyboard_maps.dart
@@ -2020,3 +2020,306 @@ 'NumpadParenLeft': LogicalKeyboardKey.numpadParenLeft, 'NumpadParenRight': LogicalKeyboardKey.numpadParenRight, }; + +/// Maps Windows KeyboardEvent codes to the matching [LogicalKeyboardKey]. +const Map<int, LogicalKeyboardKey> kWindowsToLogicalKey = <int, LogicalKeyboardKey>{ + 95: LogicalKeyboardKey.sleep, + 65: LogicalKeyboardKey.keyA, + 66: LogicalKeyboardKey.keyB, + 67: LogicalKeyboardKey.keyC, + 68: LogicalKeyboardKey.keyD, + 69: LogicalKeyboardKey.keyE, + 70: LogicalKeyboardKey.keyF, + 71: LogicalKeyboardKey.keyG, + 72: LogicalKeyboardKey.keyH, + 73: LogicalKeyboardKey.keyI, + 74: LogicalKeyboardKey.keyJ, + 75: LogicalKeyboardKey.keyK, + 76: LogicalKeyboardKey.keyL, + 77: LogicalKeyboardKey.keyM, + 78: LogicalKeyboardKey.keyN, + 79: LogicalKeyboardKey.keyO, + 80: LogicalKeyboardKey.keyP, + 81: LogicalKeyboardKey.keyQ, + 82: LogicalKeyboardKey.keyR, + 83: LogicalKeyboardKey.keyS, + 84: LogicalKeyboardKey.keyT, + 85: LogicalKeyboardKey.keyU, + 86: LogicalKeyboardKey.keyV, + 87: LogicalKeyboardKey.keyW, + 88: LogicalKeyboardKey.keyX, + 89: LogicalKeyboardKey.keyY, + 90: LogicalKeyboardKey.keyZ, + 13: LogicalKeyboardKey.enter, + 27: LogicalKeyboardKey.escape, + 8: LogicalKeyboardKey.backspace, + 9: LogicalKeyboardKey.tab, + 32: LogicalKeyboardKey.space, + 189: LogicalKeyboardKey.minus, + 187: LogicalKeyboardKey.equal, + 219: LogicalKeyboardKey.bracketLeft, + 221: LogicalKeyboardKey.bracketRight, + 220: LogicalKeyboardKey.backslash, + 186: LogicalKeyboardKey.semicolon, + 222: LogicalKeyboardKey.quote, + 188: LogicalKeyboardKey.comma, + 190: LogicalKeyboardKey.period, + 191: LogicalKeyboardKey.slash, + 20: LogicalKeyboardKey.capsLock, + 112: LogicalKeyboardKey.f1, + 113: LogicalKeyboardKey.f2, + 114: LogicalKeyboardKey.f3, + 115: LogicalKeyboardKey.f4, + 116: LogicalKeyboardKey.f5, + 117: LogicalKeyboardKey.f6, + 118: LogicalKeyboardKey.f7, + 119: LogicalKeyboardKey.f8, + 120: LogicalKeyboardKey.f9, + 121: LogicalKeyboardKey.f10, + 122: LogicalKeyboardKey.f11, + 123: LogicalKeyboardKey.f12, + 19: LogicalKeyboardKey.pause, + 45: LogicalKeyboardKey.insert, + 36: LogicalKeyboardKey.home, + 46: LogicalKeyboardKey.delete, + 35: LogicalKeyboardKey.end, + 39: LogicalKeyboardKey.arrowRight, + 37: LogicalKeyboardKey.arrowLeft, + 40: LogicalKeyboardKey.arrowDown, + 38: LogicalKeyboardKey.arrowUp, + 111: LogicalKeyboardKey.numpadDivide, + 106: LogicalKeyboardKey.numpadMultiply, + 109: LogicalKeyboardKey.numpadSubtract, + 107: LogicalKeyboardKey.numpadAdd, + 97: LogicalKeyboardKey.numpad1, + 98: LogicalKeyboardKey.numpad2, + 99: LogicalKeyboardKey.numpad3, + 100: LogicalKeyboardKey.numpad4, + 101: LogicalKeyboardKey.numpad5, + 102: LogicalKeyboardKey.numpad6, + 103: LogicalKeyboardKey.numpad7, + 104: LogicalKeyboardKey.numpad8, + 105: LogicalKeyboardKey.numpad9, + 96: LogicalKeyboardKey.numpad0, + 110: LogicalKeyboardKey.numpadDecimal, + 146: LogicalKeyboardKey.numpadEqual, + 124: LogicalKeyboardKey.f13, + 125: LogicalKeyboardKey.f14, + 126: LogicalKeyboardKey.f15, + 127: LogicalKeyboardKey.f16, + 128: LogicalKeyboardKey.f17, + 129: LogicalKeyboardKey.f18, + 130: LogicalKeyboardKey.f19, + 131: LogicalKeyboardKey.f20, + 132: LogicalKeyboardKey.f21, + 133: LogicalKeyboardKey.f22, + 134: LogicalKeyboardKey.f23, + 135: LogicalKeyboardKey.f24, + 47: LogicalKeyboardKey.help, + 41: LogicalKeyboardKey.select, + 28: LogicalKeyboardKey.convert, + 162: LogicalKeyboardKey.controlLeft, + 160: LogicalKeyboardKey.shiftLeft, + 164: LogicalKeyboardKey.altLeft, + 91: LogicalKeyboardKey.metaLeft, + 163: LogicalKeyboardKey.controlRight, + 161: LogicalKeyboardKey.shiftRight, + 165: LogicalKeyboardKey.altRight, + 92: LogicalKeyboardKey.metaRight, + 178: LogicalKeyboardKey.mediaStop, + 179: LogicalKeyboardKey.mediaPlayPause, + 180: LogicalKeyboardKey.launchMail, + 183: LogicalKeyboardKey.launchApp2, + 182: LogicalKeyboardKey.launchApp1, + 42: LogicalKeyboardKey.print, + 170: LogicalKeyboardKey.browserSearch, + 172: LogicalKeyboardKey.browserHome, + 166: LogicalKeyboardKey.browserBack, + 167: LogicalKeyboardKey.browserForward, + 169: LogicalKeyboardKey.browserStop, + 168: LogicalKeyboardKey.browserRefresh, + 171: LogicalKeyboardKey.browserFavorites, +}; + +/// Maps Windows KeyboardEvent codes to the matching [PhysicalKeyboardKey]. +const Map<int, PhysicalKeyboardKey> kWindowsToPhysicalKey = <int, PhysicalKeyboardKey>{ + 0x0000e05f: PhysicalKeyboardKey.sleep, + 0x0000e063: PhysicalKeyboardKey.wakeUp, + 0x000000ff: PhysicalKeyboardKey.usbErrorRollOver, + 0x000000fc: PhysicalKeyboardKey.usbPostFail, + 0x0000001e: PhysicalKeyboardKey.keyA, + 0x00000030: PhysicalKeyboardKey.keyB, + 0x0000002e: PhysicalKeyboardKey.keyC, + 0x00000020: PhysicalKeyboardKey.keyD, + 0x00000012: PhysicalKeyboardKey.keyE, + 0x00000021: PhysicalKeyboardKey.keyF, + 0x00000022: PhysicalKeyboardKey.keyG, + 0x00000023: PhysicalKeyboardKey.keyH, + 0x00000017: PhysicalKeyboardKey.keyI, + 0x00000024: PhysicalKeyboardKey.keyJ, + 0x00000025: PhysicalKeyboardKey.keyK, + 0x00000026: PhysicalKeyboardKey.keyL, + 0x00000032: PhysicalKeyboardKey.keyM, + 0x00000031: PhysicalKeyboardKey.keyN, + 0x00000018: PhysicalKeyboardKey.keyO, + 0x00000019: PhysicalKeyboardKey.keyP, + 0x00000010: PhysicalKeyboardKey.keyQ, + 0x00000013: PhysicalKeyboardKey.keyR, + 0x0000001f: PhysicalKeyboardKey.keyS, + 0x00000014: PhysicalKeyboardKey.keyT, + 0x00000016: PhysicalKeyboardKey.keyU, + 0x0000002f: PhysicalKeyboardKey.keyV, + 0x00000011: PhysicalKeyboardKey.keyW, + 0x0000002d: PhysicalKeyboardKey.keyX, + 0x00000015: PhysicalKeyboardKey.keyY, + 0x0000002c: PhysicalKeyboardKey.keyZ, + 0x00000002: PhysicalKeyboardKey.digit1, + 0x00000003: PhysicalKeyboardKey.digit2, + 0x00000004: PhysicalKeyboardKey.digit3, + 0x00000005: PhysicalKeyboardKey.digit4, + 0x00000006: PhysicalKeyboardKey.digit5, + 0x00000007: PhysicalKeyboardKey.digit6, + 0x00000008: PhysicalKeyboardKey.digit7, + 0x00000009: PhysicalKeyboardKey.digit8, + 0x0000000a: PhysicalKeyboardKey.digit9, + 0x0000000b: PhysicalKeyboardKey.digit0, + 0x0000001c: PhysicalKeyboardKey.enter, + 0x00000001: PhysicalKeyboardKey.escape, + 0x0000000e: PhysicalKeyboardKey.backspace, + 0x0000000f: PhysicalKeyboardKey.tab, + 0x00000039: PhysicalKeyboardKey.space, + 0x0000000c: PhysicalKeyboardKey.minus, + 0x0000000d: PhysicalKeyboardKey.equal, + 0x0000001a: PhysicalKeyboardKey.bracketLeft, + 0x0000001b: PhysicalKeyboardKey.bracketRight, + 0x0000002b: PhysicalKeyboardKey.backslash, + 0x00000027: PhysicalKeyboardKey.semicolon, + 0x00000028: PhysicalKeyboardKey.quote, + 0x00000029: PhysicalKeyboardKey.backquote, + 0x00000033: PhysicalKeyboardKey.comma, + 0x00000034: PhysicalKeyboardKey.period, + 0x00000035: PhysicalKeyboardKey.slash, + 0x0000003a: PhysicalKeyboardKey.capsLock, + 0x0000003b: PhysicalKeyboardKey.f1, + 0x0000003c: PhysicalKeyboardKey.f2, + 0x0000003d: PhysicalKeyboardKey.f3, + 0x0000003e: PhysicalKeyboardKey.f4, + 0x0000003f: PhysicalKeyboardKey.f5, + 0x00000040: PhysicalKeyboardKey.f6, + 0x00000041: PhysicalKeyboardKey.f7, + 0x00000042: PhysicalKeyboardKey.f8, + 0x00000043: PhysicalKeyboardKey.f9, + 0x00000044: PhysicalKeyboardKey.f10, + 0x00000057: PhysicalKeyboardKey.f11, + 0x00000058: PhysicalKeyboardKey.f12, + 0x0000e037: PhysicalKeyboardKey.printScreen, + 0x00000046: PhysicalKeyboardKey.scrollLock, + 0x00000045: PhysicalKeyboardKey.pause, + 0x0000e052: PhysicalKeyboardKey.insert, + 0x0000e047: PhysicalKeyboardKey.home, + 0x0000e049: PhysicalKeyboardKey.pageUp, + 0x0000e053: PhysicalKeyboardKey.delete, + 0x0000e04f: PhysicalKeyboardKey.end, + 0x0000e051: PhysicalKeyboardKey.pageDown, + 0x0000e04d: PhysicalKeyboardKey.arrowRight, + 0x0000e04b: PhysicalKeyboardKey.arrowLeft, + 0x0000e050: PhysicalKeyboardKey.arrowDown, + 0x0000e048: PhysicalKeyboardKey.arrowUp, + 0x0000e045: PhysicalKeyboardKey.numLock, + 0x0000e035: PhysicalKeyboardKey.numpadDivide, + 0x00000037: PhysicalKeyboardKey.numpadMultiply, + 0x0000004a: PhysicalKeyboardKey.numpadSubtract, + 0x0000004e: PhysicalKeyboardKey.numpadAdd, + 0x0000e01c: PhysicalKeyboardKey.numpadEnter, + 0x0000004f: PhysicalKeyboardKey.numpad1, + 0x00000050: PhysicalKeyboardKey.numpad2, + 0x00000051: PhysicalKeyboardKey.numpad3, + 0x0000004b: PhysicalKeyboardKey.numpad4, + 0x0000004c: PhysicalKeyboardKey.numpad5, + 0x0000004d: PhysicalKeyboardKey.numpad6, + 0x00000047: PhysicalKeyboardKey.numpad7, + 0x00000048: PhysicalKeyboardKey.numpad8, + 0x00000049: PhysicalKeyboardKey.numpad9, + 0x00000052: PhysicalKeyboardKey.numpad0, + 0x00000053: PhysicalKeyboardKey.numpadDecimal, + 0x00000056: PhysicalKeyboardKey.intlBackslash, + 0x0000e05d: PhysicalKeyboardKey.contextMenu, + 0x0000e05e: PhysicalKeyboardKey.power, + 0x00000059: PhysicalKeyboardKey.numpadEqual, + 0x00000064: PhysicalKeyboardKey.f13, + 0x00000065: PhysicalKeyboardKey.f14, + 0x00000066: PhysicalKeyboardKey.f15, + 0x00000067: PhysicalKeyboardKey.f16, + 0x00000068: PhysicalKeyboardKey.f17, + 0x00000069: PhysicalKeyboardKey.f18, + 0x0000006a: PhysicalKeyboardKey.f19, + 0x0000006b: PhysicalKeyboardKey.f20, + 0x0000006c: PhysicalKeyboardKey.f21, + 0x0000006d: PhysicalKeyboardKey.f22, + 0x0000006e: PhysicalKeyboardKey.f23, + 0x00000076: PhysicalKeyboardKey.f24, + 0x0000e03b: PhysicalKeyboardKey.help, + 0x0000e008: PhysicalKeyboardKey.undo, + 0x0000e017: PhysicalKeyboardKey.cut, + 0x0000e018: PhysicalKeyboardKey.copy, + 0x0000e00a: PhysicalKeyboardKey.paste, + 0x0000e020: PhysicalKeyboardKey.audioVolumeMute, + 0x0000e030: PhysicalKeyboardKey.audioVolumeUp, + 0x0000e02e: PhysicalKeyboardKey.audioVolumeDown, + 0x0000007e: PhysicalKeyboardKey.numpadComma, + 0x00000073: PhysicalKeyboardKey.intlRo, + 0x00000070: PhysicalKeyboardKey.kanaMode, + 0x0000007d: PhysicalKeyboardKey.intlYen, + 0x00000079: PhysicalKeyboardKey.convert, + 0x0000007b: PhysicalKeyboardKey.nonConvert, + 0x00000072: PhysicalKeyboardKey.lang1, + 0x00000071: PhysicalKeyboardKey.lang2, + 0x00000078: PhysicalKeyboardKey.lang3, + 0x00000077: PhysicalKeyboardKey.lang4, + 0x0000001d: PhysicalKeyboardKey.controlLeft, + 0x0000002a: PhysicalKeyboardKey.shiftLeft, + 0x00000038: PhysicalKeyboardKey.altLeft, + 0x0000e05b: PhysicalKeyboardKey.metaLeft, + 0x0000e01d: PhysicalKeyboardKey.controlRight, + 0x00000036: PhysicalKeyboardKey.shiftRight, + 0x0000e038: PhysicalKeyboardKey.altRight, + 0x0000e05c: PhysicalKeyboardKey.metaRight, + 0x0000e019: PhysicalKeyboardKey.mediaTrackNext, + 0x0000e010: PhysicalKeyboardKey.mediaTrackPrevious, + 0x0000e024: PhysicalKeyboardKey.mediaStop, + 0x0000e02c: PhysicalKeyboardKey.eject, + 0x0000e022: PhysicalKeyboardKey.mediaPlayPause, + 0x0000e06d: PhysicalKeyboardKey.mediaSelect, + 0x0000e06c: PhysicalKeyboardKey.launchMail, + 0x0000e021: PhysicalKeyboardKey.launchApp2, + 0x0000e06b: PhysicalKeyboardKey.launchApp1, + 0x0000e065: PhysicalKeyboardKey.browserSearch, + 0x0000e032: PhysicalKeyboardKey.browserHome, + 0x0000e06a: PhysicalKeyboardKey.browserBack, + 0x0000e069: PhysicalKeyboardKey.browserForward, + 0x0000e068: PhysicalKeyboardKey.browserStop, + 0x0000e067: PhysicalKeyboardKey.browserRefresh, + 0x0000e066: PhysicalKeyboardKey.browserFavorites, +}; + +/// A map of Windows KeyboardEvent 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> kWindowsNumPadMap = <int, LogicalKeyboardKey>{ + 111: LogicalKeyboardKey.numpadDivide, + 106: LogicalKeyboardKey.numpadMultiply, + 109: LogicalKeyboardKey.numpadSubtract, + 107: LogicalKeyboardKey.numpadAdd, + 97: LogicalKeyboardKey.numpad1, + 98: LogicalKeyboardKey.numpad2, + 99: LogicalKeyboardKey.numpad3, + 100: LogicalKeyboardKey.numpad4, + 101: LogicalKeyboardKey.numpad5, + 102: LogicalKeyboardKey.numpad6, + 103: LogicalKeyboardKey.numpad7, + 104: LogicalKeyboardKey.numpad8, + 105: LogicalKeyboardKey.numpad9, + 96: LogicalKeyboardKey.numpad0, + 110: LogicalKeyboardKey.numpadDecimal, + 146: LogicalKeyboardKey.numpadEqual, +};
diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart index d42471e..7b744ee 100644 --- a/packages/flutter/lib/src/services/raw_keyboard.dart +++ b/packages/flutter/lib/src/services/raw_keyboard.dart
@@ -14,6 +14,7 @@ import 'raw_keyboard_linux.dart'; import 'raw_keyboard_macos.dart'; import 'raw_keyboard_web.dart'; +import 'raw_keyboard_windows.dart'; import 'system_channels.dart'; /// An enum describing the side of the keyboard that a key is on, to allow @@ -296,6 +297,14 @@ metaState: message['metaState'] as int, ); break; + case 'windows': + data = RawKeyEventDataWindows( + keyCode: message['keyCode'] as int, + scanCode: message['scanCode'] as int, + characterCodePoint: message['characterCodePoint'] as int, + modifiers: message['modifiers'] as int, + ); + break; default: // Raw key events are not yet implemented on iOS or other platforms, // but this exception isn't hit, because the engine never sends these
diff --git a/packages/flutter/lib/src/services/raw_keyboard_windows.dart b/packages/flutter/lib/src/services/raw_keyboard_windows.dart new file mode 100644 index 0000000..15a465e --- /dev/null +++ b/packages/flutter/lib/src/services/raw_keyboard_windows.dart
@@ -0,0 +1,291 @@ +// Copyright 2014 The Flutter 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 Windows. +/// +/// This object contains information about key events obtained from Windows's +/// win32 API. +/// +/// See also: +/// +/// * [RawKeyboard], which uses this interface to expose key data. +class RawKeyEventDataWindows extends RawKeyEventData { + /// Creates a key event data structure specific for Windows. + /// + /// The [keyCode], [scanCode], [characterCodePoint], and [modifiers], arguments + /// must not be null. + const RawKeyEventDataWindows({ + this.keyCode = 0, + this.scanCode = 0, + this.characterCodePoint = 0, + this.modifiers = 0, + }) : assert(keyCode != null), + assert(scanCode != null), + assert(characterCodePoint != null), + assert(modifiers != null); + + /// The hardware key code corresponding to this key event. + /// + /// This is the physical key that was pressed, not the Unicode character. + /// See [characterCodePoint] for the Unicode character. + final int keyCode; + + /// The hardware scan code id corresponding to this key event. + /// + /// These values are not reliable and vary from device to device, so this + /// information is mainly useful for debugging. + final int scanCode; + + /// The Unicode code point represented by the key event, if any. + /// + /// If there is no Unicode code point, this value is zero. + final int characterCodePoint; + + /// A mask of the current modifiers. The modifier values must be in sync with + /// the ones defined in https://github.com/flutter/engine/blob/master/shell/platform/windows/key_event_handler.cc + final int modifiers; + + @override + String get keyLabel => characterCodePoint == 0 ? null : String.fromCharCode(characterCodePoint); + + @override + PhysicalKeyboardKey get physicalKey => kWindowsToPhysicalKey[scanCode] ?? 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 = kWindowsNumPadMap[keyCode]; + if (numPadKey != null) { + return numPadKey; + } + + // If it has a non-control-character label, then either return the existing + // constant, or construct a new Unicode-based key from it. Don't mark it as + // autogenerated, since the label uniquely identifies an ID from the Unicode + // plane. + if (keyLabel != null && keyLabel.isNotEmpty && !LogicalKeyboardKey.isControlCharacter(keyLabel)) { + final int keyId = LogicalKeyboardKey.unicodePlane | (characterCodePoint & LogicalKeyboardKey.valueMask); + return LogicalKeyboardKey.findKeyByKeyId(keyId) ?? LogicalKeyboardKey( + keyId, + keyLabel: keyLabel, + debugName: kReleaseMode ? null : 'Key ${keyLabel.toUpperCase()}', + ); + } + // Look to see if the keyCode is one we know about and have a mapping for. + LogicalKeyboardKey newKey = kWindowsToLogicalKey[keyCode]; + if (newKey != null) { + return newKey; + } + + // 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 windowsKeyIdPlane = 0x00700000000; + newKey ??= LogicalKeyboardKey( + windowsKeyIdPlane | keyCode | LogicalKeyboardKey.autogeneratedMask, + debugName: kReleaseMode ? null : 'Unknown Windows key code $keyCode', + ); + return newKey; + } + + bool _isLeftRightModifierPressed(KeyboardSide side, int anyMask, int leftMask, int rightMask) { + if (modifiers & anyMask == 0 && + modifiers & leftMask == 0 && + modifiers & rightMask == 0) { + return false; + } + // If only the "anyMask" bit is set, then we respond true for requests of + // whether either left or right is pressed. + // Handles the case where Windows supplies just the "either" modifier flag, + // but not the left/right flag. (e.g. modifierShift but not + // modifierLeftShift). + final bool anyOnly = modifiers & (leftMask | rightMask | anyMask) == anyMask; + switch (side) { + case KeyboardSide.any: + return true; + case KeyboardSide.all: + return modifiers & leftMask != 0 && modifiers & rightMask != 0 || anyOnly; + case KeyboardSide.left: + return modifiers & leftMask != 0 || anyOnly; + case KeyboardSide.right: + return modifiers & rightMask != 0 || anyOnly; + } + return false; + } + + @override + bool isModifierPressed(ModifierKey key, {KeyboardSide side = KeyboardSide.any}) { + bool result; + switch (key) { + case ModifierKey.controlModifier: + result = _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl); + break; + case ModifierKey.shiftModifier: + result = _isLeftRightModifierPressed(side, modifierShift, modifierLeftShift, modifierRightShift); + break; + case ModifierKey.altModifier: + result = _isLeftRightModifierPressed(side, modifierAlt, modifierLeftAlt, modifierRightAlt); + break; + case ModifierKey.metaModifier: + // Windows does not provide an "any" key for win key press. + result = _isLeftRightModifierPressed(side, modifierLeftMeta | modifierRightMeta , modifierLeftMeta, modifierRightMeta); + break; + case ModifierKey.capsLockModifier: + result = modifiers & modifierCaps != 0; + break; + case ModifierKey.scrollLockModifier: + result = modifiers & modifierScrollLock != 0; + break; + case ModifierKey.numLockModifier: + result = modifiers & modifierNumLock != 0; + break; + // The OS does not expose the Fn key to the drivers, it doesn't generate a key message. + case ModifierKey.functionModifier: + case ModifierKey.symbolModifier: + // These modifier masks are not used in Windows keyboards. + result = false; + break; + } + assert(!result || getModifierSide(key) != null, "$runtimeType thinks that a modifier is pressed, but can't figure out what side it's on."); + return result; + } + + + @override + KeyboardSide getModifierSide(ModifierKey key) { + KeyboardSide findSide(int leftMask, int rightMask, int anyMask) { + 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; + } else if (modifiers & (combinedMask | anyMask) == anyMask) { + // Handles the case where Windows supplies just the "either" modifier + // flag, but not the left/right flag. (e.g. modifierShift but not + // modifierLeftShift). + return KeyboardSide.any; + } + return null; + } + + switch (key) { + case ModifierKey.controlModifier: + return findSide(modifierLeftControl, modifierRightControl, modifierControl); + case ModifierKey.shiftModifier: + return findSide(modifierLeftShift, modifierRightShift, modifierShift); + case ModifierKey.altModifier: + return findSide(modifierLeftAlt, modifierRightAlt, modifierAlt); + case ModifierKey.metaModifier: + return findSide(modifierLeftMeta, modifierRightMeta, 0); + 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; + } + + // These are not the values defined by the Windows header for each modifier. Since they + // can't be packaged into a single int, we are re-defining them here to reduce the size + // of the message from the embedder. Embedders should map these values to the native key codes. + // Keep this in sync with https://github.com/flutter/engine/blob/master/shell/platform/windows/key_event_handler.cc + + /// This mask is used to check the [modifiers] field to test whether one of the + /// SHIFT modifier keys is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierShift = 1 << 0; + + /// This mask is used to check the [modifiers] field to test whether the left + /// SHIFT modifier key is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierLeftShift = 1 << 1; + + /// This mask is used to check the [modifiers] field to test whether the right + /// SHIFT modifier key is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierRightShift = 1 << 2; + + /// This mask is used to check the [modifiers] field to test whether one of the + /// CTRL modifier keys is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierControl = 1 << 3; + + /// This mask is used to check the [modifiers] field to test whether the left + /// CTRL modifier key is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierLeftControl = 1 << 4; + + /// This mask is used to check the [modifiers] field to test whether the right + /// CTRL modifier key is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierRightControl = 1 << 5; + + /// This mask is used to check the [modifiers] field to test whether one of the + /// ALT modifier keys is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierAlt = 1 << 6; + + /// This mask is used to check the [modifiers] field to test whether the left + /// ALT modifier key is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierLeftAlt = 1 << 7; + + /// This mask is used to check the [modifiers] field to test whether the right + /// ALT modifier key is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierRightAlt = 1 << 8; + + /// This mask is used to check the [modifiers] field to test whether the left + /// WIN modifier keys is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierLeftMeta = 1 << 9; + + /// This mask is used to check the [modifiers] field to test whether the right + /// WIN modifier keys is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierRightMeta = 1 << 10; + + /// This mask is used to check the [modifiers] field to test whether the CAPS LOCK key + /// is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierCaps = 1 << 11; + + /// This mask is used to check the [modifiers] field to test whether the NUM LOCK key + /// is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierNumLock = 1 << 12; + + /// This mask is used to check the [modifiers] field to test whether the SCROLL LOCK key + /// is pressed. + /// + /// {@macro flutter.services.rawKeyEventDataWindows.modifiers} + static const int modifierScrollLock = 1 << 13; +}
diff --git a/packages/flutter/test/services/raw_keyboard_test.dart b/packages/flutter/test/services/raw_keyboard_test.dart index caa2781..2790e85 100644 --- a/packages/flutter/test/services/raw_keyboard_test.dart +++ b/packages/flutter/test/services/raw_keyboard_test.dart
@@ -15,7 +15,7 @@ void main() { group('RawKeyboard', () { testWidgets('keysPressed is maintained', (WidgetTester tester) async { - for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia']) { + for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows']) { RawKeyboard.instance.clearKeysPressed(); expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform'); await simulateKeyDownEvent(LogicalKeyboardKey.shiftLeft, platform: platform); @@ -70,8 +70,8 @@ ); 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') { + // The Fn key isn't mapped on linux or Windows. + if (platform != 'linux' && platform != 'windows') { await simulateKeyDownEvent(LogicalKeyboardKey.fn, platform: platform); expect( RawKeyboard.instance.keysPressed, @@ -107,7 +107,7 @@ }, skip: kIsWeb); testWidgets('keysPressed is correct when modifier is released before key', (WidgetTester tester) async { - for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia']) { + for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows']) { RawKeyboard.instance.clearKeysPressed(); expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform'); await simulateKeyDownEvent(LogicalKeyboardKey.shiftLeft, platform: platform, physicalKey: PhysicalKeyboardKey.shiftLeft); @@ -176,6 +176,31 @@ ); }); + testWidgets('keysPressed modifiers are synchronized with key events on Windows', (WidgetTester tester) async { + expect(RawKeyboard.instance.keysPressed, isEmpty); + // Generate the data for a regular key down event. + final Map<String, dynamic> data = KeyEventSimulator.getKeyData( + LogicalKeyboardKey.keyA, + platform: 'windows', + isDown: true, + ); + // Change the modifiers so that they show the shift key as already down + // when this event is received, but it's not in keysPressed yet. + data['modifiers'] |= RawKeyEventDataWindows.modifierLeftShift | RawKeyEventDataWindows.modifierShift; + // dispatch the modified data. + await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage(data), + (ByteData data) {}, + ); + expect( + RawKeyboard.instance.keysPressed, + equals( + <LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA}, + ), + ); + }); + testWidgets('keysPressed modifiers are synchronized with key events on android', (WidgetTester tester) async { expect(RawKeyboard.instance.keysPressed, isEmpty); // Generate the data for a regular key down event. @@ -746,6 +771,151 @@ expect(data.logicalKey.keyLabel, isNull); }); }, skip: isBrowser); + + group('RawKeyEventDataWindows', () { + const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ + RawKeyEventDataWindows.modifierLeftAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left), + RawKeyEventDataWindows.modifierRightAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right), + RawKeyEventDataWindows.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left), + RawKeyEventDataWindows.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right), + RawKeyEventDataWindows.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left), + RawKeyEventDataWindows.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right), + RawKeyEventDataWindows.modifierLeftMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left), + RawKeyEventDataWindows.modifierRightMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right), + RawKeyEventDataWindows.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.any), + RawKeyEventDataWindows.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.any), + RawKeyEventDataWindows.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any), + RawKeyEventDataWindows.modifierCaps: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all), + RawKeyEventDataWindows.modifierNumLock: _ModifierCheck(ModifierKey.numLockModifier, KeyboardSide.all), + RawKeyEventDataWindows.modifierScrollLock: _ModifierCheck(ModifierKey.scrollLockModifier, KeyboardSide.all), + }; + + test('modifier keys are recognized individually', () { + for (final int modifier in modifierTests.keys) { + final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{ + 'type': 'keydown', + 'keymap': 'windows', + 'keyCode': 0x04, + 'characterCodePoint': 0, + 'scanCode': 0x04, + 'modifiers': modifier, + }); + final RawKeyEventDataWindows data = event.data as RawKeyEventDataWindows; + for (final 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 modifier $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 (final int modifier in modifierTests.keys) { + if (modifier == RawKeyEventDataWindows.modifierCaps) { + // No need to combine caps lock key with itself. + continue; + } + final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{ + 'type': 'keydown', + 'keymap': 'windows', + 'keyCode': 0x04, + 'characterCodePoint': 0, + 'scanCode': 0x04, + 'modifiers': modifier | RawKeyEventDataWindows.modifierCaps, + }); + final RawKeyEventDataWindows data = event.data as RawKeyEventDataWindows; + for (final ModifierKey key in ModifierKey.values) { + if (modifierTests[modifier].key == key || key == ModifierKey.capsLockModifier) { + expect( + data.isModifierPressed(key, side: modifierTests[modifier].side), + isTrue, + reason: '$key should be pressed with metaState $modifier ' + "and additional key ${RawKeyEventDataWindows.modifierCaps}, but isn't.", + ); + if (key != ModifierKey.capsLockModifier) { + 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 ' + 'and additional key ${RawKeyEventDataWindows.modifierCaps}.', + ); + } + } + } + }); + test('Printable keyboard keys are correctly translated', () { + const int unmodifiedCharacter = 97; // ASCII value for 'a'. + final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{ + 'type': 'keydown', + 'keymap': 'windows', + 'keyCode': 0x00000000, + 'characterCodePoint': unmodifiedCharacter, + 'scanCode': 0x0000001e, + 'modifiers': 0x0, + }); + final RawKeyEventDataWindows data = keyAEvent.data as RawKeyEventDataWindows; + 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': 'windows', + 'keyCode': 27, // keycode for escape key + 'scanCode': 0x00000001, // scanCode for escape key + 'characterCodePoint': 0, + 'modifiers': 0x0, + }); + final RawKeyEventDataWindows data = escapeKeyEvent.data as RawKeyEventDataWindows; + 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': 'windows', + 'keyCode': 160, // keyCode for left shift. + 'scanCode': 0x0000002a, // scanCode for left shift. + 'characterCodePoint': 0, + 'modifiers': RawKeyEventDataWindows.modifierLeftShift, + }); + final RawKeyEventDataWindows data = shiftLeftKeyEvent.data as RawKeyEventDataWindows; + expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft)); + expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft)); + expect(data.keyLabel, isNull); + }); + test('Unprintable keyboard keys are correctly translated', () { + final RawKeyEvent leftArrowKey = RawKeyEvent.fromMessage(const <String, dynamic>{ + 'type': 'keydown', + 'keymap': 'windows', + 'keyCode': 37, // keyCode for left arrow. + 'scanCode': 0x0000e04b, // scanCode for left arrow. + 'characterCodePoint': 0, + 'modifiers': 0, + }); + final RawKeyEventDataWindows data = leftArrowKey.data as RawKeyEventDataWindows; + expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowLeft)); + expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft)); + expect(data.logicalKey.keyLabel, isNull); + }); + }, skip: isBrowser); group('RawKeyEventDataLinux-GFLW', () { const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{ GLFWKeyHelper.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any),
diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart index 2a96d31..a38c87f 100644 --- a/packages/flutter_test/lib/src/event_simulation.dart +++ b/packages/flutter_test/lib/src/event_simulation.dart
@@ -40,6 +40,7 @@ case 'macos': case 'linux': case 'web': + case 'windows': return true; } return false; @@ -62,6 +63,9 @@ case 'linux': map = kLinuxToPhysicalKey; break; + case 'windows': + map = kWindowsToPhysicalKey; + break; case 'web': // web doesn't have int type code return null; @@ -95,6 +99,9 @@ case 'linux': map = kGlfwToLogicalKey; break; + case 'windows': + map = kWindowsToLogicalKey; + break; } for (final int code in map.keys) { if (key.keyId == map[code].keyId) { @@ -132,6 +139,9 @@ case 'web': map = kWebToPhysicalKey; break; + case 'windows': + map = kWindowsToPhysicalKey; + break; } for (final PhysicalKeyboardKey physicalKey in map.values) { if (key.debugName == physicalKey.debugName) { @@ -195,6 +205,12 @@ result['code'] = _getWebKeyCode(key); result['key'] = ''; result['metaState'] = _getWebModifierFlags(key, isDown); + break; + case 'windows': + result['keyCode'] = keyCode; + result['scanCode'] = scanCode; + result['characterCodePoint'] = key.keyLabel?.codeUnitAt(0) ?? 0; + result['modifiers'] = _getWindowsModifierFlags(key, isDown); } return result; } @@ -272,6 +288,59 @@ return result; } + static int _getWindowsModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + int result = 0; + final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shift)) { + result |= RawKeyEventDataWindows.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataWindows.modifierLeftShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataWindows.modifierRightShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataWindows.modifierLeftMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataWindows.modifierRightMeta; + } + if (pressed.contains(LogicalKeyboardKey.control)) { + result |= RawKeyEventDataWindows.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataWindows.modifierLeftControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataWindows.modifierRightControl; + } + if (pressed.contains(LogicalKeyboardKey.alt)) { + result |= RawKeyEventDataWindows.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataWindows.modifierLeftAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataWindows.modifierRightAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataWindows.modifierCaps; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataWindows.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataWindows.modifierScrollLock; + } + return result; + } + static int _getFuchsiaModifierFlags(LogicalKeyboardKey newKey, bool isDown) { int result = 0; final Set<LogicalKeyboardKey> pressed = RawKeyboard.instance.keysPressed;