From 389d28651886148d8d29d830fb3328b294c3da58 Mon Sep 17 00:00:00 2001 From: fluffyspace Date: Tue, 31 Mar 2026 13:59:12 +0200 Subject: [PATCH 1/3] [KeyboardManager] Fix sticky Ctrl caused by stale isAltRightKeyInvoked flag The static isAltRightKeyInvoked flag was set to true when AltGr (RAlt+LCtrl) was pressed, but only reset inside Case 1's else branch which requires an active shortcut invocation. If AltGr was pressed when no shortcut was active, the flag stayed true permanently, blocking modifier restoration and state resets throughout HandleShortcutRemapEvent, causing LCtrl to become stuck. Reset the flag when VK_RMENU is released regardless of shortcut state. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp index 0e8529396bc2..1a8462793c4c 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp @@ -307,6 +307,10 @@ namespace KeyboardEventHandlers { isAltRightKeyInvoked = true; } + else if (data->lParam->vkCode == VK_RMENU && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) + { + isAltRightKeyInvoked = false; + } // If the shortcut has been pressed down if (!it->second.isShortcutInvoked && it->first.CheckModifiersKeyboardState(ii)) From b59657e01a16ed243ad2c58ea278ea71a44c8c96 Mon Sep 17 00:00:00 2001 From: fluffyspace Date: Wed, 1 Apr 2026 08:04:35 +0200 Subject: [PATCH 2/3] [KeyboardManager] Add tests for AltGr stale flag causing sticky Ctrl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three test cases covering the bug where isAltRightKeyInvoked stays true after AltGr is pressed without an active shortcut: 1. Basic: AltGr press-release followed by shortcut invoke and full key release — verifies state resets properly 2. Revert: AltGr press-release followed by shortcut with extra key pressed — verifies Case 3 else path restores modifiers 3. Repeated: Multiple AltGr press-release cycles followed by shortcut — verifies no accumulated corruption Co-Authored-By: Claude Opus 4.6 (1M context) --- .../OSLevelShortcutRemappingTests.cpp | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp index 4b470ec5a6e0..a9ec5aee6eb2 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp @@ -2370,5 +2370,145 @@ namespace RemappingLogicTests // LWin should be pressed Assert::AreEqual(true, mockedInputHandler.GetVirtualKeyState(VK_LWIN)); } + + // Tests for AltGr (isAltRightKeyInvoked) flag handling + + // Test that pressing and releasing AltGr without an active shortcut does not leave the shortcut state corrupted + TEST_METHOD (AltGrPressedAndReleased_ShouldNotCorruptShortcutState_WhenNoShortcutIsActive) + { + // Remap LCtrl+Y to Backspace + Shortcut src; + src.SetKey(VK_LCONTROL); + src.SetKey(0x59); + testState.AddOSLevelShortcut(src, (DWORD)VK_BACK); + + // Simulate AltGr press (Windows sends LCtrl down then RAlt down) and release + std::vector altGrInputs{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_RMENU } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_RMENU, .dwFlags = KEYEVENTF_KEYUP } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL, .dwFlags = KEYEVENTF_KEYUP } }, + }; + mockedInputHandler.SendVirtualInput(altGrInputs); + + // Now invoke the shortcut: LCtrl+Y down, then Y up, then LCtrl up + std::vector shortcutDown{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = 0x59 } }, + }; + mockedInputHandler.SendVirtualInput(shortcutDown); + + // Shortcut should have fired: LCtrl released, Backspace pressed + Assert::AreEqual(true, testState.osLevelShortcutReMap[src].isShortcutInvoked); + Assert::AreEqual(true, mockedInputHandler.GetVirtualKeyState(VK_BACK)); + + // Release Y (action key) + std::vector releaseY{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = 0x59, .dwFlags = KEYEVENTF_KEYUP } }, + }; + mockedInputHandler.SendVirtualInput(releaseY); + + // Backspace should be released + Assert::AreEqual(false, mockedInputHandler.GetVirtualKeyState(VK_BACK)); + + // Release LCtrl + std::vector releaseCtrl{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL, .dwFlags = KEYEVENTF_KEYUP } }, + }; + mockedInputHandler.SendVirtualInput(releaseCtrl); + + // Shortcut state should be fully reset + Assert::AreEqual(false, testState.osLevelShortcutReMap[src].isShortcutInvoked); + // All keys should be released + Assert::AreEqual(false, mockedInputHandler.GetVirtualKeyState(VK_LCONTROL)); + Assert::AreEqual(false, mockedInputHandler.GetVirtualKeyState(VK_BACK)); + Assert::AreEqual(false, mockedInputHandler.GetVirtualKeyState(0x59)); + } + + // Test that after AltGr is pressed without a shortcut, a subsequent shortcut-to-key remap still properly reverts state when another key is pressed + TEST_METHOD (AltGrPressedAndReleased_ShouldNotPreventStateRevert_WhenShortcutToKeyRemapIsInvokedAndOtherKeyIsPressed) + { + // Remap LCtrl+Y to Backspace + Shortcut src; + src.SetKey(VK_LCONTROL); + src.SetKey(0x59); + testState.AddOSLevelShortcut(src, (DWORD)VK_BACK); + + // Simulate AltGr press and release (no shortcut active) + std::vector altGrInputs{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_RMENU } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_RMENU, .dwFlags = KEYEVENTF_KEYUP } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL, .dwFlags = KEYEVENTF_KEYUP } }, + }; + mockedInputHandler.SendVirtualInput(altGrInputs); + + // Invoke shortcut and press an extra key + std::vector inputs1{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = 0x59 } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = 0x42 } }, + }; + mockedInputHandler.SendVirtualInput(inputs1); + + // Shortcut should be invoked: Backspace and B pressed + Assert::AreEqual(true, testState.osLevelShortcutReMap[src].isShortcutInvoked); + Assert::AreEqual(true, mockedInputHandler.GetVirtualKeyState(VK_BACK)); + + // Release Y (action key) — should revert to physical keys since B is held + std::vector releaseY{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = 0x59, .dwFlags = KEYEVENTF_KEYUP } }, + }; + mockedInputHandler.SendVirtualInput(releaseY); + + // State should be reverted: Backspace released, Ctrl restored, B still held + Assert::AreEqual(false, testState.osLevelShortcutReMap[src].isShortcutInvoked); + Assert::AreEqual(false, mockedInputHandler.GetVirtualKeyState(VK_BACK)); + Assert::AreEqual(true, mockedInputHandler.GetVirtualKeyState(VK_LCONTROL)); + Assert::AreEqual(true, mockedInputHandler.GetVirtualKeyState(0x42)); + } + + // Test that multiple AltGr press-release cycles do not accumulate corruption + TEST_METHOD (MultipleAltGrPressReleaseCycles_ShouldNotCorruptShortcutState) + { + // Remap LCtrl+Y to Backspace + Shortcut src; + src.SetKey(VK_LCONTROL); + src.SetKey(0x59); + testState.AddOSLevelShortcut(src, (DWORD)VK_BACK); + + // Simulate AltGr press-release three times + for (int i = 0; i < 3; i++) + { + std::vector altGrInputs{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_RMENU } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_RMENU, .dwFlags = KEYEVENTF_KEYUP } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL, .dwFlags = KEYEVENTF_KEYUP } }, + }; + mockedInputHandler.SendVirtualInput(altGrInputs); + } + + // Invoke shortcut normally + std::vector shortcutDown{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = 0x59 } }, + }; + mockedInputHandler.SendVirtualInput(shortcutDown); + + Assert::AreEqual(true, testState.osLevelShortcutReMap[src].isShortcutInvoked); + + // Release Y then Ctrl + std::vector releaseAll{ + { .type = INPUT_KEYBOARD, .ki = { .wVk = 0x59, .dwFlags = KEYEVENTF_KEYUP } }, + { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LCONTROL, .dwFlags = KEYEVENTF_KEYUP } }, + }; + mockedInputHandler.SendVirtualInput(releaseAll); + + // State should be fully reset after all releases + Assert::AreEqual(false, testState.osLevelShortcutReMap[src].isShortcutInvoked); + Assert::AreEqual(false, mockedInputHandler.GetVirtualKeyState(VK_LCONTROL)); + Assert::AreEqual(false, mockedInputHandler.GetVirtualKeyState(VK_BACK)); + } }; } From d495a2a7e719cb6d9b61857459214c8daf58d66a Mon Sep 17 00:00:00 2001 From: fluffyspace Date: Wed, 1 Apr 2026 09:38:56 +0200 Subject: [PATCH 3/3] [KeyboardManager] Gate AltGr flag set to key-down only The set branch for isAltRightKeyInvoked did not check for key-down, so on VK_RMENU key-up with LCtrl still held (normal AltGr release order) it would re-set the flag to true before the reset branch could execute. Add WM_KEYDOWN/WM_SYSKEYDOWN check to the set condition so the else-if reset on key-up is reached reliably. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp index 1a8462793c4c..17b16bf2f076 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp @@ -303,7 +303,7 @@ namespace KeyboardEventHandlers static bool isAltRightKeyInvoked = false; // Check if the right Alt key (AltGr) is pressed. - if (data->lParam->vkCode == VK_RMENU && ii.GetVirtualKeyState(VK_LCONTROL)) + if (data->lParam->vkCode == VK_RMENU && ii.GetVirtualKeyState(VK_LCONTROL) && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { isAltRightKeyInvoked = true; }