diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp index 2bf9d302a79a..04bb461a5eeb 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp @@ -228,6 +228,52 @@ namespace KeyboardEventHandlers for (auto& itShortcut : state.GetSortedShortcutRemapVector(activatedApp)) { const auto it = reMap.find(itShortcut); + static bool isAltRightKeyInvoked = false; + + // Release key and delete from previous modifier key vector + if ((Helpers::IsModifierKey(data->lParam->vkCode) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))) + { + std::vector keyEventList; + if (!(isAltRightKeyInvoked && data->lParam->vkCode == VK_LCONTROL)) + { + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(data->lParam->vkCode), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + state.ResetPreviousModifierKey(data->lParam->vkCode); + } + + if (isAltRightKeyInvoked && data->lParam->vkCode == VK_RMENU && state.FindPreviousModifierKey(VK_LCONTROL)) + { + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, VK_LCONTROL, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + state.ResetPreviousModifierKey(it->first.GetCtrlKey()); + } + + if (data->lParam->vkCode == VK_RWIN || data->lParam->vkCode == VK_LWIN) + { + it->second.winKeyInvoked = ModifierKey::Disabled; + } + + ii.SendVirtualInput(keyEventList); + + } + else if ((Helpers::IsModifierKey(data->lParam->vkCode) && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))) + { + // Remember which win key was pressed initially + if (data->lParam->vkCode == VK_RWIN) + { + it->second.winKeyInvoked = ModifierKey::Right; + } + else if (data->lParam->vkCode == VK_LWIN) + { + it->second.winKeyInvoked = ModifierKey::Left; + } + + // Set the previous modifier key of the invoked shortcut + SetPreviousModifierKey(it, data->lParam->vkCode, state); + // Check if the right Alt key (AltGr) is pressed. + if (data->lParam->vkCode == VK_RMENU && state.FindPreviousModifierKey(VK_LCONTROL)) + { + isAltRightKeyInvoked = true; + } + } // If a shortcut is currently in the invoked state then skip till the shortcut that is currently invoked if (isShortcutInvoked && !it->second.isShortcutInvoked) @@ -235,6 +281,12 @@ namespace KeyboardEventHandlers continue; } + // If action key is pressed check modifier key from shortcut with previous modifier key saved at state + if ((data->lParam->vkCode == it->first.GetActionKey()) && (state.GetPreviousModifierKey().size() == 0 || !CheckPreviousModifierKey(it, state))) + { + continue; + } + // Check if the remap is to a key or a shortcut const bool remapToKey = it->second.targetShortcut.index() == 0; const bool remapToShortcut = it->second.targetShortcut.index() == 1; @@ -247,14 +299,6 @@ namespace KeyboardEventHandlers bool isMatchOnChordEnd = false; bool isMatchOnChordStart = false; - static bool isAltRightKeyInvoked = false; - - // Check if the right Alt key (AltGr) is pressed. - if (data->lParam->vkCode == VK_RMENU && ii.GetVirtualKeyState(VK_LCONTROL)) - { - isAltRightKeyInvoked = true; - } - // If the shortcut has been pressed down if (!it->second.isShortcutInvoked && it->first.CheckModifiersKeyboardState(ii)) { @@ -310,16 +354,6 @@ namespace KeyboardEventHandlers std::vector keyEventList; - // Remember which win key was pressed initially - if (ii.GetVirtualKeyState(VK_RWIN)) - { - it->second.winKeyInvoked = ModifierKey::Right; - } - else if (ii.GetVirtualKeyState(VK_LWIN)) - { - it->second.winKeyInvoked = ModifierKey::Left; - } - if (isRunProgram) { auto threadFunction = [it]() { @@ -648,22 +682,13 @@ namespace KeyboardEventHandlers if (!remapToText && ((!it->first.HasChord() && data->lParam->vkCode == it->first.GetActionKey()) || (it->first.HasChord() && data->lParam->vkCode == it->first.GetSecondKey())) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) { std::vector keyEventList; - if (remapToShortcut && !it->first.HasChord()) - { - // Just lift the action key for no chords. - Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - } - else if (remapToShortcut && it->first.HasChord()) + if (remapToShortcut) { - // If it has a chord, we'll want a full clean contemplated in the else, since you can't really repeat chords by pressing the end key again. - - // Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated + // Just lift the action key. Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - // Release new shortcut state (release in reverse order of shortcut to be accurate) Helpers::SetModifierKeyEvents(std::get(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first); - // Set old shortcut key down state Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get(it->second.targetShortcut)); // Reset the remap state @@ -711,7 +736,7 @@ namespace KeyboardEventHandlers // Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->V, press Shift+Win+A and release A, since Win will be pressed here we need to send a dummy event after it Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - if (!isAltRightKeyInvoked) + if (!isAltRightKeyInvoked || (isAltRightKeyInvoked && data->lParam->vkCode != VK_LCONTROL)) { // Reset the remap state it->second.isShortcutInvoked = false; @@ -848,7 +873,7 @@ namespace KeyboardEventHandlers // Do not send a dummy key as we want the current key press to behave as normal i.e. it can do press+release functionality if required. Required to allow a shortcut to Win key remap invoked directly after shortcut to shortcut is released to open start menu } - if (!isAltRightKeyInvoked) + if (!isAltRightKeyInvoked || (isAltRightKeyInvoked && data->lParam->vkCode != VK_LCONTROL)) { // Reset the remap state it->second.isShortcutInvoked = false; @@ -899,7 +924,7 @@ namespace KeyboardEventHandlers { std::vector keyEventList; - if (!isAltRightKeyInvoked) + if (!isAltRightKeyInvoked || (isAltRightKeyInvoked && data->lParam->vkCode != VK_LCONTROL)) { // Set original shortcut key down state Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); @@ -917,7 +942,7 @@ namespace KeyboardEventHandlers // Do not send a dummy key as we want the current key press to behave as normal i.e. it can do press+release functionality if required. Required to allow a shortcut to Win key remap invoked directly after another shortcut to key remap is released to open start menu - if (!isAltRightKeyInvoked) + if (!isAltRightKeyInvoked || (isAltRightKeyInvoked && data->lParam->vkCode != VK_LCONTROL)) { // Reset the remap state it->second.isShortcutInvoked = false; @@ -1727,4 +1752,104 @@ namespace KeyboardEventHandlers return 1; } + + bool CheckPreviousModifierKey(const ShortcutRemapTable::iterator it, State& state) + { + auto previousKeys = state.GetPreviousModifierKey(); + if (!previousKeys.empty()) + { + if (it->first.GetShiftKey() != 0) + { + if (!state.FindPreviousModifierKey(it->first.GetShiftKey())) + { + return false; + } + } + else + { + for (auto key : previousKeys) + { + if ((VK_SHIFT == key) || (VK_LSHIFT == key) || (VK_RSHIFT == key)) + { + return false; + } + } + } + + if (it->first.GetAltKey() != 0) + { + if (!state.FindPreviousModifierKey(it->first.GetAltKey())) + { + return false; + } + } + else + { + for (auto key : previousKeys) + { + if ((VK_MENU == key) || (VK_LMENU == key) || (VK_RMENU == key)) + { + return false; + } + } + } + + if (it->first.GetCtrlKey() != 0) + { + if (!state.FindPreviousModifierKey(it->first.GetCtrlKey())) + { + return false; + } + } + else + { + for (auto key : previousKeys) + { + if ((VK_CONTROL == key) || (VK_LCONTROL == key) || (VK_RCONTROL == key)) + { + return false; + } + } + } + + if (it->first.GetWinKey(it->second.winKeyInvoked) != 0) + { + if (!state.FindPreviousModifierKey(it->first.GetWinKey(it->second.winKeyInvoked))) + { + return false; + } + } + else + { + for (auto key : previousKeys) + { + if ((VK_LWIN == key) || (VK_RWIN == key)) + { + return false; + } + } + } + } + return true; + } + + void SetPreviousModifierKey(const ShortcutRemapTable::iterator it, const DWORD key, State& state) + { + if (it->first.GetWinKey(it->second.winKeyInvoked) == key) + { + state.SetPreviousModifierKey(it->first.GetWinKey(it->second.winKeyInvoked)); + } + else if (it->first.GetCtrlKey() == key) + { + state.SetPreviousModifierKey(it->first.GetCtrlKey()); + } + else if (it->first.GetAltKey() == key) + { + state.SetPreviousModifierKey(it->first.GetAltKey()); + } + else if (it->first.GetShiftKey() == key) + { + state.SetPreviousModifierKey(it->first.GetShiftKey()); + } + } } diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h index 67eeed69775f..67ca79468fd3 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h @@ -84,4 +84,10 @@ namespace KeyboardEventHandlers // Function to ensure Ctrl/Shift/Alt modifier key state is not detected as pressed down by applications which detect keys at a lower level than hooks when it is remapped for scenarios where its required void ResetIfModifierKeyForLowerLevelKeyHandlers(KeyboardManagerInput::InputInterface& ii, DWORD key, DWORD target); + + // Function to check previous modifier key with state + bool CheckPreviousModifierKey(const ShortcutRemapTable::iterator it, State& state); + + // Function to set previous modifier key to state + void SetPreviousModifierKey(const ShortcutRemapTable::iterator it, const DWORD key, State& state); }; diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.cpp index e56b1dc0e6a9..d004d6b163d9 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.cpp @@ -73,3 +73,30 @@ std::wstring State::GetActivatedApp() { return activatedAppSpecificShortcutTarget; } + +// Sets the previous modifier key to check in another shortcut +void State::SetPreviousModifierKey(const DWORD prevKey) +{ + if (!FindPreviousModifierKey(prevKey)) + { + previousModifierKey.emplace_back(prevKey); + } +} + +// Gets the previous modifier key +std::vector State::GetPreviousModifierKey() +{ + return previousModifierKey; +} + +// Check if a key exists in the previousModifierKey vector +bool State::FindPreviousModifierKey(const DWORD prevKey) +{ + return std::find(previousModifierKey.begin(), previousModifierKey.end(), prevKey) != previousModifierKey.end(); +} + +// Resets the previous modifier key +void State::ResetPreviousModifierKey(const DWORD prevKey) +{ + previousModifierKey.erase(std::remove(previousModifierKey.begin(), previousModifierKey.end(), prevKey), previousModifierKey.end()); +} diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h index 53b476302ad1..d5b8632f1426 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h @@ -6,6 +6,8 @@ class State : public MappingConfiguration private: // Stores the activated target application in app-specific shortcut std::wstring activatedAppSpecificShortcutTarget; + // Stores the previous modifier key + std::vector previousModifierKey; public: // Function to get the iterator of a single key remap given the source key. Returns nullopt if it isn't remapped @@ -26,4 +28,16 @@ class State : public MappingConfiguration // Gets the activated target application in app-specific shortcut std::wstring GetActivatedApp(); + + // Sets the previous modifier key to check in another shortcut + void SetPreviousModifierKey(const DWORD prevKey); + + // Gets the previous modifier key + std::vector GetPreviousModifierKey(); + + // Check a key if exist in previous modifier key vector + bool FindPreviousModifierKey(const DWORD prevKey); + + // Resets the previous modifier key + void ResetPreviousModifierKey(const DWORD prevKey); }; \ No newline at end of file diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp index 4b470ec5a6e0..33c17b6af327 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp @@ -861,7 +861,7 @@ namespace RemappingLogicTests Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x56), false); } - // Test if target modifier is still held down even if the action key of the original shortcut is released - required for Alt+Tab/Win+Space cases + // Test if original modifier is still held down even if the action key of the original shortcut is released - required for Alt+Tab/Win+Space cases TEST_METHOD (RemappedShortcutModifiers_ShouldBeDetectedAsPressed_OnReleasingActionKeyButHoldingModifiers) { // Remap Ctrl+A to Alt+Tab @@ -882,10 +882,10 @@ namespace RemappingLogicTests // Send Ctrl+A, release A mockedInputHandler.SendVirtualInput(inputs); - // Ctrl, A, Tab key states should be unchanged, Alt should be true - Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false); + // Alt, A, Tab key states should be unchanged, Ctrl should be true + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true); Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x41), false); - Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_MENU), true); + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_MENU), false); Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_TAB), false); }