The Remapping System is the core logic layer of Keyboard Manager that intercepts keyboard events via low-level hooks and transforms them according to user-defined mappings. This document covers the event handling pipeline, remapping logic for single keys and shortcuts, state management, and special input scenarios. For information about the Keyboard Manager module interface and settings management, see Keyboard Manager. For UI components and configuration persistence, see UI and Configuration.
The remapping system operates through a low-level keyboard hook that intercepts all keyboard events before they reach applications. Events are processed through a series of handlers that apply remappings in a specific order, maintaining state to handle complex scenarios like chord shortcuts and app-specific remaps.
Sources:
| Class/Structure | File | Purpose |
|---|---|---|
KeyboardManager | src/modules/keyboardmanager/dll/dllmain.cpp32-195 | Module interface, owns keyboard hook and state |
KeyboardManagerState | src/modules/keyboardmanager/common/KeyboardManagerState.cpp | Stores all remap tables, UI states, manages concurrent access |
Shortcut | src/modules/keyboardmanager/common/Shortcut.h13-190 | Represents a key combination with modifiers + action key |
RemapShortcut | src/modules/keyboardmanager/common/RemapShortcut.h | Target mapping with state flags for execution tracking |
Helpers | src/modules/keyboardmanager/common/Helpers.h | Utility functions for key events, filtering, state checking |
InputInterface | Mock: src/modules/keyboardmanager/KeyboardManagerEngineTest/MockedInput.h | Abstraction for keyboard state and input injection |
Sources:
The low-level keyboard hook is registered via SetWindowsHookEx and executes hook_proc for every keyboard event system-wide. Since hook procedures must be static, a static pointer keyboardmanager_object_ptr provides access to the KeyboardManager instance.
Sources:
The HandleKeyboardHookEvent method processes events through a specific sequence to ensure correct precedence between remap types:
Key ordering principles:
X affects all shortcuts containing that keySources:
Single key remapping handles two scenarios:
A โ BA โ Ctrl+VKey implementation details:
KEYBOARDMANAGER_SINGLEKEY_FLAG are ignored to prevent infinite loopsVK_WIN_BOTH (custom constant 0x104) converts to VK_LWIN for injectionSources:
When releasing modifiers without pressing another key, Windows can trigger unintended actions (Win โ Start Menu, Alt โ Menu bar). To prevent this, dummy key events are injected between modifier state changes:
The dummy key sends a key-down and key-up sequence that has no side effects but prevents modifier-only detection.
Sources:
Shortcut remapping is more complex than single key remapping because it must:
Sources:
The HandleShortcutRemapEvent maintains state via the isShortcutInvoked flag and metadata in RemapShortcut:
| State Variable | Type | Purpose |
|---|---|---|
isShortcutInvoked | bool | Whether a shortcut is currently active |
targetShortcutStarted | bool (in RemapShortcut) | Whether target shortcut action key was pressed |
isOriginalActionKeyPressed | bool (in RemapShortcut) | Tracks original action key for Disable remaps |
winKeyInvoked | ModifierKey | Which Win key (L/R/Both) triggered the remap |
Shortcut detection logic:
Ctrl+Shift+A over Ctrl+A)CheckModifiersKeyboardState via GetAsyncKeyStateIsKeyboardStateClearExceptShortcut ensures exact matchVK_LWIN vs VK_RWIN for proper releaseSources:
Once a shortcut is invoked, subsequent key events are handled by six cases:
Reset to physical keyboard state since the shortcut is no longer held:
Sources:
Simply repeat the target key/shortcut:
Sources:
Release the target, check if reset needed:
Ctrl+A โ B to work with combinations like Ctrl+A+XSources:
Suppress the event (redundant modifier press during invocation).
Sources:
For shortcut-to-shortcut: Reset to physical keyboard (shortcuts can't combine with other keys).
For shortcut-to-key: Allow if target key is held; otherwise reset to physical keyboard.
Sources:
Forward the event without suppression (no corresponding key down was suppressed).
Sources:
App-specific shortcuts apply only when a particular application is in the foreground. The handler:
KeyboardManagerState.GetActivatedApp()GetCurrentApplicationappSpecificShortcutReMap (case-insensitive, with/without extension)HandleShortcutRemapEvent with the activatedApp parameterForeground app persistence: activatedApp is cached while a shortcut is invoked to handle cases where the remap causes focus loss (e.g., Ctrl+A โ Alt+Tab in Edge).
Sources:
GetCurrentApplication uses two strategies:
GetForegroundWindow + get_process_pathApplicationFrameHost.exe, use GetGUIThreadInfo to find the actual UWP windowSources:
The KeyboardManagerState class stores all remapping tables and UI state:
| Member | Type | Purpose |
|---|---|---|
singleKeyReMap | std::unordered_map<DWORD, KeyShortcutTextUnion> | Key-to-key/key-to-shortcut mappings |
osLevelShortcutReMap | std::map<Shortcut, RemapShortcut> | Global shortcut remaps |
appSpecificShortcutReMap | std::map<std::wstring, std::map<Shortcut, RemapShortcut>> | Per-app shortcut remaps |
uiState | KeyboardManagerUIState (enum) | Current UI window state |
activatedApp | std::wstring | Currently invoked app-specific remap target |
reloadRemappings | std::atomic<bool> | Concurrent access control flag |
Sources:
The reloadRemappings atomic flag prevents race conditions between the UI thread (updating remaps) and the hook thread (reading remaps):
Why not mutexes? Mutexes were removed from the hook to prevent reentrant deadlocks, as keyboard hooks can be called from multiple contexts.
Sources:
Certain keys require the KEYEVENTF_EXTENDEDKEY flag to distinguish them from NumPad equivalents:
Without this flag, these keys send NumPad scan codes, causing incorrect behavior when NumLock is active.
Sources:
SendInput events set wScan via MapVirtualKey because some applications (e.g., Windows Terminal) filter events with scan code 0:
Sources:
NumLock state toggles before the hook sees the event. To suppress NumLock, the hook sends additional NumLock events to revert the state:
The extra events use KEYBOARDMANAGER_SUPPRESS_FLAG and are suppressed at the start of HandleKeyboardHookEvent.
Sources:
Japanese IME uses Shift/Alt/Ctrl + Caps Lock to switch modes. These combinations are detected before hooks, causing stuck modifier states. The workaround sends a suppressed modifier key-up event:
Sources:
Keyboard Manager uses the dwExtraInfo field to tag generated events:
| Flag Constant | Hex Value | Purpose |
|---|---|---|
KEYBOARDMANAGER_SINGLEKEY_FLAG | 0x11 | Events from single key remaps |
KEYBOARDMANAGER_SHORTCUT_FLAG | 0x101 | Events from shortcut remaps |
KEYBOARDMANAGER_SUPPRESS_FLAG | 0x111 | Internal workaround events (NumLock, IME) |
These flags prevent infinite loops (KBM ignoring its own events) and enable correct remap precedence.
Sources:
Sources:
The test suite uses MockedInput to simulate keyboard input without actual hardware or low-level hooks:
| Method | Purpose |
|---|---|
SetHookProc | Registers the hook handler under test |
SendVirtualInput | Simulates SendInput, updates keyboard state, calls hook |
GetVirtualKeyState | Returns simulated key state |
ResetKeyboardState | Clears all key states |
SetSendVirtualInputTestHandler | Counts SendInput calls matching a condition |
SetForegroundProcess | Sets the simulated foreground app name |
The mocked input maintains a std::vector<bool> of size 256 to track all virtual key states, updates them based on key up/down events (unless suppressed by the hook), and handles modifier key codes (e.g., VK_CONTROL affects VK_LCONTROL/VK_RCONTROL).
Sources:
Tests are organized by remap type:
Example test structure:
Sources:
Refresh this wiki
This wiki was recently refreshed. Please wait 4 days to refresh again.