PowerToys Keyboard Manager Remapping Engine Internals: Low-Level Hook Architecture Explained

The PowerToys Keyboard Manager intercepts every keystroke via a global WH_KEYBOARD_LL hook, processes it through a stateful mapping engine, and synthesizes replacement input events using Windows SendInput to execute remappings without modifying registry scan codes.

The Keyboard Manager remapping engine inside microsoft/PowerToys transforms raw hardware keystrokes into user-defined outputs through a sophisticated low-level input processing pipeline. This C++ engine runs inside the PowerToys runner process and handles everything from simple key swaps to complex chord shortcuts and application launches without requiring administrator privileges for most operations.

Hook Installation and Global Event Interception

The engine begins its lifecycle in KeyboardManager::StartLowlevelKeyboardHook() within src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp. This method calls SetWindowsHookEx(WH_KEYBOARD_LL, HookProc, …) to install a global low-level keyboard hook that receives every keystroke before applications process it.

The static callback KeyboardManager::HookProc normalizes the virtual-key code using Helpers::EncodeKeyNumpadOrigin to handle numpad variations correctly. It then delegates to the active engine instance via keyboardManagerObjectPtr->HandleKeyboardHookEvent(&event). When remappings are cleared, KeyboardManager::StopLowlevelKeyboardHook() removes the hook to eliminate performance overhead. The static members hookHandle, hookHandleCopy, and keyboardManagerObjectPtr bridge the C-style Windows API callback to the C++ object instance holding current state.

State Management and Configuration Storage

All user remappings reside in the State class (defined in State.h), which inherits from MappingConfiguration shared with the Settings UI. Key data structures include:

  • singleKeyReMap – std::unordered_map<DWORD, Remap> for key-to-key translations
  • singleKeyToTextReMap – Maps source keys to Unicode strings for text injection
  • osLevelShortcutReMap / appSpecificShortcutReMap – Shortcut-to-target mappings using std::variant to hold keys, shortcuts, program paths, URIs, or text blocks
  • activatedApp – Tracks which application triggered an app-specific shortcut

KeyboardManager::LoadSettings() parses the JSON configuration that the Settings UI writes to PTSettingsHelper::get_module_save_folder_location. The engine hot-reloads configurations without restarting via an EventWaiter subscription to SettingsEventName, triggering the changeSettingsCallback lambda when users modify mappings.

Event Processing Pipeline

The central dispatcher KeyboardManager::HandleKeyboardHookEvent in KeyboardManager.cpp implements a priority-ordered pipeline:

  1. Ignore synthetic events generated by the engine itself to prevent infinite loops
  2. Suspend remapping when the Editor window is active
  3. Delegate single-key remaps
  4. Check app-specific shortcuts
  5. Process single-key-to-text injections
  6. Finally evaluate OS-level shortcuts

Single-Key Remapping Logic

HandleSingleKeyRemapEvent (implemented in KeyboardEventHandlers.cpp around lines 94-140) queries state.GetSingleKeyRemap(vk) for the source virtual key. If the target is VK_DISABLED, the function returns 1 to suppress the keystroke entirely. Otherwise, it constructs a std::vector<INPUT> containing the target key events and calls ii.SendVirtualInput(keyEventList) through the KeyboardManagerInput::InputInterface abstraction, which ultimately invokes SendInput. Telemetry fires once daily via Trace::DailyKeyToKeyRemapInvoked.

Shortcut-to-Shortcut Handling

HandleShortcutRemapEvent (starting at line 70 in KeyboardEventHandlers.cpp) manages complex chord sequences using ResetChordsIfNeeded to handle two-key shortcuts correctly. The engine checks state.CheckShortcutRemapInvoked to prevent re-triggering during modifier holds.

When a match occurs, the engine releases original shortcut modifiers and presses the target combination. To avoid "modifier-press-release" glitches where Windows misinterprets modifier up/down events, the code inserts a dummy key event between modifier releases and target key presses. For program launch targets, the engine spawns a detached thread calling ShellExecute while suppressing the original shortcut keystrokes.

Text Injection and Unicode Support

HandleSingleKeyToTextRemapEvent retrieves the Unicode string from singleKeyToTextReMap and calls Helpers::SetTextKeyEvents, which builds INPUT structures with the KEYEVENTF_UNICODE flag. This allows injecting arbitrary Unicode characters, including those not present on the physical keyboard layout.

Numpad Normalization

