diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp index 2bf9d302a79a..742456d7a7b0 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp @@ -228,9 +228,46 @@ 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(it->first.GetCtrlKey())) + { + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(it->first.GetCtrlKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + state.ResetPreviousModifierKey(it->first.GetCtrlKey()); + } + + ii.SendVirtualInput(keyEventList); + + } + else if ((Helpers::IsModifierKey(data->lParam->vkCode) && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))) + { + // 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(it->first.GetCtrlKey())) + { + 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) + // If a shortcut is currently in the invoked state then skip till the shortcut that is currently invoked and pressed key is not action key + if (data->lParam->vkCode != it->first.GetActionKey() && isShortcutInvoked && !it->second.isShortcutInvoked) + { + 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()) && !CheckPreviousModifierKey(it, state.GetPreviousModifierKey())) { continue; } @@ -247,19 +284,11 @@ 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)) + if (!it->second.isShortcutInvoked && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN) && it->first.CheckModifiersKeyboardState(ii)) { // if not a mod key, check for chord stuff - if (!resetChordsResults.CurrentKeyIsModifierKey && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) + if (!resetChordsResults.CurrentKeyIsModifierKey) { if (itShortcut.HasChord()) { @@ -297,13 +326,13 @@ namespace KeyboardEventHandlers } } - if (isMatchOnChordEnd || (!resetChordsResults.AnyChordStarted && !itShortcut.HasChord() && (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)))) + if (isMatchOnChordEnd || (!resetChordsResults.AnyChordStarted && !itShortcut.HasChord() && (data->lParam->vkCode == it->first.GetActionKey()))) { ResetAllStartedChords(state, activatedApp); resetChordsResults.AnyChordStarted = false; // Check if any other keys have been pressed apart from the shortcut. If true, then check for the next shortcut. This is to be done only for shortcut to shortcut remaps - if (!it->first.IsKeyboardStateClearExceptShortcut(ii) && (remapToShortcut || (remapToKey && std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED))) + if (!it->first.IsKeyboardStateClearExceptShortcut(ii, static_cast(state.GetPreviousActionKey())) && (remapToShortcut || (remapToKey && std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED))) { continue; } @@ -361,6 +390,7 @@ namespace KeyboardEventHandlers } } + auto threadFunction = [newUri]() { HINSTANCE result = ShellExecute(NULL, L"open", newUri.c_str(), NULL, NULL, SW_SHOWNORMAL); @@ -397,10 +427,14 @@ namespace KeyboardEventHandlers } else { - // Dummy key, key up for all the original shortcut modifier keys and key down for all the new shortcut keys but common keys in each are not repeated // Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->Ctrl+V, press Win+A, since Win will be released here we need to send a dummy event before it Helpers::SetDummyKeyEvent(keyEventList, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + if (isAltRightKeyInvoked) + { + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(it->first.GetCtrlKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + } + // Release original shortcut state (release in reverse order of shortcut to be accurate) Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get(it->second.targetShortcut)); @@ -516,10 +550,16 @@ namespace KeyboardEventHandlers } } + // Set the previous action key with the action key of the invoked shortcut + if (state.GetPreviousActionKey() == 0) + { + state.SetPreviousActionKey(it->first.GetActionKey()); + } + return 1; } } - else if (it->second.isShortcutInvoked) + else if (it->second.isShortcutInvoked || isShortcutInvoked) { // The shortcut has already been pressed down at least once, i.e. the shortcut has been invoked // There are 6 cases to be handled if the shortcut has been pressed down @@ -539,7 +579,6 @@ namespace KeyboardEventHandlers // Get the common keys between the two shortcuts int commonKeys = (remapToShortcut && !isRunProgram) ? it->first.GetCommonModifiersCount(std::get(it->second.targetShortcut)) : 0; - // Case 1: If any of the modifier keys of the original shortcut are released before the action key if ((it->first.CheckWinKey(data->lParam->vkCode) || it->first.CheckCtrlKey(data->lParam->vkCode) || it->first.CheckAltKey(data->lParam->vkCode) || it->first.CheckShiftKey(data->lParam->vkCode)) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) { @@ -557,15 +596,16 @@ namespace KeyboardEventHandlers } Helpers::SetModifierKeyEvents(std::get(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first, data->lParam->vkCode); + + // Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message + Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get(it->second.targetShortcut), data->lParam->vkCode); - if (!isAltRightKeyInvoked) - { - // Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message - Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, std::get(it->second.targetShortcut), data->lParam->vkCode); - } - else + // If the released key is AltGr, release the left control key as well. + if (isAltRightKeyInvoked && data->lParam->vkCode == VK_RMENU) { isAltRightKeyInvoked = false; + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(it->first.GetCtrlKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + state.ResetPreviousModifierKey(static_cast(it->first.GetCtrlKey())); } // Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+Ctrl+A->Ctrl+V, press Win+Ctrl+A and release A then Ctrl, since Win will be pressed here we need to send a dummy event after it @@ -580,16 +620,16 @@ namespace KeyboardEventHandlers { Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); } + + // Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message + Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode); - // Ensures that after releasing both the action key and AltGr, Ctrl does not remain falsely pressed. - if (!isAltRightKeyInvoked) - { - // Set original shortcut key down state except the action key and the released modifier since the original action key may or may not be held down. If it is held down it will generate it's own key message - Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, Shortcut(), data->lParam->vkCode); - } - else + // If the released key is AltGr, release the left control key as well. + if (isAltRightKeyInvoked && data->lParam->vkCode == VK_RMENU) { isAltRightKeyInvoked = false; + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(it->first.GetCtrlKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + state.ResetPreviousModifierKey(static_cast(it->first.GetCtrlKey())); } // Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+Ctrl+A->V, press Win+Ctrl+A and release A then Ctrl, since Win will be pressed here we need to send a dummy event after it @@ -599,7 +639,7 @@ namespace KeyboardEventHandlers // Reset the remap state it->second.isShortcutInvoked = false; it->second.winKeyInvoked = ModifierKey::Disabled; - it->second.isOriginalActionKeyPressed = false; + it->second.isOriginalActionKeyPressed = false; // If app specific shortcut has finished invoking, reset the target application if (activatedApp) @@ -607,16 +647,60 @@ namespace KeyboardEventHandlers state.SetActivatedApp(KeyboardManagerConstants::NoActivatedApp); } + // Reset previous action key + if (state.GetPreviousActionKey() != 0) + { + state.SetPreviousActionKey(0); + } + ii.SendVirtualInput(keyEventList); return 1; } // The system will see the modifiers of the new shortcut as being held down because of the shortcut remap - if (!remapToShortcut || (remapToShortcut && std::get(it->second.targetShortcut).CheckModifiersKeyboardState(ii))) + if (!remapToShortcut || (remapToShortcut && std::get(it->second.targetShortcut).CheckModifiersKeyboardState(ii)) || isAltRightKeyInvoked) { // Case 2: If the original shortcut is still held down the keyboard will get a key down message of the action key in the original shortcut and the new shortcut's modifiers will be held down (keys held down send repeated keydown messages) - if (((data->lParam->vkCode == it->first.GetActionKey() && !it->first.HasChord()) || (data->lParam->vkCode == it->first.GetSecondKey() && it->first.HasChord())) && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) + if (((data->lParam->vkCode == it->first.GetActionKey() && !it->first.HasChord()) || (data->lParam->vkCode == it->first.GetSecondKey() && it->first.HasChord()) || (state.GetPreviousActionKey() != 0)) && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { + // Check if the pressed key is the previous action key and if the pressed key is the action key. If not, first release current shortcut and repeat the process to continue searching in the shortcut table + if (state.GetPreviousActionKey() != 0 && state.GetPreviousActionKey() != data->lParam->vkCode && data->lParam->vkCode != it->first.GetActionKey()) + { + std::vector keyEventList; + + if (remapToShortcut && !it->first.HasChord()) + { + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + + if (std::get(it->second.targetShortcut).GetCtrlKey() == NULL && std::get(it->second.targetShortcut).GetAltKey() == NULL && std::get(it->second.targetShortcut).GetShiftKey() == NULL) + { + ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, std::get(it->second.targetShortcut).GetActionKey()); + } + } + else if (remapToShortcut && it->first.HasChord()) + { + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + + if (std::get(it->second.targetShortcut).GetCtrlKey() == NULL && std::get(it->second.targetShortcut).GetAltKey() == NULL && std::get(it->second.targetShortcut).GetShiftKey() == NULL) + { + ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, std::get(it->second.targetShortcut).GetActionKey()); + } + } + else if (remapToKey) + { + Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(state.GetPreviousActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); + + auto maybeTargetKey = std::get_if(&it->second.targetShortcut); + + if (maybeTargetKey) + { + ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, Helpers::FilterArtificialKeys(*maybeTargetKey)); + } + } + + ii.SendVirtualInput(keyEventList); + continue; + } // In case of mapping to disable do not send anything if (remapToKey && std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED) { @@ -641,13 +725,26 @@ namespace KeyboardEventHandlers } ii.SendVirtualInput(keyEventList); + + + // Set the previous action key with the action key of the invoked shortcut + if (state.GetPreviousActionKey() == 0) + { + state.SetPreviousActionKey(it->first.GetActionKey()); + } return 1; } // Case 3: If the action key is released from the original shortcut, keep modifiers of the new shortcut until some other key event which doesn't apply to the original shortcut - 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)) + if (!remapToText && ((!it->first.HasChord() && data->lParam->vkCode == it->first.GetActionKey()) || (it->first.HasChord() && data->lParam->vkCode == it->first.GetSecondKey()) || (state.GetPreviousActionKey() != 0)) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) { std::vector keyEventList; + // Check if the released key is the previous action key. If not, continue + if (state.GetPreviousActionKey() != 0 && data->lParam->vkCode != it->first.GetActionKey()) + { + continue; + } + if (remapToShortcut && !it->first.HasChord()) { // Just lift the action key for no chords. @@ -663,13 +760,16 @@ namespace KeyboardEventHandlers // 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 - it->second.isShortcutInvoked = false; - it->second.winKeyInvoked = ModifierKey::Disabled; - it->second.isOriginalActionKeyPressed = false; + // Ensures that after releasing both the action key and AltGr, Ctrl does not remain falsely pressed. + if (!isAltRightKeyInvoked) + { + // 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 + it->second.isShortcutInvoked = false; + it->second.winKeyInvoked = ModifierKey::Disabled; + it->second.isOriginalActionKeyPressed = false; + } // If app specific shortcut has finished invoking, reset the target application if (activatedApp) @@ -682,12 +782,19 @@ namespace KeyboardEventHandlers // If remapped to disable, do nothing and suppress the key event // Since the original shortcut's action key is released, set it to false it->second.isOriginalActionKeyPressed = false; + + // Check if the released key is the previous action key. If so, reset previous action key + if (state.GetPreviousActionKey() != 0 && state.GetPreviousActionKey() == data->lParam->vkCode) + { + state.SetPreviousActionKey(0); + } + return 1; } else { // Check if the keyboard state is clear apart from the target remap key (by creating a temp Shortcut object with the target key) - bool isKeyboardStateClear = Shortcut(std::vector({ Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut)) })).IsKeyboardStateClearExceptShortcut(ii); + bool isKeyboardStateClear = Shortcut(std::vector({ Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut)) })).IsKeyboardStateClearExceptShortcut(ii, static_cast(state.GetPreviousActionKey())); // If the keyboard state is clear, we release the target key but do not reset the remap state if (isKeyboardStateClear) @@ -701,23 +808,21 @@ namespace KeyboardEventHandlers // Release new key state Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - - if (!isAltRightKeyInvoked) - { - // Set original shortcut key down state except the action key - Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - } + + // Set original shortcut key down state except the action key + Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); // 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_RMENU)) { // Reset the remap state it->second.isShortcutInvoked = false; it->second.winKeyInvoked = ModifierKey::Disabled; it->second.isOriginalActionKeyPressed = false; } + // If app specific shortcut has finished invoking, reset the target application if (activatedApp != KeyboardManagerConstants::NoActivatedApp) @@ -728,6 +833,13 @@ namespace KeyboardEventHandlers } ii.SendVirtualInput(keyEventList); + + // Check if the released key is the previous action key. If so, reset previous action key + if (state.GetPreviousActionKey() != 0 && state.GetPreviousActionKey() == data->lParam->vkCode) + { + state.SetPreviousActionKey(0); + } + return 1; } @@ -808,12 +920,11 @@ namespace KeyboardEventHandlers newRemapping.isShortcutInvoked = true; } - // Remember which win key was pressed initially - if (ii.GetVirtualKeyState(VK_RWIN)) + if (ii.GetVirtualKeyState(VK_RWIN) || ii.GetVirtualKeyState(VK_RCONTROL) || ii.GetVirtualKeyState(VK_RMENU) || ii.GetVirtualKeyState(VK_RSHIFT)) { newRemapping.winKeyInvoked = ModifierKey::Right; } - else if (ii.GetVirtualKeyState(VK_LWIN)) + else if (ii.GetVirtualKeyState(VK_LWIN) || ii.GetVirtualKeyState(VK_LCONTROL) || ii.GetVirtualKeyState(VK_LMENU) || ii.GetVirtualKeyState(VK_LSHIFT)) { newRemapping.winKeyInvoked = ModifierKey::Left; } @@ -827,11 +938,11 @@ namespace KeyboardEventHandlers if (isActionKeyPressed) { Helpers::SetKeyEvent(keyEventList, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - } + } if (!isAltRightKeyInvoked) { 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)); } @@ -898,12 +1009,9 @@ namespace KeyboardEventHandlers if (isRemapToDisable || !isOriginalActionKeyPressed) { std::vector keyEventList; - - if (!isAltRightKeyInvoked) - { - // Set original shortcut key down state - Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); - } + + // Set original shortcut key down state + Helpers::SetModifierKeyEvents(it->first, it->second.winKeyInvoked, keyEventList, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG); // Send the original action key only if it is physically pressed. For remappings to keys other than disabled we already check earlier that it is not pressed in this scenario. For remap to disable if (isRemapToDisable && isOriginalActionKeyPressed) @@ -917,13 +1025,10 @@ 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) - { - // Reset the remap state - it->second.isShortcutInvoked = false; - it->second.winKeyInvoked = ModifierKey::Disabled; - it->second.isOriginalActionKeyPressed = false; - } + // Reset the remap state + it->second.isShortcutInvoked = false; + it->second.winKeyInvoked = ModifierKey::Disabled; + it->second.isOriginalActionKeyPressed = false; // If app specific shortcut has finished invoking, reset the target application if (activatedApp != KeyboardManagerConstants::NoActivatedApp) @@ -1727,4 +1832,142 @@ namespace KeyboardEventHandlers return 1; } + + bool CheckPreviousModifierKey(const ShortcutRemapTable::iterator it, std::vector previousKeys) + { + if (!previousKeys.empty()) + { + bool isKeyFound = false; + if (it->first.GetShiftKey() != 0) + { + for (auto key : previousKeys) + { + if (it->first.GetShiftKey() == key) + { + isKeyFound = true; + break; + } + else + { + isKeyFound = false; + } + } + + if (!isKeyFound) + { + return false; + } + } + + if (it->first.GetAltKey() != 0) + { + for (auto key : previousKeys) + { + if (it->first.GetAltKey() == key) + { + isKeyFound = true; + break; + } + else + { + isKeyFound = false; + } + } + + if (!isKeyFound) + { + return false; + } + } + + if (it->first.GetCtrlKey() != 0) + { + for (auto key : previousKeys) + { + if (it->first.GetCtrlKey() == key) + { + isKeyFound = true; + break; + } + else + { + isKeyFound = false; + } + } + + if (!isKeyFound) + { + return false; + } + } + + if (it->first.GetWinKey(it->second.winKeyInvoked) != 0) + { + for (auto key : previousKeys) + { + if (it->first.GetWinKey(it->second.winKeyInvoked) == key) + { + isKeyFound = true; + break; + } + else + { + isKeyFound = false; + } + } + + if (!isKeyFound) + { + return false; + } + } + + if (!isKeyFound) + { + return false; + } + } + + return true; + } + + void SetPreviousModifierKey(const ShortcutRemapTable::iterator it, const DWORD key, State& state) + { + if (it->first.GetWinKey(it->second.winKeyInvoked) == key) + { + if (!state.FindPreviousModifierKey(it->first.GetWinKey(it->second.winKeyInvoked))) + { + state.SetPreviousModifierKey(it->first.GetWinKey(it->second.winKeyInvoked)); + } + } + else if (it->first.GetCtrlKey() == key) + { + if (!state.FindPreviousModifierKey(it->first.GetCtrlKey())) + { + state.SetPreviousModifierKey(it->first.GetCtrlKey()); + } + } + else if (it->first.GetAltKey() == key) + { + if (!state.FindPreviousModifierKey(it->first.GetAltKey())) + { + state.SetPreviousModifierKey(it->first.GetAltKey()); + } + + if (it->first.GetAltKey() == VK_RMENU) + { + if (!state.FindPreviousModifierKey(it->first.GetCtrlKey())) + { + state.SetPreviousModifierKey(it->first.GetCtrlKey()); + } + } + } + else if (it->first.GetShiftKey() == key) + { + if (!state.FindPreviousModifierKey(it->first.GetShiftKey())) + { + state.SetPreviousModifierKey(it->first.GetShiftKey()); + } + } + } } diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.h index 67eeed69775f..01a12f274184 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, std::vector previousKeys); + + // 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..9c0d342cb907 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.cpp @@ -73,3 +73,45 @@ std::wstring State::GetActivatedApp() { return activatedAppSpecificShortcutTarget; } + +// Sets the previous action key to use in another shortcut +void State::SetPreviousActionKey(const DWORD prevKey) +{ + previousActionKey = prevKey; +} + +// Gets the previous action key +DWORD State::GetPreviousActionKey() +{ + return previousActionKey; +} + +// Sets the previous modifier key to check in another shortcut +void State::SetPreviousModifierKey(const DWORD 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()); +} + +// Clear all previous modifier key +void State::ClearPreviousModifierKey() +{ + previousModifierKey.clear(); +} diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h index 53b476302ad1..be2a9b83fa2a 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h +++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/State.h @@ -6,6 +6,10 @@ class State : public MappingConfiguration private: // Stores the activated target application in app-specific shortcut std::wstring activatedAppSpecificShortcutTarget; + // Stores the previous action key + DWORD previousActionKey = {}; + // 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 +30,25 @@ class State : public MappingConfiguration // Gets the activated target application in app-specific shortcut std::wstring GetActivatedApp(); + + // Sets the previous action key to use in another shortcut + void SetPreviousActionKey(const DWORD prevKey); + + // Gets the previous action key + DWORD GetPreviousActionKey(); + + // 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); + + // Clear all previous modifier key + void ClearPreviousModifierKey(); }; \ No newline at end of file diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp index 4b470ec5a6e0..91fca6c67d35 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEngineTest/OSLevelShortcutRemappingTests.cpp @@ -1977,12 +1977,12 @@ namespace RemappingLogicTests mockedInputHandler.SendVirtualInput(inputs2); - // Check that Ctrl+A+B was pressed - Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), true); - Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), true); + // Check that Ctrl, A released and B was pressed + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(VK_CONTROL), false); + Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(actionKey), false); Assert::AreEqual(mockedInputHandler.GetVirtualKeyState(0x42), true); - // Shortcut invoked state should be false - Assert::AreEqual(false, testState.osLevelShortcutReMap[src].isShortcutInvoked); + // Shortcut invoked state should be true + Assert::AreEqual(true, testState.osLevelShortcutReMap[src].isShortcutInvoked); } // Test that shortcut is not disabled if the shortcut which was remapped to Disable is pressed and the action key is released, followed by pressing another key @@ -2149,8 +2149,8 @@ namespace RemappingLogicTests Assert::AreEqual(false, testState.osLevelShortcutReMap[src].isOriginalActionKeyPressed); } - // Test that the isOriginalActionKeyPressed flag is set to false on pressing another key - TEST_METHOD (ShortcutDisable_ShouldResetIsOriginalActionKeyPressed_OnPressingAnotherKey) + // Test that the isOriginalActionKeyPressed flag is set to true on pressing another key + TEST_METHOD (ShortcutDisable_ShouldNotResetIsOriginalActionKeyPressed_OnPressingAnotherKey) { Shortcut src; src.SetKey(VK_CONTROL); @@ -2178,8 +2178,8 @@ namespace RemappingLogicTests // press B mockedInputHandler.SendVirtualInput(inputs2); - // IsOriginalActionKeyPressed state should be false - Assert::AreEqual(false, testState.osLevelShortcutReMap[src].isOriginalActionKeyPressed); + // IsOriginalActionKeyPressed state should be true + Assert::AreEqual(true, testState.osLevelShortcutReMap[src].isOriginalActionKeyPressed); } // Tests for dummy key events in shortcut remaps diff --git a/src/modules/keyboardmanager/common/Shortcut.cpp b/src/modules/keyboardmanager/common/Shortcut.cpp index ecd8b58c80fa..7806e6e357cd 100644 --- a/src/modules/keyboardmanager/common/Shortcut.cpp +++ b/src/modules/keyboardmanager/common/Shortcut.cpp @@ -746,7 +746,7 @@ bool IgnoreKeyCode(DWORD key) } // Function to check if any keys are pressed down except those in the shortcut -bool Shortcut::IsKeyboardStateClearExceptShortcut(KeyboardManagerInput::InputInterface& ii) const +bool Shortcut::IsKeyboardStateClearExceptShortcut(KeyboardManagerInput::InputInterface& ii, int prevKey) const { // Iterate through all the virtual key codes - 0xFF is set to key down because of the Num Lock for (int keyVal = 1; keyVal < 0xFF; keyVal++) @@ -884,8 +884,8 @@ bool Shortcut::IsKeyboardStateClearExceptShortcut(KeyboardManagerInput::InputInt continue; } } - // If any other key is pressed check if it is the action key - else if (keyVal != static_cast(actionKey)) + // If any other key is pressed check if it is the action key or check if it is previous action key + else if (keyVal != static_cast(actionKey) && (prevKey != 0 && keyVal != prevKey)) { return false; } diff --git a/src/modules/keyboardmanager/common/Shortcut.h b/src/modules/keyboardmanager/common/Shortcut.h index 0bbe00bb4f4f..7720273a1ac0 100644 --- a/src/modules/keyboardmanager/common/Shortcut.h +++ b/src/modules/keyboardmanager/common/Shortcut.h @@ -181,7 +181,7 @@ class Shortcut bool CheckModifiersKeyboardState(KeyboardManagerInput::InputInterface& ii) const; // Function to check if any keys are pressed down except those in the shortcut - bool IsKeyboardStateClearExceptShortcut(KeyboardManagerInput::InputInterface& ii) const; + bool IsKeyboardStateClearExceptShortcut(KeyboardManagerInput::InputInterface& ii, int prevKey) const; // Function to get the number of modifiers that are common between the current shortcut and the shortcut in the argument int GetCommonModifiersCount(const Shortcut& input) const;