From 209d0896f46f9866761048b626ada29245d32698 Mon Sep 17 00:00:00 2001 From: Clint Rutkas Date: Fri, 12 Jun 2026 15:36:32 -0700 Subject: [PATCH] fix(runner): Harden centralized keyboard hook against stuck keys - Tag dummy 0xFF SendInput with dwExtraInfo to prevent cross-hook interference - Skip LLKHF_INJECTED events from OSK/macro tools (dwExtraInfo==0) - Revalidate key state in PressedKeyTimerProc before firing action - Kill pending timers and reset state in Stop() - Make vkCodePressed atomic to fix data race Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/runner/centralized_kb_hook.cpp | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/runner/centralized_kb_hook.cpp b/src/runner/centralized_kb_hook.cpp index 72b473bd08c5..3e05a6de4085 100644 --- a/src/runner/centralized_kb_hook.cpp +++ b/src/runner/centralized_kb_hook.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "centralized_kb_hook.h" +#include #include #include #include @@ -42,7 +43,7 @@ namespace CentralizedKeyboardHook // keep track of last pressed key, to detect repeated keys and if there are more keys pressed. const DWORD VK_DISABLED = CommonSharedConstants::VK_DISABLED; - DWORD vkCodePressed = VK_DISABLED; + std::atomic vkCodePressed{ VK_DISABLED }; // Save the runner window handle for registering timers. HWND runnerWindow; @@ -72,7 +73,12 @@ namespace CentralizedKeyboardHook { if (it.idTimer == idTimer) { - it.action(); + // Revalidate that the key is still physically held before firing. + // This prevents ghost activations after the key was already released. + if (GetAsyncKeyState(static_cast(it.virtualKey)) & 0x8000) + { + it.action(); + } } } @@ -177,6 +183,7 @@ namespace CentralizedKeyboardHook dummyEvent[0].type = INPUT_KEYBOARD; dummyEvent[0].ki.wVk = 0xFF; dummyEvent[0].ki.dwFlags = KEYEVENTF_KEYUP; + dummyEvent[0].ki.dwExtraInfo = PowertoyModuleIface::CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG; SendInput(1, dummyEvent, sizeof(INPUT)); // Swallow the key press @@ -263,6 +270,18 @@ namespace CentralizedKeyboardHook void Stop() noexcept { + // Kill all pending pressed-key timers before unhooking to prevent + // ghost callbacks firing after the hook is removed. + { + std::unique_lock lock{ pressedKeyMutex }; + for (const auto& it : pressedKeyDescriptors) + { + KillTimer(runnerWindow, it.idTimer); + } + } + + vkCodePressed = VK_DISABLED; + if (hHook && UnhookWindowsHookEx(hHook)) { hHook = NULL;