This repo documents a production failure we hit shipping a push-to-talk dictation app (VocalCode) on Windows, and provides a minimal logger to test it on your own machine. We are publishing it because the failure cost us a week, the usual search results don't describe it, and we suspect every global-hotkey app that meets an Electron/CEF/WebView2 window eventually meets some version of it.
Our app installs a low-level keyboard hook (SetWindowsHookExW(WH_KEYBOARD_LL, …)) to
implement push-to-talk. It worked everywhere — terminals, editors, native apps — except:
when a Chromium-family window (our own WebView2 settings UI, and separately CEF/Electron
windows) was in the foreground, the hook callback simply stopped being invoked. No error,
no unhook, no GetLastError. The events were delivered to the focused application, but our
hook never saw them.
Release the focus to a native window: events flow again. Focus the Chromium window: silence.
While debugging, we installed a second diagnostic hook to log what the first one was missing — and the system started delivering events to both. Remove the diagnostic hook, and the original went deaf again under the same conditions. A watched hook never misbehaves.
Because of this, a minimal repro does not reliably reproduce — the repro program itself perturbs the hook chain it is trying to observe. That is exactly why this repo exists as a logger you run against your own real setup rather than a guaranteed-failing test case: run it alongside the actual application whose hook goes quiet, not instead of it.
LowLevelHooksTimeout budget can be silently removed or skipped by the system, and
Windows 10/11 will silently stop calling hooks it considers slow. Chromium pumps input
very differently from native apps (raw input, a busy message loop, high-frequency
pointer events), which plausibly changes timing enough to trip the threshold — for the
whole chain or for your position in it.We stopped relying on the global hook while our own WebView2 window had focus, and
handled the key inside the page (a keydown listener in the webview answers the
push-to-talk chord itself). The global hook still serves every native window; the
webview serves itself. This shipped, and the failure disappeared for all users.
If your hotkey must work over other people's Chromium windows (Chrome, VS Code,
Slack…), keep your hook callback microscopic (post to your own thread, return
immediately), never log/allocate/lock inside it, and re-install the hook when you detect
prolonged silence while keys are clearly being typed (compare against GetAsyncKeyState
polling — ugly, effective).
src/main.rs installs a WH_KEYBOARD_LL hook and prints one line per event:
timestamp, key, and the executable + title of the foreground window. Run it, type into
different apps, and watch whether lines stop when a Chromium window is focused.
cargo run --release --bin khook-logger
Interpreting results:
Windows 11 (23H2 and 24H2), Rust hook thread with a dedicated message pump, WebView2 (Edge 126+) same-process window, also reproduced against CEF-based apps. The dictation app has since shipped the in-page fix; current builds are unaffected.
MIT licensed. Issues and war stories welcome — especially if you can disprove the timeout hypothesis with something better.
1 commits
Rust
100.0%
This repo documents a production failure we hit shipping a push-to-talk dictation app (VocalCode) on Windows, and provides a minimal logger to test it on your own machine. We are publishing it because the failure cost us a week, the usual search results don't describe it, and we suspect every global-hotkey app that meets an Electron/CEF/WebView2 window eventually meets some version of it.
Our app installs a low-level keyboard hook (SetWindowsHookExW(WH_KEYBOARD_LL, …)) to
implement push-to-talk. It worked everywhere — terminals, editors, native apps — except:
when a Chromium-family window (our own WebView2 settings UI, and separately CEF/Electron
windows) was in the foreground, the hook callback simply stopped being invoked. No error,
no unhook, no GetLastError. The events were delivered to the focused application, but our
hook never saw them.
Release the focus to a native window: events flow again. Focus the Chromium window: silence.
While debugging, we installed a second diagnostic hook to log what the first one was missing — and the system started delivering events to both. Remove the diagnostic hook, and the original went deaf again under the same conditions. A watched hook never misbehaves.
Because of this, a minimal repro does not reliably reproduce — the repro program itself perturbs the hook chain it is trying to observe. That is exactly why this repo exists as a logger you run against your own real setup rather than a guaranteed-failing test case: run it alongside the actual application whose hook goes quiet, not instead of it.
LowLevelHooksTimeout budget can be silently removed or skipped by the system, and
Windows 10/11 will silently stop calling hooks it considers slow. Chromium pumps input
very differently from native apps (raw input, a busy message loop, high-frequency
pointer events), which plausibly changes timing enough to trip the threshold — for the
whole chain or for your position in it.We stopped relying on the global hook while our own WebView2 window had focus, and
handled the key inside the page (a keydown listener in the webview answers the
push-to-talk chord itself). The global hook still serves every native window; the
webview serves itself. This shipped, and the failure disappeared for all users.
If your hotkey must work over other people's Chromium windows (Chrome, VS Code,
Slack…), keep your hook callback microscopic (post to your own thread, return
immediately), never log/allocate/lock inside it, and re-install the hook when you detect
prolonged silence while keys are clearly being typed (compare against GetAsyncKeyState
polling — ugly, effective).
src/main.rs installs a WH_KEYBOARD_LL hook and prints one line per event:
timestamp, key, and the executable + title of the foreground window. Run it, type into
different apps, and watch whether lines stop when a Chromium window is focused.
cargo run --release --bin khook-logger
Interpreting results:
Windows 11 (23H2 and 24H2), Rust hook thread with a dedicated message pump, WebView2 (Edge 126+) same-process window, also reproduced against CEF-based apps. The dictation app has since shipped the in-page fix; current builds are unaffected.
MIT licensed. Issues and war stories welcome — especially if you can disprove the timeout hypothesis with something better.
1 commits
Rust
100.0%