wudaming00/wh-keyboard-ll-chromium

Windows stops delivering WH_KEYBOARD_LL events when a Chromium window has focus - a logger + writeup of a real production failure

3

stars

1

commits

Rust

primary language

Aug 16, 2026

updated

README

Windows stopped delivering our WH_KEYBOARD_LL events when a Chromium window had focus

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.

The symptom

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.

The observer effect (this is the maddening part)

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.

What we believe is going on (hypotheses, not verdicts)

  • The documented contract allows this: a low-level hook that exceeds the 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.
  • Hook-chain position matters, and installing/removing any hook re-orders the chain. That would explain the observer effect: the diagnostic hook changed the chain, and the timing, and the outcome.
  • We could not find an API that reports "your hook was skipped/removed for slowness." If you know one, please open an issue — that's half the reason this repo exists.

What actually fixed it for us

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).

The logger

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:

  • Lines flow in every app, including Chrome: your machine/timing doesn't trip it (ours usually didn't either, until the app also had audio callbacks and a webview running).
  • Lines stop in exactly one app family: congratulations, you've reproduced our week.
  • Your other hotkey app goes deaf only while this logger is NOT running: that's the observer effect above, and we'd love a comment in the issues with your setup.

Environment where we hit it

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.

Contributors

wudaming00

1 commits

wudaming00/wh-keyboard-ll-chromium

Windows stops delivering WH_KEYBOARD_LL events when a Chromium window has focus - a logger + writeup of a real production failure

3

stars

1

commits

Rust

primary language

Aug 16, 2026

updated

README

Windows stopped delivering our WH_KEYBOARD_LL events when a Chromium window had focus

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.

The symptom

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.

The observer effect (this is the maddening part)

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.

What we believe is going on (hypotheses, not verdicts)

  • The documented contract allows this: a low-level hook that exceeds the 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.
  • Hook-chain position matters, and installing/removing any hook re-orders the chain. That would explain the observer effect: the diagnostic hook changed the chain, and the timing, and the outcome.
  • We could not find an API that reports "your hook was skipped/removed for slowness." If you know one, please open an issue — that's half the reason this repo exists.

What actually fixed it for us

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).

The logger

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:

  • Lines flow in every app, including Chrome: your machine/timing doesn't trip it (ours usually didn't either, until the app also had audio callbacks and a webview running).
  • Lines stop in exactly one app family: congratulations, you've reproduced our week.
  • Your other hotkey app goes deaf only while this logger is NOT running: that's the observer effect above, and we'd love a comment in the issues with your setup.

Environment where we hit it

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.

Contributors

wudaming00

1 commits

Languages

Rust

100.0%