Commit Graph
3061 Commits
Author SHA1 Message Date
enricobuehler d0a7b262d2 chore(deps): regenerate notices + make audit.yml's header match what it now scans
windows-drivers / driver-build (pull_request) Successful in 1m57s
windows-drivers / probe-and-proto (pull_request) Successful in 35s
apple / swift (pull_request) Successful in 2m29s
apple / screenshots (pull_request) Skipped
ci / bun-nix (pull_request) Successful in 52s
ci / rust-arm64 (pull_request) Failing after 3m22s
ci / web (pull_request) Successful in 3m47s
ci / docs-site (pull_request) Successful in 5m10s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 7m21s
windows / build (x86_64-pc-windows-msvc) (pull_request) Failing after 24s
ci / rust (pull_request) Failing after 11m30s
nix / flake (pull_request) Failing after 30m26s
android / android (pull_request) Successful in 13m2s
THIRD-PARTY-NOTICES regenerated after the dependency changes (582 crates).

audit.yml's header claimed to cover "EVERY dependency tree the project ships"; it now
actually does, so the note spells out that each Rust lockfile needs its own `--file` —
a bare `cargo audit` reads only the root one, which is how the drivers lock stayed
unscanned while already sitting in this job's `paths:` filter. Also corrected "BOTH
Rust workspaces" for the licence gate, which covers the host + driver workspaces.

Both cargo-about legs re-run after the dependency removals: RC=0.
2026-08-13 12:41:47 +02:00
enricobuehler 51a005dd43 fix(deps): close the audit gaps, drop unused declarations, declare what is used
Acting on the 2026-08-13 dependency sweep. Every claim below was re-verified against
the tree before acting on it (greps carry a positive control; the advisories were
re-checked with cargo audit 0.22.2).

SECURITY
- event-listener 5.4.1 -> 5.4.2 (RUSTSEC-2026-0221, unsound Send/Sync on StackSlot;
  reaches the tray via zbus and the host via ashpd). This sat unnoticed because
  `cargo audit` reports unsoundness as a WARNING and the job fails only on
  vulnerabilities — audit.toml now says so out loud.
- spin 0.9.8 -> 0.9.9. 0.9.8 is YANKED and was genuinely compiled (flume via mdns-sd
  and relm4, plus lazy_static).
- wayland-scanner 0.31.10 -> 0.31.11, which moves quick-xml 0.39 -> 0.41. That is the
  exact trigger audit.toml documented for RUSTSEC-2026-0194/0195, so both ignores are
  deleted rather than left as permanent exceptions. Only RUSTSEC-2023-0071 (rsa
  Marvin, still unfixed upstream) remains.
- Corrected audit.toml's claim that `paste` arrives "via utoipa-axum": rav1d pulls it
  too, so every client has it through the decode path and dropping utoipa-axum would
  not have cleared it.

TWO CI GATES THAT SCANNED NOTHING
- `cargo audit` only ever reads the ROOT Cargo.lock. The drivers lock was already in
  this job's `paths:` filter, so edits to it triggered a run that then ignored them.
  All four secondary workspaces now get an explicit `--file` (verified: clean, bar the
  known `paste` warning in drivers).
- packaging/windows/pf-vkhdr-layer had NO lockfile at all while shipping as a DLL in
  the host installer, so every build resolved fresh and neither cargo-audit nor
  cargo-about ever saw it. Lockfile generated and committed, and added to `paths:`.

UNUSED / DUPLICATE DECLARATIONS
- punktfunk-host: removed 13 dependencies it never references — the Wayland stack
  (client, protocols{,-wlr,-misc}, scanner, backend), xkbcommon, reis, khronos-egl,
  ash, usbip-sim, parking_lot, bytemuck. The code moved to pf-inject and pf-zerocopy
  in the subsystem extraction and those crates declare them; only the manifest entries
  and their now-false comments stayed. Also dropped four redundant re-declarations
  (tokio/serde_json/futures-util in the Linux block, tower in dev-deps).
- Removed genuinely unused: bytes (punktfunk-core), anyhow (pf-win-display),
  tracing (clients/cli), anyhow (clients/session), serde (clients/windows).
- Removed the high-level `wdk` crate from all five driver crates and the drivers
  workspace: none of them ever referenced `wdk::` (62 `wdk_sys::` uses; pf-umdf-util
  is a full WDF crate that never declared it). `tracing`/`tracing-subscriber` remain
  in that lock afterwards but ONLY as wdk-sys build-dependencies, not in the DLLs.
- pf-win-display took punktfunk-core with `quic` for one type (`Mode`) that lives in
  the ungated `config` module; now `default-features = false`, which keeps
  quinn/tokio/rcgen/opus out of a leaf crate's declared closure.
- pf-encode declared the windows-rs feature `Wdk_Graphics_Direct3D` for a call that
  lives in pf-frame and is resolved via GetProcAddress on gdi32.

LATENT BREAKAGE (compiled only by feature unification)
- pf-inject uses `tokio::select!` without declaring `macros` (borrowed from
  punktfunk-core's quic feature); pf-capture uses `tokio::sync::oneshot` without
  declaring `sync` (borrowed from ashpd->zbus); pf-client-core uses the `minwindef`
  and `winnt` windows-rs headers without declaring them (borrowed from
  clients/windows). Each now declares what it uses, so an unrelated crate changing its
  features cannot break them.
- pf-console-ui took pf-client-core WITHOUT `default-features = false`, unlike every
  other consumer. That default is `pyrowave`, which compiles the vendored PyroWave C++
  — "fatal on Windows ARM64". Only safe today because the ARM64 leg passes
  --no-default-features (which also drops `ui`).

CORRECTED A FALSE INVARIANT
- clients/windows claimed "the workspace builds ONE windows-rs". It does not: wasapi
  pulls the crates.io windows 0.62.2 beside the git-rev copy. The invariant that DOES
  hold is narrower (reactor and that crate share one rev, which is what makes the
  IDXGISwapChain1 hand-off type-check). Comment rewritten, with a warning against
  "fixing" it via a blanket [patch.crates-io] — this rev uses header-named features
  while a dozen other manifests use the old Win32_* namespace ones.

Plus the safe in-compat `cargo update` sweep (no manifest edits).

Verified on macOS: punktfunk-core 385, pf-update-check 32, c_abi 1 (with
LIBRARY_PATH=/opt/homebrew/opt/opus/lib), cargo audit clean bar the two known
unmaintained warnings. Linux and Windows legs follow.
2026-08-13 12:41:47 +02:00
enricobuehler b84d37b5a0 test(host): make two socket stubs survive ureq 3 / Windows
Both failures found running the store + plugin-launch tests on the Windows runner
after the ureq 3 port. Neither is a production defect — the request/response round
trip and the 304 semantics both hold — but both tests were resting on assumptions
that ureq 2 happened to tolerate.

catalog::ureq_returns_304_as_ok: the stub answered without ever reading the request.
Closing a socket that still holds unread received data makes Windows send an RST
rather than a FIN, which discards the response already written, so the client saw a
transport error (os error 10053) instead of the 304 the test exists to pin. The stub
now drains the request first. The pinned behaviour is unchanged and still true:
ureq 3 turns only `is_client_error() || is_server_error()` into Err, so 304 arrives
as Ok exactly as before.

plugin_launch::asks_the_registered_plugin_and_takes_its_answer: hardcoded a cwd of
`/opt/emu`, which has no drive letter and is therefore NOT `Path::is_absolute` on
Windows, so `validate_reply` refused the recipe. This test could never have passed
on Windows, with either ureq version — its sibling
`a_working_directory_must_be_absolute` already had the `cfg!(windows)` split and this
one was simply missed. Confirmed by diagnostic before touching it: the body came back
over ureq 3 byte-perfect, so everything up to validation was working.
2026-08-13 12:41:47 +02:00
enricobuehler 9ec8350fc3 chore(licenses): retire ring's licence exception now that ring is gone
`about.toml` carried `OpenSSL` in the global accepted list and a `[ring]` per-crate
acceptance, both there solely because ring's licence is an AND that includes the
OpenSSL terms. The ureq 2 -> 3 upgrade removed ring from every target we build, and
aws-lc-sys 0.44's SPDX (ISC AND (Apache-2.0 OR ISC) AND Apache-2.0 AND MIT AND
BSD-3-Clause AND ... MIT-0) carries no OpenSSL clause, so neither entry has anything
left to permit.

Note about.toml sets no `targets`, so cargo-about still walks quinn-proto's wasm-only
ring edge — removing the exception is safe not because ring is invisible but because
ring 0.17.14 declares `Apache-2.0 AND ISC`, and both are globally accepted already.

Verified by running the gate itself, both legs of what audit.yml runs:
  cargo about generate about.hbs --fail                                    -> 0
  cargo about generate -m packaging/windows/drivers/Cargo.toml -c ...      -> 0
and proved non-vacuous with a negative control: dropping "ISC" from the accepted
list makes the first leg exit 1.