The static helper UpdateNumpadWithShift (lines 31-80 in KeyboardEventHandlers.cpp) corrects a Windows quirk where the low-level hook sees the Shift flag before decoding numpad keys. When remapping Shift keys, this ensures the original numpad virtual key code is preserved rather than interpreting the keystroke as a navigation key.

Synthetic Input Generation and Thread Safety

All synthetic input flows through KeyboardManagerInput::InputInterface (defined in InputInterface.h), enabling unit testing by abstracting SendInput. The low-level hook executes on the runner's main thread, and the engine maintains thread safety through single-threaded state mutations confined to the KeyboardManager object instance. The only global mutable state, keyboardManagerObjectPtr, is set during construction and cleared only at destruction, preventing re-entrancy races.

Telemetry and Error Resilience

Each remap category sends once-per-day telemetry events (e.g., Trace::DailyShortcutToShortcutRemapInvoked) using std::chrono::system_clock to calculate day indices. Error handling wraps settings loading and telemetry in try/catch blocks that log to the PowerToys logger via show_last_error_message and Trace::Error, ensuring hook exceptions never crash the runner process.

Configuration Examples

Simple Key-to-Key Remap

{
  "singleKeyReMap": {
    "0x41": { "target": 0x42, "type": "key" }
  }
}

This remaps A (0x41) to B (0x42). The engine looks up the source in singleKeyReMap, builds an INPUT structure for VK_B, sends it via SendInput, and returns 1 to swallow the original keystroke.

Shortcut-to-Shortcut with Modifier Correction

{
  "osLevelShortcutReMap": {
    "Win+A": {
      "targetShortcut": { "modifiers": ["Ctrl"], "key": "V" },
      "type": "shortcut"
    }
  }
}

When HandleShortcutRemapEvent detects Win+A, it releases the Windows key, injects a dummy key to stabilize the modifier state, then presses Ctrl+V before suppressing the original input.

Program Launch Mapping

{
  "osLevelShortcutReMap": {
    "Ctrl+Alt+P": {
      "targetShortcut": { "runProgram": "notepad.exe" },
      "type": "shortcut"
    }
  }
}

The engine matches the chord, starts Notepad in a background thread via ShellExecute, and returns 1 to prevent the original keystroke from reaching other applications.

Summary

  • Global Hook: Uses SetWindowsHookEx(WH_KEYBOARD_LL) in KeyboardManager.cpp to intercept all keystrokes system-wide without admin rights
  • Stateful Engine: Maintains mappings in State.h structures (singleKeyReMap, osLevelShortcutReMap) that hot-reload via EventWaiter callbacks
  • Synthetic Input: Generates replacement keystrokes through SendInput via KeyboardEventHandlers.cpp, handling Unicode text, modifier chords, and dummy-key insertion for glitch prevention
  • Safety: Ignores self-generated events to prevent infinite loops, wraps operations in exception handlers, and uninstalls the hook when idle to minimize overhead

Frequently Asked Questions

How does Keyboard Manager intercept keystrokes without administrator privileges?

The engine uses WH_KEYBOARD_LL (low-level) hooks rather than kernel-level keyboard filters. Windows allows global low-level hooks for processes running at the same integrity level as the input stream, meaning PowerToys can intercept keystrokes for remapping without elevated rights, unlike WH_KEYBOARD hooks which require administration for global scope.

What prevents remapped keys from triggering infinite loops?

Before processing any event, HandleKeyboardHookEvent checks if the input was synthesized by the engine itself and immediately passes it through. Additionally, the State class tracks activatedApp and CheckShortcutRemapInvoked to ensure remapped outputs don't recursively match other remapping rules, creating a deterministic one-way transformation pipeline.

How are modifier keys handled during complex shortcuts?

The ResetChordsIfNeeded helper in KeyboardEventHandlers.cpp manages chord states for two-key shortcuts, while the dummy-key insertion technique (inserting a harmless key event between modifier releases and target presses) prevents Windows from interpreting modifier transitions as unintended key combinations. This ensures Win+A → Ctrl+V doesn't leave the Windows key logically "stuck" down.

Can configuration changes apply without restarting PowerToys?

Yes. The changeSettingsCallback lambda in KeyboardManager.cpp subscribes to file system events via EventWaiter monitoring the SettingsEventName. When the Settings UI writes new JSON to the module save folder, the engine calls LoadSettings() to refresh singleKeyReMap and shortcut tables while the hook remains active, applying changes within milliseconds without dropping the low-level hook.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →