-
v0.10.1
Stableci / web (push) Successful in 52sci / docs-site (push) Successful in 1m5sapple / swift (push) Successful in 4m33sci / bench (push) Successful in 5m22sandroid-screenshots / screenshots (push) Successful in 3m30sci / rust (push) Successful in 23m9sdecky / build-publish (push) Successful in 1m23srelease / apple (push) Successful in 24m48sandroid / android (push) Successful in 12m54sarch / build-publish (push) Successful in 13m16swindows / build (aarch64-pc-windows-msvc) (push) Successful in 55sweb-screenshots / screenshots (push) Successful in 2m51sdocker / deploy-docs (push) Successful in 9sdocker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 28sdocker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 23sdocker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 24sdocker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 30sdocker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 30sflatpak / build-publish (push) Successful in 7m4swindows / build (x86_64-pc-windows-msvc) (push) Successful in 1m19slinux-client-screenshots / screenshots (push) Successful in 7m45srpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m47swindows-host / package (push) Failing after 7m43srpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m47swindows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m51sdeb / build-publish (push) Successful in 12m5swindows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m2sapple / screenshots (push) Successful in 19m24sreleased this
2026-07-13 22:05:33 +00:00 | 0 commits to main since this releaseWire-compatible with 0.10.x — no protocol or ABI change (
WIRE_VERSIONstays 2, the native C ABI stays v6). This is an input release: touchscreen input modes come to the Linux and Windows clients, and the cross-client gamepad audit lands as correctness and robustness fixes across every client and both hosts. Every change is client-local or routes an existing wire bit — nothing touches the handshake, so any 0.10.x host/client pair keeps working with no re-pairing.It lands the cross-client + host gamepad audit (G1–G25) as ~20 targeted fixes spanning every client (Apple, Android, Linux, Windows) and both host input backends (Linux uinput/uhid, Windows XUSB/UMDF), plus one Steam Deck boot fix that gets the Deck pad back into Game Mode on image-based hosts — and brings the SDL presenter's touchscreen handling up to Android/Apple parity.
Touchscreen input — three modes on Linux & Windows
The SDL presenter (Linux / Steam Deck + Windows) had no finger handling, so SDL synthesized mouse events from touch — and under the stream's relative-mouse lock that walked the host cursor into the corner (the reported Steam Deck bug). Touch is now first-class, matching what the Android and Apple clients already do: a persisted Touch input setting (new picker in both the GTK and WinUI settings screens) selects how a touchscreen drives the host, with SDL's touch→mouse synthesis turned off.
- Trackpad (default) — the screen acts as a relative trackpad with pointer ballistics and the shared gesture vocabulary: tap = left click, two-finger tap = right click, two-finger drag = scroll, tap-then-drag = held left-drag, three-finger tap = cycle the stats overlay.
- Direct pointer — the cursor jumps to and follows your finger (absolute positioning).
- Touch passthrough — every finger is delivered as a real host touchscreen contact.
Direct touchscreens route through a new incremental gesture engine (a port of the Android
TouchInput/ AppleTouchMouselogic), while indirect trackpads keep driving the mouse; fingers map through the aspect-fit letterbox onto the content rect. Passthrough is opt-in (default stays Trackpad), matching the other clients. The gesture engine, letterbox mapping, and settings back-compat are unit-tested (28 tests).Host input — a transient failure no longer kills every controller
All seven virtual-pad managers (Linux uinput/uhid + Windows XUSB/UMDF) carried an identical
brokenlatch that was set on the first pad-creation error and never cleared — so a single transient hiccup (a startup race on/dev/uinput, a momentaryEBUSY, the Windows companion driver not being ready yet) permanently disabled every controller for the rest of the session. That latch is now one shared, unit-testedPadGatewith capped exponential backoff (1 s doubling to 30 s): after a failure, creation is blocked only until the backoff elapses, so a genuinely broken setup self-heals within one window (udev reload / driver install / next connect) with no host restart — and the manager stops re-attempting and re-logging on every one of the 60–240 input frames/sec.Host input — held state stays held
- Linux — the uinput backend emitted only XOR-changed button edges while advancing its "previous" state unconditionally. Because the kernel write is best-effort, a single dropped
EV_KEYedge left a button stuck pressed (or stuck released) until it next toggled. Buttons now re-assert their absolute state every frame, exactly like the axes — restating an unchanged key is free (the input core discards a no-opEV_KEY). - Steam Deck (host) — a held trackpad click was wiped on the very next button/stick frame and only flickered back on the next rich-plane event; it's now persisted across the frame rebuild like the touch/motion bits, and the two owners of the click bit (rich plane vs. the DualSense touchpad-click wire button) release independently.
- Windows XUSB — the pad is now eager-created on device Arrival (with
last_activerefreshed), the change-detect fields are ordered with properRelease/Acquireso a concurrent update can't be missed, and the per-section security descriptor is freed instead of leaked.
Host input — buttons that were being dropped
- DualSense mute / capture — clients have been sending
BTN_MISC1(mic-mute / Steam quick-access), but the PlayStation-family virtual pads never mapped it, so the mute button was inert on every DualSense/DualShock 4 session. It now drives the DualSense mute bit. - Steam back grips on Windows — the Windows DualSense and DualShock 4 backends passed raw wire buttons straight through, silently dropping paddle buttons (
BTN_PADDLE1..4) and ignoringPUNKTFUNK_STEAM_REMAP. They now fold the grips through the same remap policy the Linux backends already used (default still Drop; set the env to map them onto stick-clicks or shoulders).
Apple clients
- Hold guide and keep playing. While the PS/guide button was physically held, the first stick, trigger, or face-button move emitted a spurious guide-release — so you couldn't hold guide to keep the host's Steam overlay engaged while touching anything else. Guide is now preserved through the button diff and toggled solely by the Home handler.
- Share / Create is its own button. The dedicated Share/Create/Capture element was mapped onto the same bit as View, so on an Xbox-Series pad Share was indistinguishable from View and never delivered the capture bit the host decodes. It now routes to
BTN_MISC1, matching the Rust client. (On-glass verify on a real Xbox-Series pad still owed.)
Android client
Four fixes bringing the Android pad to SDL/Apple parity:
- Rapid d-pad taps register. Android batches joystick motion, so a press-and-release inside one batch lived only in the event's historical samples and was missed; every historical HAT sample is now fed through the transition logic.
- A malformed rumble lease can't kill the rumble thread. A
ttl_ms == 0lease with nonzero amplitude threw on the one-shot duration and took down the whole rumble poll thread; the duration is now floored at 1 ms. - No feedback-binding leak on disconnect. A controller unplugged mid-session leaked its open lights/rumble session; feedback binds are now evicted when a slot closes.
- Plus the held exit chord fix (G24).
Every client
- Menu diagonal tie-break now resolves horizontally across all clients, so a perfectly diagonal stick push in the console/gamepad UI moves predictably instead of arbitrarily.
- Stick/trigger axes truncate uniformly across clients, removing a one-LSB skew between platforms.
- Legacy Steam Deck rumble is bounded when a stop-frame is lost, so a dropped "stop" can't leave a Deck motor buzzing on the older path.
Steam Deck — the controller comes back to Game Mode
On image-based (systemd-sysext) hosts such as Bazzite, the virtual Steam Deck pad was silently degrading to a non-promotable UHID device and vanishing from the host's Game Mode. A sysext image merges after
systemd-modules-loadand early udev have already run, so the shippedmodules-load.d+ udev rule were read too late andvhci_hcdnever loaded — and the unprivileged host can'tmodprobeit itself. The sysext post-merge step now mirrors both files into real/etc(read at the normal early-boot time), reloads udev, loadsvhci_hcd, and re-triggers the device for the live session, so the pad arrives as a real USB device that Steam Input will promote. (deb/arch/rpm were never affected — real/usris present at early boot.)Windows — the dead gamepad fork is gone
The Windows shell carried a 629-line near-verbatim fork of
pf-client-core's SDL gamepad service, frozen at an old single-pad design and left mostly unreachable — it had also drifted from core (opening every device for metadata, force-enabling the Valve HIDAPI drivers, missing thesteam_virtualskip so it could pin Steam Input's own virtual pad and kill gyro, and deriving the pin key from an opened handle). The shell now points straight atpf_client_core::gamepad::GamepadService, and the spawned session already runs the full service for all real forwarding.Internals
- Gamepad
BTN_*wire constants are now sourced frompunktfunk_corewith the bits pinned, and the Apple side asserts every wire button/axis constant against the generated C ABI header in CI, so any client-side drift fails the build. - The shared
SwCreateCtxis hoisted intogamepad_raii, the DualSense HID-output feedback plane is de-duplicated across the Windows backends, and the two-motorvibratorIdsordering assumption on Android is documented.
Downloads