THIRD-PARTY-NOTICES regenerated: 601 -> 580 crates (ureq 3 pulls a smaller tree than
ureq 2 + ring), now listing ureq 3.4.0.
2026-08-13 12:41:47 +02:00
enricobuehler 3ccfd01699 feat(deps): upgrade ureq 2 -> 3, removing ring from the tree entirely
The aws-lc-rs move left `ring` compiled in for one reason: ureq 2 names
`features = ["ring", ...]` inside its own `[dependencies.rustls]` block, and cargo
features are additive, so no dependent could switch it off. ureq 3 declares rustls
with `default-features = false` and selects no backend, which finally makes the
choice ours.

`cargo tree -i ring` is now empty for macOS, Windows and Linux. The one remaining
hit under `--target all` is quinn-proto's wasm-only dependency
(`cfg(all(target_family = "wasm", target_os = "unknown"))`), a target we never build.

⚠ The dependency must be spelled `features = ["rustls-no-provider",
"rustls-webpki-roots"]`. ureq 3's convenience `rustls` feature expands to include
`_ring` — the same shape of trap as rustls's own `features = ["ring"]`, and the
reason punktfunk-webos still carries both backends today.

Ported 9 files. The two pinning call sites (the desktop client's library fetch and
the tray's status poll) needed a custom ureq Connector, because ureq 3's `TlsConfig`
exposes roots/client-cert/off-switch but no hook for a custom ServerCertVerifier.
That glue lives once in punktfunk-core behind a new off-by-default `ureq-tls`
feature rather than being hand-rolled twice on a trust boundary; the Apple/Android
cdylib embedders do not enable it and pull no HTTP stack. The connector is modelled
on ureq's own crate-private RustlsConnector and is transport glue only.

Two behaviour changes worth noting, both improvements:
- Body caps are enforced by the reader, so an over-cap response is now an Err rather
  than ureq 2's silent truncation — which used to surface as a signature failure
  pointing at the wrong thing.
- A pin mismatch matches ureq 3's typed `Error::Rustls(..)` instead of sniffing a
  substring out of a transport message, which could also fire on unrelated cert
  errors.
Conditional requests are unchanged: 304 still arrives as Ok (only 4xx/5xx are Err),
which the existing `ureq_returns_304_as_ok` socket test still pins.

Also removed four now-dead `std::io::Read` imports. One of them (plugin_launch) is
still needed by its test module, so it moved there rather than being deleted: host
CI lints without `--all-targets`, so a top-level import used only under cfg(test)
fails the shipping build.

Verified on macOS: punktfunk-core (quic + ureq-tls) checks clean, pf-update-check
32/32, cargo fmt clean.
2026-08-13 12:41:47 +02:00
enricobuehler 79d755cd98 feat(crypto): move the rustls backend from ring to aws-lc-rs, with PQ key exchange
The workspace pinned `ring` everywhere because aws-lc-sys 0.41.0 failed to C-compile
on the Windows CI runner. Re-tested on that runner (.133) with aws-lc-sys 0.44.0: the
`params.c` cl.exe failure does not reproduce under MSVC 14.44, and rustls's `aws_lc_rs`
feature turns on `aws-lc-rs/prebuilt-nasm`, so no NASM is needed on the box either.

That unblocks post-quantum TLS: `prefer-post-quantum` offers X25519MLKEM768 first on
every TLS 1.3 handshake (mgmt API, native control plane, QUIC), which ring cannot do —
it has no ML-KEM. Classical curves stay in the list, so older clients still connect.

rustls, quinn, rcgen and tokio-rustls each select a backend independently, so all four
had to move together; a single dissenter pulls a second crypto stack in via feature
unification. The direct `ring` users (ed25519 in pf-update-check, SHA-256 in the Windows
updater) moved to aws-lc-rs, whose API is ring-compatible.

`ring` does NOT leave the tree: ureq 2 names `features = ["ring"]` in its own rustls
dependency line and cargo features are additive, so no dependent can switch it off. Two
backends compiled in means rustls refuses to infer one, and anything built via
`ClientConfig::builder()` panics instead of picking — which is what ureq's default agent
does on its first HTTPS request. `tls::install_default_provider()` makes the choice
explicit; it runs at each binary's entry point and defensively in pf-client-core, which
several binaries link. Dropping ring entirely needs the ureq 2 -> 3 upgrade (36 call
sites), deliberately left out of this change.

Verified on macOS: pf-update-check 32, punktfunk-core 385, c_abi 1 (the last with
LIBRARY_PATH=/opt/homebrew/opt/opus/lib) — aws-lc-sys links into the C ABI harness, so
the Swift/Kotlin embedders keep working. cargo fmt --all --check clean.
2026-08-13 12:41:47 +02:00
enricobuehler 5fbf04f56d Ship punktfunk-gamescope on apt, support Debian 13, and state the real host floor (#190)
ci / docs-site (push) Successful in 1m19s
windows-host / package (push) Failing after 55s
ci / rust-arm64 (push) Successful in 1m40s
windows-host / canary-manifest (push) Skipped
windows-host / winget-source (push) Skipped
ci / bun-nix (push) Successful in 20s
apple / swift (push) Successful in 1m39s
deb / build-publish-gamescope (push) Failing after 2s
android / android (push) Successful in 6m22s
deb / build-publish-host (push) Successful in 4m38s
deb / build-publish (push) Successful in 5m31s
ci / web (push) Successful in 7m54s
apple / screenshots (push) Successful in 5m57s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 3m30s
deb / build-publish-client-arm64 (push) Successful in 8m26s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 6m4s
docker / builders (ci/gamescope-trixie.Dockerfile, punktfunk-gamescope-trixie) (push) Successful in 1m45s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 5m41s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m27s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 3m19s
decky / build-publish (push) Successful in 51s
arch / build-publish (push) Successful in 10m45s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10m24s
ci / rust (push) Canceled after 6m35s
deb / smoke-install (push) Canceled after 2m50s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 2m34s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 1m2s
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 1m33s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 6m22s
2026-08-13 10:21:52 +00:00
enricobuehler 85980b425e docs: state the real host floor — Ubuntu 26.04+ / Debian 13+ — and name Debian everywhere
ci / docs-site (pull_request) Successful in 1m17s
ci / rust-arm64 (pull_request) Successful in 1m29s
ci / bun-nix (pull_request) Successful in 1m33s
apple / swift (pull_request) Successful in 1m44s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 3m56s
android / android (pull_request) Successful in 9m55s
ci / rust (pull_request) Successful in 15m59s
The floor that matters is the DESKTOP, not the package: 24.04 installs
punktfunk-host and then has no compositor over the version floors and no
gamescope, which reads as a bug rather than an unsupported base. The
requirements page now leads with a per-release table separating 'package
installs' from 'can actually host', and install.md carries the same
caveat next to the apt row.

Debian was already a supported target after the previous commits but was
still invisible at the entry points — README's install table, the docs
index cards, and the 'what you need' list all said Ubuntu only. All three
now name Debian and carry the version floor.
2026-08-13 12:20:33 +02:00
enricobuehler 107fa3472d docs: Linux Mint 22.x cannot host, and 24.04 gives the host no compositor
Measured on a real linuxmintd/mint22-amd64 image and on Ubuntu 24.04. The
package installs on both, which is exactly what makes this easy to miss —
nothing on the box can then produce a stream:

  * Cinnamon cannot host a virtual display (Muffin has no RecordVirtual).
  * gamescope is absent from 24.04 and cannot be built for it: the tree needs
    wayland >= 1.23.1 (has 1.22.0), libinput >= 1.26 (1.25), libavif >= 1.2.1
    (1.0.4), pixman >= 0.44 (0.42), plus libdisplay-info2 and libxcb-errors0,
    neither of which 24.04 packages at all.
  * Switching desktop does not rescue it — 24.04 has KWin 5.27 (floor 6.5.6)
    and GNOME Shell 46 (floor 48). Only sway 1.9 is even a candidate.

So the gamescope route documented for Cinnamon holds for LMDE 7 (Debian 13,
verified end to end) but NOT for Linux Mint 22.x — which is every mainstream
Mint until Mint 23 lands on a 26.04 base in December 2026. Both the Debian and
Ubuntu pages now say so, and the Debian page carries a per-edition table.

Also states what Debian 13 itself can drive: GNOME 48.7 and sway 1.10 are above
the floors; its KWin 6.3.6 is below.
2026-08-13 12:12:52 +02:00
enricobuehler 5d8682d7b7 feat(vdisplay): explain a cinnamon/muffin compositor pin instead of listing backends
PUNKTFUNK_COMPOSITOR=cinnamon is the first thing a Mint or LMDE user
reaches for, and the bare list of accepted values invites the
next-closest guess — 'mutter', since Muffin is a Mutter fork — which
starts a session that fails deep inside an org.gnome.Mutter.ScreenCast
call Muffin does not serve. No value of the variable can work, so say
that and name headless gamescope, which needs no desktop compositor.
2026-08-13 11:50:05 +02:00
enricobuehler 030bc8a1c2 docs(debian): use deb822 sources for non-free, not apt-add-repository
software-properties-common is not available in Debian 13, so the
apt-add-repository line could not have worked. Debian 13 keeps its
sources in deb822 format; edit Components there instead (verified in a
trixie container — the NVIDIA driver it then offers is 550, above our
535 floor).
2026-08-13 11:47:20 +02:00
enricobuehler 346385bad8 fix(deb): ship punktfunk-gamescope on apt at last, and support Debian 13
`punktfunk-gamescope` had never been published to the apt registry — not in any
release. It was built inside the host job's Ubuntu 24.04 image, where it cannot
build: our pin vendors wlroots 0.19.3, which floors `wayland-server` at 1.23.1,
and noble ships 1.22.0 (it also lacks libxcb-errors-dev and has only
libdisplay-info 0.1.1). Every rung of that path was a `::warning::` returning 0
and the one hard gate ran last by design, so v0.26.0 and v0.27.0 both released
with the package missing while docs-site told apt users to install it. The same
tags shipped it fine for Arch, Fedora 44 and Bazzite.

It now builds in its own job on Debian 13 (ci/gamescope-trixie.Dockerfile), the
oldest apt base the tree configures on. One package serves Debian 13 AND Ubuntu
26.04 — measured by installing and running it on both — because the build also
vendors libdisplay-info via the new `--extra-fallback` option: linked against
the distro copy it demands `libdisplay-info2` on trixie, which Ubuntu 26.04 does
not have (it carries libdisplay-info3). The option is opt-in, so the
Arch/Fedora/nix outputs are byte-for-byte unchanged. Ubuntu 24.04 gets no
gamescope package and cannot — its wayland is too old to run one however built.

Debian 13 is now a documented host target. That needed no packaging change at
all: the host .deb's glibc-2.39 floor and bundled FFmpeg already made it
installable, and it had been working for a long time while docs-site said Debian
was unsupported and unverified. Verified by installing: host, web console and
plugin runner install, resolve every soname and run. The desktop client stays
Ubuntu-26.04-only (built there, floors at `libc6 >= 2.43`; Debian 13 has 2.41).

Compositor detection now answers Cinnamon (Mint, LMDE) with the route that works
instead of advice that cannot help. Muffin forked from Mutter 3.36:
`org.cinnamon.Muffin.ScreenCast` has only RecordMonitor/RecordWindow, never
RecordVirtual, and xdg-desktop-portal-xapp implements no ScreenCast — so no
value of PUNKTFUNK_COMPOSITOR makes a Cinnamon desktop host a virtual display.
The error names headless gamescope, which needs no desktop compositor. The XDG
sniff moved into a pure function so those branches are testable; Cinnamon is
matched before GNOME, since it is a GNOME derivative and the generic arm would
otherwise hand it the Mutter backend (caught by the new test).

New `smoke-install` job installs every published package from the registry in
pristine ubuntu:24.04, ubuntu:26.04 and debian:trixie images, asserts each
binary resolves its libraries and runs, and insists the version served is the
one this run built. Nothing in deb.yml had ever installed a package it produced,
which is how both of the above survived unnoticed.

⚠ Bootstrap: seed `punktfunk-gamescope-trixie:latest` into the LAN registry once
(docker.yml builds it thereafter) or the new job cannot start.
2026-08-13 11:43:54 +02:00
enricobuehler 0026143164 Merge pull request 'fix(bazzite): the shipped template pinned ATTACH, so Game Mode mirrored the box's screen instead of giving the client its own display' (#189) from worktree-no-attach-default into main
ci / bun-nix (push) Successful in 30s
ci / web (push) Successful in 1m9s
apple / swift (push) Successful in 1m44s
ci / rust-arm64 (push) Successful in 1m35s
windows-host / package (push) Failing after 1m31s
windows-host / canary-manifest (push) Skipped
windows-host / winget-source (push) Skipped
android / android (push) Failing after 3m35s
deb / build-publish-client-arm64 (push) Successful in 3m12s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 13s
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 8s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
ci / docs-site (push) Successful in 6m7s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 3m2s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 20s
apple / screenshots (push) Successful in 6m7s
deb / build-publish-host (push) Successful in 6m41s
docker / builders-arm64cross (push) Successful in 11s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3m9s
arch / build-publish (push) Successful in 11m45s
deb / build-publish (push) Successful in 12m33s
docker / deploy-docs (push) Failing after 7m31s
ci / rust (push) Successful in 18m21s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m33s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 24m26s
Reviewed-on: #189
2026-08-13 09:24:21 +00:00
enricobuehler ba16237c35 fix(bazzite): the shipped template pinned ATTACH, so Game Mode mirrored the box's screen instead of giving the client its own display
apple / swift (pull_request) Successful in 1m46s
apple / screenshots (pull_request) Skipped
android / android (pull_request) Successful in 8m43s
ci / bun-nix (pull_request) Successful in 22s
ci / web (pull_request) Successful in 1m7s
ci / docs-site (pull_request) Successful in 3m13s
ci / rust-arm64 (pull_request) Successful in 3m18s
ci / rust (pull_request) Successful in 15m34s
Field report: "on Bazzite when using gaming mode it is mirroring the main display
instead of giving the client its own." It is our own template that does it.

`packaging/bazzite/host.env` set `PUNKTFUNK_GAMESCOPE_ATTACH=1`, and every install
path — rpm, deb, Arch, nix — ships that file as `/usr/share/punktfunk/host.env.bazzite`
with the docs telling people to copy it verbatim. So the recommended Bazzite setup
turned the attach override ON for everyone.

That override is rung 2 of `pick_gamescope_mode`, ABOVE `dedicated_launch` at rung 3.
The rung comment calls the operator overrides a debug/CI escape hatch, which is right —
but we were shipping one as a distro default, so on a Bazzite box the managed takeover
and the dedicated game session were both unreachable. A game launched from a client's
library could not get a session of its own either, which is the case the dedicated
route exists for. With a physical display connected, attach then takes the
`physical_display_connected()` arm and streams the box's own head at the box's own
mode: the mirror the reporter saw.

The template now forces nothing and lets the per-connect detection answer, which on a
box with `gamescope-session-plus` is MANAGED. Attach stays available, documented as the
opt-in it is, with the mirror and the dedicated-session cost stated. Because managed
depends on the `punktfunk` group to stop the display manager, the template now says so
where someone choosing a model will read it, rather than only in the distro guide.

Also fixes the off-switch. Both overrides were read with `var_os(..).is_some()`, so
`PUNKTFUNK_GAMESCOPE_ATTACH=0` meant ATTACH ON — the opposite of what the line says,
and of every other knob on this host. They now use the shared `env_on` grammar, so
`0|false|off|no` disable and a bare `=1` keeps working. Anyone who "turned attach off"
in an older host.env had it on the whole time.

Note an upgrade never rewrites an existing `~/.config/punktfunk/host.env`, so boxes set
up from an older template keep the pin until the line is deleted by hand; the Bazzite
and HDR pages now say that.

Verified: `scripts/xcheck.sh linux` check + clippy `-D warnings` clean, pf-vdisplay
206/0 under rust:1.96, `cargo fmt --all --check` clean. Gate proved non-vacuous against
a planted `compile_error!` in routing.rs.
2026-08-13 09:45:52 +02:00
enricobuehler 981f32b8f6 Merge pull request 'Gamescope streams could tear pink at 120 fps — zero-copy handed the capture buffer back while the VCN was still reading it' (#188) from worktree-gamescope-tear-race into main
ci / bun-nix (push) Successful in 24s
ci / rust-arm64 (push) Successful in 1m29s
apple / swift (push) Successful in 1m44s
ci / web (push) Successful in 4m45s
ci / docs-site (push) Successful in 6m3s
ci / rust (push) Successful in 6m19s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 17s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m25s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 30s
deb / build-publish (push) Successful in 7m18s
apple / screenshots (push) Successful in 6m25s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 26s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 39s
deb / build-publish-host (push) Successful in 8m3s
deb / build-publish-client-arm64 (push) Successful in 4m57s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m54s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 15s
docker / builders-arm64cross (push) Successful in 16s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3m25s
android / android (push) Successful in 11m52s
arch / build-publish (push) Successful in 14m31s
windows-host / package (push) Successful in 13m55s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 33s
docker / deploy-docs (push) Successful in 7m2s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m48s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m41s
Reviewed-on: #188
2026-08-13 07:24:07 +00:00
enricobuehler a9a1b923a2 fix(pf-capture): withhold zero-copy buffers from the producer until the encoder is done reading them
ci / bun-nix (pull_request) Successful in 29s
ci / web (pull_request) Successful in 1m8s
apple / swift (pull_request) Successful in 1m50s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 4m14s
ci / rust-arm64 (pull_request) Successful in 4m52s
ci / rust (pull_request) Successful in 4m57s
android / android (pull_request) Successful in 7m6s
The raw-dmabuf passthrough handed the SPA buffer back to gamescope at
.process return while the encode thread had not yet imported - let alone
read - its dmabuf, and nothing ordered the producer's writes against the
consumer's read (no explicit sync; the implicit-fence wait measures
NoFence on every compositor x vendor pairing we have). On the direct-VCN
arms (native NV12, RGB-direct EFC) the captured buffer IS the encode
source for the whole ring-2-deep encode plus the phase-lock hold, so at
120 fps gamescope cycles back into the buffer mid-encode and the stream
ships torn frames: luma/chroma desync (magenta tint) plus block
corruption propagating through the P-chain until the next intra. Field
report: Nobara, gamescope mode at 120 fps - KDE sessions were clean
because cursor_blend routes them to the compute-CSC copy arm whose read
window is microseconds.

The fix defers the requeue: a published passthrough frame carries a
FrameHold (new on DmabufFrame), and the buffer rejoins the producer's
pool only when the last clone drops. The Vulkan encoder clones the hold
into the ring slot at submit and releases it when the slot's fence
retires (poll/backpressure/reset), extending "the producer must not
rewrite this" across exactly the GPU read. The host loop's repeat path
is fixed by the same mechanism: a re-submitted frame now aliases a
buffer the producer never got back, instead of whatever gamescope last
composited into it.

Bookkeeping lives in a per-stream HoldBook (loop-thread mutations only):
holds release through a pw channel onto the loop thread, a generation
tag keeps a stale release from requeueing a renegotiated pool's reused
address, and at most pool_depth - HOLD_POOL_RESERVE buffers are ever
withheld - a pool at the old floor of 2 cannot spare any and falls back
(with one warn) to the previous racy contract. PUNKTFUNK_ZEROCOPY_HOLD=0
restores the old behavior outright for field bisects.

Gates (.25): cargo check + clippy --all-targets -D warnings on
pf-frame/pf-capture/pf-encode/punktfunk-host; pf-capture 68/68 (4 new
HoldBook tests), pf-encode 75/75 (+15 ignored, host-feature set);
workspace cargo fmt --check clean. punktfunk-host's
hooks::prep_runs_do_in_order_and_undo_in_reverse fails on that box on
pristine main too (pre-existing; crate untouched here).
2026-08-13 09:00:23 +02:00
enricobuehler 124cb66324 Merge pull request 'Wake-on-LAN: support WoWLAN so Wi-Fi hosts wake like wired ones' (#187) from worktree-wowlan-support into main
apple / swift (push) Successful in 1m40s
ci / rust-arm64 (push) Successful in 1m41s
ci / bun-nix (push) Successful in 2m2s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m45s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 1m1s
deb / build-publish-client-arm64 (push) Successful in 3m24s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 45s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 31s
ci / rust (push) Successful in 5m55s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 22s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 23s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 3m59s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m17s
apple / screenshots (push) Successful in 6m3s
arch / build-publish (push) Successful in 8m55s
ci / web (push) Successful in 1m3s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 2m42s
ci / docs-site (push) Successful in 1m12s
docker / builders-arm64cross (push) Successful in 20s
docker / deploy-docs (push) Successful in 48s
deb / build-publish (push) Failing after 6m34s
deb / build-publish-host (push) Successful in 14m19s
android / android (push) Successful in 10m58s
release / apple (push) Successful in 10m0s
flatpak / build-publish (push) Successful in 12m38s
windows-host / package (push) Successful in 16m30s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 46s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m19s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m2s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m6s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m20s
2026-08-12 22:51:57 +00:00
enricobuehler 6774c4e7a2 feat(wol): support WoWLAN so Wi-Fi hosts wake like wired ones
ci / bun-nix (pull_request) Successful in 1m33s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m25s
apple / swift (pull_request) Successful in 2m5s
ci / web (pull_request) Successful in 2m14s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 2m57s
ci / docs-site (pull_request) Successful in 3m4s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m41s
android / android (pull_request) Successful in 7m3s
ci / rust (pull_request) Successful in 10m23s
The host's arming check asked `ethtool` about every NIC, which is the wrong
question for Wi-Fi: the magic-packet trigger lives in nl80211's WoWLAN state,
and most wireless drivers print `Wake-on: d` whether or not it is armed. An
armed Wi-Fi host was therefore told it was NOT armed, and handed an
`ethtool -s wlan0 wol g` its driver rejects. A NIC with an nl80211 phy
(`/sys/class/net/<i>/phy80211`) is now asked `iw phy <phy> wowlan show`
instead, and the warning carries WoWLAN-correct guidance — `iw ... wowlan
enable magic-packet`, plus the NetworkManager
`802-11-wireless.wake-on-wlan magic` that survives a reconnect. Two fallbacks
for when `iw` can't answer (missing binary, driver without the command, or
privilege the user-level host service lacks): a POSITIVE ethtool reading
counts (brcmfmac & co do report there), a negative one never does, and sysfs
`device/power/wakeup` reading `disabled` is conclusive in the negative.

The client sender now emits from a socket bound to EACH non-loopback
interface's own address rather than leaving the path to the routing table. A
station in WoWLAN sleep stays associated and its AP buffers broadcast frames
for it until the next DTIM beacon — but only if the datagram reaches the
wireless segment at all, and with a VPN or mesh interface holding the default
route `255.255.255.255` never did. A failed bind falls back to the routed
socket, so no segment is lost.

Tests: `iw`/`ethtool` output parsing split from the commands so both are unit-
tested on any platform, and a new end-to-end test asserts a real listener
receives the 102 magic-packet bytes.

Verified on Linux (Ubuntu 26.04, 12 interfaces): `cargo fmt --all --check`,
`cargo clippy -p punktfunk-core -p punktfunk-host --all-targets --locked
-- -D warnings`, and both wol test sets green. NOT yet exercised against real
Wi-Fi hardware — no Wi-Fi Linux box was reachable.
2026-08-13 00:49:21 +02:00
enricobuehler f06b84be63 Merge pull request 'plugin-kit 0.4.1 — publish the icon field, without which no plugin can name its mark' (#186) from worktree-launcher-icon-kit-release into main
ci / web (push) Successful in 1m9s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 10s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 7s
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 8s
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 10s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m16s
ci / bun-nix (push) Successful in 3m7s
ci / docs-site (push) Successful in 4m32s
ci / rust-arm64 (push) Successful in 5m27s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m24s
docker / builders-arm64cross (push) Successful in 16s
docker / deploy-docs (push) Successful in 33s
plugin-kit-publish / publish (push) Successful in 1m2s
ci / rust (push) Successful in 9m41s
nix / flake (push) Failing after 12m47s
plugin-kit-v0.4.1
2026-08-12 22:30:11 +00:00
enricobuehler d6dbb391d6 chore(plugin-kit): 0.4.1 — publish the icon field, without which no plugin can name its mark
ci / bun-nix (pull_request) Successful in 40s
ci / docs-site (pull_request) Successful in 1m18s
ci / web (pull_request) Successful in 1m31s
ci / rust-arm64 (pull_request) Successful in 2m25s
ci / rust (pull_request) Successful in 11m40s
nix / flake (pull_request) Successful in 14m54s
`ProviderEntry.icon` landed in f62a48d4 along with the token's whole
supporting cast: the host-side shape guard, the seven masters, six client
renderers, the SDK and the OpenAPI. What it did not get was a version
bump, and the kit had cut 0.4.0 the day before.

So the registry's 0.4.0 is the tarball WITHOUT the field, and it is the
newest thing any plugin can resolve. A plugin that emits `icon` on a
launcher entry therefore fails `tsc --noEmit` — "Object literal may only
specify known properties, and 'icon' does not exist" — which is a CI gate
in every plugin repo. That is why the three plugins that were supposed to
carry the token never shipped it: the edits could not be committed
against a kit that had no field to fill.

Nothing but the version moves here. The only plugin-kit change since
0.4.0 was published is f62a48d4 itself, so 0.4.1 is exactly that commit's
kit surface — one optional string on an existing struct, additive, and
inert for a plugin that never sets it.
2026-08-13 00:29:26 +02:00
enricobuehler 907080f92b Merge pull request 'The Windows host could tank a local game's 1% lows — mint retries broadcast PnP device changes at the whole box, and session tuning never reverted' (#185) from worktree-audio-stutter-fixes into main
apple / swift (push) Successful in 1m43s
ci / rust-arm64 (push) Successful in 2m10s
ci / web (push) Successful in 2m48s
ci / docs-site (push) Successful in 1m18s
ci / bun-nix (push) Successful in 2m6s
ci / rust (push) Successful in 6m12s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 12s
android / android (push) Successful in 6m42s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 11s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 12s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 18s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 15s
apple / screenshots (push) Successful in 5m58s
deb / build-publish-host (push) Successful in 6m34s
docker / deploy-docs (push) Successful in 29s
deb / build-publish-client-arm64 (push) Failing after 5m41s
docker / builders-arm64cross (push) Successful in 1m54s
windows-host / package (push) Successful in 13m17s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 22s
arch / build-publish (push) Successful in 11m8s
deb / build-publish (push) Successful in 12m23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m15s
Reviewed-on: #185
2026-08-12 22:03:47 +00:00
enricobuehler 9425c6d40a docs(changelog): audio no longer costs local-game frame time on Windows hosts
ci / bun-nix (pull_request) Successful in 24s
ci / rust (pull_request) Failing after 57s
apple / swift (pull_request) Successful in 1m54s
apple / screenshots (pull_request) Skipped
ci / rust-arm64 (pull_request) Successful in 5m1s
ci / docs-site (pull_request) Successful in 4m46s
android / android (pull_request) Successful in 11m12s
ci / web (pull_request) Failing after 13m50s
2026-08-12 22:38:35 +02:00
enricobuehler ab8c7ec37c fix(audio/windows): stop the mint retry path from broadcasting PnP device changes at the whole box, and revert session tuning when streaming ends
Field report (2026-08-12): Punktfunk's audio devices tank Helldivers 2 to
1% lows of 2-5 FPS; uninstalling restores performance. Two host-side
mechanisms can plausibly do that, both fixed here.

The mint retry storm: minted::ensure_blocking() ran a FULL provisioning
pass on every mic-pump open with no cooldown, no in-flight guard, and no
give-up - and ensure_role() reached UpdateDriverForPlugAndPlayDevicesW
even when the devnode already existed. On a box where minting never
latches, the pump's reopen backoff (capped 60 s) turned that into a PnP
driver rebind + system-wide device-change broadcast roughly once a
minute, forever - and games rebuild their audio graph on each broadcast.
Now:

* ensure_role() gets a steady-state fast path: a marker devnode whose
  endpoints are all live resolves without touching PnP or the
  default-device policy.
* ensure_blocking() waits on an in-flight pass instead of racing a
  second SetupAPI sweep against it (the dead-mic-air deploy race),
  honours RETRY_COOLDOWN after a failed pass (first-ever resolve still
  blocks, per the cold-boot mint contract), and
* five unlatched passes stop minting for the host lifetime (a service
  restart re-arms) - counted across the worker and the blocking path.

The never-reverted session tuning: pf-frame's tune_process_once() put
the whole host at HIGH_PRIORITY_CLASS with timeBeginPeriod(1) and DWM
MMCSS on the first hot stream thread and documented 'reverts at process
exit' - but the host is a 24/7 service, so after one stream it competed
at HIGH class with a 1 ms global timer against whatever the user played
locally, forever. The process-wide tuning is now refcounted across the
hot threads via a TLS guard: the first hot thread applies it, the last
one's exit reverts it (timeEndPeriod, DwmEnableMMCSS(0), NORMAL class) -
the same thread-exit lifetime the MMCSS and execution-state effects
already ride. Every on_hot_thread() call site is a session-scoped
thread (capture/encode, packetizer, send, NVENC retrieve), so the
revert lands at session teardown.
2026-08-12 22:29:54 +02:00
enricobuehler 64e2af17c5 Merge pull request 'fix(decky): duplicate shortcut minted every boot + toast noise cut' (#184) from worktree-decky-shortcut-dup into main
decky / build-publish (push) Successful in 1m10s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 23s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 22s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 14s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 1m34s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 20s
docker / builders-arm64cross (push) Successful in 7s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 1m36s
ci / rust-arm64 (push) Successful in 1m32s
ci / web (push) Successful in 3m45s
ci / docs-site (push) Successful in 4m1s
ci / bun-nix (push) Successful in 29s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m8s
docker / deploy-docs (push) Successful in 32s
ci / rust (push) Canceled after 4m8s
2026-08-12 20:25:57 +00:00
enricobuehler 72189b29ec Merge branch 'main' into worktree-decky-shortcut-dup
ci / rust (pull_request) Successful in 6m55s
ci / web (pull_request) Successful in 1m10s
ci / docs-site (pull_request) Successful in 1m21s
ci / bun-nix (pull_request) Successful in 34s
ci / rust-arm64 (pull_request) Successful in 1m30s
2026-08-12 22:24:34 +02:00
enricobuehler 339a1d70f9 fix(decky): stop toasting on every launch and every failed panel refresh
Field complaint: the plugin toasts too much. Inventory of all 14 toast
sites says almost all are rare, explicit-tap feedback (pairing, update
buttons, recovery actions) — but two were routine-volume offenders:

  * startStream toasted "Starting stream — <host>" on EVERY successful
    launch, i.e. the overwhelming majority of all toasts the plugin ever
    shows. It repeats the button the user just pressed, and lands ON TOP
    of the starting stream after the QAM closes. Gone; launch FAILURES
    still toast (the QAM may already be closed, so inline state would go
    unseen).
  * useHosts.refresh() toasted "Couldn't list hosts" from its catch —
    and the panel remounts (and refreshes) on every QAM open, so a broken
    backend nagged on each open. It's now a third inline `problem` row
    ("Couldn't scan for hosts"), sitting next to the Refresh button that
    retries it, like the client-unavailable/client-outdated states
    already did.

The update-flow, pairing, trust and recovery toasts stay: each is a rare,
single, information-carrying response to an explicit tap (or, for the
request-access hint, the only warning that the connect is about to park).

Verified: tsc --noEmit and the rollup bundle pass.
2026-08-12 22:05:16 +02:00
enricobuehler 79dba7f95a fix(decky): a boot race minted a new library shortcut on every plugin load
Field report: each Steam start added another visible "Punktfunk" entry
(spotted in the desktop client, where the pile is plain to see).

Mechanism: db063792 made shortcutStillExists() actually answer for the
first time — and its callers treat a null overview as "the user deleted
the shortcut" and AddShortcut a replacement. But the plugin mounts while
Steam is still starting up, BEFORE appStore has registered its overviews,
so the remembered (perfectly live) appId looks up as null on every boot:
mint a duplicate, remember the new id, orphan yesterday's. One new entry
per load, forever.

The deleted verdict now has to be earned, and creation is a last resort:

  * shortcutStillExists() only believes "absent" once the store is
    demonstrably hydrated: wait out App.WaitForServicesInitialized (raced
    against the poll budget so a wedged signal can't hang the guard),
    poll until allApps is non-empty, then one grace recheck — overview
    registration can trail the bulk hydration. Unverifiable within budget
    answers true: a false "alive" merely no-ops until the next ask, a
    false "dead" duplicates forever.
  * On a genuinely lost id, both ensure paths first ADOPT an existing
    same-named shortcut (excluding the other role's) instead of minting
    an N+1th — which also heals installs the old builds already littered.
  * Both ensures are single-flight: mount's fire-and-forget can now be
    mid-wait when a QAM press arrives, and two ensures racing past the
    liveness check would each AddShortcut.
  * "Recreate library shortcut" additionally sweeps surplus "Punktfunk"
    shortcuts (RemoveShortcut) and toasts the count — cleanup for piles
    already minted. Deliberately button-only, never mount: automatic
    library deletion at boot is a bigger hazard than the mess.

Verified: tsc --noEmit and the rollup bundle both pass; the launch paths
(launchStream / launchGamepadUi) hit the fast path unchanged — a live
overview answers the first query and nothing waits.
2026-08-12 21:54:06 +02:00
enricobuehler d7430fe2bd Merge pull request 'fix(ci): gate C counts comments, and a comment named the env mutators verbatim' (#183) from fix/gate-c-comment-token into main
ci / rust-arm64 (push) Successful in 2m24s
ci / web (push) Successful in 1m46s
ci / bun-nix (push) Successful in 46s
ci / docs-site (push) Successful in 1m50s
deb / build-publish (push) Successful in 4m25s
deb / build-publish-client-arm64 (push) Successful in 6m32s
flatpak / build-publish (push) Successful in 6m59s
deb / build-publish-host (push) Successful in 12m2s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m42s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m0s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m23s
arch / build-publish (push) Successful in 13m38s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 2m34s
ci / rust (push) Successful in 13m31s
2026-08-12 16:57:38 +00:00
enricobuehler 6f81ec24ba fix(ci): gate C counts comments, and a comment named the env mutators verbatim
ci / rust-arm64 (pull_request) Successful in 1m50s
ci / docs-site (pull_request) Successful in 1m33s
ci / web (pull_request) Successful in 2m30s
ci / rust (pull_request) Successful in 14m12s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m21s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m32s
ci / bun-nix (pull_request) Failing after 13m41s
55a3d8b9 (#181) added the edition-2024 lint-level rationale to the session
bin's header naming std::env::set_var/remove_var — gate C's grep counts
comments by contract, so main went red at 5 mentions against the 4-call-site
baseline. Reword the comment instead of raising the baseline: a baseline of 5
with one comment inside would hide the next real call site.

Verified: scripts/ci/check-unsafe-hygiene.sh clean, cargo fmt clean.
2026-08-12 18:57:07 +02:00
enricobuehler 539236de91 Merge pull request 'feat(pad-audio): Linux hosts stream pad audio — the per-pad PipeWire sink (WP3)' (#182) from worktree-linux-pad-audio into main
arch / build-publish (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / web (push) Canceled after 41s
ci / rust-arm64 (push) Canceled after 0s
ci / docs-site (push) Canceled after 14s
ci / bun-nix (push) Canceled after 0s
deb / build-publish (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 17s
deb / build-publish-host (push) Canceled after 16s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 17s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 15s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 20s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 12s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 13s
apple / swift (push) Successful in 1m44s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m41s
android / android (push) Successful in 6m37s
apple / screenshots (push) Successful in 5m49s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 3m48s
docker / builders-arm64cross (push) Successful in 26s
windows-host / package (push) Successful in 13m15s
windows-host / winget-source (push) Skipped
docker / deploy-docs (push) Successful in 46s
windows-host / canary-manifest (push) Successful in 32s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 28m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 29m10s
2026-08-12 16:55:06 +00:00
enricobuehler 118758ff0b fix(pad-audio): the Linux pad sink speaks GE-Proton's AUX0-3 channel shape
ci / bun-nix (pull_request) Successful in 1m31s
ci / rust-arm64 (pull_request) Successful in 1m47s
ci / docs-site (pull_request) Successful in 1m54s
apple / swift (pull_request) Successful in 1m59s
ci / web (pull_request) Successful in 2m3s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 3m18s
android / android (pull_request) Successful in 6m59s
A field report (GE-Proton 11-5, real DualSense on-host) surfaced the missing
constraint: haptics only work when the pad's card runs the Pro Audio profile —
because GE's route opens the node through its bundled pipewire-alsa plugin
with aux_channels=1, and its pulse fallback forces a PA AUX0..3 map with
stream.dont-remix (proton-ds5-haptic patches 0013/0115/0116: "the hidden
PipeWire parent for a DualSense output exposes AUX0 through AUX3"). A
positioned FL FR RL RR sink puts those writers through position channelmix
instead of index passthrough.

The sink now advertises AUX0..AUX3. Proven on the box: an AUX-mapped
rear-pair-only tone captures index-exact (speaker pair 0.0000, coil pair
0.3662); a positioned stray stream folds into the speaker pair and never
excites the coils. The devtest reports per-pair peaks so exactly this class
of remix bug is visible.

Also confirmed from the GE patch set while here: device matching is
device.bus/vendor.id/product.id + the Sony/Wireless_Controller name
substrings (both of which the sink carries), and the MMDevice container is
now synthesized from the wine-side HID USB parent (patch 0112) — the old
pure-PW-node GUID_NULL concern no longer applies on GE >= 11-4.
2026-08-12 18:53:35 +02:00
enricobuehler dcde856178 feat(pad-audio): Linux hosts stream pad audio — the per-pad PipeWire sink (WP3)
The 0xD1 plane was Windows-host-only: host_cap() answered false and spawn()
was a stub everywhere else, so an Android tier-A client against a Linux host
negotiated the cap off and stayed on wire rumble. The whole downstream
machinery (framer, silence gate, lanes, 0xD1 send) was already capture-
agnostic — only the capturer was WASAPI.

- audio/linux/pad_sink.rs: one Audio/Sink stream node per DualSense-family
  pad, minted with the identity the matchers read (ALSA-style node.name with
  the pad's pairing MAC, description "Wireless Controller", bus/vendor/
  product/form-factor proplist, per-pad serial), 4-ch F32 48 kHz FL FR RL RR,
  no default-sink claim, priority.session 50. The process() callback IS the
  capture. PUNKTFUNK_PAD_SINK_NAME/_DESC override the strings for field
  debugging ({pad}/{mac} expand).
- native/pad_audio.rs: the shared logic and lanes compile on Linux;
  pad_audio_thread is generic over the capturer (open-with-backoff kept);
  host_cap() Linux arm = client asked + PUNKTFUNK_PAD_AUDIO + a reachable
  PipeWire socket; spawn() Linux arm mints the sink lazily in the streamer
  thread. spawn() gains an edge flag (Edge identity; ignored on Windows).
- devtest pad-sink-test: mint one sink and capture from it, no client — the
  WP3 on-glass gate. Verified on a Bazzite 44 host: identity served through
  pipewire-pulse, rear-pair (voice-coil) tone captured bit-exact over both
  the native and pulse legs.
- docs: PUNKTFUNK_PAD_AUDIO{,_SLOTS} are no longer (Windows); the roadmap
  non-goal narrows to Bluetooth client pads.

Gates (fedora:44 container, natively on the .41 box): cargo build --release
--locked (nvenc+vulkan-encode), clippy --all-targets -D warnings, cargo test
pad_audio+pad_sink 11/11, cargo fmt.
2026-08-12 18:53:35 +02:00
enricobuehler 77918674c3 Merge pull request 'An over-declared HEVC level no longer demotes native Vulkan decode, and the Windows client legs build again' (#181) from worktree-vk-level-gate-clamp into main
android / android (push) Failing after 1m20s
apple / swift (push) Successful in 1m37s
ci / rust-arm64 (push) Successful in 1m45s
ci / docs-site (push) Successful in 1m29s
ci / rust (push) Failing after 3m2s
ci / bun-nix (push) Successful in 2m3s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m4s
ci / web (push) Successful in 3m46s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 16s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 18s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
apple / screenshots (push) Canceled after 3m29s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m53s
arch / build-publish (push) Canceled after 5m25s
deb / build-publish (push) Canceled after 3m2s
deb / build-publish-host (push) Canceled after 2m45s
deb / build-publish-client-arm64 (push) Canceled after 2m39s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 33s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 30s
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 28s
flatpak / build-publish (push) Canceled after 2m37s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 3m11s
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 2s
windows / build (x86_64-pc-windows-msvc) (push) Canceled after 0s
2026-08-12 16:48:56 +00:00
enricobuehler faefbae830 fix(pf-presenter): spell MAKEINTRESOURCE(1) as ptr::without_provenance — clippy 1.96's manual_dangling_ptr reads the integer-ordinal cast as a dangling pointer and fails the Windows -D warnings gate (masked on main by the client bins failing to build first)
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 1m16s
ci / bun-nix (pull_request) Successful in 26s
ci / web (pull_request) Failing after 1m35s
apple / swift (pull_request) Successful in 1m42s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 1m18s
ci / rust (pull_request) Failing after 2m21s
ci / rust-arm64 (pull_request) Successful in 3m45s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 2m42s
android / android (pull_request) Successful in 4m29s
2026-08-12 18:47:45 +02:00
enricobuehler 44fa12a298 test(pf-vkdecode): bind the PTL level reads so the SAFETY comments precede their unsafe blocks (clippy::undocumented_unsafe_blocks counts nothing inside macro arguments) 2026-08-12 18:47:45 +02:00
enricobuehler 55a3d8b919 fix(clients/session): the edition-2024 session bin cannot build on Windows
The half of the #177 fallout #180's follow-up could not reach: WP20 wrapped
the session bin's single-threaded-startup env writes in the `unsafe {}`
blocks edition 2024 requires — under `#![forbid(unsafe_code)]`, which no
inner attribute can override, so `punktfunk-client-session` fails with two
hard errors on every Windows leg (main push runs 17615/17616 red at Build;
verified on .173). Same resolution as #180 gave the GTK shell: `forbid`
becomes `deny`, and the three documented SAFETY sites carry the localized
`#[allow(unsafe_code)]` pf-update models.
2026-08-12 18:47:45 +02:00
enricobuehler a02014ec19 fix(pf-vkdecode): treat an over-declared stream level as a clamp, not a refusal
A 2026-08-12 field report (RTX 5060 client): every HEVC session demoted to
D3D11VA with 81 "outside device caps: stream level (Std code point 12) above
the device's maxLevelIdc (H.265 Std level 11)" refusals — the host's AMF
encoder stamps general_level_idc 6.2 (the codec maximum) on a 4K120 stream
that needs 5.2, and NVIDIA's driver caps H.265 decode at 6.1. The hardware
decodes the actual stream trivially; only the declaration was oversized.
AV1 passed the same gate, which is why "native-vulkan runs only with AV1".

The declared level is a claim, and the stream's real demands are enforced
where they are physical facts — coded extent and DPB depth, both checked at
session build. So the up-front level gate (H.264 + H.265) now warns once and
proceeds, and every SPS/VPS handed to the Vulkan parameters object has its
level clamped to the device ceiling (a set above maxLevelIdc is invalid
usage). AV1's gate is untouched: its code space is the bitstream's own and
no over-declaration has been seen in the field.

Verified on .173 (RTX 4090, driver 610.88): HEVC and AV1 both decode on the
native Vulkan rung at 60 fps against an NVENC host; unit tests pin the clamp
(lowers, only lowers, mutates the driver-visible block in place).
2026-08-12 18:46:57 +02:00
enricobuehler 5f55b820bc Merge pull request 'Edition-2024 follow-up: the three gates only the PR's own CI could reach' (#180) from worktree-edition-2024 into main
ci / rust (push) Failing after 3m38s
apple / swift (push) Successful in 1m49s
android / android (push) Failing after 5m32s
ci / docs-site (push) Successful in 1m12s
ci / rust-arm64 (push) Failing after 3m36s
ci / bun-nix (push) Successful in 1m24s
ci / web (push) Successful in 2m24s
arch / build-publish (push) Failing after 5m50s
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
deb / build-publish (push) Failing after 1m29s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 10s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 1m1s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 17s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 1m14s
docker / builders-arm64cross (push) Successful in 10s
deb / build-publish-client-arm64 (push) Failing after 3m43s
apple / screenshots (push) Successful in 5m38s
deb / build-publish-host (push) Successful in 6m17s
docker / deploy-docs (push) Failing after 4m38s
windows-host / package (push) Successful in 12m27s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 17s
flatpak / build-publish (push) Canceled after 6m57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 6m50s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 6m50s
2026-08-12 16:34:13 +00:00
enricobuehler 57703fe31c Merge remote-tracking branch 'origin/main' into worktree-edition-2024
ci / rust-arm64 (pull_request) Failing after 58s
ci / web (pull_request) Successful in 1m0s
ci / bun-nix (pull_request) Successful in 2m9s
apple / swift (pull_request) Successful in 1m38s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 4m10s
ci / rust (pull_request) Failing after 5m14s
android / android (pull_request) Successful in 9m15s
2026-08-12 18:30:50 +02:00
enricobuehler 90a3304c2b Merge pull request 'The Windows host no longer blocks system sleep while idle' (#179) from worktree-win-sleep-blockers into main
deb / build-publish (push) Failing after 1m16s
ci / bun-nix (push) Successful in 27s
ci / web (push) Successful in 1m5s
ci / docs-site (push) Successful in 1m11s
apple / swift (push) Successful in 1m37s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 12s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 9s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
deb / build-publish-client-arm64 (push) Failing after 1m24s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 7s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
android / android (push) Canceled after 3m59s
apple / screenshots (push) Canceled after 2m19s
ci / rust-arm64 (push) Failing after 3m56s
ci / rust (push) Failing after 3m56s
arch / build-publish (push) Canceled after 4m1s
deb / build-publish-host (push) Canceled after 2m54s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 1m11s
docker / deploy-docs (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 1m7s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 17s
windows-host / package (push) Canceled after 4m4s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
Reviewed-on: #179
2026-08-12 16:30:11 +00:00
enricobuehler 017867f211 fix(ci): the gates the PR run reaches and the boxes could not
ci / rust-arm64 (pull_request) Failing after 1m43s
ci / web (pull_request) Successful in 1m52s
ci / bun-nix (pull_request) Successful in 28s
ci / docs-site (pull_request) Successful in 3m36s
apple / swift (pull_request) Successful in 1m44s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 2m52s
android / android (pull_request) Successful in 6m23s
- adl_emul.rs adl_malloc: panic-free (a reachable expect in an extern fn is an
  abort — gate B; the Err arm is unreachable, ADL treats null as failure)
- punktfunk-host main.rs: reword the carve-out comments so gate C's textual
  count stays at its baseline (comments count)
- clients/linux: forbid(unsafe_code) -> deny with two named allows — the SDL
  device-filter clear and the spawn test's HOME scoping are unsafe calls in
  edition 2024 (caught by the aarch64 leg, the only one with glib)
2026-08-12 17:44:15 +02:00
enricobuehler ef0af3b558 docs(changelog): the Windows host no longer blocks system sleep while idle
ci / rust (pull_request) Failing after 1m1s
ci / web (pull_request) Successful in 1m7s
ci / bun-nix (pull_request) Successful in 27s
ci / rust-arm64 (pull_request) Successful in 6m17s
ci / docs-site (pull_request) Successful in 1m35s
apple / swift (pull_request) Successful in 1m42s
apple / screenshots (pull_request) Skipped
android / android (pull_request) Successful in 9m51s
2026-08-12 17:41:08 +02:00
enricobuehler 535e95c4c0 fix(punktfunk-host): idle-stop the virtual-mic stream so a Windows host can sleep
The mic pump's WASAPI backend rendered silence into the virtual mic's
render endpoint (typically the Steam Streaming Microphone) for the whole
host lifetime. A RUNNING stream makes the Windows audio stack hold a
kernel power request ("An audio stream is currently in use", visible in
powercfg /requests), so every idle Punktfunk host blocked system sleep
forever — field-reported 2026-08-12 ("doesn't go to sleep anymore since
installing punktfunk; powercfg shows the Steam Streaming Microphone").

Stop the render stream (IAudioClient::Stop — client stays initialized,
the mic endpoint keeps existing) after 10 s of silence-only output at an
unchanged queue length, and park the render thread on a condvar the
producer notifies on the empty->non-empty edge, so the next mic frame
resumes the stream within one device period — well under the jitter
buffer's prime depth, so nothing audible changes. Keying the idle window
on the queue LENGTH (not emptiness) also covers a sub-prime tail a
vanished client left behind, while any fresh burst moves the length and
resets the window instead of being dropped.

During a session the box stays awake through the session's own
DisplayWakeRequest (pf-frame), never through this silence.
PUNKTFUNK_MIC_ALWAYS_ON=1 restores the old always-running stream in case
a virtual audio driver misbehaves while its render side is paused.
2026-08-12 17:39:22 +02:00
enricobuehler b385f0a031 Merge pull request 'Rust edition 2024: the whole tree, with the env-mutation class made visible (WP20)' (#177) from worktree-edition-2024 into main
ci / rust (push) Failing after 56s
apple / swift (push) Successful in 1m36s
ci / web (push) Successful in 1m9s
ci / docs-site (push) Successful in 1m15s
deb / build-publish-client-arm64 (push) Failing after 2m11s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 15s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Failing after 2m28s
apple / screenshots (push) Successful in 5m59s
ci / rust-arm64 (push) Failing after 8m18s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 1m5s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3m43s
windows-drivers / probe-and-proto (push) Successful in 23s
deb / build-publish-host (push) Successful in 7m58s
decky / build-publish (push) Successful in 36s
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 7s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 8s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
windows-drivers / driver-build (push) Successful in 1m50s
arch / build-publish (push) Failing after 11m24s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 51s
ci / bun-nix (push) Failing after 10m37s
deb / build-publish (push) Failing after 3m38s
flatpak / build-publish (push) Failing after 8m49s
android / android (push) Successful in 16m29s
docker / builders-arm64cross (push) Successful in 9s
docker / deploy-docs (push) Successful in 35s
release / apple (push) Successful in 10m11s
nix / flake (push) Successful in 15m25s
windows-host / package (push) Successful in 13m20s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 34s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 2m16s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 56s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m13s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m15s
Reviewed-on: #177
2026-08-12 15:38:22 +00:00
enricobuehler 7528fd48a9 Merge pull request 'A stats tier picked between streams reached nothing until the app was restarted' (#178) from worktree-console-stats-tier-relatch into main
android / android (push) Canceled after 21s
apple / swift (push) Canceled after 21s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 26s
ci / rust (push) Canceled after 32s
ci / rust-arm64 (push) Canceled after 12s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bun-nix (push) Canceled after 0s
deb / build-publish (push) Canceled after 1s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 2s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 1s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 0s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 4s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows-msix / package (x64, , 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
Reviewed-on: #178
2026-08-12 15:38:03 +00:00
enricobuehler c68e0be688 Merge remote-tracking branch 'origin/main' into worktree-edition-2024
ci / bun-nix (pull_request) Successful in 24s
windows-drivers / probe-and-proto (pull_request) Successful in 34s
ci / docs-site (pull_request) Successful in 1m23s
apple / swift (pull_request) Successful in 1m45s
apple / screenshots (pull_request) Skipped
ci / web (pull_request) Successful in 3m4s
windows-drivers / driver-build (pull_request) Successful in 2m23s
ci / rust (pull_request) Failing after 4m6s
ci / rust-arm64 (pull_request) Failing after 5m5s
windows / build (x86_64-pc-windows-msvc) (pull_request) Failing after 1m54s
nix / flake (pull_request) Successful in 13m36s
windows / build (aarch64-pc-windows-msvc) (pull_request) Failing after 54s
android / android (pull_request) Successful in 15m6s
2026-08-12 17:27:27 +02:00
enricobuehler 71c1970b93 fix(client/console): a stats tier picked between streams reached nothing until a restart
ci / bun-nix (pull_request) Successful in 31s
apple / swift (pull_request) Successful in 1m43s
apple / screenshots (pull_request) Skipped
ci / rust (pull_request) Failing after 59s
android / android (pull_request) Successful in 3m59s
ci / web (pull_request) Successful in 4m18s
ci / rust-arm64 (pull_request) Successful in 5m5s
windows / build (aarch64-pc-windows-msvc) (pull_request) Successful in 4m17s
ci / docs-site (pull_request) Successful in 4m32s
windows / build (x86_64-pc-windows-msvc) (pull_request) Successful in 3m15s
Field report: "no matter what I select the stats overlay is stuck showing as
detailed" on the Deck, cured by restarting the client app.

The console (Gaming Mode, and therefore Decky) builds its window and its run loop
ONCE and streams every session through them, and the loop took its stats tier from
the settings snapshot read at process start. Its own settings screen writes the
chosen tier to the file and redraws its row, so the choice looked taken while every
stream kept the tier the process happened to start on — Detailed for anyone who had
been on Detailed. Only a restart re-read it. The desktop shells were never affected:
they spawn a session process per stream, which resolves settings for itself.

The tier now rides `SessionParams` per launch, so browse mode adopts what THIS launch
resolved and the start-of-process value only seeds the loop until the first stream.
Two things fall out of resolving per launch rather than per process: a profile bound
to a host can finally move the tier in console mode (part of the documented P4 gap),
and the adoption sits in the `Start` arm rather than `StreamState::new`, so the
codec-fallback retry can't snap the overlay back and undo an in-stream cycle.

The `--stats` rule (a floor that lifts Off to Normal and demotes nothing) was written
out three times and is now one tested helper. The rest of the console's latched
presentation tier — touch and mouse model, shortcut inhibit, match-window, render
scale — is unchanged and still needs the models rebuilt per launch.

Gate: clippy --all-targets -D warnings, plain build, and tests for pf-client-core,
pf-presenter and punktfunk-client-session, all green in pf-lxcheck2 (linux/amd64);
clippy proven non-vacuous by touching the four edited files. cargo fmt --all --check
clean.
2026-08-12 17:26:33 +02:00
enricobuehler f373dffb5e chore: migrate the main workspace and pf-vkhdr-layer to edition 2024 (WP20)
The safety half of the rust-safety programme's §8.4: `std::env::set_var`/`remove_var` are
`unsafe fn` in edition 2024, converting the class of bug the programme found the hard way
(the 972af299 environ data race lived in a file with ZERO occurrences of the word
`unsafe`) from invisible to counted and compiler-enforced.

Manifests: [workspace.package] edition 2021→2024, rust-version 1.82→1.85 (the pinned
toolchain is 1.96.0, so no toolchain bump — only the declared floor rises); the 13 crates
pinning `edition = "2021"` literally now inherit it (Trap 1: the root bump alone reaches
only `edition.workspace = true` crates and would have left pf-encode/pf-capture/pf-inject
et al. on 2021 while reading as complete); pf-driver-proto's stale rust-version 1.82 pin
now inherits; pf-vkhdr-layer (a separate workspace, inherits nothing) bumped to 2024. The
four vendored crates (fec-rs, cros-codecs, usbip-sim, the patched ndk) stay on 2021
deliberately — upstream code stays pristine. The excluded usbip-poc standalone PoC is
untouched.

Mechanical, done textually across ALL cfg branches so no platform's half is left behind
(Trap 3 — 44% of the host's unsafe is Windows-only and a one-platform `cargo fix` misses
it): 148 `#[no_mangle]` → `#[unsafe(no_mangle)]` (83 in abi.rs); 12 bare extern blocks →
`unsafe extern`; `gen` is a reserved keyword, so pf-vdisplay's generation stamps
(registry.rs, windows/manager.rs) and the WinUI shell's animation counters rename
gen → generation (internal identifiers only, no serde/wire surface); two
match-ergonomics patterns take the compiler's suggested reference form.

env mutation: every `set_var`/`remove_var` site (20 files) now sits in an `unsafe` block
whose SAFETY comment states the real serialization argument (pf-vdisplay's ENV_LOCK,
CONFIG_DIR_TEST_LOCK, ART_ROOTS_LOCK, vkdecode's gpu_lock, the `--test-threads=1`
contracts of the hardware spikes, or single-threaded startup). Two genuine hazards
surfaced en route — exactly the WP3b-class finds this migration exists to make visible —
and are fixed here:
- windows/service.rs spawned the network-profile warner thread BEFORE `load_host_env()`,
  so a child-spawning thread (child spawn snapshots the env block) was live while
  `set_var` ran in a loop; the load now precedes the spawn.
- pf-console-ui's `fake_home()` re-set HOME outside its OnceLock on EVERY call, so two
  parallel tests could race the write; the set now happens exactly once inside
  `get_or_init`.

cbindgen (Trap 2): 0.29.4 parses `#[unsafe(no_mangle)]` — verified empirically; the
header regenerates byte-identical. The ci.yml drift check could never catch "failed to
regenerate" (build.rs demotes a cbindgen failure to a warning and writes nothing, leaving
the checked-in header untouched and the diff clean), so the step now first asserts the
"punktfunk-core: wrote" line and the absence of "cbindgen failed" (sh -e safe: no `!`
pipeline, no tee-masked exit).

rustfmt: style_edition pinned to 2021 at the root — edition 2024 would otherwise flip the
style edition and reformat ~370 untouched files inside this same commit, burying the
migration diff. The drivers workspace pins its already-current 2024 style. Adopting the
2024 style tree-wide is its own future one-line-plus-reformat commit.

Census: the primary metric moves UP BY DESIGN — 2435 → 2453 operations, unsafe blocks
1534 → 1577, and env_set_var is now a counted category (45 ops). The newly counted env
sites are a truer number, not a regression; baseline snapshot saved as punktfunk-planning
design/rust-safety-census-baseline-2026-08-12-edition-2024.txt. Gate C's env ratchet is
now compiler-enforced (the hygiene-script header says so); the two shrunk file counts
(nvenc_cuda 49→2 via the test helpers, shell/tests 2→1) are lowered in the same commit
per the gate's own rule.

Drop order (the semantic change most likely to bite this codebase): the migration lint
`-W tail-expr-drop-order` reports zero findings on the macOS-visible halves of
pf-encode / pf-zerocopy / pf-capture / pf-frame; the Linux and Windows halves run the
same lint on the gate boxes. The four #[ignore]d alloc/drop-cycle tests on the hardware
boxes remain owed, as before this change.
2026-08-12 16:12:35 +02:00
enricobuehler 28b6633058 Merge pull request 'feat(display): edid_lock policy axis — pin AMD connector EDID emulation while streaming' (#176) from worktree-edid-lock-toggle into main
audit / bun-audit (sdk) (push) Successful in 25s
audit / bun-audit (plugin-kit) (push) Successful in 26s
audit / bun-audit (web) (push) Successful in 23s
audit / docs-site-audit (push) Successful in 22s
audit / pnpm-audit (push) Successful in 13s
apple / swift (push) Successful in 1m36s
audit / license-gate (push) Successful in 4m52s
ci / bun-nix (push) Successful in 23s
audit / miri (push) Successful in 5m50s
apple / screenshots (push) Successful in 5m56s
ci / web (push) Successful in 3m49s
ci / rust-arm64 (push) Successful in 4m6s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 16s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 23s
ci / docs-site (push) Successful in 3m50s
audit / c-abi-asan (push) Successful in 5m55s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 16s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 15s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 15s
deb / build-publish-client-arm64 (push) Successful in 2m56s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 2m38s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 2m54s
audit / cargo-audit (push) Successful in 36s
deb / build-publish-host (push) Successful in 8m38s
release / apple (push) Successful in 9m55s
deb / build-publish (push) Successful in 6m27s
docker / deploy-docs (push) Successful in 37s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Failing after 14m53s
docker / builders-arm64cross (push) Skipped
android / android (push) Successful in 15m34s
ci / rust (push) Failing after 14m34s
flatpak / build-publish (push) Successful in 20m11s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m1s
nix / flake (push) Failing after 20m46s
arch / build-publish (push) Successful in 21m46s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m45s
windows-host / package (push) Successful in 38m25s
windows-host / winget-source (push) Skipped
windows-host / canary-manifest (push) Successful in 29s
windows-msix / package (x64, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 5m20s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m17s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m45s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 4m20s
2026-08-12 13:12:11 +00:00
enricobuehler c58217e403 Merge pull request 'fix(client): stop the double-arm race re-freezing RFI-healed streams' (#174) from worktree-gate-double-arm-fix into main
android / android (push) Canceled after 0s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
audit / cargo-audit (push) Canceled after 0s
audit / bun-audit (plugin-kit) (push) Canceled after 0s
audit / bun-audit (sdk) (push) Canceled after 0s
audit / bun-audit (web) (push) Canceled after 0s
audit / docs-site-audit (push) Canceled after 0s
audit / pnpm-audit (push) Canceled after 0s
audit / license-gate (push) Canceled after 0s
audit / miri (push) Canceled after 0s
audit / c-abi-asan (push) Canceled after 0s
ci / rust (push) Canceled after 0s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bun-nix (push) Canceled after 0s
deb / build-publish (push) Canceled after 0s
deb / build-publish-host (push) Canceled after 0s
deb / build-publish-client-arm64 (push) Canceled after 0s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Canceled after 0s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Canceled after 0s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Canceled after 0s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / builders-arm64cross (push) Canceled after 0s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 0s
nix / flake (push) Canceled after 0s
release / apple (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 0s
windows-host / package (push) Canceled after 0s
windows-host / canary-manifest (push) Canceled after 0s
windows-host / winget-source (push) Canceled after 0s
windows-msix / package (arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows-msix / package (x64, , 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
2026-08-12 13:11:51 +00:00