Commit Graph
86 Commits
Author SHA1 Message Date
enricobuehlerandClaude Opus 5 5db93c1156 fix(packaging/windows): a release must not sign itself with a throwaway cert
Both pack scripts fell back to a fresh `New-SelfSignedCertificate` whenever
MSIX_CERT_PFX_B64 was absent, and said nothing about it beyond one Write-Host.
That fallback is right for canary and dev builds. On a tag it is not: an ephemeral
cert is regenerated per build, so nobody can pin it, and a release signed with one
is indistinguishable from a release signed by whoever else got to the artifact.
A secret that got renamed, rotated away, or simply wasn't inherited by a new
workflow would have downgraded a real release silently and shipped it.

pack-msix.ps1 and pack-host-installer.ps1 now resolve -RequireSignedCert (default
'auto' = true iff GITHUB_REF is refs/tags/v*) and throw instead of falling back;
pack-host-installer.ps1 also refuses -NoSign on the same condition. Reading
GITHUB_REF inside the scripts rather than taking a workflow flag means a future
packaging workflow inherits the guard instead of having to remember it.

No effect on today's releases: MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD are both
set as repo secrets, so the supplied-cert branch is the one tag builds take. This
only closes the trapdoor underneath it.

Verified on the windows-amd64 runner: both files parse, and the guard resolves
true for refs/tags/v0.21.0, false for refs/heads/main and for an unset ref, with
'true'/'false' overriding as intended.

Not touched: the pf-vdisplay / gamepad driver builds use the same pattern with
DRIVER_CERT_PFX_B64, which has no secret behind it — those need attestation
signing (a Partner Center action), and a guard there would just fail every tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 12:12:21 +02:00
enricobuehler 9b00ad6658 fix(client/windows): prove the deep-link module the new deny caught
ci / docs-site (push) Successful in 1m18s
ci / web (push) Successful in 2m1s
apple / swift (push) Successful in 4m46s
android / android (push) Failing after 6m31s
deb / build-publish-host (push) Failing after 5m29s
ci / rust (push) Failing after 7m33s
arch / build-publish (push) Failing after 7m35s
ci / bench (push) Failing after 7m30s
deb / build-publish (push) Failing after 6m29s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 22s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 22s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 24s
decky / build-publish (push) Successful in 31s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 19s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 22s
ci / rust-arm64 (push) Successful in 10m5s
deb / build-publish-client-arm64 (push) Successful in 7m48s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 7m5s
docker / deploy-docs (push) Successful in 27s
flatpak / build-publish (push) Failing after 8m30s
windows-host / package (push) Successful in 19m0s
windows-host / winget-source (push) Skipped
docker / build-push-arm64cross (push) Successful in 5m2s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 14m1s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 3m13s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 14m15s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Failing after 2m44s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m49s
release / apple (push) Successful in 29m9s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m58s
apple / screenshots (push) Successful in 23m16s
`deeplink.rs` arrived with c27065c2 (punktfunk:// handling) while this branch was in flight, so its
eight `unsafe` blocks predate the crate's `deny(clippy::undocumented_unsafe_blocks)` and only became
visible on the rebase. This is the ratchet doing its job on brand-new code, and it is also the whole
argument for turning the convention into a lint: the module was written correctly and documented
prosaically, but nothing had required a proof at each block.

The one worth reading is the `WM_COPYDATA` handler, which dereferences an `lparam` from ANOTHER
PROCESS and builds a `u16` slice from the sender's pointer and length. What makes that sound is not
that the sender is trusted — anyone can post `WM_COPYDATA` — but that the OS marshals both the
struct and its buffer into this process and keeps them valid for the handler's duration, and that
`len` is `cbData / 2` so the slice cannot outrun the copy even for an odd `cbData`. The proof says
that, so the next reader knows which half of it is a guarantee and which is just a tag check.

⚠ Caught only because Windows was re-verified AFTER the rebase. A Linux-only check was clean —
`deeplink.rs` does not exist there — so pushing on that evidence would have re-broken Windows CI,
which is the same mistake as the `warn`-that-was-really-`deny`, one rebase later.

Verified: Windows .47 full CI clippy set + the Windows-only crates, rc=0, pf-capture's 18 tests
pass; Linux .21 fmt + both CI clippy steps rc=0.
2026-07-29 08:54:12 +02:00
enricobuehler 8a5a5edc37 fix(small crates): the proof lint now covers every first-party crate but one
Six crates were still unguarded, and all six are OURS — none vendored: pf-console-ui, pf-gpu,
punktfunk-tray, clients/windows, tools/display-disturb, wdk-probe. Two of them ship (the tray and
the Windows client), so "small" was about item count, not exposure.

Five are closed here. Only 17 of their 75 unsafe items actually lacked a proof — pf-gpu,
punktfunk-tray and display-disturb were already fully documented and needed nothing but the deny,
which is the good case: the convention was being followed, just not enforced.

The 17 that were missing are the usual Win32/COM shapes, and two were worth stating properly.
`clients/windows`'s `GetCurrentPackageFullName` is called with `len = 0` and no buffer — that is the
documented identity PROBE, which writes nothing, and reading it as a normal query would be a
mistake. `pf-console-ui`'s two `destroy_image_view` calls are the load-bearing ones: the comment
above one already argued that in-flight sampling of that slot ended two presents ago (the ring
alternates and the presenter waits its fence before each record), which is exactly the kind of
reasoning a `// SAFETY:` should carry and it was sitting there unlabelled.

Also fixes a real Windows-only clippy error this uncovered: `pf-gpu` had a
`#[cfg(target_os = "windows")]` fn AFTER its `mod tests`, tripping `items_after_test_module`. It
never fired on Linux (the item does not exist there) and no CI job clippies pf-gpu on Windows, so it
sat unseen. Moved above the test module.

Remaining: `wdk-probe` (26 items) alone, and only because it needs the WDK to build — .47 cannot,
so nothing here can verify a deny on it.

Verified: Linux .21 fmt + both CI clippy steps rc=0; Windows .47 the four Windows-relevant crates at
`-D warnings` rc=0.
2026-07-29 08:48:42 +02:00
enricobuehlerandClaude Opus 5 bf98102714 feat(packaging): ship punktfunk everywhere, and deprecate the session's stray pairing flag
ci / web (push) Successful in 57s
ci / docs-site (push) Successful in 1m5s
android / android (push) Canceled after 3m17s
apple / swift (push) Canceled after 3m18s
apple / screenshots (push) Canceled after 0s
arch / build-publish (push) Canceled after 3m18s
ci / rust (push) Canceled after 3m19s
ci / rust-arm64 (push) Canceled after 3m20s
ci / bench (push) Canceled after 3m18s
deb / build-publish (push) Canceled after 2m23s
deb / build-publish-host (push) Canceled after 2m13s
deb / build-publish-client-arm64 (push) Canceled after 0s
decky / build-publish (push) Canceled after 0s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
flatpak / build-publish (push) Canceled after 2s
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 3m25s
windows-host / winget-source (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 0s
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 rest of C1. A CLI that only exists in a dev tree is not a door anyone can use, so it
now builds and installs in deb, rpm, arch, nix, flatpak and MSIX alongside the client and
the session. On Windows it gets its own `AppListEntry="none"` application with the alias
`punktfunk.exe` — a command, not a Start-menu tile — which is what the Playnite importer
will shell to for `punktfunk library <host> --json`.

`punktfunk-session --pair` prints a deprecation notice and forwards. Pairing is a trust
ceremony and belongs to the brain, fronted by `punktfunk pair` or a shell; a renderer
owning one is precisely the mixing of concerns this split exists to undo. It keeps working
for a release, because someone's provisioning script is calling it today.

Decky's invocations are untouched and verified still working (`--list-hosts` JSON shape
unchanged, `--reachable` exit codes unchanged): those flags are a frozen compat contract
until Decky migrates to the verbs at its own pace.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 01:16:17 +02:00
enricobuehlerandClaude Opus 5 c27065c260 feat(client/windows): punktfunk:// opens the Windows client, and cards can write a shortcut
windows / build (aarch64-pc-windows-msvc) (push) Canceled after 0s
windows / build (x86_64-pc-windows-msvc) (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 0s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (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
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
decky / build-publish (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
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 / bench (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
android / android (push) Canceled after 0s
WP4 / D2. Protocol activation of a full-trust packaged app delivers the URI as the command
line, so a browser prompt, `start punktfunk://…` and a written `.lnk` all arrive the same
way — as a positional argument, the same door the Linux shell uses.

What Windows does not give us is single-instancing: unlike GApplication, a second
activation is simply a second process. So the first instance claims the named mutex
`unom.punktfunk.client` and any later one hands its URL to the winner over `WM_COPYDATA`
and exits — one window, and the link opens where the user's hosts already are. The hand-off
retries while the primary's window is still coming up (a shortcut double-clicked during
startup is the ordinary case), and a hand-off that finds nobody falls through so this
process becomes the shell that opens it. A link is never silently dropped, which is also
why the inbox is a queue: two shortcuts in quick succession are two links.

Routing is the same four lines of translation as on Linux, because the decisions belong to
`plan_from_link`: a resolved link becomes the call a tile click makes, with the same wake,
trust and error surfaces. Never preempting a live session is checked here, since only this
layer knows one is running.

The manifest declares the protocol and an app execution alias rather than writing registry
keys, so uninstall leaves nothing behind — and Windows' own "allow this app to handle
punktfunk links?" prompt is the origin friction the design wants, not duplicated in-app.
(Manifest comments are free of double hyphens: XML forbids them and makepri rejects the
whole file, which is how the console flag broke the v0.15.0 build. A test parses the
substituted manifest and asserts both declarations, so that can't rot silently.)

"Create shortcut…" writes a `.lnk` targeting the ALIAS with the URL as an argument, not a
`.url` internet shortcut: both work while the scheme is registered, only this one still
works if it isn't, and targeting the alias keeps it valid across updates when the package
path changes.

Verified on the CI VM (192.168.1.133): clippy -D warnings clean and its tests green.
Not yet exercised on glass — `start punktfunk://…` cold and with the app running, and a
written shortcut, still want a real desktop session.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 00:49:56 +02:00
enricobuehlerandClaude Opus 5 f5de661c9e feat(client/windows): overridden rows are marked and resettable, and the speed test targets the right layer
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (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
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Canceled after 0s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Canceled after 0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
decky / build-publish (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
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 / bench (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
apple / swift (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
android / android (push) Canceled after 0s
The two remaining pieces of P1 on Windows, both closing the same kind of gap the Linux
client closed earlier.

Overridden rows in profile scope now say so — an accent-marked caption — and carry the
only way back to inheriting. Without it a profile is a one-way door here too, because
overrides are recorded on touch and never inferred from comparing values. Reset calls the
shared `SettingsOverlay::clear`, so the list of what can be cleared stays in one place
across both shells. It also bumps a revision counter: the catalog changes behind the
controls and nothing the page reads as state does, and root state compares before
re-rendering, so without the bump the row would keep showing the value it just dropped.

The speed test writes to the layer the TESTED host reads bitrate from, not the global —
measuring one host used to re-tune every other one. Resolved exactly the way a connect
resolves it: the one-off the test was started with (a pinned tile carries one), else the
host's binding, else the global; a dangling binding falls back. The button names its
target, so it says where it will write before you press it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 00:21:13 +02:00
enricobuehlerandClaude Opus 5 3d25482469 feat(client/windows): pinned host+profile tiles render in the grid
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m40s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m27s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m19s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m56s
apple / swift (push) Successful in 5m12s
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
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
decky / build-publish (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
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 / bench (push) Canceled after 0s
arch / build-publish (push) Canceled after 0s
android / android (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
The menu could pin since the last commit but nothing appeared, which is worse than not
offering it. Each host's pins now render right after it, sharing its live status because
they read the same record, with the profile named on an accent chip. A pin whose profile
was deleted simply doesn't render.

A pinned tile carries no menu of its own: it is a shortcut, not a second host, and
pin/unpin already live on the primary tile's menu — the one place you decide it. Its
hover key is `<fp>#<profile-id>` so two tiles for one host don't light up together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 00:18:12 +02:00
enricobuehlerandClaude Opus 5 2a088e13a4 feat(client/windows): bind a host to a profile, connect with one, pin one, copy its link
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m8s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m37s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m25s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 3m43s
apple / swift (push) Successful in 5m17s
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
flatpak / build-publish (push) Canceled after 0s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Canceled after 0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Canceled after 0s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Canceled after 0s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Canceled after 0s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
decky / build-publish (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
apple / screenshots (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Canceled after 0s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Canceled after 0s
android / android (push) Canceled after 2m34s
ci / rust-arm64 (push) Canceled after 0s
ci / web (push) Canceled after 0s
ci / docs-site (push) Canceled after 0s
ci / bench (push) Canceled after 0s
ci / rust (push) Canceled after 2m30s
arch / build-publish (push) Canceled after 2m39s
The WinUI half of §5.2: `Target` carries a one-off profile into the session's `--profile`,
the host editor gains the binding picker, and the tile names its bound profile on a chip so
what a plain click will do is visible without opening anything.

Shape notes, both forced by this toolkit rather than chosen:

- The flyout has no submenus, so "Connect with", "Pin as card" and "Unpin card" are flat
  items, one per profile, matched by prefix. The prefixes end in ": " so a profile named
  "Copy link" can't collide with a fixed entry.
- The binding picker commits on change rather than at Save. The rest of that sheet is text
  boxes with draft refs, which need a Save; a ComboBox doesn't, and making it wait would be
  the odd one out.

Semantics stay identical to the Linux client, because they are the design's, not the
platform's: "Connect with" never rebinds, "Default settings" is `Some("")` so it really
does override a bound host for one session, and a binding whose profile was deleted reads
as Default settings and resolves to it.

`pf_client_core::clipboard::set_text` is the small addition the crate needed for "Copy
link" — the OS clipboard plumbing was already there, just private to the streaming bridge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 00:17:15 +02:00
enricobuehlerandClaude Opus 5 b220daf537 feat(client/windows): the settings screen edits profiles too
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m9s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m30s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m19s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m53s
apple / swift (push) Successful in 5m13s
ci / docs-site (push) Successful in 1m4s
ci / web (push) Successful in 3m3s
ci / bench (push) Successful in 5m43s
deb / build-publish-host (push) Failing after 6m17s
arch / build-publish (push) Failing after 9m36s
ci / rust-arm64 (push) Failing after 9m41s
ci / rust (push) Failing after 9m44s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 32s
decky / build-publish (push) Successful in 43s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 25s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 24s
deb / build-publish (push) Successful in 8m52s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 16s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 21s
docker / build-push-arm64cross (push) Canceled after 0s
docker / deploy-docs (push) Canceled after 0s
apple / screenshots (push) Canceled after 0s
android / android (push) Canceled after 13m2s
deb / build-publish-client-arm64 (push) Canceled after 6m50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Canceled after 2m47s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Canceled after 2m42s
P1 on the WinUI shell. The same surface now edits either layer, switched by a combo above
the section title — one settings UI, not a second editor that drifts from the first.

The interesting difference from GTK is where the override gets recorded. This shell
commits PER CONTROL, so it cannot hand over a list of touched rows the way a
commit-on-close dialog can. Instead each control hands `SettingsOverlay::absorb` the
effective settings before and after it fired, and absorb records the field that moved.
The comparison is against what the control was SHOWING, not against the globals, so
setting a value to whatever the global happens to be still records an override — the pin
the design asks for — and nothing is ever removed by inference.

⚠️ The content key becomes `(scope, section)`. Keyed on section alone, switching scope
diffs one scope's controls into the other's, which re-sets each reused ComboBox's items
(clearing WinUI's selection) while skipping `selected_index` wherever the two scopes'
values compare equal — the combos then render blank until touched. That is the same trap
the section key already existed for, one axis wider.

Only profileable rows render in profile scope: auto-wake, the library toggle, the decoder
and GPU pickers and the forwarded-pad picker are facts about this device or this network,
not about "Game vs Work".

Two shapes follow from this toolkit rather than from the design. Creating a profile takes
an auto-numbered name and is renamed from the row below, because ContentDialog here is
text-only and cannot hold a text field — the same constraint that put the host editor in a
tile. And Delete arms a root state that renders the confirmation declaratively, since
dialogs here are elements with `is_open`, not calls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 00:12:43 +02:00
enricobuehler 5219107177 chore(unsafe): the workspace adopts the drivers' unsafe discipline
`packaging/windows/drivers/*` has run `deny(unsafe_op_in_unsafe_fn)` +
`deny(clippy::undocumented_unsafe_blocks)` for a while, with `forbid(unsafe_code)`
on the modules that need no unsafe at all. The main workspace had no lint config
whatsoever, so nothing stopped a clean crate from quietly growing an `unsafe`, and
nothing distinguished the handful of genuinely-unsafe lines inside a 600-line
`unsafe fn` from the safe ones surrounding them.

Three things, all mechanical:

* `#![forbid(unsafe_code)]` on the eight crates that already contain zero unsafe
  (`pf-driver-proto`, `pf-host-config`, `pf-paths`, the three clean clients, both
  tools). These were clean by accident, not by contract; now they are clean by
  contract.

* `unsafe_op_in_unsafe_fn = "warn"` workspace-wide. `unsafe fn` states a contract
  the CALLER must uphold — it was never meant to switch off checking for the whole
  body. Measured fallout is 300 sites on Linux, and they are concentrated: six
  files carry all of them, while `punktfunk-core`, `pf-frame`, `pf-clipboard` and
  `pf-vdisplay` are already at zero. `warn` (not `deny`) so the build stays green
  while those six are worked down; it flips to `deny` once they are. This is also
  the Rust 2024 default, so it pays off the edition migration early.

* `proc::current_uid()` replaces eight `unsafe { libc::getuid() }` blocks. Each
  site had copied out the same SAFETY note verbatim, which is the tell: `getuid()`
  is parameterless, always succeeds and touches no memory, so there is no contract
  for a caller to uphold and no reason for the unsafe to be visible eight times.
  One `unsafe` behind a safe wrapper, none at the call sites.

Verified: `pf-vdisplay` builds clean on Linux (Nobara) at zero E0133; the
macOS-buildable crates build clean locally. No behaviour change.
2026-07-28 21:31:41 +02:00
enricobuehlerandClaude Opus 5 37edb1cc1a refactor(clients): both shells wake through the shared state machine
audit / bun-audit (push) Successful in 21s
ci / web (push) Successful in 1m39s
ci / docs-site (push) Successful in 2m7s
audit / cargo-audit (push) Successful in 2m55s
apple / swift (push) Successful in 5m25s
ci / bench (push) Successful in 7m47s
ci / rust-arm64 (push) Successful in 10m28s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 27s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
windows-host / package (push) Failing after 11m22s
windows-host / winget-source (push) Skipped
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m17s
deb / build-publish-client-arm64 (push) Failing after 6m0s
deb / build-publish (push) Successful in 13m3s
arch / build-publish (push) Successful in 16m27s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m36s
flatpak / build-publish (push) Successful in 6m50s
android / android (push) Successful in 19m52s
deb / build-publish-host (push) Successful in 16m54s
ci / rust (push) Successful in 22m54s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m48s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m1s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 20m33s
release / apple (push) Successful in 30m41s
docker / build-push-arm64cross (push) Successful in 24s
docker / deploy-docs (push) Successful in 26s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 28m24s
apple / screenshots (push) Successful in 22m37s
The last of C0's duplication: GTK's `wake_and_connect` and the WinUI shell's ran their own
copies of the same 90 s / 6 s / 1 s loop, and the Windows one's comment said it "mirrors
the Apple HostWaker" — now it literally does, because both drive `WakeWait`. What is left
in each shell is the part that genuinely differs: its dialog or screen, its advert drain,
its re-key, and its route back into the trust gate.

Behavioural review, since a sleeping host to test against wasn't available:

- GTK polled every 500 ms and resent on a wall-clock timer; it now ticks once a second
  like every other implementation. Up to half a second slower to notice a host coming
  back, and exactly the reference cadence in exchange.
- GTK took the FIRST matching advert in a drain and returned mid-loop; it now drains fully
  and takes the last, i.e. the freshest address. Same connect, fresher re-key.
- Windows sent one packet before the loop AND one on its first pass; the machine owns the
  packets now, so that duplicate is gone. Resends still land at 6 s, 12 s, … and the
  budget still expires at 90 s.
- Both keep their own cancel path, their own re-key, and their own error surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 21:01:47 +02:00
enricobuehlerandClaude Opus 5 a2be3f4de9 feat(client): settings profiles — the override catalog, the one resolver, and the session flag
A client setting is global today: pick 4K@120 HDR and the retro box gets it too.
The one escape hatch (per-host `clipboard_sync`) proves the shape works but covers a
single field. This is P0 of design/client-settings-profiles.md — the headless core the
shells, Apple, Android and the deep-link grammar all build against. No UI yet.

A profile is a NAMED BUNDLE OF OVERRIDES, not a snapshot: `SettingsOverlay` is sparse
`Option`s, so a field the user never touched keeps following the global value live, and
fixing a global once fixes it everywhere. `Some(x)` equal to today's global is still
meaningful — it pins x against a later global change — which is why the UI will write
`Some` on touch and `None` on an explicit reset, never by diffing.

The catalog is its own `client-profiles.json`, deliberately not part of the settings
file: that file has five whole-file load-modify-save writers with no merge, so a profile
written by one would be dropped by the next. Which host uses which profile is a field on
the host record instead of a map keyed by host — that is what dissolves the client's
long-standing "what identifies a host record" question for this feature.

Resolution has exactly one implementation, `trust::effective_settings()`:

    effective = overlay(profile).apply(global)
    profile   = one-off pick ?? host binding ?? none

Both session construction sites go through it (`main.rs` AND `console.rs` — touching one
and not the other is a Windows-only build break), so the console, and therefore Decky,
honor host bindings with no work of their own. Nothing here can fail a connect: a
deleted binding resolves as "no profile", and a one-off that can't be honored falls back
to the DEFAULTS rather than to the host's own profile — "connect with Work" must never
quietly stream "Game". `--profile <id|name>` is the one-off door for the shells' coming
"Connect with ▸" and for `punktfunk://…&profile=`; `--profile ""` forces the defaults on
a bound host. The active profile's name closes the stats overlay's first line, so
"which profile am I on?" is answerable without leaving the stream.

Also here, because this effort touches every one of these anyway:

- `KnownHost` gains `profile_id`, `pinned_profiles` and a lazily minted stable `id`. The
  id is groundwork (design §4.5) — no lookup is re-keyed, `fp_hex`/`addr:port` stay.
- `upsert` now preserves the state the USER set on a record — the profile binding, its
  pins, the clipboard decision, the id — against refreshes that carry none of it.
  `clipboard_sync` survived that only because the function happened not to mention it.
- `KnownHost::default()` exists so construction sites read `..Default::default()`; five
  hand-written literals were exactly how a new field gets silently dropped on re-pair.
- All three client stores now write temp+rename. A torn settings file loads as `Default`
  — i.e. silently resets every setting the user has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 20:22:33 +02:00
enricobuehlerandClaude Fable 5 94f0207cbd feat(windows/client): logs persist — %LOCALAPPDATA%\punktfunk\logs\client.log
apple / swift (push) Successful in 4m54s
ci / web (push) Successful in 58s
decky / build-publish (push) Successful in 34s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 23s
ci / bench (push) Successful in 7m9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 18s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 30s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 22s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 40s
deb / build-publish-client-arm64 (push) Successful in 7m35s
deb / build-publish (push) Successful in 10m19s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m25s
ci / rust-arm64 (push) Successful in 13m57s
deb / build-publish-host (push) Successful in 14m30s
ci / docs-site (push) Successful in 1m27s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m41s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 14s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m32s
android / android (push) Successful in 15m23s
arch / build-publish (push) Successful in 14m1s
flatpak / build-publish (push) Failing after 8m16s
docker / deploy-docs (push) Successful in 11s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m25s
release / apple (push) Successful in 29m47s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 7m52s
ci / rust (push) Failing after 16m15s
docker / build-push-arm64cross (push) Failing after 4m25s
windows-host / package (push) Successful in 11m35s
windows-host / winget-source (push) Skipped
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 1m59s
apple / screenshots (push) Successful in 23m44s
The shell is a windows_subsystem binary and spawns punktfunk-session with
CREATE_NO_WINDOW + inherited stderr: a normal GUI/MSIX launch has no
console, so every log line — the shell's and the session's whole
receive/decode/present forensic trail — evaporated exactly when a user
hit something worth reporting. The 2026-07 PyroWave latency-sawtooth
report had to be triaged from host logs alone; the deciding evidence
(clock re-sync vs backlog-flush lines) lives client-side.

- New logfile module: %LOCALAPPDATA%\punktfunk\logs\client.log,
  the host's rotation convention (10 MB cap -> .old at next start, one
  generation), best-effort (no dir -> plain stderr, never a startup
  failure).
- The tracing subscriber tees to stderr AND the file (ANSI off — the
  file is what users send); startup logs the path.
- The spawned session's stderr is piped through the same tee,
  line-buffered — dev-terminal runs keep their interleaved output,
  GUI runs finally keep anything at all.
- The --console path and the punktfunk-console.exe MSIX shim forward
  the same way (a couch launch has no console either).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 12:51:11 +02:00
enricobuehlerandClaude Fable 5 4a01bc4463 feat(core+host+client): cursor channel — remote-desktop sweep M2a+M2b
The host cursor stops riding the video and becomes a real OS cursor on
the client (the Parsec/RDP model): pointer feel no longer pays the
capture→encode→network→decode→present round trip.

Wire (M2a):
- Hello grows a client_caps trailing byte (CLIENT_CAP_CURSOR) after the
  fixed display_hdr block — presence disambiguated by remaining length,
  which caps the post-HDR tail at 27 bytes (documented); Welcome answers
  HOST_CAP_CURSOR (capable-and-asked, the 444/clipboard precedent).
- CursorShape (0x50, control stream): serial + dims + hotspot + straight
  RGBA, ≤120px/side so the u16 frame always fits (128² would overshoot);
  client caches by serial — re-showing a known shape costs 14 bytes, not
  a bitmap (RDP pointer-cache for free).
- CursorState (0xD0 datagram): serial + visible/relative_hint flags +
  position, sent once per encode-loop tick — latest-wins, self-healing
  under loss, no refresh timer. relative_hint is reserved for M3.
- Client core: two new planes (control-task + datagram-task arms) →
  next_cursor_shape/next_cursor_state; connect() grows client_caps
  (C ABI passes 0 until the v11 cursor poll fns exist).

Host (M2b, Linux portal only):
- handshake::cursor_forward is THE predicate (client asked ∧ Linux ∧
  compositor ≠ gamescope) — Welcome bit and session wiring both read it.
- SessionPlan.cursor_blend goes false for a forwarding session; the
  encode loop ticks a CursorForwarder every iteration: shape-serial diff
  → control-task bridge (mirrors probe_result), state datagram → conn.
- CursorOverlay/capture CursorState carry the hotspot through
  (nearest-neighbor downscale backstop for XL cursors, unit-tested).

Presenter:
- CursorChannel drains both planes per loop iteration; shapes become
  SDL color cursors (from_surface + hotspot), applied while the desktop
  mouse model is engaged; visibility follows the host; capture/released
  hands back the system cursor. Sessions advertise the cap when they
  START in desktop mode.

Verified on .21: fmt + clippy -D warnings (7 crates) + tests green
(core 218 incl. new wire roundtrips, host 245 incl. e2e + forwarder
downscale tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 23:43:23 +02:00
enricobuehlerandClaude Fable 5 df04880273 feat(client/input): desktop (absolute) mouse mode — remote-desktop sweep M1
New physical-mouse model beside capture: Desktop leaves the pointer
uncaptured and sends absolute positions through the letterbox
(MouseMoveAbs — every host injector already consumes it). Capture
stays the default and the game model.

- pf-client-core: MouseMode (capture|desktop) persisted in Settings,
  default capture so existing stores are unchanged.
- pf-presenter: Capture grows a desktop model — latest-wins pending
  abs position coalesced per loop iteration (same 1000 Hz discipline
  as relative), flushed before clicks/keys/wheel so they land where
  the cursor is; Ctrl+Alt+Shift+M flips the model live; local cursor
  stays hidden over the window (the host's composited cursor is the
  one you see until the M2 cursor channel); Windows keyboard grab
  only engages for capture (a desktop stream is something you
  Alt-Tab away from).
- gamescope gating: its EIS is relative-only, so desktop mode is
  pinned off there (resolved_compositor), with a log note.
- Settings surfaces: GTK row (dynamic caption), WinUI combo,
  console-UI row + step test, capture-hint line.

Verified: fmt + clippy -D warnings + tests (33/40/19) on Linux .21;
clippy -D warnings for all five crates incl. punktfunk-client-windows
native on .173.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 23:37:32 +02:00
enricobuehlerandClaude Opus 4.8 134874b3c8 fix(client/windows): repair the v0.15.0 MSIX build — ARM64 feature leak + illegal XML comment
ci / docs-site (push) Successful in 52s
ci / web (push) Successful in 54s
decky / build-publish (push) Successful in 24s
apple / swift (push) Successful in 1m20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 15s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 6m21s
ci / bench (push) Successful in 6m57s
flatpak / build-publish (push) Successful in 6m21s
deb / build-publish (push) Successful in 9m54s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 6m58s
android / android (push) Successful in 13m35s
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
deb / build-publish-host (push) Has been cancelled
ci / rust (push) Failing after 20m38s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 7m56s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m9s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m26s
docker / deploy-docs (push) Successful in 25s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 9m18s
Both MSIX matrix jobs failed at the v0.15.0 tag, for two unrelated reasons, both
introduced by this release. 0.13.0 and 0.14.0 shipped both packages; 0.15.0 shipped
neither.

ARM64 failed to BUILD. The PyroWave Windows un-gating (1ef0229b) moved `pyrowave-sys`
in pf-client-core out of `cfg(target_os = "linux")` into a general optional dependency
that pf-client-core's own `default = ["pyrowave"]` turns on. The ARM64 leg builds
`--no-default-features` precisely to skip it, but that only disables defaults for the
two packages named with `-p` — every internal edge kept inheriting them:
clients/windows -> pf-client-core, clients/session -> pf-client-core, clients/session
-> pf-presenter, and pf-presenter's own `default = ["pyrowave"]` -> pf-client-core.
So the vendored Granite C++ compiled on ARM64 and stopped where it always does:
muglm.cpp reaching for _MM_TRANSPOSE4_PS/_mm_storeu_ps, then
`simd.hpp:103 #error "Implement me."`.

Those three internal edges now pass `default-features = false`; the feature is enabled
only through an explicit chain (clients/session's `pyrowave` -> pf-client-core/pyrowave
+ pf-presenter/pyrowave, and pf-presenter's `pyrowave` -> pf-client-core/pyrowave), so
`--no-default-features` finally means what it says. Verified by feature resolution
rather than assertion — `cargo tree -i pyrowave-sys`:

  aarch64-pc-windows-msvc, --no-default-features -> not in the graph (was: present)
  x86_64-pc-windows-msvc,  default features      -> present
  x86_64-unknown-linux-gnu, default features     -> present

PyroWave is NOT dropped from the Windows client. Decode has always run in the spawned
punktfunk-session binary, whose own default keeps the feature on, and unification turns
it back on for the shared pf-client-core wherever that binary is in the build (x64,
Linux). The shell only ever offered "pyrowave" as a codec preference string — it holds
no decoder and never called one, so linking the C++ into it was dead weight even on x64.

x64 built fine and failed at PACKAGING: `makepri new` rejected the manifest with
PRI191 "Appx manifest not found or is invalid" / malformed comment syntax. The console
tile added in 2508b720 documented its flag inside an XML comment, and XML forbids `--`
anywhere in a comment body, so the literal flag spelling made the whole manifest
unparseable. Reworded, with a note to keep the next person from reintroducing it.
Confirmed by parsing both revisions after template substitution: the tagged manifest
fails at line 63 col 9, this one parses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 23:20:29 +02:00
enricobuehlerandClaude Fable 5 8fe90a8a4b fix(client): clipboard poll cadence was never applied (and CI clippy)
ci / web (push) Successful in 1m8s
apple / swift (push) Successful in 1m17s
ci / docs-site (push) Successful in 1m9s
apple / screenshots (push) Successful in 6m17s
ci / bench (push) Successful in 6m51s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
deb / build-publish (push) Successful in 9m11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
android / android (push) Successful in 13m32s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 8m1s
arch / build-publish (push) Successful in 17m21s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 5m6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7m43s
deb / build-publish-host (push) Successful in 11m42s
flatpak / build-publish (push) Failing after 8m23s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Failing after 6m14s
ci / rust (push) Successful in 25m38s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 5m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m54s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m32s
docker / deploy-docs (push) Successful in 27s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 9m8s
POLL was dead: the local clipboard was re-read once per inbound event wait
(<=120 ms) instead of on its own 400 ms cadence, so the constant documenting
the interval described something the code did not do. Give it a deadline of
its own -- the event wait is short because it bounds teardown latency, which
is no reason to hammer the Win32 clipboard eight times a second while the
user is copying in another app.

Build State in one expression while here, and note for next time: CI runs
clippy as --workspace --all-targets -- -D warnings, so a scoped run without
-D warnings (what I did) does not reproduce it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:22:24 +02:00
enricobuehlerandClaude Fable 5 cac23b7a05 feat(client/windows): console UI in the header, one Edit dialog per host
ci / web (push) Successful in 48s
ci / docs-site (push) Successful in 57s
apple / swift (push) Successful in 1m20s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Failing after 4m57s
ci / bench (push) Successful in 6m31s
ci / rust (push) Failing after 6m36s
apple / screenshots (push) Successful in 6m17s
deb / build-publish (push) Successful in 10m38s
flatpak / build-publish (push) Failing after 8m17s
docker / deploy-docs (push) Successful in 26s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Failing after 6m15s
deb / build-publish-host (push) Successful in 10m24s
arch / build-publish (push) Successful in 12m43s
android / android (push) Successful in 14m5s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 6m1s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m15s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m40s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 8m56s
Three related bits of the same cleanup:

- The console UI moves from a card in the page body to a button in the
  header row, beside Add host / Shortcuts / Settings. It also opens the
  console's OWN host view now (bare --browse) rather than diving straight
  into one host's library, which is what the card did -- the couch
  counterpart of this page, not a shortcut past it.
- spawn_browse / open_console take an Option target to express that.
- The per-host overflow menu collapses: rename and the two clipboard labels
  become one Edit dialog carrying every per-host property, mirroring the
  Apple client's add/edit sheet (name, address, port, Wake-on-LAN MAC,
  share clipboard). A menu item per field read as clutter and buried the
  entries that matter.

Cleared fields mean leave-as-is rather than erase, except the MAC, which is
legitimately clearable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 2508b72062 feat(client/windows): make the console UI findable, and launchable on its own
Two problems, one feature. The gamepad/couch UI existed but you had to
already know it was there:

- On the hosts page its card only rendered when a controller was CONNECTED,
  so with no pad plugged in there was no visible entry point at all -- the
  only other door being a per-host overflow menu behind a "..." nobody
  opens. The card now always shows, with copy that adapts to whether a pad
  is present, so the feature is findable before you own the hardware.
- There was no way to start it directly. An HTPC or TV box wants the couch
  interface as its first screen, not the desktop shell. "punktfunk-client
  --console" now hands straight off to it (fullscreen unless --windowed),
  which covers shortcuts, Steam entries, autostart and Task Scheduler.

The Start-menu tile needs its own executable: an MSIX <Application> cannot
pass arguments to a full-trust exe, so "punktfunk-client.exe --console" is
not expressible there. punktfunk-console.exe is a ~20-line hand-off to the
session binary's browse mode, staged into the package and given its own
tile ("Punktfunk Console").

Nothing new is implemented behind either door: a bare "--browse" was
already a complete standalone client (host list, discovery, PIN pairing,
settings, Wake-on-LAN, library). It just had no front door.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 aa45757a72 feat(client/windows): per-host clipboard toggle
The bridge landed always-on whenever the host permitted it; sharing a
clipboard is a trust decision about a specific host, so it needs to be
opted into. Mirrors the Apple client's per-host model
(StoredHost.clipboardSync, "Share clipboard with this host") rather than a
global switch: KnownHost::clipboard_sync, toggled from the host card's
overflow menu, default off.

The session binary resolves the stored flag itself in session_params, so a
direct connect and the console's own launches honor the same decision
without every caller having to remember to pass it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 a042dc2850 fix(client/windows): settings combos render blank until touched
The section-switch key was moved onto the scroll_view's direct child during
the parity restructure, which silently disabled it: ScrollView::children()
is Children::PositionalSingle, reconciled positionally with keys ignored.
The column was therefore reused across section switches, diffing one
section's controls into another's -- which re-sets a reused ComboBox's items
(clearing WinUI's selection) but skips selected_index whenever the two
sections' values compare equal, so every combo rendered empty until changed.

Put the keyed column back inside a panel's child list, where the keyed diff
path actually runs and the switch remounts. This is the same bug the
original code carried a comment about; the comment now names the
PositionalSingle trap that makes the key placement load-bearing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 67d2964db9 fix(client/windows): render the settings category title in the content column
NavigationView.Header carries WinUI's own left inset -- the reactor exposes
it as a string prop, so the template's padding is not ours to move, and the
title sat visibly right of the cards beneath it. Render the title as the
first element of the content column instead, where it shares the cards'
left edge by construction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 344618e1f0 fix(client/windows): flush the settings headings with the cards below
The shared section() helper carries a 2px left inset -- right for the
hosts/licenses lists it was written for, but in Settings it left every
sub-section heading hanging one nudge right of its card's edge. Use a
settings-local heading pinned flush left, and drop the same inset from the
footer notes, so heading, card and note all share one left edge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 51f7ff1b3f feat(client/windows): invert scroll direction
The Apple client's toggle, wired through the shared session presenter:
Settings::invert_scroll -> SessionOpts -> Capture, applied at the single
seam where wheel deltas enter (before accumulation, so the fractional
remainders stay consistent with what was actually sent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 7a1809547b fix(client/windows): left-align setting captions, drop the width cap, add auto-wake
Three follow-ups to the parity sweep:

- Captions read centred: a TextBlock defaults to Stretch alignment, so the
  MaxWidth cap left it floating in the middle of the leftover width instead
  of sitting under its control. Pin them (and the footers) Left.
- The settings column kept the shared page() 640px cap, which -- next to the
  NavigationView pane spending the left third -- squeezed the cards into a
  narrow ribbon. Drop the cap here.
- Auto-wake on connect (the Apple client's toggle) is now a setting rather
  than unconditional behavior. Default on, which is what it did before.
  Gating the flow's ENTRY, not its packet sends: with it off a failed dial
  just fails, instead of showing a "Waking..." wait that would never send a
  packet to end itself. The explicit "Wake" host-card menu action is
  deliberately NOT gated -- that is the user asking directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 72583b272e feat(client/windows): settings parity with the Apple client's revamp
Adopt the Apple client's 2026-07 settings structure so the two desktop
clients read the same way. Category map is now theirs verbatim -- General
(session/app behavior), Display (everything about the picture), Input,
Audio, Controllers, About -- which moves four things: statistics and the
game-library toggle out of Video/About into General, controllers out of
Input into their own category, and the codec/bitrate/HDR/decoder controls
out of the old Windows-only "Video" tab into Display, where Apple keeps
them. Sub-section headers (Resolution / Quality / Decoding / Host output)
mirror their Sections, as do the two "applies from the next session"
footers.

Every field now carries its explanation directly under it instead of only
in a hover tooltip -- the same move Apple made, for the same reason. The
old comment here argued against prose in the card because a paragraph
ABOVE a control reads as its label; that holds, so the caption goes below,
which is what Apple does and what Windows Settings itself does. Wording is
shared verbatim wherever a setting means the same thing on both platforms.
Where behavior differs the text stays Windows-specific -- notably the
forwarded-controller picker, where Apple forwards one pad as player 1 and
this client forwards every controller as its own player.

Two fixes fell out of the sweep: the decoder picker never offered D3D11VA
even though the chain accepts it (and 40030e90 made it Intel's default), and
settings opened on Display rather than the first sidebar item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 1ef0229bd9 feat(client/windows): PyroWave decode + surface it in the GUI codec picker
The decoder was gated to Linux because, when it landed, the Windows client
still had its own in-process WinUI/D3D11 presenter and the PyroWave present
path there was an open question. That client has since been retired: Windows
now spawns the SAME Vulkan session presenter as Linux, and the decoder is
plain Vulkan compute on the presenter's device (no fds, no dmabuf, no D3D11
interop), so the question that gated it answered itself. pyrowave-sys already
builds on Windows too -- the Windows HOST encoder ships on it.

So this is a port by un-gating: every cfg(all(target_os = "linux", feature =
"pyrowave")) becomes any(linux, windows) -- decoder module, backend variant,
Decoder::new_pyrowave, the CODEC_PYROWAVE advertisement, the session pump's
opt-in/build/label arms, and the presenter's planar CSC pass. No new code.

Then offer it in the Windows GUI, which is what prompted this. It stays
preference-only (resolve_codec never auto-picks it) and a host or device that
can't do PyroWave just falls back down the ladder to HEVC.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:06:06 +02:00
enricobuehlerandClaude Fable 5 1d587a259e fix(client/d3d11va): green bar at the bottom — clamp the blit to the frame
ci / web (push) Successful in 47s
ci / docs-site (push) Successful in 1m8s
apple / swift (push) Successful in 1m18s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 13s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 6m49s
ci / bench (push) Successful in 7m2s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 5m4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
apple / screenshots (push) Successful in 6m12s
deb / build-publish (push) Successful in 9m49s
android / android (push) Successful in 13m29s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 6m53s
docker / deploy-docs (push) Successful in 27s
flatpak / build-publish (push) Successful in 6m32s
deb / build-publish-host (push) Successful in 13m26s
arch / build-publish (push) Successful in 17m13s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 8m0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m10s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m2s
ci / rust (push) Successful in 28m51s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 9m0s
The decode surface is DXVA-aligned (height rounded up to the profile's
alignment, 128 for HEVC/AV1), so it is taller than the frame: a 2400-line
stream decodes into a 2432-line texture. VideoProcessorBlt was called with
no stream source rect, so it blitted the WHOLE surface — the uninitialized
padding rows (NV12 Y=0,U=V=0, which converts to vivid green) landed at the
bottom of the output and the picture was squashed to fit. Set the source
rect to the real frame.

Pre-existing on this backend, but 40030e90 made D3D11VA auto's first choice
on Intel, so it went from a corner case to what every Intel user sees — a
~32 px green bar at 3840x2400, reported on glass within the hour.

Also dedupe the Settings GPU picker by description: that string IS the
identity downstream (persisted as Settings::adapter, matched by name as
PUNKTFUNK_VK_ADAPTER), so repeated enumerations of one adapter offered the
user the same choice twice — live on an Intel Arc laptop, whose Vulkan ICD
also enumerates the single physical iGPU twice. Both paths now log their
raw enumeration (decode texture dims; DXGI name/LUID/ids) for the next
report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 14:44:05 +02:00
enricobuehlerandClaude Fable 5 871ebb31ce feat(clients): render-scale setting on every client — shared punktfunk_core::render_scale
Client-side supersampling/downscaling: the client asks the host to render
and encode at chosen-resolution × scale (the host does no scaling) and the
presenter rescales the decoded frame to the display. >1 supersamples for
sharpness; <1 lightens the host GPU and the link. Default 1.0 = Native, the
prior behavior.

The geometry lives once in punktfunk_core::render_scale (multiply, preserve
aspect ratio, floor to even, clamp to the codec's per-axis ceiling — 4096
for H.264, 8192 otherwise), the Rust twin of the Apple client's
RenderScale.swift, consumed by the native session client, the presenter's
match-window path, the Windows/Linux settings UIs, Decky, and Android
(settings + host connect + unit test).

Implemented and platform-verified by the Apple-client-features session
(Linux+Android+Apple green there); the punktfunk-core wiring
(pub mod render_scale) is restored here after being lost in a working-tree
reconciliation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:20:15 +02:00
enricobuehlerandClaude Fable 5 1fc9ef0050 feat(core,host,clients): typed pairing rejections — every client says WHY, not "not accepted"
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 52s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 44s
ci / bench (push) Successful in 5m56s
flatpak / build-publish (push) Successful in 6m41s
docker / deploy-docs (push) Successful in 27s
apple / swift (push) Successful in 4m40s
deb / build-publish (push) Successful in 12m7s
arch / build-publish (push) Successful in 14m10s
android / android (push) Successful in 17m16s
ci / rust (push) Successful in 17m57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 16m50s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m15s
windows-host / package (push) Successful in 14m33s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m55s
release / apple (push) Successful in 25m31s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m49s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m11s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m9s
apple / screenshots (push) Successful in 19m41s
A host's pairing-gate rejections (not armed / bound to another device /
rate-limited / identity required / denied / approval timeout / superseded /
wire-version mismatch) used to drop the connection with a bare code-0 close,
and every client collapsed that — plus plain unreachability — into one
"wrong PIN / not accepted" message. A dead network path, a disarmed host,
and an operator denial were indistinguishable, which is exactly the
misdiagnosis behind the recent Android pairing support thread.

- core: new ungated `reject` module — shared close-code block 0x60–0x67
  (+ 0x42 busy promoted from the host), `RejectReason`, and
  `PunktfunkError::Rejected`; `pair()`/`connect()` decode the host's
  ApplicationClosed code into `Rejected` instead of a generic Io error.
  C ABI v7: status block −20…−28 and `punktfunk_connect_ex8` (`status_out`
  reports the failure cause; NULL-return alone can't). Wire unchanged —
  old peers see exactly the old bare close.
- host: every gate rejection `conn.close()`s with its typed code (and the
  human reason as close bytes) before erroring out of the session task.
- pf-client-core: shared `pair_error_message`/`connect_reject_message`
  wording consumed by the Windows + Linux + console-UI + CLI surfaces; a
  connect failure now renders the host's stated reason.
- android: `nativeTakeLastError()` JNI token + `ConnectErrors.kt` — a
  network timeout is no longer reported as "wrong PIN, or the host isn't
  armed", and a typed rejection skips the wake-and-wait fallback (the host
  is demonstrably awake).
- apple: `HostRejection` + `.rejected`; the pair sheet and session alerts
  show the stated reason; connect moves to `ex8`.

Completes the cross-client half of the hunks that rode along in 12148243
(client.rs / trust.rs / punktfunk1.rs) — main did not build without this.

Validated: workspace clippy -D warnings + full test suite green on .21
(EXIT=0, 309 host / 148 core suites); macOS core 147+c_abi green; swift
build green; Android Kotlin + native crate green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 09:58:43 +02:00
enricobuehlerandClaude Opus 4.8 f88d0ae4dc feat(touch): cross-client touch-input modes on Linux + Windows
ci / web (push) Successful in 43s
ci / rust (push) Failing after 53s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
decky / build-publish (push) Successful in 16s
ci / docs-site (push) Successful in 1m5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m41s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m53s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 52s
apple / swift (push) Successful in 4m41s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 54s
ci / bench (push) Successful in 6m30s
docker / deploy-docs (push) Successful in 23s
flatpak / build-publish (push) Failing after 8m9s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
deb / build-publish (push) Has been cancelled
Bring the SDL presenter (Linux/Deck + Windows) to parity with the Android and
Apple clients: a persisted TouchMode selects how a touchscreen drives the host —

  * Trackpad (default): relative cursor with pointer ballistics + the shared
    gesture vocabulary (tap = left click, two-finger tap = right click,
    two-finger drag = scroll, tap-then-drag = held left drag, three-finger tap =
    cycle the stats overlay).
  * Direct pointer: the cursor jumps to and follows the finger (absolute).
  * Touch passthrough: every finger is a real host touchscreen contact.

Previously the presenter had no finger handling, so SDL synthesized mouse events
from touch and — under the stream's relative-mouse lock — walked the host cursor
into the corner (the reported Deck bug). SDL touch->mouse synthesis is now off;
DIRECT touchscreens route through a new incremental gesture engine (a port of
Android TouchInput.kt / Apple TouchMouse.swift), while INDIRECT trackpads keep
driving the mouse. Fingers map through the aspect-fit letterbox onto the content
rect.

TouchMode lives in the shared trust::Settings (default trackpad, so passthrough
is opt-in like the other clients); the GTK and WinUI settings screens both gained
a "Touch input" picker. Gesture engine, letterbox mapping, and settings
back-compat are unit-tested (28 tests green); clippy -D warnings clean; full
Linux client + session build verified on-host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 23:51:29 +02:00
enricobuehlerandClaude Opus 4.8 236c59754b refactor(gamepad/windows): drop the dead shell fork, use pf-client-core's service
clients/windows/src/gamepad.rs was a 629-line near-verbatim fork of
pf-client-core's SDL gamepad service, frozen at an old single-pad design.
Commit 9822fc3b removed its attach/detach entry points but left the machinery,
so `Worker.attached` was initialized None and never set — ~300-400 lines
(button/axis/touchpad/motion forwarding, Ds5Feedback, the rumble/HID feedback
loop) were logically unreachable, never flagged because the guards read a
runtime Option the compiler can't prove is always None. The live remainder
(pad enumeration + pin persistence) had drifted from core: it opened every
device for metadata (vs core's no-open id-getters), force-enabled the Valve
HIDAPI drivers unconditionally, lacked the steam_virtual skip (so it could pin
Steam Input's virtual pad and kill gyro), and derived the pin key from an
opened handle — risking a cross-process byte-mismatch with the session, which
resolves the same key from id-getters.

The shell's only live job is enumerating pads for the Settings list and
persisting the pin; the spawned punktfunk-session already runs the full
pf-client-core service and does all real forwarding (session/main.rs). So
delete the fork and point the shell at pf_client_core::gamepad::GamepadService
directly — its start()/pads()/set_pinned()/clone() + PadInfo{key,name,
kind_label()} are a strict superset of what the shell uses. Idle, core's
service is hands-off the hardware (id-getter metadata, no device open, HIDAPI
off), which is the intended behavior and fixes the drift class above.

- delete clients/windows/src/gamepad.rs (-629) and `mod gamepad;`
- main.rs / app/mod.rs: use pf_client_core::gamepad::GamepadService
- drop the now-unused direct sdl3 dep (pf-client-core pulls it on Windows with
  the same build-from-source,hidapi features); sync Cargo.lock

Pre-checks (dev Mac): std mpsc Sender<T>: Sync confirmed on the pinned 1.96.0
(so core's GamepadService is Sync for the WinUI cross-thread sharing, no core
change needed); rustfmt clean; no dangling refs. Windows compile is deferred
to CI (windows-only crate, unbuildable on macOS).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 11:25:03 +02:00
enricobuehlerandClaude Opus 4.8 a9dc6efe55 fix(windows): drop the orphaned touch_last_used re-export
ci / web (push) Successful in 44s
ci / docs-site (push) Successful in 1m0s
ci / bench (push) Successful in 6m1s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m2s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m29s
ci / rust (push) Successful in 22m28s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 13s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
decky / build-publish (push) Successful in 19s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
android-screenshots / screenshots (push) Successful in 3m6s
flatpak / build-publish (push) Successful in 7m12s
web-screenshots / screenshots (push) Successful in 2m53s
linux-client-screenshots / screenshots (push) Successful in 10m8s
docker / deploy-docs (push) Successful in 23s
arch / build-publish (push) Successful in 11m54s
deb / build-publish (push) Successful in 12m53s
windows-host / package (push) Successful in 15m26s
android / android (push) Successful in 15m45s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m25s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m16s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m59s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m59s
apple / swift (push) Successful in 4m56s
release / apple (push) Successful in 24m44s
apple / screenshots (push) Successful in 20m44s
More builtin-removal fallout: trust.rs re-exported pf_client_core::trust::
touch_last_used, whose only consumer was the deleted in-process session pump. In
a binary crate an unused pub-use is a hard -D warnings error (it surfaced only
after the gamepad dead-code errors were cleared, which had suppressed the
unused_imports pass). Drop it; every other re-export still has a user.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 08:37:24 +02:00
enricobuehlerandClaude Opus 4.8 9822fc3b1c fix(windows): drop the orphaned in-process gamepad forwarding hooks
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 1m0s
ci / bench (push) Successful in 5m49s
android-screenshots / screenshots (push) Successful in 3m17s
ci / rust (push) Successful in 17m50s
decky / build-publish (push) Successful in 18s
windows / build (aarch64-pc-windows-msvc) (push) Failing after 59s
apple / swift (push) Successful in 4m25s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 5m22s
android / android (push) Successful in 14m55s
arch / build-publish (push) Successful in 13m12s
deb / build-publish (push) Successful in 11m29s
web-screenshots / screenshots (push) Successful in 2m42s
docker / deploy-docs (push) Successful in 12s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6m10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
linux-client-screenshots / screenshots (push) Successful in 7m16s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 12m58s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m36s
windows-host / package (push) Successful in 15m17s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m10s
release / apple (push) Successful in 22m45s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m59s
flatpak / build-publish (push) Successful in 5m41s
apple / screenshots (push) Successful in 19m29s
Removing the builtin stream path (ef580825) left GamepadService's attach/detach/
active/auto_pref + the Ctl::Attach/Detach variants with no callers — the spawned
punktfunk-session binary owns pad forwarding now. The client still compiled, but
clippy -D warnings tripped on the dead code. Drop the forwarding hooks + the
active-pad mirror; the service keeps pads() (Settings list) and set_pinned()
(persist the forwarded-pad selection the session child reads).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 07:49:38 +02:00
enricobuehlerandClaude Opus 4.8 ef5808254a refactor(windows): remove the legacy in-process builtin stream path
The real Windows client is the spawned punktfunk-session Vulkan binary
(pf-client-core); the in-process builtin GUI stream — reachable only via
PUNKTFUNK_BUILTIN_STREAM=1 — was dead weight kept alive by nothing and a
recurring source of wasted effort. Remove it: delete present/render/input/
audio.rs and the builtin remainder of session/video.rs, rip all the builtin
wiring (app/mod, connect, stream), and make connect always spawn.

Preserve the two shipped keepers that happened to live in those files by
relocating them to a new probe.rs: run_speed_probe (the per-host network speed
test used by the Settings speed page and --headless --speed-test) and
decodable_codecs (the codec-capability advert on the probe connect). Trim gpu.rs
to just the Settings adapter picker (adapter_names + helpers). --headless now
supports only --speed-test — the in-process decode/frame-counter went with the
pump.

Drops the now-orphaned deps opus, wasapi, crossbeam-channel, anyhow; keeps
ffmpeg-next (probe::decodable_codecs still needs it). Net 4432 deletions.
Statically verified (module wiring, imports, orphaned symbols/deps all clean);
the type-level compile runs on the windows-amd64 CI runner, which has the
toolchain this non-Windows host lacks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 01:22:09 +02:00
enricobuehlerandClaude Opus 4.8 9514a8c0e2 fix(client): correct Linux/Windows "Forwarded controller" copy for multi-controller
ci / web (push) Successful in 51s
ci / docs-site (push) Successful in 56s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
decky / build-publish (push) Successful in 52s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
apple / swift (push) Successful in 4m39s
ci / bench (push) Successful in 6m27s
windows-host / package (push) Successful in 8m34s
flatpak / build-publish (push) Successful in 7m15s
docker / deploy-docs (push) Successful in 24s
android / android (push) Successful in 12m49s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m16s
deb / build-publish (push) Successful in 12m50s
arch / build-publish (push) Successful in 16m32s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m44s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m14s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m53s
ci / rust (push) Successful in 25m18s
release / apple (push) Successful in 22m3s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m57s
apple / screenshots (push) Successful in 18m56s
The relm4 (Linux) and native (Windows) shells spawn punktfunk-session, which
since the native-plane rework forwards ALL controllers by default — but the
"Forwarded controller" settings dropdowns still described the pre-rework world
("Automatic (most recent)", "Exactly one controller is forwarded to the host").

The dropdown already lists every detected pad and wires set_pinned(None)=all /
set_pinned(key)=single-player; this fixes only the misleading labels, subtitle,
tooltip, and stale leading comments to match: Automatic forwards every real
controller (each its own player); pick one to force single-player.

cargo check -p punktfunk-client-linux green; Windows is windows-gated (pure
string edits, CI windows.yml).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 21:53:07 +02:00
enricobuehlerandClaude Opus 4.8 a95b518ef3 fix(windows): show app version on About screen + capitalize Punktfunk on licenses
ci / docs-site (push) Successful in 51s
ci / web (push) Successful in 57s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
decky / build-publish (push) Successful in 19s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 7s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 38s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m1s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m3s
apple / swift (push) Successful in 5m1s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m4s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m25s
ci / bench (push) Successful in 8m10s
docker / deploy-docs (push) Successful in 20s
arch / build-publish (push) Successful in 11m11s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m47s
android / android (push) Successful in 16m10s
deb / build-publish (push) Successful in 16m57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m47s
ci / rust (push) Successful in 23m26s
apple / screenshots (push) Successful in 19m57s
The About settings card had no version at all — add an identity block (app name +
"Version <CARGO_PKG_VERSION>", the workspace version) at the top, the WinUI
Settings convention and matching the Apple client's "Version X" wording. Also
capitalize the brand name on the licenses screen (was lowercase "punktfunk").

Verified against the pinned windows-reactor source + cargo fmt --check; full
Windows link left to CI (Windows-only crate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 02:46:45 +02:00
enricobuehlerandClaude Opus 4.8 7cea893db5 feat(recovery): wire LTR-RFI loss recovery into every client
ci / web (push) Successful in 48s
ci / rust (push) Failing after 48s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
ci / docs-site (push) Successful in 1m0s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
decky / build-publish (push) Successful in 17s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
apple / swift (push) Successful in 4m51s
ci / bench (push) Successful in 6m13s
docker / deploy-docs (push) Successful in 20s
windows-host / package (push) Failing after 8m22s
flatpak / build-publish (push) Failing after 8m4s
arch / build-publish (push) Successful in 11m53s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m45s
android / android (push) Successful in 12m53s
deb / build-publish (push) Successful in 13m4s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m16s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m29s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m42s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m2s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 5m29s
release / apple (push) Successful in 25m48s
apple / screenshots (push) Successful in 19m31s
Centralize the client-side loss-range detector in punktfunk-core so every
embedder shares one implementation instead of re-deriving the wrapping
frame-index arithmetic:

- NativeClient::note_frame_index(frame_index) folds each received AU (in
  receive order) through RfiRecovery::observe, firing a throttled RFI request
  for the exact lost span [first_missing, frame_index-1] on a forward gap. A
  host that can RFI (AMD LTR / NVENC) re-references a known-good frame instead
  of paying a 20-40x IDR spike; the frames_dropped-driven keyframe path stays
  the backstop for when the recovery frame itself is lost.
- Export request_rfi + note_frame_index over the C ABI (Apple client).
- Call it from the Android (hw+sw pumps), Apple (StreamPump + Stage2Pipeline
  via PunktfunkConnection.noteFrameIndex), and Windows in-process pumps.
  Linux/Deck inherit it through pf-client-core's session pump.
- Split the decision into a pure RfiRecovery::observe(frame_index, now) and add
  8 unit tests: arming, contiguous runs, exact lost-range, single-frame drop,
  the 100ms throttle (burst-suppress then re-open), reorder stragglers, and
  u32 wraparound (contiguous + gap-range).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 19:11:01 +02:00
enricobuehler 890c7531d8 Merge branch 'midstream-resize': mid-stream resolution resize
apple / swift (push) Successful in 4m19s
ci / rust (push) Failing after 46s
ci / web (push) Successful in 51s
ci / docs-site (push) Successful in 1m2s
decky / build-publish (push) Successful in 20s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
ci / bench (push) Successful in 5m17s
arch / build-publish (push) Successful in 10m44s
docker / deploy-docs (push) Successful in 11s
android / android (push) Successful in 16m43s
windows-host / package (push) Failing after 8m29s
flatpak / build-publish (push) Failing after 8m13s
deb / build-publish (push) Successful in 11m24s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m51s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m1s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 12m24s
release / apple (push) Successful in 24m35s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m24s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m2s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 5m25s
apple / screenshots (push) Successful in 19m55s
Lands the mid-stream resolution resize feature (client-driven Reconfigure so
the host's virtual display + encoder follow a resized client window without a
reconnect), all paths default OFF:

- host hardening H1-H5 + session-binary Match window (C1)
- Apple macOS/iPadOS Match-window trigger + settings (C3) and the resize
  overlay (blur + spinner) client UX
- Windows on-glass fixes: corrective-ack actual resolution + pf-vdisplay
  monitor re-arrival for out-of-list mid-stream modes
- Linux backend matrix + the live-reconfigure gate unit tests

Validated on-glass: Windows IDD-push (.173), Linux Mutter + KWin. Android
(C4) deferred; Apple full build pending on a Mac.
2026-07-11 15:59:07 +02:00
enricobuehlerandClaude Opus 4.8 e6fbcecdb9 fix(clients/windows): GUI text inputs read the live value, not a stale render snapshot
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 1m5s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 16s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 14s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 14s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m0s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m33s
apple / swift (push) Successful in 4m24s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m18s
ci / bench (push) Successful in 7m10s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m17s
arch / build-publish (push) Successful in 11m38s
deb / build-publish (push) Successful in 12m6s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m9s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m46s
android / android (push) Successful in 17m7s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m48s
docker / deploy-docs (push) Successful in 22s
ci / rust (push) Successful in 26m33s
apple / screenshots (push) Successful in 19m29s
A component() page re-renders reliably only when its props change: root wraps
every screen in a stable animated border, so once the entrance tween settles the
reconciler skips that unchanged-props subtree and a page's own use_state writes
never force a re-render. Three text fields read their value at click time from
that stranded local state:

- PIN pairing sent an empty PIN, so pairing always failed with "wrong PIN, or not
  armed?" — the reported bug. The CLI --pair path bypasses the reactor and worked.
- "Add host" Connect captured the empty mount-time address and silently did
  nothing (you open the modal precisely when the host isn't being discovered, so
  no discovery tick re-renders the page while you type).
- Rename round-tripped the draft through an always-deferred AsyncSetState into a
  controlled text box, fighting the caret on fast typing and dropping the last
  character when Save was clicked before the write landed.

Fix: hold each field's live value in a use_ref cell written on every keystroke
and read at commit time (uncontrolled input), instead of a render-time snapshot.
Rename is seeded when its target changes and no longer re-renders the whole page
per keystroke. Reviewed the rest of the app (settings, speed test, library,
stream, connect/request-access/waking, forget) — all driven by root-state props
and wired correctly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:58:25 +02:00
enricobuehlerandClaude Fable 5 d0d9bd5bfb feat(resize): mid-stream resolution resize — host hardening (H1–H5) + session-binary Match window (C1)
design/midstream-resolution-resize.md Phase 0 + Phase 1.

Host (Phase 0):
- H1/H5: per-backend Reconfigure acceptance gate — reject for gamescope
  (all sub-modes; a resize must never relaunch the title) and under the
  per-client-mode identity policy (a resize would resolve a different
  display slot). Synthetic stays reconfigurable on purpose (the protocol
  test source; the C-ABI roundtrip test rides it). Plus a 500 ms host-side
  min-interval backstop against Reconfigure spam.
- H2: rollback/corrective ack — the data plane reports the mode actually
  live after a failed rebuild (or a refresh the backend capped) through a
  reconfig_result channel; the control task forwards it as a second
  accepted Reconfigured so the client's mode slot self-corrects.
- H3: live stats mode — SendStats reads a packed AtomicU64 (w|h|hz)
  updated on every switch instead of latching the session-start mode.
- H4: registry::retire(gen) — a mode-switch rebuild force-releases the
  superseded Linux display, so linger/forever keep-alive policies don't
  accumulate kept monitors at stale modes. VirtualOutput carries pool_gen
  (fresh AND reused) and the Pipeline tuple threads it to the switch arm.

Client (Phase 1, default off):
- Settings: match_window policy + persisted last window size; exposed as
  the Resolution tri-state (Native / Match window / explicit) in the Skia
  console, GTK and WinUI settings pages.
- pf-presenter: window opens at the persisted size; Hello mode follows the
  window's pixel size; D2 trigger discipline (400 ms debounce to
  resize-end, ≥1 s spacing, even-floor + ≥320×200 clamp, each distinct
  size requested at most once — covers rejects and host rollbacks) as a
  pure, unit-tested decision; HUD line + title refresh on a switch.
- Session binary wires both --connect and --browse paths; the WinUI shell
  is session-always, so this covers Windows too.

Verified: workspace tests + clippy green; synthetic --remode end-to-end;
live session-binary run (window at persisted 1000×600 → Hello 1000×600@60).
On-glass per-backend matrix (Mutter/KWin/gamescope-reject, keep-alive
accumulation) still pending before any default flip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 12:53:47 +02:00
enricobuehlerandClaude Fable 5 73c911cae4 feat(rumble): host-authoritative self-terminating envelopes (0xCA v2)
Rumble was level-triggered, unbounded state on a lossy channel: a non-zero
level meant "buzz until further notice", healed only by the host re-sending
state every 500 ms, and every client guessed when the host had died with its own
magic timeout (SDL 1.5 s, Apple 1.6 s, Android up to 60 s). A lost stop, a
reordered start, or a dead host could drone the motor for seconds.

Make "stuck rumble" inexpressible on the wire. The 0xCA datagram grows a
length-tolerant tail — [u8 seq][u16 ttl_ms] — so it self-terminates: the host
authorizes a level for at most ttl_ms and renews it (~120 ms) while it holds,
letting an abandoned one lapse client-side. seq is a per-pad wrapping reorder
gate (reusing GamepadSnapshot::seq_newer) so a reordered stale start can't
re-light a stopped motor. Decoders read the first 7 bytes as a plain level and
ignore the tail, so no wire-version bump: an old client renders a new host's
levels, and a new client falls back to its prior staleness heuristic against an
old host (ttl = None). All four generation pairings render correctly.

- core: encode_rumble_datagram_v2 / decode_rumble_envelope (datagram.rs); the
  client demux applies the seq gate then forwards (pad, low, high, Option<ttl>);
  next_rumble is unchanged (drops ttl), next_rumble_ttl keeps it; ABI adds
  punktfunk_connection_next_rumble2 + PUNKTFUNK_RUMBLE_NO_TTL, ABI_VERSION 4->5
  (WIRE_VERSION unchanged — the tail is backward-compatible).
- host (punktfunk1.rs): the flat 500 ms refresh becomes a renewal loop that bumps
  seq + stamps a fresh TTL on active pads and drains a short post-stop zero burst,
  then goes quiet. Hatches: PUNKTFUNK_RUMBLE_ENVELOPE=0 (legacy v1 + flat refresh,
  a bisect switch), PUNKTFUNK_RUMBLE_TTL_MS (clamped [150, 5000]).
- renderers honor the TTL as their playback duration/deadline and keep their old
  heuristic only for a legacy (ttl=None) update: pf-client-core (the Deck haptic
  keep-alive is now deadline-bounded so it can't sustain a host-stopped rumble),
  clients/windows (SDL duration), android (JNI packs the lease out-of-band in bit
  48 so any u16 ttl is unambiguous; Kotlin createOneShot(ttl)), apple
  (RumbleRenderer.envelopeDeadline + nextRumble2; sessionStaleSeconds demoted to
  the legacy fallback).
- tests: codec round-trip + tail tolerance + seq-gate reorder (Rust); the probe
  asserts the v2 tail arrived under PUNKTFUNK_TEST_FEEDBACK; the Apple loopback
  asserts ttlMs round-trips end to end; RumbleTuning lease-decision cases.

The host-side idle-timeout from the previous commit is defense in depth on the
game side; this is the guarantee on the client side. Design:
punktfunk-planning/design/rumble-envelope-plan.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 03:08:27 +02:00
enricobuehlerandClaude Fable 5 35d97ae6ac feat(windows): parallel virtual displays — proto v3 ring binding, manager slot map, group topology (W0–W3)
windows-drivers / probe-and-proto (push) Successful in 41s
ci / web (push) Successful in 1m2s
ci / docs-site (push) Successful in 1m7s
apple / swift (push) Successful in 1m11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
decky / build-publish (push) Successful in 15s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 38s
windows-drivers / driver-build (push) Successful in 1m46s
ci / bench (push) Successful in 6m35s
docker / deploy-docs (push) Successful in 20s
windows-host / package (push) Successful in 9m37s
deb / build-publish (push) Successful in 13m48s
arch / build-publish (push) Successful in 14m21s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m34s
android / android (push) Successful in 15m45s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m20s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m18s
ci / rust (push) Successful in 18m48s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m41s
release / apple (push) Successful in 19m30s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m49s
flatpak / build-publish (push) Failing after 2m9s
apple / screenshots (push) Successful in 19m3s
design/windows-parallel-virtual-displays.md (display-management Stage 7 / §6.6): N
simultaneously-live pf-vdisplay monitors, one sealed ring each, every idd-push-security
invariant preserved per-ring.

- proto v3: SharedHeader._pad → target_id — the ring NAMES its monitor, host-stamped
  before the magic; the driver publisher refuses a cross-bound ring via the shared,
  unit-tested frame::check_attach (new DRV_STATUS_BIND_FAIL — the gamepad pad_index
  validation applied to frames, invariant #10); the host's wait_for_attach surfaces the
  refusal loudly and self-checks its own stamp.
- manager: the one-monitor MgrState becomes a slot map keyed by the client's identity
  slot (0 = anonymous/GameStream); per-slot reconnect + dead-WUDFHost preempts,
  slot-scoped begin_idd_setup (a different identity is an admission question, never a
  preempt), ONE device-level watchdog pinger, per-slot /display/state + /display/release.
- group topology: isolate_displays_ccd takes the managed target SET (a sibling slot is
  never deactivated); SavedConfig + the DDC/PnP axes move to the group record (first-in
  captures, last-out restores); desktop layout via CCD source origins from the pure
  layout::arrange (auto-row default, manual pins win), re-applied on create + reconfigure.
- admission: the Windows separate→reject override now sits behind the
  PUNKTFUNK_WIN_SEPARATE=1 validation hatch (the wedge it guarded is structurally gone —
  a second identity gets its own monitor + ring; default flips in W5 after soak);
  max_displays and NVENC session-unit budgets decline an unaffordable display AT
  admission; kick_dwm_compose is process-globally throttled and per-display — cursor
  jump + 35 ms dwell (a sub-tick jump composes nothing; DWM reads dirties from current
  state at the next vsync tick).

On-glass on the RTX box: V1/V2/V4/V5/V6/V9 green — two paired clients on two monitors
streaming ~60 fps each with zero mismatches and zero bind failures, churn-hammer clean
(no 0x80070490), per-ring mode-change recreate leaves the sibling untouched, typed
budget rejection, fault-injected cross-bind refused loudly with the sibling undisturbed.
V7: WUDFHost-kill shared fate is clean; in-process device recovery is a known follow-up
(the retired-never-closed control handles block the adapter cycle — reset-pf-vdisplay.ps1
recovers). DWM composes two IDD monitors concurrently at 60 fps — the plan's
load-bearing unknown, answered yes.

Also carries the client-HDR EDID forwarding that shared this working tree
(Hello::display_hdr → AddRequest luminance tail → the monitor's CTA-861.3 HDR block,
PUNKTFUNK_CLIENT_PEAK_NITS hatch) and the Deck client fixes (40 ms rumble keep-alive
with 1-LSB jitter, HDR self-diagnosing presenter warn, flatpak HDR env).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 01:06:44 +02:00
enricobuehlerandClaude Fable 5 d55cde61d3 style: cargo fmt — settle the CSC/tvOS changes' layout (CI Format gate)
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows-host / package (push) Has been cancelled
arch / build-publish (push) Has been cancelled
decky / build-publish (push) Successful in 16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / deploy-docs (push) Successful in 18s
deb / build-publish (push) Successful in 14m10s
android / android (push) Successful in 16m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m2s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 19m21s
ci / docs-site (push) Successful in 46s
ci / web (push) Successful in 51s
flatpak / build-publish (push) Failing after 2m15s
ci / bench (push) Successful in 15m8s
ci / rust (push) Successful in 18m22s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
Formatting only, no code change: the signaled-CSC and tvOS commits
(1fcf9e11, 3ba19f28) left six files unformatted and the rust job's
Format step rejects main. cargo fmt --all --check is clean after this.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:11:27 +02:00
enricobuehlerandClaude Fable 5 1fcf9e11ec fix(video): honor the signaled CSC matrix end-to-end + tvOS HDR presentation
Clients derive Y'CbCr->RGB from the stream's SIGNALED matrix x range x depth
via shared csc rows (Rust csc_rows + Swift CscRows) instead of hardcoded
709/2020 - a BT.601-signaled stream (a Linux host's RGB-input NVENC) no longer
renders with a constant hue error. Host-side signaling made honest across
NVENC/VAAPI/openh264/GameStream and the session plan's chroma/bit-depth.
Decoded color-bar fixtures (601/709 x limited/full) pin the math in tests on
both cores.

Same presenter, tvOS HDR: tvOS has no Metal EDR API and a bare PQ colorspace
tag composites UNTONE-MAPPED (the "overblown" Apple TV report), so HDR now
splits on the display's live EDR headroom - PQ passthrough when the
per-session AVDisplayManager mode switch landed (a real HDR10 output
tone-maps itself), else an in-shader PQ->SDR tone-map (203-nit reference
white, extended-Reinhard 1000-nit knee, 2020->709) into the proven SDR layer
config. The 10-bit stream keeps its full decode depth either way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 16:59:01 +02:00
enricobuehlerandClaude Fable 5 d4467a44e2 feat(core): mid-stream clock re-sync — live offset survives wall-clock steps and drift
Networking-audit deferred plan §2. The host↔client offset was measured once
at connect; an NTP step or slow drift silently corrupted the clock-based
jump-to-live signal, the ABR one-way-delay signal, and every latency stat —
4a3b1ae2's disarm backstop stopped the IDR storm but lost the detector for
the session. Now the client re-estimates mid-stream and recovers it.

- quic: ClockResync — the connect-time 8-round probe/echo estimate as a
  select!-driven state machine (rounds matched by echoed t1, stale batches
  ignored), plus accept_resync (batch min-RTT ≤ max(2 ms, 1.5× connect RTT)
  so a congested window can never bias the offset). No wire change: the
  host has always answered ClockProbe at any time on the control stream.
- client: the offset lives in an Arc<AtomicI64> seeded at connect; the
  control task re-probes every 60 s and immediately after the pump's FIRST
  no-op clock flush (the "clock stepped under me" signal, sent on the next
  report tick). On apply: store, reset stale_frames/noop_clock_flushes,
  re-arm the clock detector if a step had disarmed it. The disarm heuristic
  stays as the final backstop. Public NativeClient::clock_offset_ns keeps
  the connect-time value (ABI untouched); new clock_offset_now_ns() /
  clock_offset_shared() expose the live value.
- consumers migrated to the live offset: pf-client-core session stats, the
  pf-presenter e2e stamp, Windows session/render, Android feeder/drain/
  DisplayTracker (the tracker holds the shared handle, not the client, so
  the leaked render-callback refcount can't pin the session).
- probe: --clock-resync runs a second full handshake mid-connection and
  asserts a sane, consistent estimate. Live against the local canary host:
  offsets 8646/2139 ns, disagreement 6 µs, 8/8 rounds — OK.

Unit tests cover the round collection, stale-echo rejection, batch restart,
min-RTT selection, and the acceptance guard. cargo ndk check green.
Remaining manual validation: `sudo date -s "+2 sec"` on a live streaming
client → expect one no-op flush, a re-sync, re-armed detector, no IDR pulse.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:45:12 +02:00
enricobuehlerandClaude Fable 5 ef39050dbc docs: repo-wide housekeeping — sync README & docs with the code as shipped
windows-drivers / probe-and-proto (push) Successful in 47s
ci / web (push) Successful in 58s
apple / swift (push) Successful in 1m13s
decky / build-publish (push) Successful in 16s
ci / docs-site (push) Successful in 1m19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 43s
windows-drivers / driver-build (push) Successful in 1m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 55s
ci / bench (push) Successful in 7m18s
flatpak / build-publish (push) Successful in 6m18s
docker / deploy-docs (push) Successful in 25s
windows-host / package (push) Successful in 8m31s
release / apple (push) Successful in 11m41s
android / android (push) Successful in 13m4s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m57s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m16s
arch / build-publish (push) Successful in 16m24s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m6s
deb / build-publish (push) Successful in 17m54s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m27s
ci / rust (push) Successful in 18m23s
apple / screenshots (push) Successful in 5m58s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m24s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m43s
Six parallel audits swept the root docs, docs-site, every per-directory
README, and the packaging docs; every claim below was verified against
the source before editing.

- README: Layout gains the six missing crates (pf-client-core,
  pf-presenter, pf-console-ui, pf-ffvk, pf-driver-proto, punktfunk-tray),
  clients/session, api/ and ci/; Linux/Windows client rows reflect the
  shell + Vulkan-session split and the Vulkan Video -> VAAPI/D3D11VA ->
  software decode chains; the "every client over a C ABI" claim is
  corrected (Rust clients link the core directly); tiered stats overlay
  + console shell noted; Apple row mentions AV1.
- CONTRIBUTING: drop the dead CLAUDE.md link (deliberately untracked);
  point at the README's build/invariants sections. SECURITY: 0.9.0.
- host-cli/pairing: --allow-pairing/--require-pairing are no-op legacy
  names — pairing is required by default, --allow-tofu is the real flag;
  document --data-port and --idle-timeout-ms.
- configuration: document PUNKTFUNK_RECOVER_SESSION_CMD (session-crash
  recovery hook), PUNKTFUNK_MDNS, PUNKTFUNK_DATA_PORT.
- virtual-displays/gnome: GNOME per-client scaling shipped (host-
  persisted) — flip the  to  and describe how it works.
- stats: new "Detail levels" section (Off/Compact/Normal/Detailed +
  per-platform cycle gestures); retire the GTK hand-off note.
- clients/install-client/status/roadmap: decode chains, Windows client
  validation narrowed to HDR-only pending, adaptive bitrate, console
  shell, Apple AV1, Windows host vendor list.
- Sub-READMEs: clients/linux rewritten for the re-architecture; session
  Windows decode rung + d3d11va knob; Windows tiered overlay; Android
  minSdk 28; decky file table; host zerocopy/ path; scripts port
  47992 and steamos-host.md; pf-dualsense source path.
- packaging: canary version bases are tag-derived (<next-minor> via
  pf-version.sh/.ps1), codecs-extra not ffmpeg-full, document the
  pinned offline-Skia tarball + SKIA_BINARIES_URL and vulkan-headers.
- Convert 15 dangling design/*.md links to the punktfunk-planning
  prose convention (those docs live in the private planning repo).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 09:13:42 +02:00
enricobuehlerandClaude Fable 5 38b9f310e2 feat(clients): tiered stats overlay everywhere — Compact/Normal/Detailed on every platform
ci / docs-site (push) Successful in 1m5s
apple / swift (push) Successful in 1m10s
ci / web (push) Successful in 1m12s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m46s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m57s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 53s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m10s
ci / bench (push) Successful in 5m43s
decky / build-publish (push) Successful in 17s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
release / apple (push) Successful in 8m10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
arch / build-publish (push) Successful in 11m59s
android / android (push) Successful in 13m3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6m39s
docker / deploy-docs (push) Successful in 18s
apple / screenshots (push) Successful in 5m32s
deb / build-publish (push) Successful in 14m54s
ci / rust (push) Successful in 23m19s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m53s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m44s
flatpak / build-publish (push) Failing after 35s
Ship the Android client's 3-tier stats-overlay semantics in every other client
(design/stats-unification.md vocabulary): Off → Compact (one line: fps · e2e ms ·
Mb/s + loss flag) → Normal (mode + e2e p50/p95 + loss counters) → Detailed
(decoder path, HDR tag, per-stage latency equation).

Apple: new StatsVerbosity in PunktfunkKit persisted under punktfunk.statsVerbosity
(migrates the legacy hudEnabled bool: explicit off → Off, else Normal). The
existing three-finger tap (TouchMouse, trackpad/pointer modes only — touch
passthrough untouched) now cycles the tiers instead of toggling, matching
Android; ⌃⌥⇧S (menu + captured-state monitor) cycles the same ladder. Tiered
StreamHUDView (compact glass pill / headline HUD / full equation HUD); the iOS
corner disconnect also shows in Compact (the pill carries no button). Tier
pickers on iOS, macOS, tvOS and the gamepad settings UI.

Session stack (Linux + Windows + Deck share punktfunk-session): shared
pf_client_core::trust::StatsVerbosity; Settings grows stats_verbosity with a
show_stats fallback, and writes keep the legacy bool in sync so pre-tier
binaries reading the same JSON agree on off vs on. Ctrl+Alt+Shift+S cycles the
tier and re-renders the OSD immediately from the last stats window; the stdout
stats: line always carries the full Detailed text so the shell status card and
scripts keep a stable shape; --stats bumps Off → Normal without demoting a
richer tier. Tier pickers in the GTK dialog, the WinUI settings page and the
console-UI settings row; shortcut copy updated (GTK shortcuts window, Windows
help, session README). The Windows legacy builtin path keeps its bool HUD.

Tests: tier migration/round-trip in trust.rs, tiered stats_text output in
pf-presenter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 01:42:46 +02:00
enricobuehlerandClaude Fable 5 bf9be59f0b feat(clients/windows): rename the Help button to Shortcuts
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m39s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m37s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m15s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m30s
apple / swift (push) Successful in 1m12s
apple / screenshots (push) Successful in 5m48s
android / android (push) Successful in 4m30s
arch / build-publish (push) Successful in 5m47s
ci / web (push) Successful in 1m0s
ci / docs-site (push) Successful in 1m19s
ci / rust (push) Successful in 5m10s
deb / build-publish (push) Successful in 3m30s
ci / bench (push) Successful in 5m5s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
decky / build-publish (push) Successful in 13s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 11m25s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 11m33s
docker / deploy-docs (push) Successful in 12s
"Shortcuts" says what the page actually is — the in-stream keyboard/controller
reference — and the keyboard glyph reads better than the generic help icon
(user feedback from the live-test round). Page title follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 01:07:03 +02:00
enricobuehlerandClaude Fable 5 f4850625bd feat(clients/windows): game library page + session window placement + help polish
apple / swift (push) Successful in 1m10s
android / android (push) Successful in 4m56s
arch / build-publish (push) Successful in 5m56s
audit / bun-audit (push) Successful in 13s
audit / cargo-audit (push) Successful in 1m34s
ci / web (push) Successful in 1m11s
windows-host / package (push) Successful in 7m57s
ci / docs-site (push) Successful in 1m27s
release / apple (push) Successful in 8m39s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m26s
ci / bench (push) Successful in 5m0s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m16s
apple / screenshots (push) Successful in 5m35s
ci / rust (push) Successful in 10m32s
decky / build-publish (push) Successful in 13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m47s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m36s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
deb / build-publish (push) Successful in 6m38s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m39s
flatpak / build-publish (push) Successful in 5m32s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 11m0s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 10m33s
docker / deploy-docs (push) Successful in 18s
UX polish batch 2 (plan workstreams D/E5/E6):

- D: a mouse/keyboard game library page over pf-client-core::library (the GTK
  ui_library.rs counterpart): responsive 2-6 column poster grid (2:3 portraits,
  store badge, monogram placeholder while art streams in), loading / error+retry
  / empty / grid states, tap-to-play via a normal spawn carrying --launch id.
  Poster bytes land in a disk cache (%LOCALAPPDATA%\punktfunk\art-cache) so the
  Image widget loads file:/// URIs (reactor has no from-bytes source) and
  revisits render instantly. Reached from a paired host's "Browse library..."
  menu item behind the new "Show game library (experimental)" Settings toggle
  (the shared library_enabled field, Apple/GTK parity). A Shared::library_gen
  generation guard keeps a superseded fetch from publishing (speed-test pattern).

- E5: the session window opens at the shell's own top-left (--window-pos, new
  SessionOpts::window_pos) instead of centered on the primary display - streams
  land on the monitor the shell is on, and the hide/restore handoff reads as one
  window changing content. Fullscreen follows that display.

- E6: the Help shortcuts add Alt+Enter and the controller escape chord
  (LB+RB+Start+Back, hold to disconnect).

Also: rustfmt normalization of the merged probe helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 00:43:46 +02:00