From 5688441127fa694ca0e8b6f6bae8ec00dbf05e25 Mon Sep 17 00:00:00 2001
From: Matheus Mol <80729568+oMatheusmol@users.noreply.github.com>
Date: Thu, 18 Jun 2026 05:33:56 -0300
Subject: [PATCH] [KBM] Fix modifier key remapped to non-modifier delivering
WM_SYSKEYDOWN (#47192)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary of the Pull Request
When a modifier key (Ctrl/Alt/Shift) is remapped to a non-modifier key
using
Keyboard Manager, the injected key event is delivered to applications as
WM_SYSKEYDOWN instead of WM_KEYDOWN. This causes unexpected behavior —
for
example, remapping Left Alt to Backspace results in whole words being
deleted
instead of single characters, because applications interpret
WM_SYSKEYDOWN +
VK_BACK as Alt+Backspace.
The fix resets the modifier state with a suppress-flag key-up event
before
injecting the target key, consistent with the existing approach used for
the
Caps Lock remapping scenario.
## PR Checklist
- [ ] Closes: #47191
- [ ] Communication: I've discussed this with core contributors already.
If the work hasn't been agreed, this work might be rejected
- [x] Tests: Added/updated and all pass
- [ ] Localization: All end-user-facing strings can be localized
- [ ] Dev docs: Added/updated
- [ ] New binaries: Added on the required places
## Detailed Description of the Pull Request / Additional comments
The root cause is that SendInput is called inside the low-level keyboard
hook
callback before the original modifier event is suppressed. At that point
the
modifier state is still active, so the OS delivers the injected key as
WM_SYSKEYDOWN (system key with Alt context) rather than WM_KEYDOWN.
This is the same mechanism that was already fixed for the Ctrl/Alt/Shift
↔
Caps Lock case. This PR extends the fix to cover modifier → non-modifier
remaps.
## Validation Steps Performed
1. Remapped Left Alt → Backspace in Keyboard Manager
2. Opened a text editor, typed text, pressed the remapped key
3. Confirmed single characters are deleted instead of whole words
4. All 93 unit tests pass (KeyboardManager.Engine.UnitTests)
---
.../KeyboardEventHandlers.cpp | 8 +++++++
.../SingleKeyRemappingTests.cpp | 21 +++++++++++++++++++
2 files changed, 29 insertions(+)
diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp
index 50f5166940..46958bfc14 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp
+++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp
@@ -139,6 +139,14 @@ namespace KeyboardEventHandlers
if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)
{
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, it->first, target);
+
+ // If a Ctrl/Alt/Shift key is remapped to a non-modifier key, reset the modifier state to prevent the injected key from being delivered as WM_SYSKEYDOWN instead of WM_KEYDOWN
+ if (Helpers::IsModifierKey(it->first) && !Helpers::IsModifierKey(target) && target != VK_CAPITAL && !(it->first == VK_LWIN || it->first == VK_RWIN || it->first == CommonSharedConstants::VK_WIN_BOTH))
+ {
+ std::vector suppressList;
+ Helpers::SetKeyEvent(suppressList, INPUT_KEYBOARD, static_cast(it->first), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
+ ii.SendVirtualInput(suppressList);
+ }
}
if (remapToKey)
diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineTest/SingleKeyRemappingTests.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineTest/SingleKeyRemappingTests.cpp
index de6597ae5c..efb03aae2e 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEngineTest/SingleKeyRemappingTests.cpp
+++ b/src/modules/keyboardmanager/KeyboardManagerEngineTest/SingleKeyRemappingTests.cpp
@@ -226,6 +226,27 @@ namespace RemappingLogicTests
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
}
+ // Test if SendVirtualInput is sent exactly once with the suppress flag when a Ctrl/Alt/Shift key is remapped to a non-modifier key
+ TEST_METHOD (HandleSingleKeyRemapEvent_ShouldSendVirtualInputWithSuppressFlagExactlyOnce_WhenCtrlAltShiftIsMappedToNonModifierKey)
+ {
+ mockedInputHandler.SetSendVirtualInputTestHandler([](LowlevelKeyboardEvent* data) {
+ if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
+ return true;
+ else
+ return false;
+ });
+
+ testState.AddSingleKeyRemap(VK_LMENU, (DWORD)VK_BACK);
+
+ std::vector inputs{
+ { .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LMENU } },
+ };
+
+ mockedInputHandler.SendVirtualInput(inputs);
+
+ Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
+ }
+
// Test if correct keyboard states are set for a single key to two key shortcut remap
TEST_METHOD (RemappedKeyToTwoKeyShortcut_ShouldSetTargetKeyState_OnKeyEvent)
{