Skip to content

fix(windows): stop retrying SendInput forever when it is refused - #516

Open
minh-tg wants to merge 1 commit into
feschber:mainfrom
minh-tg:windows-bounded-sendinput
Open

minh-tg wants to merge 1 commit into
feschber:mainfrom
minh-tg:windows-bounded-sendinput

Conversation

@minh-tg

@minh-tg minh-tg commented Sep 29, 2026 •

Copy link
Copy Markdown

Windows can refuse an injected event, most often when a higher integrity window has focus. SendInput returns zero every time that happens, and the old code retried it in a tight loop until it succeeded, so it never stopped.

That call is synchronous, so the whole emulation task sat inside it. No more input went out, and the cleanup that releases held keys couldn't run either, because it injects through the same path. The key repeat task had the same problem.

What changed in input-emulation/src/windows.rs:

  • Give up after a few attempts and return an error.
  • Stop the repeat task and log it when an injection fails.
  • Let the error reach the dispatcher, which ends the emulation session. That is a normal state: emulation can be turned back on, and it releases the tracked keys on the way out.

DOC.md gets one line.

Before, a refused injection froze lan-mouse until you killed it. After, emulation stops and says so, and you can turn it back on. Input isn't delivered while it's stopped, but that is what happens when Windows won't let us inject.

Testing:

  • cargo fmt --all --check
  • cargo test --workspace --all-features
  • cargo check --workspace --all-targets --all-features
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • git diff --check
  • The Windows file gets compiled for real: cargo check -p input-emulation --no-default-features --target x86_64-pc-windows-msvc, using a temporary toolchain with the Windows target installed. I checked that this covers the file by putting a type error in it first, and the error showed up.

Gaps:

  • No Windows machine here, so it has been compiled but not run.
  • Only the crate is checked for the Windows target, not the full app with GTK.

Came out of #510 when I trimmed it. Otherwise unrelated.

`send_input_safe` retried `SendInput` in a tight loop until it reported success.
Windows refuses an injection in some cases, most commonly while a higher
integrity window has focus, and then `SendInput` returns zero every time, so the
loop never ends.

The call is synchronous, so the whole emulation task stays stuck in it. No
further input is sent, and the cleanup that releases held keys cannot run either,
because it injects through the same path. The key repeat task could spin the same
way.

Give up after a bounded number of attempts and return an error, and stop the
repeat task when the injection fails. The error reaches the dispatcher, which
ends the emulation session, so it is recoverable by re-enabling emulation instead
of freezing.
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