6de78213ee208ea75611bf84ed34c9d123bbfaaa
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6de78213ee |
feat(decky): the settings tab covers the whole store, as a SteamOS-style sidebar
The stats overlay was the visible half of a general problem: nine of the client's settings had a row here and twenty didn't, so a Deck that never sees a desktop could not reach its own decoder, chroma, HDR, audio layout, echo cancellation, touch or mouse model, scroll direction, auto-wake, or either audio endpoint. Everything the store holds is here now — except the two things a plugin backend genuinely cannot answer, named in `backend.ts` so the next reader doesn't go looking: which physical pad is player 1 (SDL's live device list lives in the client process, and no CLI enumerates it) and the session's remembered window size, which is not a preference. Thirty rows is too many to scroll past on a thumbstick, so they are split across a `SidebarNavigation` — the left-rail-of-categories layout SteamOS's own Settings uses, and the one Deck users already know. Every page fits on screen without scrolling, which is the point: the rail is the index, so nothing is more than one hop away. The categories, their order and the wording of the rows are the console settings screen's — it is the other settings editor reachable without leaving Gaming Mode, and two different orders for one store is how people stop trusting either. It shows them as one steppable list because it has no pointer and no room for a rail; here they become the rail's pages. The six pages take one shared settings object rather than each holding state, so a change on one is visible on the others the moment you switch. Three more rules: - A dependent setting is INDENTED under what it depends on and DISABLED, never hidden: mic device and echo cancellation under the microphone, controller type under forwarding. The console dims those rows for the same reason, and a row that vanishes as you toggle the one above it is a moving target for a thumbstick. The device row at the foot of Audio is rendered even while it reads, for that reason. - A picker with nothing to pick doesn't appear: the GPU row shows up only where the enumeration found more than one adapter, so it is absent on a Deck and present on a Bazzite desktop with a dGPU. - A setting that behaves differently HERE says so in its own description rather than being dropped. Capture system shortcuts holds nothing back under gamescope; fullscreen-on-stream can't lose to a launch that always passes `--fullscreen`; the client's library toggle isn't this plugin's browser. Each says which. The device pickers are real, not stubs: `list_devices` reads `--list-adapters` and `--list-audio` off the SESSION binary, the same two enumerations the GTK shell shells out for because it links no Vulkan itself. It is cached for the life of the backend (that call inits Vulkan and PipeWire) with an explicit Refresh for the headset you just plugged in, and a failure — a client too old to ship the session binary — leaves the pickers on Automatic and says so instead of claiming you have no devices. `_parse_audio_endpoints` is split out and unit-tested with the malformed lines that must never reach a picker. Two smaller honesty fixes fall out of building it. A Dropdown can only display a value that is one of its options, and this store has four other writers — so a stored value the table doesn't list is carried as its own entry rather than rendering blank or, worse, showing a different value than the stream will use. And a stored audio endpoint that isn't currently connected keeps a "(not connected)" entry, the way the Linux picker keeps "(not detected)", instead of silently re-pointing the next stream at the default. The Settings tab's wrapper deliberately stops being a scroll area: a SidebarNavigation given an indefinite height to fill collapses its rail, so the pane hands it the full height and keeps its hands off the overflow, and the footer inset moves inside the pages. The docs claimed nine things about this plugin that are no longer true, and three about the console home that stopped being true when its own row set grew on 2026-07-31 (4:4:4, echo cancellation, auto-wake and the library toggle are all there in `screens/settings.rs`). Both corrected. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
8af6e2dd02 |
feat(decky): the stats overlay gets an off switch in Gaming Mode
Field report: "as of version 0.23 of this plugin, there is no setting to toggle off the stat overlay." Correct, and it never had one — no commit in `clients/decky` has ever touched a stats key. Every other client does: the GTK dialog, the Windows page, the Apple app, and the console's own settings screen all carry the four-tier picker. The tier defaults to on. `Settings::default` is `show_stats: true` and `stats_verbosity: None`, which `Settings::stats_verbosity` resolves to Normal — so a Deck that has only ever been configured through this panel streams with the overlay up and no way here to put it down. What escapes exist are not discoverable: Ctrl+Alt+Shift+S wants a keyboard, and the three-finger touchscreen tap is documented in `docs/stats`, not on the glass. The console's picker is reachable (X on console home), but that is a different shortcut than the one-tap stream this panel launches, and a user editing stream settings here has no reason to look there. So the row lands here, last in the section, matching the console's wording. It writes `stats_verbosity` AND the legacy `show_stats` in the same pairing `Settings::set_stats_verbosity` keeps, so a client too old for the tiers still honours an Off chosen here; it reads them back the way `Settings::stats_verbosity` does, so a pre-tier file — including every file this plugin wrote before today — shows the Normal the stream actually runs at. `set_settings` stops replacing the file and merges onto it instead. This JSON is shared with the desktop client and the console, and holds many more keys than this panel models (decoder, GPU, profiles, touch/mouse model). The panel reads it once when it mounts, so a wholesale write posts a snapshot that predates anything another editor stored while it sat open — silently reverting it. That was invisible until 0.23.0: `9c5af8d7` fixed the GTK shell handing the session a spec built from `Settings::default()`, and only since then does this file reach a stream at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
b297542c4d |
feat(clients/input): controllers can stop being forwarded, for couches that hand the pad over another way
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m4s
apple / swift (pull_request) Successful in 1m21s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 1m39s
ci / docs-site (pull_request) Successful in 2m6s
ci / rust-arm64 (pull_request) Successful in 2m50s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 1m55s
android / android (pull_request) Successful in 3m27s
ci / rust (pull_request) Successful in 7m59s
A controller that reaches the host by USB passthrough — VirtualHere and friends, or simply a pad plugged into the host — arrived there twice: once as the real device, once as the virtual pad this client built from the same hands. Games read both, so a stick drifts against the centred second pad and menus take every input twice. New per-client setting, "Forward controllers", default on (today's behaviour). It is tier-P, so a profile can decline what another profile forwards. On Linux and Windows it is deliberately stronger than "send nothing". Opening a controller is what CLAIMS it — SDL's HIDAPI drivers take the device node — and a claimed device is one a passthrough tool cannot bind, so with this off the session opens no slot at all and never enables the Valve HIDAPI drivers. Menu navigation is untouched: the launcher still opens the active pad, and a session supersedes menu mode whether it forwards or not, so the pad is free for the whole time a stream is up. The consequence, documented at both the setting and the chord: the controller escape chord is read off forwarded pads, so it is unavailable there. The Apple and Android input stacks claim nothing, so those clients keep their slots and their chords and only gate the wire sends — losing tvOS's only controller way out of a stream would have been the worse bug. Android does stop its DualSense and Steam Controller 2 USB captures, which do claim the device. Surfaces: GTK, WinUI, the console settings screen, Apple's touch and gamepad settings, the Android touch and gamepad settings, and Decky (which also hides the rows that now have nothing to act on). Everywhere the "which pad" and "pad type" rows grey out while it is off. Verified: cargo clippy --all-targets -D warnings + 79 tests on pf-client-core, pf-console-ui, punktfunk-client-session and punktfunk-client-linux (linux/amd64 container, gate proven non-vacuous with a planted error); swift build for the Apple clients; gradle compile + 49 unit tests for Android (likewise proven); tsc for Decky. clients/windows is UNCOMPILED — both Windows boxes were offline; its edits were reviewed against the helper signatures by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
7ec3107b4a |
feat(client): the mic has an off switch, and echo cancellation has a switch too
Ctrl+Alt+Shift+V mutes and unmutes the microphone mid-stream — V for
voice, since M and S were taken. The uplink keeps running while muted:
`MicStreamer::spawn` takes a shared AtomicBool the capture callback reads
every quantum, and a muted callback drains whole frames and sends
nothing. Stopping the stream instead would have re-primed the device
buffers and, on Linux, re-run source selection on every unmute — a
second of glitch for a key people press mid-sentence. The sequence
counter deliberately does NOT advance while muted, so the host sees one
continuous sequence with a pause rather than a gap the size of the mute,
which its de-jitter would try to conceal frame by frame (its 600 ms
stale-flush covers the rest).
The mute lives on SessionHandle as a MicControl with two flags, not one:
`live` is raised by the pump only once the uplink is actually running, so
a session with the mic off in Settings — or whose capture device wouldn't
open — reports "nothing to mute", the chord says so in the log, and no
indicator appears. Per session, never persisted.
Muted state draws as a persistent "Microphone muted" badge in the stream's
top-right corner, off `FrameCtx::mic_muted` rather than the stats text: it
has to be there with the stats overlay Off, which is where most people
leave it. The Detailed mic line still reads throughput, so it simply falls
to zero — the badge is what answers "am I muted".
Echo cancellation stops being an env-only lever. `Settings::echo_cancel`
(default on, `#[serde(default)]` so every stored file loads with it on)
now gates the same hooks PUNKTFUNK_NO_AEC gated: the echo-cancelled
PipeWire source preference and WASAPI's Communications stream category.
The env var still wins, one-way — it can only turn AEC off, never back on
— and both `aec_enabled` helpers say so. The row ships in the GTK, WinUI
and console settings, under the microphone toggle and greyed out while it
is off, matching what Apple and Android shipped in wave 1.
SettingsOverlay grows `echo_cancel` as a first-class field — apply,
absorb, clear, is_empty — instead of riding the `extra` passthrough, where
`clear_override("echo_cancel")` answered false. The JSON key is the one
Apple and Android already write, so one catalog round-trips through all
three.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
fd3c54bd43 |
feat(client): "Capture system shortcuts" finally decides where Alt+Tab goes
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 2m22s
ci / rust-arm64 (push) Successful in 3m34s
android / android (push) Canceled after 4m8s
apple / swift (push) Canceled after 4m13s
apple / screenshots (push) Canceled after 0s
decky / build-publish (push) Successful in 42s
arch / build-publish (push) Canceled after 4m25s
ci / rust (push) Canceled after 4m30s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 11s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 10s
deb / build-publish (push) Canceled after 4m35s
deb / build-publish-host (push) Canceled after 3m29s
deb / build-publish-client-arm64 (push) Canceled after 2m15s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 10s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 13s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 6s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 4s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 10s
docker / builders-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 30s
flatpak / build-publish (push) Canceled after 0s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 6m14s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 0s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
The toggle has been stored, profileable and rendered in two settings UIs since profiles landed, and nothing read it. Windows grabbed the keyboard whenever input was captured, setting or no setting; Linux never grabbed at all, because the grab sat behind `#[cfg(windows)]` with a comment deferring the compositor story to "the shells" — which never picked it up. `Settings.inhibit_shortcuts` now reaches the presenter and gates the grab, on both platforms. SDL3 already maps `SDL_SetWindowKeyboardGrab` onto `zwp_keyboard_shortcuts_inhibit_manager_v1` on Wayland and `XGrabKeyboard` (plus `_XWAYLAND_MAY_GRAB_KEYBOARD`) on X11, so dropping the cfg is the Linux fix. Capture state still gates it, so releasing input hands the chords straight back, and the desktop mouse model never grabs. A compositor with no shortcuts-inhibit global says so once instead of failing silently — at debug under gamescope, which has no shortcuts to inhibit in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d383161723 |
docs: the docs catch up with five releases of shipped work
ci / rust (push) Failing after 2m31s
ci / docs-site (push) Successful in 1m22s
ci / web (push) Successful in 1m48s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m1s
ci / rust-arm64 (push) Successful in 2m2s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 11s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 9s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / builders-arm64cross (push) Successful in 20s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 36s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 36s
docker / deploy-docs (push) Canceled after 0s
~1150 feat/fix commits landed since v0.19 and the docs drifted badly. This is a full sweep of every page against the code as shipped: ~280 verified corrections, nine new pages, and one deletion. The worst of what was wrong: the quickstart's five-minute path could not work (`serve` never started the web console, so step 3 had no PIN to read); every packaged Linux host runs `serve --gamestream` while security.md told readers to leave GameStream off; HDR was documented as Windows-only; `PUNKTFUNK_SECURE_DDA` was documented as a working knob that nothing reads; `PUNKTFUNK_INPUT_BACKEND` listed a `uinput` value that does not exist and named libei for KDE instead of kwin; README linked three pages deleted on 2026-07-05; and the rpm-ostree update command pointed at a script no package installs. Completeness: about half of what shipped since v0.19 had no page at all. New: support-matrix (what works where, from 217 verified capability cells), input (mouse/touch/pen — and the in-stream chords, so the docs finally say how to get your mouse back), client-settings, profiles-and-links, game-library, clipboard, wake-on-lan, hdr, uninstall. Updating existed but had zero inbound links. status.md is gone: its facts moved into the support matrix, its shell stays as a redirect so the public URL does not 404. roadmap.md is themes now, not a feature checklist — checkboxes are what rotted. Debian is no longer claimed. The .deb's Depends resolve against Ubuntu images, nothing in CI builds or tests Debian, and Debian 12 is below the glibc 2.39 floor. The `debian` in the repo URL is the package format. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |