mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
[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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user