Skip to content

M3: hold stray keys - #1

Merged
hazeliscoding merged 8 commits into
mainfrom
m3-hold-keys
Oct 24, 2026
Merged

hazeliscoding merged 8 commits into
mainfrom
m3-hold-keys

Conversation

@hazeliscoding

Copy link
Copy Markdown
Owner

Finishes M3 in ROADMAP.md: a WH_KEYBOARD_LL hook on its own thread holds keys typed during a steal, replays them into your window after the restore, and drops them for a game.

  • Restore fixes: a restore no longer calls BringWindowToTop, which could leave no window in front, and a restore that does end with no window in front is finished from your window's thread.
  • Key test: the steal tool now steals when the test asks, so the test checks keys typed during each steal with no race against keys already in your window.
  • Evidence: StealTests passed three runs in a row (45 keys held during 15 steals, none reached the stealer, all 120 reached your window). A Defender scan of the published exe was clean.

The decisions, with what each gives up, are in ROADMAP.md under 2026-10-24.

…window

A low-level keyboard hook on its own thread holds keys typed while focus is
on a window Staypaw hasn't judged yet. After a restore they're replayed into
your window; for a game (the protected app, an exe Windows' Game Bar lists,
or an app Staypaw can't name) or a change that stands, they're dropped. A
hold that makes no progress for 500 ms gives up.

Work in progress: the live key test still loses a key now and then.
BringWindowToTop's own activation could leave no window in front during a
restore. In a steal test that types without pause, about 40 of 1,000 keys
then reached no window, and each steal logged two Restored rows, counting
twice toward the fight limit. SetForegroundWindow raises the window anyway.
After a steal, SetForegroundWindow on your window can return true and still
leave no window in front: the switch to your window's thread never
completes. Your keys then go nowhere, the log says Restored, and the
stealer can't even steal again. A plain retry never fixed it.

While no window is in front, the 15 ms check now retries the restore, for
as long as held keys wait (500 ms), with Staypaw's thread sharing the
input of your window's thread instead of the stealer's, so the switch
happens at once. That recovered 17 of 17 such restores. A hung window is
skipped, since sharing its input would hang Staypaw too, and the no-op key
fallback isn't sent when no stealer is in front to receive it.
A key still unread in your window when a stealer joins its input with
AttachThreadInput and takes focus goes to the stealer, or nowhere, and no
hook can hold it back: tagged keys showed every such key was let through
0-3 ms before the steal, while your window had focus. The test's typing
and the stealer's 400 ms timer woke on the same system tick, so it failed
now and then.

The steal tool now also steals when its thread gets WM_APP. The test types
into your window, goes quiet for 30 ms, asks for a steal, and types three
keys before Staypaw can judge it, 15 times 400 ms apart. The exact checks
stay: no key reaches the stealer and every key reaches your window. With
key holding turned off, the stealer got 36 of the 45 keys and it failed.
Tick M3 with its evidence, and record the held-key rules, the restore
without BringWindowToTop, the restore finished from your window's thread,
and the keys no hook can hold. The trust contract names that one shared
input, and the README no longer calls holding keys in progress.
@hazeliscoding
hazeliscoding merged commit 7b14947 into main Oct 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant