From 90ddcb30bf5e76edd691bb5a22707fd798bdfad8 Mon Sep 17 00:00:00 2001 From: Arjun Balgovind <32061677+arjunbalgovind@users.noreply.github.com> Date: Thu, 26 Mar 2020 11:22:25 -0700 Subject: [PATCH] Add support for shortcuts with >2 keys (dev/keyboardManager) (#1697) * Added check keyboard state function * Added keyboard state check to avoid n-key shortcuts being affected by 2 key shortcuts * Added support for n key shortcuts in backend and UI * Added a fix to ensure mouse buttons states are not checked * reverted some debugging changes * reverted some debugging changes --- src/modules/keyboardmanager/dll/dllmain.cpp | 396 ++++++++++++++---- .../ui/EditShortcutsWindow.cpp | 4 +- 2 files changed, 320 insertions(+), 80 deletions(-) diff --git a/src/modules/keyboardmanager/dll/dllmain.cpp b/src/modules/keyboardmanager/dll/dllmain.cpp index 04f693c469..18d03b6cdc 100644 --- a/src/modules/keyboardmanager/dll/dllmain.cpp +++ b/src/modules/keyboardmanager/dll/dllmain.cpp @@ -322,7 +322,7 @@ public: { return 1; } - + int key_count = 1; LPINPUT keyEventList = new INPUT[size_t(key_count)](); memset(keyEventList, 0, sizeof(keyEventList)); @@ -392,134 +392,374 @@ public: return 0; } + // Function to check if any keys are pressed down except those passed in the argument + bool IsKeyboardStateClearExceptArgs(const std::vector& args) + { + bool isIgnore = false; + for (int keyVal = 0; keyVal < 0x100; keyVal++) + { + // Skip mouse buttons. Keeping this could cause a remapping to fail if a mouse button is also pressed at the same time + if (keyVal == VK_LBUTTON || keyVal == VK_RBUTTON || keyVal == VK_MBUTTON || keyVal == VK_XBUTTON1 || keyVal == VK_XBUTTON2) + { + continue; + } + // Check state of the key + if (GetAsyncKeyState(keyVal) & 0x8000) + { + isIgnore = false; + // If the key is not part of the argument then the keyboard state is not clear + for (int i = 0; i < args.size(); i++) + { + // If the key matches one of the args, ignore + if (args[i] == keyVal) + { + isIgnore = true; + break; + } + // If the key is Control and either of the args is L/R Control, ignore + else if ((args[i] == VK_LCONTROL || args[i] == VK_RCONTROL) && keyVal == VK_CONTROL) + { + isIgnore = true; + break; + } + // If the key is Alt and either of the args is L/R Alt, ignore + else if ((args[i] == VK_LMENU || args[i] == VK_RMENU) && keyVal == VK_MENU) + { + isIgnore = true; + break; + } + // If the key is Shift and either of the args is L/R Shift, ignore + else if ((args[i] == VK_LSHIFT || args[i] == VK_RSHIFT) && keyVal == VK_SHIFT) + { + isIgnore = true; + break; + } + // If the key is L/R Control and either of the args is Control, ignore + else if ((keyVal == VK_LCONTROL || keyVal == VK_RCONTROL) && args[i] == VK_CONTROL) + { + isIgnore = true; + break; + } + // If the key is L/R Alt and either of the args is Alt, ignore + else if ((keyVal == VK_LMENU || keyVal == VK_RMENU) && args[i] == VK_MENU) + { + isIgnore = true; + break; + } + // If the key is L/R Shift and either of the args is Shift, ignore + else if ((keyVal == VK_LSHIFT || keyVal == VK_RSHIFT) && args[i] == VK_SHIFT) + { + isIgnore = true; + break; + } + } + + if (!isIgnore) + { + return false; + } + } + } + + return true; + } + + // Function to check if the modifiers in the shortcut have been pressed down + template + bool CheckModifiersKeyboardState(const std::vector& args) + { + // Check all keys except last + for (int i = 0; i < args.size() - 1; i++) + { + if (!(GetAsyncKeyState(args[i]) & 0x8000)) + { + return false; + } + } + + return true; + } + + // Function to check if all the modifiers in the first shorcut are present in the second shortcut, i.e. Modifiers(src) are a subset of Modifiers(dest) + std::vector GetCommonModifiers(const std::vector& src, const std::vector& dest) + { + std::vector commonElements; + for (auto it = src.begin(); it != src.end() - 1; it++) + { + if (std::find(dest.begin(), dest.end() - 1, *it) != dest.end() - 1) + { + commonElements.push_back(*it); + } + } + + return commonElements; + } + // Function to a handle a shortcut remap intptr_t HandleShortcutRemapEvent(LowlevelKeyboardEvent* data, std::map, std::pair, bool>>& reMap) noexcept { for (auto& it : reMap) { - DWORD src_1 = it.first[0]; - DWORD src_2 = it.first[1]; - WORD dest_1 = it.second.first[0]; - WORD dest_2 = it.second.first[1]; + const size_t src_size = it.first.size(); + const size_t dest_size = it.second.first.size(); // If the shortcut has been pressed down - if ((GetAsyncKeyState(src_1) & 0x8000) && !it.second.second) + if (!it.second.second && CheckModifiersKeyboardState(it.first)) { - if (data->lParam->vkCode == src_2 && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) + if (data->lParam->vkCode == it.first[src_size - 1] && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { - int key_count = 4; - LPINPUT keyEventList = new INPUT[size_t(key_count)](); - memset(keyEventList, 0, sizeof(keyEventList)); - if (src_1 == dest_1) + // Check if any other keys have been pressed apart from the shortcut. If true, then check for the next shortcut + if (!IsKeyboardStateClearExceptArgs(it.first)) { - key_count = 1; - keyEventList[0].type = INPUT_KEYBOARD; - keyEventList[0].ki.wVk = dest_2; - keyEventList[0].ki.dwFlags = 0; - keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + continue; + } + + size_t key_count; + LPINPUT keyEventList; + // Get the common keys between the two shortcuts + std::vector commonKeys = GetCommonModifiers(it.first, it.second.first); + + // If the original shortcut modifiers are a subset of the new shortcut + if (commonKeys.size() == src_size - 1) + { + // key down for all new shortcut keys except the common modifiers + key_count = dest_size - commonKeys.size(); + keyEventList = new INPUT[key_count](); + memset(keyEventList, 0, sizeof(keyEventList)); + long long i = 0; + long long j = 0; + // Add a key down only for the non-common keys in the new shortcut + while (i < (long long)key_count) + { + if (std::find(commonKeys.begin(), commonKeys.end(), it.second.first[j]) == commonKeys.end()) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = it.second.first[j]; + keyEventList[i].ki.dwFlags = 0; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j++; + } } 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 + key_count = 1 + (src_size - 1) + (dest_size) - (2 * commonKeys.size()); + keyEventList = new INPUT[key_count](); + memset(keyEventList, 0, sizeof(keyEventList)); + + // Send dummy key keyEventList[0].type = INPUT_KEYBOARD; keyEventList[0].ki.wVk = (WORD)DUMMY_KEY; keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; - keyEventList[1].type = INPUT_KEYBOARD; - keyEventList[1].ki.wVk = (WORD)src_1; - keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP; - keyEventList[1].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; - keyEventList[2].type = INPUT_KEYBOARD; - keyEventList[2].ki.wVk = dest_1; - keyEventList[2].ki.dwFlags = 0; - keyEventList[2].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; - keyEventList[3].type = INPUT_KEYBOARD; - keyEventList[3].ki.wVk = dest_2; - keyEventList[3].ki.dwFlags = 0; - keyEventList[3].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + + // Release original shortcut state (release in reverse order of shortcut to be accurate) + long long i = 1; + long long j = (long long)src_size - 2; + while (j >= 0) + { + // Release only those keys which are not common + if (std::find(commonKeys.begin(), commonKeys.end(), it.first[j]) == commonKeys.end()) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = (WORD)it.first[j]; + keyEventList[i].ki.dwFlags = KEYEVENTF_KEYUP; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j--; + } + + // Set new shortcut key down state + j = 0; + while (i < (long long)key_count) + { + // Key down only those keys which are not common + if (std::find(commonKeys.begin(), commonKeys.end(), it.second.first[j]) == commonKeys.end()) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = it.second.first[j]; + keyEventList[i].ki.dwFlags = 0; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j++; + } } it.second.second = true; - UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); + UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } } // The shortcut has already been pressed down at least once, i.e. the shortcut has been invoked + // There are 4 cases to be handled if the shortcut has been pressed down + // 1. The user lets go of one of the modifier keys - reset the keyboard back to the state of the keys actually being pressed down + // 2. The user keeps the shortcut pressed - the shortcut is repeated (for example you could hold down Ctrl+V and it will keep pasting) + // 3. The user lets go of the last key - reset the keyboard back to the state of the keys actually being pressed down + // 4. The user presses another key while holding the shortcut down - the system now sees all the new shortcut keys and this extra key pressed at the end. Not handled as resetting the state would trigger the original shortcut once more else if (it.second.second) { - // If the modifier key of the original shortcut is released before the normal key - if (data->lParam->vkCode == src_1 && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) + // Get the common keys between the two shortcuts + std::vector commonKeys = GetCommonModifiers(it.first, it.second.first); + + // Case 1: If any of the modifier keys of the original shortcut are released before the normal key + auto keyIt = std::find(it.first.begin(), it.first.end() - 1, data->lParam->vkCode); + if (keyIt != (it.first.end() - 1) && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) { - int key_count = 2; - LPINPUT keyEventList = new INPUT[size_t(key_count)](); + // Release new shortcut, and set original shortcut keys except the one released + size_t key_count; + if (std::find(commonKeys.begin(), commonKeys.end(), data->lParam->vkCode) != commonKeys.end()) + { + // release all new shortcut keys and the common released modifier except the other common modifiers, and add all original shortcut modifiers except the common ones + key_count = (dest_size - commonKeys.size() + 1) + (src_size - 1 - commonKeys.size()); + } + else + { + // release all new shortcut keys except the common modifiers and add all original shortcut modifiers except the common ones + key_count = dest_size + (src_size - 2) - (2 * commonKeys.size()); + } + LPINPUT keyEventList = new INPUT[key_count](); memset(keyEventList, 0, sizeof(keyEventList)); - keyEventList[0].type = INPUT_KEYBOARD; - keyEventList[0].ki.wVk = dest_2; - keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; - keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; - keyEventList[1].type = INPUT_KEYBOARD; - keyEventList[1].ki.wVk = dest_1; - keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP; - keyEventList[1].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + + // Release new shortcut state (release in reverse order of shortcut to be accurate) + long long i = 0; + long long j = (long long)dest_size - 1; + while (j >= 0) + { + // Do not release if it is a common modifier, except the case where a common modifier is released (second part of the if condition)) + if ((std::find(commonKeys.begin(), commonKeys.end(), it.second.first[j]) == commonKeys.end()) || it.second.first[j] == data->lParam->vkCode) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = it.second.first[j]; + keyEventList[i].ki.dwFlags = KEYEVENTF_KEYUP; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j--; + } + + // Set original shortcut key down state except the last key and the released modifier + j = 0; + while (i < (long long)key_count) + { + // Do not set key down for the released modifier and for the common modifiers + if (it.first[j] != data->lParam->vkCode && (std::find(commonKeys.begin(), commonKeys.end(), it.first[j]) == commonKeys.end())) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = (WORD)it.first[j]; + keyEventList[i].ki.dwFlags = 0; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j++; + } + it.second.second = false; - UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); + UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } - // The system will see dest_1 as being held down because of the shortcut remap - if (GetAsyncKeyState(dest_1) & 0x8000) + + // The system will see the modifiers of the new shortcut as being held down because of the shortcut remap + if (CheckModifiersKeyboardState(it.second.first)) { - // If the original shortcut is still held down the keyboard will see the original normal key along with the new modifier (keys held down send repeated keydown messages) - if (data->lParam->vkCode == src_2 && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) + // Case 2: If the original shortcut is still held down the keyboard will get a key down message of the last 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[src_size - 1] && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)) { - int key_count = 1; - LPINPUT keyEventList = new INPUT[size_t(key_count)](); + size_t key_count = 1; + LPINPUT keyEventList = new INPUT[key_count](); memset(keyEventList, 0, sizeof(keyEventList)); keyEventList[0].type = INPUT_KEYBOARD; - keyEventList[0].ki.wVk = dest_2; + keyEventList[0].ki.wVk = it.second.first[dest_size - 1]; keyEventList[0].ki.dwFlags = 0; keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; it.second.second = true; - UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); + UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } - // If the normal key is released from the original shortcut then revert the keyboard state to just the original modifier being held down - if (data->lParam->vkCode == src_2 && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) + + // Case 3: If the last key is released from the original shortcut then revert the keyboard state to just the original modifiers being held down + if (data->lParam->vkCode == it.first[src_size - 1] && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP)) { - int key_count = 4; - LPINPUT keyEventList = new INPUT[size_t(key_count)](); - memset(keyEventList, 0, sizeof(keyEventList)); - if (src_1 == dest_1) + size_t key_count; + LPINPUT keyEventList; + + // If the original shortcut is a subset of the new shortcut + if (commonKeys.size() == src_size - 1) { - key_count = 1; - keyEventList[0].type = INPUT_KEYBOARD; - keyEventList[0].ki.wVk = dest_2; - keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; - keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + key_count = dest_size - commonKeys.size(); + keyEventList = new INPUT[key_count](); + memset(keyEventList, 0, sizeof(keyEventList)); + long long i = 0; + long long j = (long long)dest_size - 1; + while (i < (long long)key_count) + { + if (std::find(commonKeys.begin(), commonKeys.end(), it.second.first[j]) == commonKeys.end()) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = it.second.first[j]; + keyEventList[i].ki.dwFlags = KEYEVENTF_KEYUP; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j--; + } } else { - keyEventList[0].type = INPUT_KEYBOARD; - keyEventList[0].ki.wVk = dest_2; - keyEventList[0].ki.dwFlags = KEYEVENTF_KEYUP; - keyEventList[0].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; - keyEventList[1].type = INPUT_KEYBOARD; - keyEventList[1].ki.wVk = dest_1; - keyEventList[1].ki.dwFlags = KEYEVENTF_KEYUP; - keyEventList[1].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; - keyEventList[2].type = INPUT_KEYBOARD; - keyEventList[2].ki.wVk = (WORD)src_1; - keyEventList[2].ki.dwFlags = 0; - keyEventList[2].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; - keyEventList[3].type = INPUT_KEYBOARD; - keyEventList[3].ki.wVk = (WORD)DUMMY_KEY; - keyEventList[3].ki.dwFlags = KEYEVENTF_KEYUP; - keyEventList[3].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + // Key up for all new shortcut keys, key down for original shortcut modifiers and dummy key but common keys aren't repeated + key_count = (dest_size) + (src_size - 1) + 1 - (2 * commonKeys.size()); + keyEventList = new INPUT[key_count](); + memset(keyEventList, 0, sizeof(keyEventList)); + + // Release new shortcut state (release in reverse order of shortcut to be accurate) + long long i = 0; + long long j = (long long)dest_size - 1; + while (j >= 0 && i < (long long)(dest_size - commonKeys.size())) + { + // Release only those keys which are not common + if (std::find(commonKeys.begin(), commonKeys.end(), it.second.first[j]) == commonKeys.end()) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = it.second.first[j]; + keyEventList[i].ki.dwFlags = KEYEVENTF_KEYUP; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j--; + } + + // Set old shortcut key down state + j = 0; + while (i < (long long)key_count) + { + // Key down only those keys which are not common + if (std::find(commonKeys.begin(), commonKeys.end(), it.first[j]) == commonKeys.end()) + { + keyEventList[i].type = INPUT_KEYBOARD; + keyEventList[i].ki.wVk = (WORD)it.first[j]; + keyEventList[i].ki.dwFlags = 0; + keyEventList[i].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; + i++; + } + j++; + } + + // Send dummy key + keyEventList[key_count - 1].type = INPUT_KEYBOARD; + keyEventList[key_count - 1].ki.wVk = (WORD)DUMMY_KEY; + keyEventList[key_count - 1].ki.dwFlags = KEYEVENTF_KEYUP; + keyEventList[key_count - 1].ki.dwExtraInfo = POWERKEYS_SHORTCUT_FLAG; } + it.second.second = false; - UINT res = SendInput(key_count, keyEventList, sizeof(INPUT)); + UINT res = SendInput((UINT)key_count, keyEventList, sizeof(INPUT)); delete[] keyEventList; return 1; } diff --git a/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp b/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp index 4dc100f642..5324ccd028 100644 --- a/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp +++ b/src/modules/keyboardmanager/ui/EditShortcutsWindow.cpp @@ -140,8 +140,8 @@ void createEditShortcutsWindow(HINSTANCE hInst, KeyboardManagerState& keyboardMa std::vector originalKeys = convertWStringVectorToIntegerVector(splitwstring(originalShortcut.c_str(), L' ')); std::vector newKeys = convertWStringVectorToIntegerVector(splitwstring(newShortcut.c_str(), L' ')); - // Check if number of keys is two since only that is currently implemented - if (originalKeys.size() == 2 && newKeys.size() == 2) + // Shortcut should consist of atleast two keys + if (originalKeys.size() > 1 && newKeys.size() > 1) { keyboardManagerState.AddOSLevelShortcut(originalKeys, newKeys); }