Files
enricobuehler dfcffcdd50
ci / bun-nix (pull_request) Successful in 36s
ci / docs-drift (pull_request) Successful in 35s
ci / web (pull_request) Successful in 1m36s
ci / docs-site (pull_request) Successful in 1m37s
ci / rust-arm64 (pull_request) Successful in 1m39s
ci / rust (pull_request) Successful in 5m36s
android / android (pull_request) Successful in 5m51s
fix(gamestream/windows): Moonlight sessions built the XUSB pad that Steam cannot see
Reported from the field: gamepad input does nothing on GameStream clients, and it
reproduces across every client and device a user tries (Artemis on a Steam Deck and
on an Android phone both). That breadth is the tell — it is not a client at all.

On Windows there are two virtual Xbox backends and they are not interchangeable to a
game. The XUSB companion registers only GUID_DEVINTERFACE_XUSB and exposes no HID
collection (pf_xusb.inx says so in its own header: "a non-HID UMDF2 driver", Class =
System), so Steam's hidapi enumeration, SDL, RawInput, DirectInput, joy.cpl and
WGI/GameInput cannot see it at all — only classic XInputGetState can. The native plane
moved to the real HID pad as its DEFAULT on 2026-08-09 (bd5735b8) for exactly that
reason; its doc comment records the reporter who lost a controller for two weeks to it.

The GameStream plane never got that change. It has bound `crate::inject::gamepad`
since the first gamepad commit, when that name meant uinput and Windows had no second
backend; Windows later gave the same name the XUSB companion, so this plane inherited
it by module-name coincidence rather than by any decision. bd5735b8 did touch
control.rs — but only to widen the rumble closure's arity, and its note reasons about
"the uinput backend", not about the Windows one sitting behind the same import. So
every Moonlight session since has presented a pad most games cannot enumerate, while
native punktfunk sessions on the same host got the good one.

There was also no way out: `windows_xbox_hid` was `pub(super)`, i.e. unreachable from
this module, so PUNKTFUNK_XBOX_BACKEND did nothing here — and the Windows manager
ignores the arrival `kind` byte, so a client could not ask for a DualSense either, the
escape that saved the earlier reporter.

- native.rs / native/gamepad.rs: `mod gamepad` and `windows_xbox_hid` become
  pub(crate). The knob keeps ONE definition and one name; widening visibility breaks
  no existing caller. Its doc now records that both planes read it, and why being
  `pub(super)` was itself the bug.
- gamestream/control.rs: a `SessionPads` enum is the one place this plane picks a
  backend — the HID pad when the shared knob says so, the XUSB companion otherwise,
  and on every other platform the single backend that exists.
  `PUNKTFUNK_XBOX_BACKEND=xusb` now reverts both planes together.

The HID pad's rich-feedback plane is dropped rather than plumbed: an Xbox pad has no
lightbar or adaptive triggers, and GameStream has no vocabulary for one — its rumble
message (0x010B) carries the two handle motors and nothing else, which is why the
trigger levels were already dropped at the call site.

No test: a `cfg(target_os = "windows")` #[test] would compile NOWHERE. ci.yml excludes
it by target, and windows-host.yml lints the host with `cargo clippy -p punktfunk-host`
without `--all-targets`, which does not build `cfg(test)` modules at all — the same
blind spot that workflow's own comments blame for letting the Linux twin's tests rot to
the wrong arity. It would be dead weight, not coverage.

VERIFIED
  * ON WINDOWS (.133, the only box where this arm compiles at all):
    `cargo check -p punktfunk-host` clean in 57s, then
    `cargo clippy -p punktfunk-host -- -D warnings` clean in 42s — both on default
    features, which include `gamestream`. This compiles BOTH enum arms: `Xusb` and
    `Hid` are Windows types alike.
    Non-vacuous by construction: `cargo clean -p punktfunk-host` ran first (a scp'd
    tree plus this box's lagging clock and a warm shared target dir otherwise yield a
    fresh `Finished` having compiled nothing), and both passes logged
    `Compiling punktfunk-host v0.31.0`. The only warning is punktfunk-core's
    header-write notice — benign and pre-existing.
  * `cargo fmt --all --check` clean.

NOT VERIFIED — owed
  * Linux: no box could run it (.25's root filesystem is 100% full; .21/.136/.138 were
    down; OrbStack here hangs even on `docker run alpine echo`). Exposure is small —
    the Linux arm is three calls on a `GamepadManager` whose Windows XUSB sibling has
    the identical method surface and just compiled — and ci.yml DOES run on
    pull_request, so opening a PR closes this.
  * On glass: no Moonlight session has driven the HID pad through this plane yet. That
    is the real acceptance test, and the log line to look for is
    "virtual Xbox pad created (Windows UMDF HID)" where it used to say
    "virtual Xbox 360 created (Windows XUSB companion)".
  * windows-host.yml has NO `pull_request` trigger (push to main, v* tags,
    workflow_dispatch only), so CI will not re-check this arm on a PR — the .133 run
    above is deliberately standing in for it.
2026-08-20 18:19:19 +02:00
..