51cdaea3f3236779e5c8bd85b98ac68bfc6e6b9c
12 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8c854e0a19 |
feat(gamepad): classic Steam Controller backend — Linux UHID via hid-steam (N3)
The reserved GamepadPref::SteamController = 5 slot goes live: the same hid-steam driver under the wired-SC identity (28DE:1102, ID_CONTROLLER_STATE), UHID-only in v1 (no captured SC USB interface layout, so no Steam-Input promotion — the pre-usbip Deck state; acceptable for discontinued hardware). Layout pinned against the kernel's ID_CONTROLLER_STATE table: 24-bit buttons at 8..11 (low bits shared with the Deck; grips at 9.7/10.0 = the Deck's L5/R5 positions; right-pad click 10.2; joystick click 10.6), u8 triggers at 11/12, the joystick/left-pad MULTIPLEX at 16..20 (a left-pad contact shadows the stick, like real hardware's lpad_touched flag), right pad at 20..24. Mapping: wire left stick -> SC stick; wire right stick -> right-pad coords + touched bit (the SC's camera surface — the second-stick loss is inherent); PADDLE1/2 -> the two grips (natively, masked out of the fold input); PADDLE3/4 + MISC1 -> the remap policy. The SC parser has NO gamepad_mode gate, so no mode-entry pulse. SteamDeckPad grew a SteamModel (open_model); ScProto/SteamCtrlManager; pick_gamepad flips SteamController -> itself on Linux (replacing the Xbox360 fold); SDL picker splits Valve PIDs (Deck 1205 stays SteamDeck, SC 1102/1142 now declare SteamController). Verified: .21 clippy -D warnings + 304/0 tests + on-box UHID smoke (hid-steam binds 1102, BTN_A + right-pad ABS_RX land on evdev, no mode pulse); .133 clippy -D warnings green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
384fc30833 |
refactor(inject/linux/steam_controller): convert to UhidManager<SteamProto> (3.3)
The most hook-laden conversion: SteamControllerManager becomes a pub type alias of UhidManager<SteamProto>. The Steam-specific pieces map cleanly onto the trait — open() delegates to open_transport (usbip → gadget → UHID fallback, which keeps its own per-transport logging, so no extra success line, matching the old ensure), merge_frame preserves the trackpad coords/touch-bits/clicks + motion across button-only frames (the G2 fix, verbatim), and the gamepad-mode-entry pulse rides the force_heartbeat hook. DeckTransport goes pub (type Pad in a public-trait impl). Also un-fuses a doc-comment glitch where the manager's doc had been merged onto the DeckTransport enum. Verified on .21: clippy --all-targets -D warnings clean; full suite 290 pass / 0 fail. Part of G12/3.3 (§3a.4 commit 8) — all five stateful managers now share one skeleton. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
94802795e7 |
Merge branch 'gamepad-apple-cleanup': cross-client + host gamepad review cleanup (G1–G25)
audit / bun-audit (push) Successful in 13s
ci / docs-site (push) Successful in 49s
ci / web (push) Successful in 53s
decky / build-publish (push) Successful in 21s
audit / cargo-audit (push) Successful in 2m27s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 41s
apple / swift (push) Successful in 4m22s
ci / bench (push) Successful in 5m54s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m1s
windows-host / package (push) Successful in 9m55s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11m12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9m58s
flatpak / build-publish (push) Successful in 6m18s
docker / deploy-docs (push) Successful in 27s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m14s
arch / build-publish (push) Successful in 16m1s
android / android (push) Successful in 16m36s
deb / build-publish (push) Successful in 16m58s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m46s
ci / rust (push) Successful in 22m42s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m19s
release / apple (push) Successful in 20m57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m8s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m49s
apple / screenshots (push) Successful in 18m55s
48-finding cross-client + host gamepad audit (2026-07-13). Apple/Android/SDL-core capture + feedback and the Linux/Windows host injectors: held-guide release, the permanent broken-latch cliff (PadGate), Steam Deck trackpad clicks, DualSense mute, Windows DS/DS4 paddle fold, uinput button re-sync, gamestream BTN_* dedup, the dead Windows shell fork, legacy-Deck rumble ceiling, XUSB arrival, ARM64 fences, the truncate-everywhere value convention, and more. See punktfunk-planning/design/gamepad-review-cleanup.md. |
||
|
|
e5166c6e6e |
fix(host/steam): load vhci_hcd at boot on sysext hosts so the Deck pad is Steam-Input-promotable
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m9s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
decky / build-publish (push) Successful in 30s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
apple / swift (push) Successful in 4m25s
ci / bench (push) Successful in 6m50s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5m49s
docker / deploy-docs (push) Successful in 26s
windows-host / package (push) Successful in 8m40s
arch / build-publish (push) Successful in 11m22s
deb / build-publish (push) Successful in 12m27s
android / android (push) Successful in 16m50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m19s
apple / screenshots (push) Successful in 19m19s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m14s
ci / rust (push) Successful in 25m14s
The virtual Steam Deck pad only appears in the host's Game Mode (and is navigable) when it arrives as a real USB device via the usbip/vhci_hcd transport — Steam Input won't promote the UHID hid-steam fallback (Interface: -1). The host runs as an unprivileged --user service, so it cannot modprobe vhci_hcd itself; the module must be loaded at boot and the vhci attach/detach sysfs files chgrp'd to the `input` group by the udev rule. Packaging ships modules-load.d/punktfunk.conf + 60-punktfunk.rules under the sysext's /usr/lib, but a systemd-sysext image MERGES after systemd-modules-load and early udev have already run, so on a plain reboot of a sysext host (e.g. Bazzite) those files are read too late: vhci_hcd is never loaded, usbip fails, and the pad silently degrades to non-promoted UHID — the controller vanishes from Game Mode. (deb/arch/rpm are unaffected: real /usr is present at early boot.) Fix: sysext post_merge now mirrors BOTH files into real /etc (read at the normal early-boot time, shadowing the /usr copies by filename; refreshed every merge since neither is user config), then reloads udev, modprobes vhci-hcd, and re-triggers the vhci platform device for the live session. Also raise the UHID-fallback log INFO->WARN with an actionable hint. Verified on the .41 sysext host: after the /etc mirror, unloading vhci_hcd and restarting systemd-modules-load (the real reader of /etc/modules-load.d) reloads the module; a udev coldplug trigger makes attach/detach root:input 0660; the unprivileged host user can then write attach — the exact working precondition for the usbip transport, now durable across reboot. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5109a4c80a |
refactor(inject/host): extract the shared PadGate create-retry policy + fix the permanent broken latch (G3/G12)
All seven virtual-pad managers (Linux uinput/uhid: gamepad, dualsense, dualshock4, steam_controller; Windows XUSB/UMDF: gamepad, dualsense, dualshock4) carried an identical copy-pasted `broken: bool` latch that was set on the FIRST pad-creation error and never cleared — so a single transient failure (a startup race on /dev/uinput, a momentary EBUSY, the Windows companion driver not yet ready) permanently disabled EVERY controller for the rest of the session, even after the cause cleared. Extract that latch into one shared, unit-tested `PadGate` (inject/pad_gate.rs) with the fix baked in: capped exponential backoff (1s doubling to 30s) instead of a permanent kill. After a failure, creation is blocked only until the backoff elapses — so the manager no longer re-attempts (and re-logs) on every one of the 60–240 input frames/sec — then a single retry is allowed; a success resets the backoff. A genuinely broken setup therefore self-heals within one backoff window of the fix (udev reload / driver install / next client connect) with no host restart. The gate is manager-wide, matching the old flag's semantics (these failures are systemic, not per-slot). This folds G3 (broken latch) into G12 (dedup the manager skeleton): the latch now lives in one place across all seven backends. Verified on the Linux host build (.21): cargo clippy -D warnings clean, full punktfunk-host suite 277 passed / 0 failed, 4 new PadGate tests green. Windows managers verified separately on the x64 box. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2642ba6ad0 |
fix(gamepad/host): keep Steam Deck trackpad clicks across a button frame (G2)
SteamControllerManager::handle rebuilds `SteamState.buttons` from the gamepad
frame every tick via from_gamepad, preserving only the rich-plane TOUCH bits —
so a held trackpad CLICK (set on the rich plane by apply_rich, stored in
`buttons`) was wiped on the very next button/stick frame and only flickered
back on the next rich event. This is the exact trap the DualSense backend
already dodges by keeping click in a separate `touch_click` field.
Mirror that: add persisted `lpad_click`/`rpad_click` bools to SteamState set by
apply_rich (instead of pressing LPAD_CLICK/RPAD_CLICK into `buttons`), OR them
into the report's click bits in serialize_deck_state, and preserve them across
the rebuild in handle() like touch/coords/motion. RPAD_CLICK's other owner —
the DualSense touchpad-click wire button via from_gamepad — stays in `buttons`
and is OR'd at serialize, so the two sources release independently (a released
BTN_TOUCHPAD can't strand a rich click, and vice-versa).
Adds a regression test (rich_click_survives_a_buttons_rebuild). All 17
inject::{steam,dualsense,dualshock4}_proto tests pass on Linux (.21).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
959ec30003 |
fix(client-linux,host): Deck video defaults to software decode + input-interception diagnostics
Video (Deck): the VAAPI zero-copy path renders corrupt/gray/washed-out on the Deck — root-caused to Mesa >= 25.1 exporting radeonsi VCN decode surfaces TILED (the Flatpak runtime's Mesa 26 drives both the decoder and GTK's GL, and GTK's tiled-NV12 dmabuf import mishandles it; desktop Tier-1 validations ran distro Mesa with linear export). `auto` now resolves to software on a Deck (clean, correct-colour, easily handles 1280x800 HEVC); PUNKTFUNK_DECODER=vaapi still forces the hw path, with the descriptor modifier dump + GSK_RENDERER as the bisect levers. Also reserve extra_hw_frames=4 on the VAAPI decoder: the presenter pins mapped surfaces past receive_frame, and the fixed pool recycling a surface the renderer still samples is intermittent block corruption anywhere. Input (Deck): with Steam Input ON for Punktfunk, SDL sees only Steam's virtual X360 pad — the right trackpad arrives as a plain right stick and the left trackpad/paddles/gyro not at all, silently. The client now checks once the post-attach enumeration settles and raises a toast + warn naming the fix (disable Steam Input for the shortcut). The host logs a one-shot warning when InputPlumber is running (Bazzite default) since it can grab the virtual Deck pad and re-emit it under a different identity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f3986b8f82 |
feat(host/steam): shippable usbip/vhci_hcd virtual Deck + client leave-shortcuts
Steam Deck pass-through (design/steam-deck-passthrough-plan.md), code-complete + all CI checks green on Linux + adversarially reviewed; on-glass validation pending: - usbip/`vhci_hcd` virtual Deck transport (inject/linux/steam_usbip.rs) for non-SteamOS hosts (Bazzite/generic) — presents a real interface-2 USB Deck so Steam Input promotes it. In-process vhci attach (loopback OP_REQ_IMPORT handshake → sysfs attach) with a bounded `usbip`-CLI fallback; detach on drop. - Backed by a vendored, libusb-free trim of the `usbip` crate (crates/punktfunk-host/vendor/usbip-sim, MIT + NOTICE; host/cdc/hid + rusb/nusb removed; interrupt-IN paced by bInterval). - Selection ladder raw_gadget (SteamOS fast-path) → usbip (universal) → UHID, with PUNKTFUNK_STEAM_USBIP / PUNKTFUNK_USBIP_ATTACH knobs. - Shared Deck descriptors + the 0x83/0xAE feature contract + a Steam-accepted serial consolidated into steam_proto.rs; the raw_gadget backend reuses them. - Linux client leave-shortcuts: Ctrl+Alt+Shift+D + holding the escape chord (L1+R1+Start+Select) >=1.5s end the session (short press still exits fullscreen); the chord state resets across sessions. Also bundles in-progress work already staged in the tree: - host(kwin): xdg-output logical-geometry mapping so the KWin fake_input backend places absolute coordinates correctly under display scaling. - docs: design/README index entries + design/controller-only-mode.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a41eac933f |
feat(host/steam): default the gadget Deck on for SteamOS (glass-confirmed)
The virtual Steam Deck is validated glass-to-glass on a Deck: it appears as a
distinct second Steam controller, a held A drives Steam's overlay ("Resume
Game"), and a button press registers in a real game (confirmed in-game).
gadget_preferred() now defaults ON for SteamOS hosts (/etc/os-release ID=steamos
or ID_LIKE), OFF elsewhere where the universal UHID path stays the default;
PUNKTFUNK_STEAM_GADGET=1/0 forces it. A Deck-as-host with a physical Deck never
reaches this path — resolve_gamepad's conflict gate degrades SteamDeck → DualSense
first, so the two-Deck case never happens in production (it was only a test-rig
confound on the dev Deck).
The feature is complete: a virtual Steam Deck that Steam Input recognizes +
promotes, churn-free, with input flowing to games. Workspace clippy/fmt/test
green. Not pushed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
3cc8f7dc65 |
feat(host/steam): raw_gadget Deck host backend (Steam-Input path, opt-in)
Port the proven raw_gadget virtual Deck to a Rust host gamepad backend, the SteamOS-only transport that gets Steam Input to actually promote the Deck. - inject/linux/steam_gadget.rs (new): SteamDeckGadget — a userspace raw_gadget emulator of the real 3-interface USB Deck (mouse=0/keyboard=1/controller=2, 28DE:1205) on a dummy_hcd loopback UDC, descriptors captured from a physical Deck, answering every control transfer incl. the HID feature reports. Driven by the same steam_proto::serialize_deck_state as the UHID pad; rumble feedback via parse_steam_output. The raw_gadget UAPI is funneled through 4 documented ioctl wrappers (the crate denies undocumented unsafe). - inject/linux/steam_controller.rs: the manager pad is now a DeckTransport enum (Uhid | Gadget); ensure() prefers the gadget when PUNKTFUNK_STEAM_GADGET=1 (best-effort modprobe dummy_hcd+raw_gadget), gracefully falling back to the universal UHID SteamDeckPad. write/pump/heartbeat dispatch through the enum. Validated on a real Deck via a static musl harness that #[path]-includes the module: enumerates, hid-steam binds + reads our serial + creates the Steam Deck + Motion Sensors evdevs — identical to the C PoC. Caught a real portability bug: raw_gadget's no-arg ioctls (RUN/CONFIGURE/EP0_STALL) reject a non-zero `value` with EINVAL, and on musl an omitted ioctl vararg is a garbage register — so they must pass an explicit 0. Opt-in (default off) while the Steam GetControllerInfo feature contract is hardened (to stop the gamepad-evdev churn). Workspace clippy/fmt/test green. Not pushed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
7cc2ed85cb |
feat(proto/steam): M3 — rich Steam wire (back buttons + 2nd trackpad)
Carry the rich Steam Controller / Steam Deck inputs end-to-end on the wire — strictly additive + forward-compatible (unknown kinds/bits drop on old peers). Core (punktfunk-core): - input.rs: BTN_PADDLE1..4 + BTN_MISC1 in Moonlight's buttonFlags2<<16 namespace (so the GameStream paddle path and native grips share one host injector map; Steam L4/L5/R4/R5 reuse the four Xbox-Elite paddle slots). - quic.rs: RichInput::TouchpadEx (kind 0x03 — surface 0/1/2, touch+click, signed coords, pressure; the second trackpad the single Touchpad can't express) and HidOutput::TrackpadHaptic (kind 0x04 — the SC voice-coil pulse). Round-tripped. - abi.rs: PUNKTFUNK_GAMEPAD_STEAMDECK=6 / _STEAMCONTROLLER=5, the paddle bits, RICH_TOUCHPAD_EX / HIDOUT_TRACKPAD_HAPTIC constants. from_hid packs TrackpadHaptic into the existing which + effect[0..6] — the legacy structs do NOT grow (guarded by new size_of==20/19 asserts); GamepadPref lockstep + paddle-bit lockstep asserts extended. include/punktfunk_core.h regenerated. Host (punktfunk-host): - steam_proto::from_gamepad maps the wire paddles -> the four Deck grips + QAM; apply_rich routes TouchpadEx left/right -> the matching pad. - every DualSense/DS4 manager (Linux + Windows) gained a TouchpadEx arm (surface 0/2 -> its one touchpad; surface 1 ignored) so the variant compiles everywhere and a Steam client streaming to a DS host keeps its right pad. - the xpad BUTTON_MAP finally consumes the GameStream paddle bits (BTN_TRIGGER_HAPPY5-8) — Sunshine/Moonlight paddle clients were silently no-op'd before (design §5.6). - Android feedback: drop TrackpadHaptic (no coils; rumble rides 0xCA). Validated on-box: the ignored backend test now drives the full wire path — from_gamepad (BTN_A + the L4 grip) + apply_rich (a left-pad TouchpadEx) reach the evdev as BTN_A + ABS_HAT0X=-8000. Wire round-trips + paddle/TouchpadEx mapping unit-tested. Workspace clippy/fmt/test green. Not pushed. Deferred to M4: the C-ABI PunktfunkRichInputEx + send_rich_input2 (only the Apple/embedder *send* path needs it; the host decodes TouchpadEx today). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
baa20ef377 |
feat(host/steam): M2 — virtual Steam Deck as a wired PadBackend (Linux)
Make the virtual hid-steam device a selectable per-session host gamepad,
end-to-end on Linux: PUNKTFUNK_GAMEPAD=steamdeck now builds a
SteamControllerManager that creates a /dev/uhid 28DE:1205 Deck, enters
gamepad_mode, and feeds the byte-exact Deck report (M1).
- inject/linux/steam_controller.rs: SteamControllerManager / SteamDeckPad,
mirroring dualsense.rs (open/create2, GET/SET_REPORT pump, heartbeat, RAII
destroy). Two Steam-specific quirks beyond the DualSense path:
* gamepad_mode entry — best-effort `lizard_mode=0` via sysfs, plus a b9.6
creation pulse (MODE_ENTER) so steam_do_deck_input_event stops
early-returning, plus an anti-toggle guard (MENU_HOLD_CAP) so a long
in-game Start-hold can't flip gamepad_mode back off.
* UHID_SET_REPORT answered err=0 (DualSense omits it; the kernel stalls
~5s/cmd otherwise); the 0xEB rumble report parsed onto the 0xCA plane.
- core config.rs: GamepadPref::SteamDeck (wire byte 6) + SteamController
(byte 5, reserved — folds to Xbox360 until its backend lands); from_u8 /
from_name / as_str. Forward-compatible (unknown byte -> Auto); the C-ABI
PUNKTFUNK_GAMEPAD_* constants stay M3, so no generated-header drift.
- punktfunk1.rs: PadBackend::SteamDeck variant + select / handle / apply_rich
/ pump / heartbeat arms; pick_gamepad Linux arm.
On-box: an #[ignore]d backend test (backend_binds_and_input_flows) drives the
real SteamDeckPad — it binds hid-steam (gamepad + IMU evdevs), enters gamepad
mode, BTN_A reaches the evdev, and the device tears down on drop. Workspace
clippy/fmt/test green. Not pushed. Next: M3 (protocol/ABI wire) + M4 (client
capture).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|