[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;