[KBM]Allow remapping keys and shortcuts to arbitrary unicode sequences (#29399)

* [KBM] Allow remapping keys and shortcuts to arbitrary unicode sequences

* f: spelling

* f: tests

* f: split shortcut configuration

* f: address ui layout comments

* [BugReport]Don't report personal info

* f: fix crash in KBME

* f: add missed type button

* f: fix shortcut line UI elements alignment

* f: align elements size

* f: add warning about non-mapped keys
This commit is contained in:
Andrey Nekrasov
2023-11-23 11:46:07 +01:00
committed by GitHub
parent 2543dee1f1
commit f742d3c1c3
32 changed files with 698 additions and 173 deletions

View File

@@ -7,13 +7,21 @@
#include <keyboardmanager/common/Helpers.h>
#include <keyboardmanager/KeyboardManagerEngineLibrary/trace.h>
namespace
{
bool GeneratedByKBM(const LowlevelKeyboardEvent* data)
{
return data->lParam->dwExtraInfo & CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG;
}
}
namespace KeyboardEventHandlers
{
// Function to a handle a single key remap
intptr_t HandleSingleKeyRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state) noexcept
{
// Check if the key event was generated by KeyboardManager to avoid remapping events generated by us.
if (!(data->lParam->dwExtraInfo & CommonSharedConstants::KEYBOARDMANAGER_INJECTED_FLAG))
if (!GeneratedByKBM(data))
{
const auto remapping = state.GetSingleKeyRemap(data->lParam->vkCode);
if (remapping)
@@ -21,7 +29,7 @@ namespace KeyboardEventHandlers
auto it = remapping.value();
// Check if the remap is to a key or a shortcut
bool remapToKey = (it->second.index() == 0);
const bool remapToKey = it->second.index() == 0;
// If mapped to VK_DISABLED then the key is disabled
if (remapToKey)
@@ -191,7 +199,9 @@ namespace KeyboardEventHandlers
}
// Check if the remap is to a key or a shortcut
bool remapToShortcut = (it->second.targetShortcut.index() == 1);
const bool remapToKey = it->second.targetShortcut.index() == 0;
const bool remapToShortcut = it->second.targetShortcut.index() == 1;
const bool remapToText = it->second.targetShortcut.index() == 2;
const size_t src_size = it->first.Size();
const size_t dest_size = remapToShortcut ? std::get<Shortcut>(it->second.targetShortcut).Size() : 1;
@@ -202,13 +212,13 @@ namespace KeyboardEventHandlers
if (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))
{
// 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 || std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED))
if (!it->first.IsKeyboardStateClearExceptShortcut(ii) && (remapToShortcut || (remapToKey && std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)))
{
continue;
}
size_t key_count;
LPINPUT keyEventList;
size_t key_count = 0;
LPINPUT keyEventList = nullptr;
// Remember which win key was pressed initially
if (ii.GetVirtualKeyState(VK_RWIN))
@@ -265,7 +275,7 @@ namespace KeyboardEventHandlers
}
}
}
else
else if (remapToKey)
{
// Dummy key, key up for all the original shortcut modifier keys and key down for remapped key
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + (src_size - 1) + dest_size;
@@ -300,6 +310,33 @@ namespace KeyboardEventHandlers
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), data->lParam->vkCode);
}
}
// Remapped to text
else
{
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + src_size;
auto remapping = std::get<std::wstring>(it->second.targetShortcut);
const UINT inputEventCount = static_cast<UINT>(remapping.length() * 2);
key_count += inputEventCount;
keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetDummyKeyEvent(keyEventList, i, 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, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
for (size_t idx = 0; idx < inputEventCount; ++idx)
{
auto& input = keyEventList[idx + i];
input.type = INPUT_KEYBOARD;
const bool upEvent = idx & 0x1;
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
input.ki.wScan = remapping[idx >> 1];
}
}
it->second.isShortcutInvoked = true;
// If app specific shortcut is invoked, store the target application
@@ -332,8 +369,8 @@ namespace KeyboardEventHandlers
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))
{
// Release new shortcut, and set original shortcut keys except the one released
size_t key_count;
LPINPUT keyEventList;
size_t key_count = 0;
LPINPUT keyEventList = nullptr;
if (remapToShortcut)
{
// if the released key is present in both shortcuts' modifiers (i.e part of the common modifiers)
@@ -373,7 +410,7 @@ namespace KeyboardEventHandlers
// 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
Helpers::SetDummyKeyEvent(keyEventList, i, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else
else if (remapToKey)
{
// 1 for releasing new key and original shortcut modifiers except the one released and dummy key
key_count = dest_size + src_size - 2 + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
@@ -432,13 +469,13 @@ namespace KeyboardEventHandlers
}
// The system will see the modifiers of the new shortcut as being held down because of the shortcut remap
if (!remapToShortcut || std::get<Shortcut>(it->second.targetShortcut).CheckModifiersKeyboardState(ii))
if (!remapToShortcut || (remapToShortcut && std::get<Shortcut>(it->second.targetShortcut).CheckModifiersKeyboardState(ii)))
{
// 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() && (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN))
{
// In case of mapping to disable do not send anything
if (!remapToShortcut && std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
if (remapToKey && std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
// Since the original shortcut's action key is pressed, set it to true
it->second.isOriginalActionKeyPressed = true;
@@ -446,15 +483,34 @@ namespace KeyboardEventHandlers
}
size_t key_count = 1;
LPINPUT keyEventList = new INPUT[key_count]{};
LPINPUT keyEventList = nullptr;
if (remapToShortcut)
{
keyEventList = new INPUT[key_count]{};
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(std::get<Shortcut>(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else
else if (remapToKey)
{
keyEventList = new INPUT[key_count]{};
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast<WORD>(Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut))), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else if (remapToText)
{
auto remapping = std::get<std::wstring>(it->second.targetShortcut);
const UINT inputEventCount = static_cast<UINT>(remapping.length() * 2);
key_count = inputEventCount;
keyEventList = new INPUT[key_count]{};
for (size_t idx = 0; idx < inputEventCount; ++idx)
{
auto& input = keyEventList[idx];
input.type = INPUT_KEYBOARD;
const bool upEvent = idx & 0x1;
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
input.ki.wScan = remapping[idx >> 1];
}
}
UINT res = ii.SendVirtualInput(static_cast<UINT>(key_count), keyEventList, sizeof(INPUT));
delete[] keyEventList;
@@ -462,10 +518,10 @@ namespace KeyboardEventHandlers
}
// 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 (data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
if (!remapToText && data->lParam->vkCode == it->first.GetActionKey() && (data->wParam == WM_KEYUP || data->wParam == WM_SYSKEYUP))
{
size_t key_count = 1;
LPINPUT keyEventList;
LPINPUT keyEventList = nullptr;
if (remapToShortcut)
{
keyEventList = new INPUT[key_count]{};
@@ -683,16 +739,22 @@ namespace KeyboardEventHandlers
// For remap to key, if the original action key is not currently pressed, we should revert the keyboard state to the physical keys. If it is pressed we should not suppress the event so that shortcut to key remaps can be pressed with other keys. Example use-case: Alt+D->Win, allows Alt+D+A to perform Win+A
// Modifier state reset might be required for this key depending on the target key - ex: Ctrl+A -> Caps, Shift is pressed. System should not see Shift and Caps pressed together
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)));
auto maybeTargetKey = std::get_if<DWORD>(&it->second.targetShortcut);
if (maybeTargetKey)
{
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, data->lParam->vkCode, Helpers::FilterArtificialKeys(*maybeTargetKey));
}
// If the shortcut is remapped to Disable then we have to revert the keyboard state to the physical keys
bool isRemapToDisable = (std::get<DWORD>(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED);
bool isRemapToDisable = maybeTargetKey && (*maybeTargetKey == CommonSharedConstants::VK_DISABLED);
bool isOriginalActionKeyPressed = false;
if (!isRemapToDisable)
if (maybeTargetKey && !isRemapToDisable)
{
// If the remap target key is currently pressed, then we do not have to revert the keyboard state to the physical keys
if (ii.GetVirtualKeyState((Helpers::FilterArtificialKeys(std::get<DWORD>(it->second.targetShortcut)))))
if (ii.GetVirtualKeyState((Helpers::FilterArtificialKeys(*maybeTargetKey))))
{
isOriginalActionKeyPressed = true;
}
@@ -850,4 +912,43 @@ namespace KeyboardEventHandlers
}
}
}
// Function to generate a unicode string in response to a single keypress
intptr_t HandleSingleKeyToTextRemapEvent(KeyboardManagerInput::InputInterface& ii, LowlevelKeyboardEvent* data, State& state)
{
if (GeneratedByKBM(data))
{
return 0;
}
// Only send the text on keydown event
if (data->wParam != WM_KEYDOWN)
{
return 0;
}
const auto remapping = state.GetSingleKeyToTextRemapEvent(data->lParam->vkCode);
if (!remapping)
{
return 0;
}
const size_t keyCount = remapping->length();
const UINT eventCount = static_cast<UINT>(keyCount * 2);
auto keyEventList = std::make_unique<INPUT[]>(keyCount * 2);
for (size_t i = 0; i < eventCount; ++i)
{
auto& input = keyEventList[i];
input.type = INPUT_KEYBOARD;
const bool upEvent = i & 0x1;
input.ki.dwFlags = KEYEVENTF_UNICODE | (upEvent ? KEYEVENTF_KEYUP : 0);
input.ki.dwExtraInfo = KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG;
input.ki.wScan = (*remapping)[i >> 1];
}
UINT res = ii.SendVirtualInput(eventCount, keyEventList.get(), sizeof(INPUT));
return 1;
}
}