Skip to content

Fix SendInput rejecting every keystroke: Input is 32 bytes, not 40 - #1

Open
verysaltyHH wants to merge 1 commit into
kmanan:mainfrom
verysaltyHH:fix/sendinput-struct-size
Open

verysaltyHH wants to merge 1 commit into
kmanan:mainfrom
verysaltyHH:fix/sendinput-struct-size

Conversation

@verysaltyHH

Copy link
Copy Markdown

The bug

SendInput has never delivered a keystroke. InputUnion declares only KEYBDINPUT, so Input marshals to 32 bytes. Win32 INPUT is 40 bytes on x64, because MOUSEINPUT is the largest member of its union. SendInput validates the structure size it is handed and rejects anything else with ERROR_INVALID_PARAMETER.

The result is the log line SendInput sent 0 of 6 keyboard events, on every machine, for every action, since the first release. The keyboard fallback the README documents has never run.

Measured on Windows 11, .NET 8, win-x64:

Marshal.SizeOf<Input>()  = 32      (expected 40)
SendInput(...)           = 0
GetLastWin32Error()      = 87      (ERROR_INVALID_PARAMETER)

Same call with MOUSEINPUT declared in the union:

Marshal.SizeOf<Input>()  = 40
SendInput(...)           = 2 of 2
GetLastWin32Error()      = 0

The second half

SendTeamsShortcut returned true unconditionally, so InvokeOrShortcut reported success for a keystroke that was never sent. A key that did nothing still showed as successful on the Stream Deck, which is what made this hard to notice: the log said "Sent Teams' Ctrl+Shift+K hand shortcut" directly under the line saying nothing had been sent.

It now returns what SendChord actually achieved, so the key raises an alert, and logs the Win32 error so the cause is readable instead of inferred. 5 there would mean Windows refused to inject into a process at a higher integrity level, which is the failure the troubleshooting section already describes.

Guard

The self-test now asserts the marshalled size, and CI runs the self-test, which it did not before. Removing the new union member makes it fail:

Input is 32 bytes, SendInput expects 40.

Scope and testing

Two files, +47/-5. No behaviour changes beyond the fallback path.

Verified against a live Teams desktop client on Windows 11: --self-test passes with the fix and fails without it, and SendInput returns success where it previously returned 0. I have not tested the fallback end to end on every action, because with UI Automation working the fallback is only reached when UIA fails.

Found while adding German control names to a fork; that work is separate and not part of this PR.

🤖 Generated with Claude Code

InputUnion declares only KEYBDINPUT, so Input marshals to 32 bytes. Win32
INPUT is 40 bytes on x64, because MOUSEINPUT is the largest member of its
union. SendInput validates the size it is handed and rejects anything else
with ERROR_INVALID_PARAMETER, so it has been returning 0 and delivering
nothing. The keyboard fallback has never run.

SendTeamsShortcut then returned true unconditionally, so InvokeOrShortcut
reported success and the Stream Deck showed a delivered keystroke for a key
that did nothing. It now returns what SendChord actually managed, and logs
the Win32 error so the cause is visible rather than inferred.

The self-test asserts the marshalled size, and CI runs the self-test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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