diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp index 0e8529396bc2..17b16bf2f076 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp @@ -303,10 +303,14 @@ 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; } + 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)) 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)); + } }; }