diff --git a/windows/src/engine/keyman32/keyman-debug-etw.man b/windows/src/engine/keyman32/keyman-debug-etw.man
index aea3d85447..e8c7331d3f 100644
--- a/windows/src/engine/keyman32/keyman-debug-etw.man
+++ b/windows/src/engine/keyman32/keyman-debug-etw.man
@@ -33,6 +33,7 @@
+
@@ -210,6 +211,7 @@
+
diff --git a/windows/src/engine/keyman32/serialkeyeventserver.cpp b/windows/src/engine/keyman32/serialkeyeventserver.cpp
index ffc0195082..a506950937 100644
--- a/windows/src/engine/keyman32/serialkeyeventserver.cpp
+++ b/windows/src/engine/keyman32/serialkeyeventserver.cpp
@@ -415,23 +415,90 @@ private:
*/
if (msg == wm_keyman_keyevent && flag_ShouldSerializeInput /*&& _td->lpActiveKeyboard*/) {
- INPUT input;
- input.type = INPUT_KEYBOARD;
- input.ki.wVk = (WORD)wParam;
- input.ki.wScan = (lParam & 0xFF0000) >> 16;
- input.ki.time = GetMessageTime();
- input.ki.dwExtraInfo = EXTRAINFO_FLAG_SERIALIZED_USER_KEY_EVENT;
- input.ki.dwFlags = lParam & 0xFFFF;
- if (!SendInput(1, &input, sizeof(INPUT))) {
- DebugLastError("SendInput");
+ if (wParam == VK_RMENU && (lParam & (KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP)) == (KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP) && GetKeyState(VK_LCONTROL) < 0) {
+ /*
+ When Windows has a European layout that uses AltGr installed, it can emit an additional LCtrl down via software
+ when RAlt is pressed. However, the corresponding LCtrl up is never received, seemingly because when Keyman
+ re-emits the LCtrl+RAlt, there are subtle differences in the event flags which we cannot duplicate -- specifically
+ the flag that emits WM_SYSKEYDOWN for the VK_LCONTROL, even though it is received before the VK_RALT event. It
+ appears that Windows figures this out by giving this VK_LCONTROL the scan code 0x21D instead of 0x1D. But we
+ are unable to emit that scan code: Windows truncates the scan code sent through SendInput so that we can only
+ send 0x1D.
+
+ So we simulate the release of the Left Control key ourselves when the release of the Right Alt is received,
+ using VK_CONTROL and scan 0x1D, and hope for the best.
+
+ The full Windows sequence is:
+
+ WM_SYSKEYDOWN VK_CONTROL 0x21D
+ WM_SYSKEYDOWN VK_MENU 0x38 EXTENDED_BIT
+ ...
+ WM_KEYUP VK_CONTROL 0x21D
+ WM_KEYUP VK_MENU 0x38 EXTENDED_BIT
+
+ The best Keyman can do is:
+
+ WM_KEYDOWN VK_CONTROL 0x1D
+ WM_KEYDOWN VK_MENU 0x38 EXTENDED_BIT
+ ...
+ WM_KEYUP VK_CONTROL 0x1D
+ WM_KEYUP VK_MENU 0x38 EXTENDED_BIT
+
+ There is a possibility that some apps may try and sniff that 0x21D scan code and get confused because Keyman
+ doesn't emit it. Hopefully this is rare.
+ */
+ INPUT input[2];
+ input[0].type = INPUT_KEYBOARD;
+ input[0].ki.wVk = VK_CONTROL;
+ input[0].ki.wScan = 0x21D; // Yeah, Windows chops this to 0x1D. Such is life.
+ input[0].ki.time = GetMessageTime();
+ input[0].ki.dwExtraInfo = EXTRAINFO_FLAG_SERIALIZED_USER_KEY_EVENT;
+ input[0].ki.dwFlags = KEYEVENTF_KEYUP;
+
+ input[1].type = INPUT_KEYBOARD;
+ input[1].ki.wVk = (WORD)wParam;
+ input[1].ki.wScan = (lParam & 0xFFF0000) >> 16;
+ input[1].ki.time = GetMessageTime();
+ input[1].ki.dwExtraInfo = EXTRAINFO_FLAG_SERIALIZED_USER_KEY_EVENT;
+ input[1].ki.dwFlags = lParam & 0xFFFF;
+
+ if (!SendInput(2, input, sizeof(INPUT))) {
+ DebugLastError("SendInput");
+ }
+
+ UpdateLocalModifierState(
+ (BYTE)input[0].ki.wVk,
+ input[0].ki.dwFlags & KEYEVENTF_EXTENDEDKEY ? TRUE : FALSE,
+ (BYTE)input[0].ki.wScan,
+ input[0].ki.dwFlags & KEYEVENTF_KEYUP ? TRUE : FALSE);
+
+ UpdateLocalModifierState(
+ (BYTE)input[1].ki.wVk,
+ input[1].ki.dwFlags & KEYEVENTF_EXTENDEDKEY ? TRUE : FALSE,
+ (BYTE)input[1].ki.wScan,
+ input[1].ki.dwFlags & KEYEVENTF_KEYUP ? TRUE : FALSE);
+ }
+ else {
+ INPUT input;
+ input.type = INPUT_KEYBOARD;
+ input.ki.wVk = (WORD)wParam;
+ input.ki.wScan = (lParam & 0xFFF0000) >> 16;
+ input.ki.time = GetMessageTime();
+ input.ki.dwExtraInfo = EXTRAINFO_FLAG_SERIALIZED_USER_KEY_EVENT;
+ input.ki.dwFlags = lParam & 0xFFFF;
+
+ if (!SendInput(1, &input, sizeof(INPUT))) {
+ DebugLastError("SendInput");
+ }
+
+ UpdateLocalModifierState(
+ (BYTE)input.ki.wVk,
+ input.ki.dwFlags & KEYEVENTF_EXTENDEDKEY ? TRUE : FALSE,
+ (BYTE)input.ki.wScan,
+ input.ki.dwFlags & KEYEVENTF_KEYUP ? TRUE : FALSE);
}
- UpdateLocalModifierState(
- (BYTE) input.ki.wVk,
- input.ki.dwFlags & KEYEVENTF_EXTENDEDKEY ? TRUE : FALSE,
- (BYTE) input.ki.wScan,
- input.ki.dwFlags & KEYEVENTF_KEYUP ? TRUE : FALSE);
}
return DefWindowProc(hwnd, msg, wParam, lParam);