39889c31025894a70e0814e746c01150a871415b
996 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
39889c3102 |
fix(host): silence write-only cur_node_id on non-Linux encoder builds
apple / swift (push) Successful in 1m10s
android / android (push) Successful in 6m0s
arch / build-publish (push) Successful in 6m5s
ci / web (push) Successful in 1m1s
windows-host / package (push) Successful in 7m17s
apple / screenshots (push) Successful in 6m43s
ci / docs-site (push) Successful in 1m18s
ci / bench (push) Successful in 4m58s
ci / rust (push) Successful in 9m27s
decky / build-publish (push) Successful in 26s
deb / build-publish (push) Successful in 6m37s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 4m9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 28s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m41s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m17s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m30s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 10m17s
cur_node_id (the capture 5-tuple's node id, added for the Linux dedicated- game-exit check) is read only under #[cfg(target_os = "linux")], so on the Windows nvenc/amf-qsv build it was assigned but never read — failing `clippy -D warnings`. Read it on non-Linux platforms (the `let _ = &launch` idiom already used in this file). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
215a8aa9dc |
chore(release): bump workspace version to 0.8.1
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, 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
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
release / apple (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
decky / build-publish (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
audit / cargo-audit (push) Has been cancelled
audit / bun-audit (push) Has been cancelled
apple / swift (push) Has been cancelled
deb / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
arch / build-publish (push) Has been cancelled
apple / screenshots (push) Has been cancelled
android / android (push) Has been cancelled
Release 0.8.1: game-mode + dedicated game sessions, zero-copy GPU import process-isolation (and zero-copy on by default on all backends), user-defined custom display presets, and a physical-monitor refresh-preservation fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
2f9e61d191 |
feat(host): enable GPU zero-copy by default on all backends
apple / swift (push) Failing after 45s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m15s
android / android (push) Successful in 4m56s
windows-host / package (push) Failing after 3m31s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
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
arch / build-publish (push) Successful in 5m39s
release / apple (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
apple / screenshots (push) Has been skipped
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
decky / build-publish (push) Has been cancelled
ci / web (push) Successful in 1m3s
ci / docs-site (push) Successful in 1m23s
ci / rust (push) Successful in 4m46s
ci / bench (push) Successful in 4m57s
deb / build-publish (push) Successful in 4m22s
Now that the per-capture worker subprocess makes an NVENC EGL/CUDA driver fault survivable (design/zerocopy-worker-isolation.md), the reason the NVENC zero-copy path stayed opt-in is gone. zerocopy::enabled() now defaults ON for both GPU backends (was ON VAAPI / OFF NVENC). Fallbacks are intact: VAAPI's one-shot CPU auto-downgrade (VAAPI-gated, never trips for NVENC) and NVENC's per-capture fallback + worker-death latch. Reframe the shipped host.env examples and setup guides to rely on the default rather than force PUNKTFUNK_ZEROCOPY=1 (an explicit =1 skips the VAAPI auto-downgrade). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a947f48d29 |
fix(host): preserve a physical monitor's refresh when adding a virtual display
Connecting reset an existing physical monitor's refresh (e.g. 120->60 Hz) because the topology code read the physical's mode AFTER the virtual output perturbed the compositor layout — by which point it had already been downgraded. Read/preserve each physical's mode from a pre-connect snapshot. - Mutter: build_primary_keeping_physicals takes the pre-virtual snapshot and preserves each physical's real mode (pick_keep_mode, unit-tested) - KWin: capture each output's mode when disabling for exclusive, re-assert it on re-enable (a bare enable defaulted to ~60 Hz) - Windows: skip the refresh-resetting SDC_TOPOLOGY_EXTEND when a physical is already active Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a4f81dec48 |
feat(host): user-defined custom display presets
Save named bundles of the display-management policy (the six behavior axes
plus the game-session axis) as custom presets, alongside the built-ins. A
custom preset is data — stored in <config>/display-presets.json — not a Preset
enum variant, so DisplayPolicy::effective() stays pure and the built-in set is
untouched; applying one writes a Custom policy via the existing PUT
/display/settings.
- policy.rs: CustomPreset/CustomPresetInput + load/add/update/delete store
- mgmt.rs: GET/POST /display/presets + PUT/DELETE /display/presets/{id},
surfaced on GET /display/settings
- web console: custom-preset cards with save-as / edit / delete + i18n
- regenerated api/openapi.json; docs
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
077d8f85ca |
feat(host): isolate the zero-copy GPU import in a worker process
The tiled EGL/GL→CUDA import crashed the whole host (SIGSEGV inside libnvidia-eglcore via cuGraphicsMapResources) when the compositor invalidated an imported dmabuf mid-map — reproduced on the Bazzite F44 Game→Desktop switch (design/zerocopy-hardening-handoff.md). A driver SIGSEGV is uncatchable in-process, so the whole EglImporter (tiled EGL/GL→CUDA and LINEAR Vulkan→CUDA) now runs in a per-capture `zerocopy-worker` subprocess: dmabuf fds go over a SEQPACKET socketpair (SCM_RIGHTS, sent once per buffer keyed by dmabuf st_ino; NeedFd resend self-heals cache desync), frames come back as CUDA-IPC pooled device buffers (still zero-copy, +one socket RTT/frame). Worker death poisons the capturer so the existing capture-loss rebuild runs — the host survives; 3 consecutive deaths latch the GPU import off (CPU/SHM path). PUNKTFUNK_ZEROCOPY_INPROC=1 keeps the old in-process import for debugging/A-B. Also fixed along the way: a failed *tiled* import no longer falls through to the CPU mmap de-pad (which scrambled tiled bytes; LINEAR keeps the fallback); Nv12Blit dropped its GL textures while still CUDA-registered (unregister now runs first); GlBlit had no Drop at all (GL objects leaked per size change); VkBridge's per-fd src cache is now invalidated on renegotiation/eviction instead of never. Design: design/zerocopy-worker-isolation.md. Unit tests: 14 new (protocol fd-passing, worker dispatch, client handshake/death/NeedFd, death latch). On-glass validated on the RTX 5070 Ti/GNOME box (.21): the worker path streams at p50 1.30 ms (NV12, 1800 frames 0-mismatched, parity with the in-process path), and a kill -9 of the worker mid-stream is survived by the host and recovered — poison -> capture lost, rebuilding pipeline in place -> a fresh worker in ~185 ms -> streaming resumes (2385 frames, 0 mismatched). A real KWin compositor-crash repro is still pending (a worker kill -9 is strictly harsher, so it corroborates). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
07d46f865c |
docs: zero-copy EGL/CUDA capture-crash hardening handoff
Describes a reproduced host SIGSEGV in cuGraphicsMapResources (inside libnvidia-eglcore), reached via zerocopy::cuda / zerocopy::egl on the tiled EGL/GL->CUDA capture path, when the KWin dmabuf is invalidated mid-map (observed on .181 during a Game->Desktop switch under zero-copy, with the compositor itself crashing). Pre-existing capture-layer issue, not the gamemode work. Issue description + root cause + solution-space considerations only -- the next agent plans the implementation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
1271fd5ea2 |
feat(host): game-mode integration + dedicated game sessions
Implements design/gamemode-and-dedicated-sessions.md (Parts A1-A5 + B0-B2):
reconciles the merged display-management registry with session-mobile
Bazzite/SteamOS hosts and adds a per-launch dedicated gamescope mode.
- A1 DisplayOwnership {Owned,External,SessionManaged} + poolable_now(): the
registry pools only what it owns, so gamescope managed/attach outputs are no
longer double-owned by the registry AND the gamescope restore worker (fixes
the game-mode-reconnect stale-node wedge).
- A2 validated reuse: (backend,mode,launch,epoch) reuse key + kept_display_alive
liveness probe + reused_gen/mark_failed on a reused-display first-frame failure.
- A3 policy-driven managed restore (keep_alive replaces the hardcoded 5s debounce;
forever = held = gaming-rig truthful) + crash-restore persist + SIGKILL teardown
(kill_unit, applied to our transient unit AND the autologin stop -- validated
live on .181 to avoid the F44 GPU-context leak).
- A4 session epoch: observe_session_instance bumps the epoch + invalidate_backend
on a desktop-compositor instance change; gamescope spawns are exempt.
- A5 per-spawn log + PID-scoped gamescope node discovery.
- B0 game_session {auto,dedicated} policy (top-level, preset-orthogonal) +
pick_gamescope_mode dedicated_launch + steam -silent command shaping.
- B1 free the autologin Steam before a dedicated Steam spawn (single-instance).
- B2 game-exit -> APP_EXITED_CLOSE_CODE (0x52) clean session end.
Adversarially reviewed (11 findings fixed). Validated on glass (.181 Bazzite F44,
RTX 4090): dedicated spawn streams a real game smoothly; keep-alive reuse; the
SIGKILL fix avoids the F44 vkCreateDevice leak. Workspace green
(build / test --workspace / clippy -D warnings / fmt), OpenAPI + C header
regenerated, web console tsc + vite build green. clients/probe: bump the
no-video timeout 8s->45s for gamescope cold starts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
42b45113a9 |
fix(android): pass dropped streams through to the ui
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
apple / swift (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / rust (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
release / apple (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, 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
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
|
||
|
|
d76a42e0e9 |
docs(security): add 2026-07-05 whole-project posture audit
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
apple / swift (push) Has been cancelled
arch / build-publish (push) Has been cancelled
audit / bun-audit (push) Has been cancelled
audit / cargo-audit (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
Assessment of the overall security architecture, the state of the prior reviews' remediations, and the process/supply-chain controls. Flags the two web-console residuals (no brute-force throttle; cookie seal key from the login password) that the accompanying web-hardening change fixes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5a51b0d8e7 |
ci: SHA-pin secret-bearing third-party actions + audit the web dep tree
- Pin android-actions/setup-android, appleboy/scp-action, and appleboy/ssh-action to commit SHAs (version kept in a trailing comment). These run in jobs holding the Android signing keystore, Play service-account, and deploy SSH key, so a moved tag on a third-party action could exfiltrate them. - Add a bun-audit job to audit.yml over web/bun.lock — the console holds the login gate, session sealing, and mgmt token, so its deps matter too — and trigger the workflow on web/bun.lock changes alongside Cargo.lock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bda5556d37 |
feat(web): harden login gate — throttle, scoped TLS, token-derived seal key
Remediates the two web-console residuals from the 2026-07-05 posture audit: - Brute-force throttle (loginThrottle.ts): per-IP exponential backoff after 5 free attempts, plus a global floor for spread-out floods, keyed on the socket peer IP (not spoofable X-Forwarded-For) with a size-capped map. The constant-time compare already stopped the timing leak; this bounds guess *volume* against a by-design LAN-exposed console. - Session seal key now derives from the high-entropy mgmt token instead of the low-entropy login password, so a captured cookie is no longer an offline password oracle. Falls back to the password only when no token is configured (dev/local). Rotating the token now invalidates sessions. - Replace the process-wide NODE_TLS_REJECT_UNAUTHORIZED=0 with per-request Bun TLS scoped to the loopback proxy hop; a non-loopback mgmt URL now verifies normally. Dropped the env var from the systemd unit, Steam Deck installer, Windows run scripts, env examples, and web README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ab56536842 |
docs(readme): Windows host capture is IDD-push, not DXGI/WGC
The DXGI Desktop Duplication + WGC relay paths were removed; sealed IDD-push (finished frames pushed straight into the host's own IddCx driver, no screen-scraping) is now the sole Windows capture path. Fix the stale "DXGI/WGC capture" claims in the root and punktfunk-host READMEs, which also contradicted the push-based IDD description already present in the root README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
27c53a4b53 |
perf(android): low-latency decode overhaul — vendor keys, async loop, system tuning
android / android (push) Has been cancelled
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 9m21s
Close the latency gap on the Android client with per-SoC decoder tuning, an event-driven decode loop, and full system integration. - Decoder selection: rank MediaCodecList decoders in Kotlin (hardware/vendor preferred, software avoided, FEATURE_LowLatency probed) and create the chosen one by name. Per-SoC low-latency keys gated on the codec-name prefix: Qualcomm picture-order + low-latency, Exynos (also Google Tensor), Amlogic, HiSilicon; MediaTek vdec-lowlatency set unconditionally. operating-rate = MAX (Qualcomm) vs priority = 0 (else) are mutually exclusive. NVIDIA/Rockchip/Realtek have no vendor key — covered by ranking + the standard low-latency key. - Async decode loop: AMediaCodec async-notify replaces the poll loop, presenting a decoded frame the instant it is ready instead of waiting out a poll interval. Behind USE_ASYNC_DECODE with the synchronous loop kept for A/B during bring-up. - System integration: Wi-Fi FULL_LOW_LATENCY lock and HDMI ALLM (setPreferMinimalPostProcessing) for the stream's lifetime; game_mode_config.xml opting out of OEM downscaling / FPS overrides. - Pipeline: boost the data-plane pump + audio thread priorities, AAudio usage=Game, DSCP marking on by default on Android, ADPF setPreferPowerEfficiency(false), and setFrameRateWithChangeStrategy(ALWAYS) to force the HDMI mode switch on TV. - lowLatencyMode master toggle (default on) as the escape hatch; the stats HUD now shows the resolved decoder name. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
69fcb6e0b1 |
docs: restructure host setup by distro, configuration by compositor
apple / swift (push) Successful in 1m8s
apple / screenshots (push) Successful in 5m33s
android / android (push) Successful in 4m43s
arch / build-publish (push) Successful in 5m38s
ci / web (push) Successful in 1m3s
ci / docs-site (push) Successful in 1m17s
ci / rust (push) Successful in 4m48s
ci / bench (push) Successful in 5m7s
decky / build-publish (push) Successful in 14s
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 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
deb / build-publish (push) Successful in 4m29s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m16s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m24s
docker / deploy-docs (push) Successful in 6s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 10m3s
Split the docs' single distro×desktop axis (ubuntu-gnome / ubuntu-kde / fedora-kde) into two,
which deduplicates the shared mechanics and scales to distros that run several desktops (Arch):
- Install the host — per distro/OS (ubuntu, fedora, arch, bazzite, steamos-host, windows-host):
GPU driver + package + input group, then a canonical "Configure your desktop" funnel.
- Configure your desktop — per compositor (kde, gnome, gamescope, sway): host.env, compositor
quirks, the headless session, and starting the host.
New shared web-console page (enable · login password · arm pairing) removes the console/password
block that was copy-pasted across all seven host pages. Merged ubuntu-gnome + ubuntu-kde into
ubuntu; renamed fedora-kde to fedora; kept bazzite and steamos-host as dedicated appliance guides
(trimmed of duplication). Moved the KWin headless session, the GNOME EGL/lock traps, and the
gamescope attach/managed model out of the distro pages onto their compositor pages.
Fixed while restructuring: distro-specific paths on kde (kde-desktop-setup.sh is Fedora/Bazzite-only;
the .deb ships host.env.kde under /usr/share/punktfunk-host), the interactive "start the host" step
that was lost in the merge, sway over-claiming Hyprland, and a pre-existing broken anchor in
how-it-works.
Removal of the three old pages was captured by the preceding commit
|
||
|
|
8ebb61400c |
fix(web): clearer topology/identity copy, capped description width, mobile More-nav, preset spacing
apple / swift (push) Successful in 1m12s
apple / screenshots (push) Successful in 5m18s
windows-host / package (push) Successful in 7m38s
android / android (push) Successful in 12m11s
arch / build-publish (push) Successful in 9m7s
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 1m18s
ci / rust (push) Successful in 4m47s
ci / bench (push) Successful in 5m3s
decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 32s
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
deb / build-publish (push) Successful in 4m31s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 57s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m48s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 10m28s
docker / deploy-docs (push) Successful in 20s
Console polish on the Virtual displays card + shell: - Topology help now leads with the streamed display's role (Extend/Primary/Exclusive) instead of the confusing physical-monitor-only framing; notes the headless case. Identity help spells out the actual behavior (stable per-client identity → the desktop reapplies that client's scaling/resolution on reconnect) + what Shared / Per-client / Per-client+resolution each do. - Cap description/help width at max-w-prose so long help text isn't a full-viewport line on large screens. - Mobile bottom nav: 8 flat tabs were too cramped → 4 pinned tabs + a "More" tab whose sheet holds the rest (Performance/Logs/Pairing/Settings), "More" highlighted when the active route is in the overflow. - More breathing room under the "Preset" heading. web tsc + biome + vite build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b57e414618 |
chore(release): bump workspace version to 0.8.0
apple / swift (push) Successful in 1m10s
audit / cargo-audit (push) Successful in 1m23s
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 1m23s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 3m58s
release / apple (push) Successful in 9m58s
ci / bench (push) Successful in 4m55s
ci / rust (push) Successful in 8m57s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 4m18s
apple / screenshots (push) Successful in 5m55s
windows-host / package (push) Successful in 7m56s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m19s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m16s
android-screenshots / screenshots (push) Successful in 2m29s
arch / build-publish (push) Successful in 5m11s
decky / build-publish (push) Successful in 19s
android / android (push) Successful in 4m36s
flatpak / build-publish (push) Successful in 5m7s
linux-client-screenshots / screenshots (push) Successful in 6m15s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m14s
web-screenshots / screenshots (push) Successful in 2m40s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m45s
docker / deploy-docs (push) Successful in 7s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
deb / build-publish (push) Successful in 4m21s
The [workspace.package] version (inherited by every crate via version.workspace) is the release being cut; refresh the 9 workspace entries in Cargo.lock to match (CI builds --locked). Canary derives from the tag (scripts/ci/pf-version.sh), so cutting v0.8.0 auto-advances canary to 0.9.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>v0.8.0 |
||
|
|
ec40a4062f |
build: pin the exact Rust toolchain (1.96.0) to stop rustfmt drift
apple / swift (push) Successful in 1m23s
arch / build-publish (push) Successful in 5m56s
android / android (push) Successful in 6m38s
apple / screenshots (push) Successful in 5m50s
ci / web (push) Successful in 1m10s
ci / docs-site (push) Successful in 1m27s
ci / bench (push) Has been cancelled
ci / rust (push) Has been cancelled
decky / build-publish (push) Successful in 14s
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 6s
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 55s
windows-host / package (push) Successful in 14m52s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
deb / build-publish (push) Has been cancelled
rust-toolchain.toml pinned the floating "stable" channel, so the CI image baked whatever stable existed at image-build time. When the image is rebuilt onto a newer stable, rustfmt's rules shift and `cargo fmt --all --check` fails on files nobody touched — the recurring format-drift that keeps red-lighting CI. Pin channel = "1.96.0" (== today's stable: rustc ac68faa20, rustfmt 1.9.0-stable), the exact build CI already runs, so this is a no-op now but locks formatting for good: local dev, the Linux CI image, and the Windows runner all use rustup and honor this file, so they converge on one rustfmt. Formatting now only changes in a deliberate bump-this-pin- and-reformat commit. ci.yml cache comment updated to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
19c35de3d7 |
style(host): cargo fmt --all (rustfmt 1.9.0 drift)
The CI image's rustfmt reformats these files (multi-line assert!/tracing! macros, match-arm and struct-variant wrapping) — pre-existing drift that the Format job caught. Reformat to match. Pure formatting; no logic change. main.rs also gets a blank line before a standalone comment so rustfmt stops mis-indenting it as a trailing-comment continuation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
aa012c6b45 |
feat(vdisplay/mutter): pin the client's refresh by default; drop PUNKTFUNK_MUTTER_VIRTUAL_REFRESH
The >60 Hz virtual-monitor path (RecordVirtual "modes" with the client's exact WxH@Hz) was gated behind PUNKTFUNK_MUTTER_VIRTUAL_REFRESH, default OFF, after a high-refresh virtual CRTC SIGSEGV'd gnome-shell on session teardown. That crash was since fixed by stopping the screencast before any monitor reconfig, so the gate is dead weight — and a silent footgun: every non-headless GNOME client was capped at Mutter's PipeWire-derived 60 Hz unless they knew the hidden flag. Make it the default: the custom-mode path now runs whenever mode.refresh_hz > 60 (≤60 Hz stays byte-identical to before — Mutter's 60 Hz default is already correct), and the virtual_refresh_enabled() env read is removed. Docs updated (configuration.md env table, vrr-plan.md reference). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
74c9e46faf |
fix(vdisplay/windows): Windows clippy — per-block SAFETY comments + then_some
apple / swift (push) Successful in 1m10s
android / android (push) Successful in 4m38s
ci / rust (push) Failing after 30s
arch / build-publish (push) Successful in 5m25s
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 59s
apple / screenshots (push) Successful in 5m37s
windows-host / package (push) Successful in 7m54s
deb / build-publish (push) Successful in 3m1s
decky / build-publish (push) Successful in 14s
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 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
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 4s
ci / bench (push) Successful in 5m34s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m16s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 10m6s
The branch's Windows-host code never ran through the fleet Windows clippy (kept off CI); the merge to main exposed it. Fix the 4 -D warnings failures in cfg(windows) code: - manager.rs: 3 unsafe blocks (isolate_displays_ccd / force_extend_topology / set_virtual_primary_ccd) had one "both arms" SAFETY comment on the `match` line — clippy::undocumented_unsafe_blocks wants it immediately before each block. Split into per-block SAFETY comments. - win_display.rs: `.then(|| …)` on a POD u32 union read → `.then_some(…)` (eager is fine, discarded when false) for clippy::unnecessary_lazy_evaluations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
95b3496bb5 |
merge: display-management (Stages 0-5 §6A + keep-alive hardening + gaming-rig)
apple / swift (push) Successful in 1m15s
windows-host / package (push) Failing after 3m56s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m18s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m20s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 57s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m2s
release / apple (push) Successful in 8m43s
android / android (push) Successful in 4m17s
ci / rust (push) Failing after 29s
ci / web (push) Successful in 49s
arch / build-publish (push) Successful in 5m33s
ci / docs-site (push) Successful in 59s
apple / screenshots (push) Successful in 5m42s
deb / build-publish (push) Successful in 3m7s
decky / build-publish (push) Successful in 14s
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 28s
ci / bench (push) Successful in 4m41s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 44s
flatpak / build-publish (push) Successful in 4m30s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m21s
docker / deploy-docs (push) Successful in 20s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m25s
Merges display-mgmt-stage0 — the user-configurable virtual-display policy layer above the
per-compositor backends. On-glass validated (KWin .116 + Mutter .21; Windows compile-verified .173):
- Policy surface (keep_alive · topology · conflict · identity · layout · max) →
display-settings.json, console-editable via /api/v1/display/{settings,state,release,layout} + a
dedicated "Virtual displays" console section. All five axes enforced, not just stored.
- Lifecycle: pure state machine + Linux keep-alive pool (registry + DisplayLease ownership split),
incl. keep_alive=forever/Pinned (freed via /display/release); topology extend/primary/exclusive
(group-aware); per-client identity (KWin per-slot names → KDE scaling round-trips); mode_conflict
admission (Windows default reject, single-capturer IDD); §6A multi-monitor (display groups +
layout engine + console arrangement table — several clients as monitors of one desktop).
- Keep-alive reconnect hardened: same-client zombie preempt (never a 2nd display), deliberate-quit
skip-linger (QUIT_CLOSE_CODE), tunable idle timeout (PUNKTFUNK_IDLE_TIMEOUT_MS).
Conflicts (packaging/{arch,debian}/README.md firewall docs): kept main's ufw/nft port commands +
the branch's --data-port documentation. build + clippy -D warnings + cargo test --workspace
(18 suites, 0 failed) green on the merged tree.
|
||
|
|
334f36ce25 |
test(punktfunk1): serialize in-process-host tests (shared admission table)
The reconnect-preempt ( |
||
|
|
88348153f3 |
feat(apple): wake-until-up overlay + host edit with MAC prefill
apple / swift (push) Successful in 1m10s
arch / build-publish (push) Successful in 5m17s
android / android (push) Successful in 7m30s
ci / web (push) Successful in 1m7s
ci / docs-site (push) Successful in 1m11s
release / apple (push) Successful in 8m39s
ci / rust (push) Successful in 4m53s
deb / build-publish (push) Successful in 2m58s
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 6s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
ci / bench (push) Successful in 4m50s
apple / screenshots (push) Successful in 5m44s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m9s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m52s
- HostWaker + WakeOverlay: after sending the Wake-on-LAN packet, wait until the host is really back (resend + mDNS poll, timeout, cancel/retry) before connecting. macOS-only in practice — WoL stays gated off on iOS/tvOS pending the multicast entitlement. - Add/Edit host sheet gains a Wake-on-LAN MAC field, prefilled from the stored MAC or the live mDNS advert; parseMacs validates aa:bb:cc:dd:ee:ff. - Gamepad chrome/home and glass-style polish. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
4a87cef98c |
feat(android): console UI, wake-on-LAN wait-until-up, host edit + TV/tablet polish
Bring the Android client to parity with Apple's gamepad experience and finish Wake-on-LAN. - Console/gamepad home: host carousel, aurora chrome, mTLS game-library coverflow, and an input-aware legend that switches between gamepad face buttons and a TV-remote select-ring + arrows based on the last-used input. - Wake-on-LAN: the fire-and-forget send is upgraded to wait-until-up (WakeController/WakeOverlay: resend + mDNS poll, 90s timeout, cancel/retry, fingerprint-matched so a host that cold-boots onto a new DHCP IP still connects), plus host edit (touch dialog + console form) with an auto-filled MAC. - Android TV: brand banner (android:banner), density-aware console scaling, D-pad/ remote nav (Up = Settings, Down or the pad Select button = host Options), emergency stream-exit chord, and 120Hz console refresh. - Touch UI: settings split into subpages with a tablet NavigationRail, axis-aware tab animation (horizontal on phones, vertical on the tablet rail), animated settings navigation, and a licenses screen with a back button + the real workspace version (read from Cargo.toml). - Vector Lock/controller icons (no emoji); bundled Geist font. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fc1e8a8a32 |
test(mgmt): display_settings_surface stays read-only (gaming-rig now accepted)
The old `..._and_forever_rejected` asserted a 400 for keep_alive=forever; now that it's accepted, that PUT succeeded and WROTE gaming-rig into the process-global prefs, racing other tests. Rewrite read-only: assert the surface (5 presets, effective, enforced axes) and read gaming-rig=forever off the preset list — no write. Acceptance is covered on-glass (.116) + the pure policy tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
69f4c987f6 |
feat(tray): surface kept virtual displays in the tray tooltip
Stage 8 polish. `GET /api/v1/local/summary` (the tray's loopback-only unauthenticated status
source) gains `kept_displays` — the count of lingering/pinned virtual displays (held with no live
session), over the already-validated `registry::snapshot()`. The tray shows it in the idle tooltip
("idle · 1 display kept"), so a user knows a display — and, under exclusive topology, their physical
monitors — is being held (e.g. a gaming-rig `forever` pin). Release stays via the console: a
state-changing release can't be an unauthenticated endpoint, and the non-elevated Windows tray
can't read the SYSTEM-DACL'd mgmt token, so a tray release button isn't cleanly cross-platform.
`#[serde(default)]` on the tray side keeps it compatible with an older host. Tray tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
468a60c88a |
docs(display-management): Stage 8 consolidation — docs page, README, CLAUDE.md, host.env
Bring the user-facing + project docs in line with the shipped, on-glass-validated state (Stages 0-5 §6A + keep-alive hardening + gaming-rig) ahead of a merge decision: - docs-site/virtual-displays.md: drop the now-false "stored but not yet enforced / following release" caveats — conflict handling, per-client identity + KDE scaling round-trip, and §6A multi-monitor layout are all live; gaming-rig/forever ships (freed via Release); document the reconnect-always-resumes + deliberate-quit-skips-linger behavior and the PUNKTFUNK_IDLE_TIMEOUT_MS knob. KDE persistent scaling → ✅ today (validated); Windows primary → shipped; Sway exclusive stays "following release". - README: a "displays you configure, not just create" differentiator bullet. - CLAUDE.md: the display-management invariant now reflects Stages 0-5 shipped (all axes enforced, forever/Pinned, hardened reconnect) instead of "Stage 0 shipped". - host.env.example: document PUNKTFUNK_IDLE_TIMEOUT_MS + that display policy lives in the console. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fad1e01408 |
docs(display-management): keep_alive=forever (gaming-rig) shipped
§5.1 + §7 matrix: Windows `Pinned` is shipped (
|
||
|
|
04a397be84 |
fix(vdisplay/windows): drop an unused mut in isolate-retry (clippy -D warnings)
`let (mut paths, mut modes)` — `modes` is only read (`modes.as_slice()`), never mutated. A
pre-existing unused_mut (from
|
||
|
|
ccbd7e8880 |
feat(vdisplay): ship keep_alive=forever (gaming-rig) — Windows MgrState::Pinned
Completes the last §6A-era preset. The Linux registry already resolved forever→Pinned (pure
lifecycle machine); the blockers were the Windows manager, the mgmt reject, and the console tag:
- Windows manager: new `MgrState::Pinned { mon }` — the last-released monitor under keep_alive=forever
is kept indefinitely (like Lingering but the linger timer never fires). A reconnect preempts +
recreates it (same as Lingering — a reused IddCx swap-chain is dead), snapshot reports "pinned",
and `force_release` (POST /display/release, the §8 escape hatch) frees a pinned monitor. release()
branches on the new `keep_alive_forever()`; all MgrState matches made exhaustive over Pinned.
- mgmt PUT /display/settings: stop rejecting keep_alive=forever (now honored on both platforms with a
release path). OpenAPI regenerated.
- web: un-disable the gaming-rig preset (DISABLED_PRESETS now empty) — one-click applies.
Linux paths + web/tsc/openapi green; 47 vdisplay tests pass. The Windows manager.rs is #[cfg(windows)]
(not compilable on the Linux dev box) — build-verified + on-glass validation on .173 to follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
a0546b36b6 |
docs(display-management): record Stage 5 §6A on-glass validation + keep-alive hardening
Honesty pass after the 2026-07-05 on-glass session — the plan now reflects what was actually
validated, no more/no less:
- Stage 5 (§6A): HOST-SIDE → DONE + on-glass validated (KWin .116 + Mutter .21): group model,
positions, identity keying, group-aware exclusive/extend coexistence, 2 concurrent Mutter
RecordVirtual monitors. Remaining is hardware-gated residuals ONLY (per-group physical-restore
EFFECT needs a monitor-attached box — headless reports also_disabled=[]; wlroots exclusive;
Mutter APPLY_TEMPORARY revert).
- Stage 3: the KDE set-scaling ROUND-TRIP is now proven live (150%/125% → disconnect → reconnect
→ reapplied, kwinoutputconfig.json) — moved from Deferred to Validated. Closes the Stage-3 gate.
- §5.1: the explicit-quit bypass (
|
||
|
|
b71dc94bb2 |
test(probe): --seconds stream cap + flush the QUIC close before exit
Two probe test-infra fixes needed to validate the keep-alive hardening (
|
||
|
|
c2bc72a8e9 |
fix(packaging): correct CachyOS firewall to ufw + ship ufw openers + web-console opener
apple / swift (push) Successful in 1m11s
android / android (push) Successful in 4m1s
apple / screenshots (push) Successful in 4m29s
arch / build-publish (push) Successful in 5m52s
ci / web (push) Successful in 1m16s
ci / docs-site (push) Successful in 1m11s
ci / rust (push) Successful in 4m54s
deb / build-publish (push) Successful in 3m0s
decky / build-publish (push) Successful in 24s
ci / bench (push) Successful in 4m44s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 32s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m50s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m30s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m18s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 10m14s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 10m5s
docker / deploy-docs (push) Successful in 22s
CachyOS ships ufw enabled by default (firewalld is not installed) — verified live on the .21 box — but the docs and shipped firewall openers claimed "CachyOS enables firewalld by default". Correct that everywhere and ship a ufw application profile (the one-liner analogue of the firewalld service files): - packaging/linux/punktfunk.ufw (new): [punktfunk-native], [punktfunk-gamestream], [punktfunk-web] profiles, installed to /etc/ufw/applications.d/punktfunk by the Arch (CachyOS) and .deb host packages. `sudo ufw allow punktfunk-native`. - packaging/linux/punktfunk-web.xml (new): firewalld service for the optional web console (TCP 47992), installed by the host package on arch/deb/rpm. Neither the native nor gamestream opener covered 47992, so a firewalld/ufw host that enabled punktfunk-web could not reach the console over the LAN. - Fix the "CachyOS enables firewalld" claim in arch.md, arch/README.md, debian/README.md, both firewalld service .xml comments, and the pacman scriptlet; firewalld now attributed to the spins that use it (EndeavourOS, Fedora/RHEL). - Docs present both one-liners (ufw + firewalld) whichever firewall you run, plus a console-opener step; postinst/scriptlet hints detect ufw as well as firewalld. The native data plane stays hole-punched (ephemeral UDP, no fixed port) — its openers correctly open only 9777/udp + mDNS; the stale "open a UDP range" note is replaced with the accurate outbound-UDP explanation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b53710da1a |
feat(vdisplay): harden keep-alive reconnect — same-client preempt, quit-skips-linger, configurable idle
On-glass testing (Test 2, KWin .116) surfaced that a reconnect within the QUIC idle-timeout window (~8s) lands on a fresh SECOND display instead of reusing the kept one: the old session was still Active (not yet Lingering), so the registry's keep-alive reuse (which only matches Lingering) skipped it and the old session kept streaming to nobody. Three fixes: #3 Same-client reconnect preempt (the real fix): admission::preempt_same_identity() lists a reconnecting client's OWN still-live session(s) (same cert fingerprint); serve_session signals their stop + waits the release grace BEFORE acquiring, so the zombie tears down → its display lingers → the reconnect REUSES it instead of making a second. Implements the "preempts downstream" the admission docs already promised. Independent of the mode_conflict policy; the pure core (same_identity_stops) is unit-tested. #2 Deliberate quit skips linger: a client that deliberately disconnects closes the QUIC connection with QUIT_CLOSE_CODE (0x51, shared in core::quic); the host reads the ApplicationClosed reason and tears the display down immediately (registry release() gained force_immediate → Linger::Immediate; multi-session-safe via the pure lifecycle machine), while a bare disconnect still lingers for reconnect. Threaded via a session quit flag → the DisplayLease. NativeClient::disconnect_quit() + punktfunk-probe --quit drive it; GameStream (Quit App / h_cancel) is a documented follow-up. #1 Configurable disconnect-detection latency: the QUIC control-connection idle timeout (stream_transport, 8s default) is host-tunable via --idle-timeout-ms / PUNKTFUNK_IDLE_TIMEOUT_MS, clamped >=1s with a keep-alive that scales to it so a live session never false-closes. Default unchanged (8s stays load-bearing for the Windows IDD-push reconnect flow). Workspace check + 63 core / 215 host / 47 vdisplay tests green; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
c1acfe8b85 |
fix(web): preset cards use the design-system animated Card (motion + material)
The preset options were raw <button>s — flat, no motion/material — unlike the rest of the console. They now render as the `interactive` AnimatedCard (motion hover + specular material, consistent with every other card), keyboard-accessible (role=button + Enter/ Space), with a 2px primary ring for the active one and a proper disabled state for gaming-rig. web tsc + vite build + biome-lint green; deployed on .21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2e43fcc27c |
fix(web): unify display-field spacing (shared Field) + clearer layout help
- Every option in the custom form now renders through one `Field` wrapper (label → control → help at a consistent `space-y-3`), so the label→input gap is roomier and identical across keep-alive, the button groups, and max-displays — the first field no longer spaces differently from the rest. - Reworded the multi-monitor layout help: it now says Auto is side-by-side and Manual gives a per-display X/Y editor "in the Live displays section below once two or more are streaming" — instead of pointing at an "arrangement table" that isn't visible until clients connect. web tsc + vite build + biome-lint green; deployed on .21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2aa7ac8c7e |
fix(web): explicit keep-alive Off/Keep toggle + roomier custom display form
Two UX fixes on the Virtual displays Configuration card: - Keep-alive is no longer implicitly "on" by typing in the seconds field. It's an explicit two-button toggle — **Off** (tear down at disconnect) vs. **Keep for** [N] seconds — and the seconds input only appears when "Keep for" is selected. The duration is remembered across toggles, and the help text explains both modes. - Opened up the cramped custom form: the fields container is `space-y-6` with more padding (`p-5`, rounded-lg), each option group is `space-y-2.5`, and the Save button sits below a divider — so it reads as sections with room instead of a pressed stack. web tsc + vite build + biome-lint green; deployed on .21. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
6b4f9f86ed |
feat(web): move Virtual displays to its own nav section; roomier preset grid
The Host page was crowded (identity, codecs, ports, GPU, displays, compositors) and the virtual-display config surface is large enough to warrant its own home. - New **Virtual displays** nav section: `/displays` route + `sections/Displays` (moved DisplayCard out of `sections/Host`), a `MonitorPlay` sidebar entry after Host, and `nav_displays` i18n. Removed the displays card from the Host page/view. - On its own page the card splits into two: **Configuration** (presets + custom axes) and **Live displays** (the live list + arrangement table) — room to breathe. - Presets now render in a max-2-column grid (`sm:grid-cols-2`) with larger padding, a bigger section heading + preset titles (text-base semibold), roomier spacing, and bottom-aligned "what it sets" badges so the cards line up. web tsc + vite build + biome-lint green; deployed + verified on the Mutter box (.21). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8986667b78 |
feat(web): full virtual-display config surface — one-click presets + every axis editable
The Virtual displays card previously only exposed keep_alive/topology/max_displays as editable custom fields; conflict/identity/layout (enforced since Stages 3-5) had no controls, and the presets weren't surfaced as one-click options. Rework the card so the whole policy is configurable WITHOUT any client connected: - Presets front-and-center: each of the five (default/shared-desktop/hotdesk/workstation/ gaming-rig) is a one-click row showing its story AND what it sets (keep-alive · topology · conflict · identity badges), highlighting the active one. A click applies it immediately. gaming-rig stays disabled + "coming soon" (keep_alive: forever isn't cross-platform yet). - Custom mode reveals EVERY axis editably — keep-alive, topology, conflict, identity, layout, max-displays — seeded from the current effective behavior, with a Save button. A reusable `Choice` button-group + a tolerant `tr()` label lookup keep it tidy. - The live-display list + multi-monitor arrangement table stay below (they need a live session); the settings above work standalone. - en+de i18n for the new controls; refreshed the effective-preview row to show all axes. web tsc + vite build + biome-lint green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
62e0367f4b |
feat(punktfunk1): configurable data-plane UDP port (--data-port)
The native data plane used a random ephemeral UDP port (hole-punched), which a strict firewall can't pre-open — so remote clients behind one couldn't connect. Add an optional fixed data port: - `Punktfunk1Options`/`NativeServe` gain `data_port`; `bind_data_socket` binds the fixed port (→ direct, no hole-punch) or falls back to a random port + hole-punch when unset or the fixed port is busy (a concurrent session already holds it). - `UdpTransport::from_socket`/`from_socket_punch` adopt an already-bound socket, so the host keeps the SAME data socket from handshake through streaming — no drop-then-rebind window in which a concurrent session could steal a fixed port. - `main.rs` wires the CLI flag through to `NativeServe`. - Firewall docs updated (troubleshooting.md + apt/pacman/bazzite READMEs): control plane is the fixed UDP 9777; the data plane is a separate random port that usually needs no rule, with the fixed-port option for strict firewalls. Unit-tested: default random+hole-punch, and fixed-port-then-fallback-when-busy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
677a4f4cf5 |
perf(gamestream): move FEC packetization off the encode loop (3-stage pipeline)
FEC/Reed-Solomon packetization ran inline on the encode loop (~3 ms/frame at 4K), serializing behind encode and capping the GameStream frame rate below what the encoder alone can sustain. Split it into a 3-stage pipeline, each stage on its own thread joined by a depth-2 bounded queue: encode loop → [raw AUs] → packetizer (FEC/RS) → [wire batch] → paced sender - `spawn_packetizer`: turns each `RawFrame`'s access units into wire datagrams via the stateful VideoPacketizer, off the encode loop. Above-normal priority (on the per-frame critical path). Tallies goodput (bytes to the wire) for the stats window. - Backpressure chains up: a slow sender blocks the packetizer, which fills the encode→packetizer queue, which makes the encode loop drop the NEWEST frame — encode itself never waits. - A dropped frame now consumes no client-visible frameIndex (packetization is downstream), so the host re-anchors the reference chain: a drop arms a keyframe on the next iteration (`recover_after_drop`), routed through the same coalesce gate as client IDR requests so a burst of drops (congestion) can't become an IDR storm. - Perf/stats relabeled: `pkt` = AU drain, `send` = enqueue to the pipeline (both should be near-zero now; nonzero = encode being stalled by pipeline backpressure). Goodput read from the packetizer's atomic at the 1 s stats boundary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
fa45608628 |
docs(display-management): Stage 5 host + web build complete — only on-glass validation + residuals left
Mark the web arrangement table done and narrow "remaining Stage 5" to validation (2 clients on a GPU box, not the dev VM) plus the two documented residuals (wlroots exclusive, Mutter APPLY_TEMPORARY revert). No further host/web build work in Stage 5. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a7ff1cf312 |
feat(web): display arrangement table — the Stage 5 console x/y editor
Completes Stage 5's web piece (design/display-management.md §6.2): a `DisplayArrangement` editor in the Virtual displays card. For a ≥2-display group, it renders an x/y table over the live displays that carry a stable identity slot (the manual-layout key), seeded from the current computed positions; Save writes `PUT /display/layout` (via the generated `useSetDisplayLayout`), which switches the host to a manual layout applied from the next connect. Shared/anonymous displays (no identity slot) are omitted (they can't be pinned). Also refreshes the now-stale `display_pending_note` copy (conflict/identity/layout ARE enforced as of Stages 3-5) in en + de. web tsc + vite build green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
87435e6547 |
feat(vdisplay): complete Stage 5 §6A group semantics — per-group restore, Mutter group-aware, gamescope groups
Host-side completion of Stage 5 (§6A many-clients-as-monitors), all unit-tested; two-session on-glass validation still pending (no GPU on the dev VM): - Per-group topology restore (§6.1): the KWin `exclusive` restore no longer rides the per-session StopGuard (which re-enabled the physical the moment the FIRST of several exclusive sessions dropped, under a live sibling). KWin hands its restore to the registry as a closure (new trait `take_topology_restore`); the registry keeps it in the display group (`Entry.topology_restore`) and, on teardown, floats it to a surviving same-group sibling (`hand_off_restore`) or runs it when the group empties — outside the lock, before the last output's keepalive drops, so the compositor never sees zero outputs. All three teardown paths (lease drop / linger expiry / mgmt release) honor it. Single-display path byte-for-byte unchanged. Unit-tested: float / run-on-last / non-carrier-first / never-cross-backend. - Mutter group-aware (new trait `set_first_in_group`): the registry tells each backend whether it's the first display of its group; a non-first Mutter session EXTENDS into the already-exclusive desktop instead of re-applying a sole-monitor ApplyMonitorsConfig that would disable the first session's virtual. (Mutter connectors are un-nameable, so it can't build a keep-all-virtuals config; skipping is the safe equivalent.) Single-session unchanged. Residual APPLY_TEMPORARY revert documented. - gamescope groups (§6.1): `registry::group_key` makes each gamescope spawn its own group (independent nested session, no shared desktop) — never auto-rowed against or restore-/topology-grouped with another gamescope. Applied in both the /display/state assembly and the acquire-time position computation. Unit-tested. Remaining Stage 5: the web console arrangement table, on-glass validation, and the documented residuals (wlroots exclusive, Mutter APPLY_TEMPORARY). design doc updated. cargo build/test (214)/clippy --all-targets/fmt green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e0f15822ae |
feat(vdisplay): Stage 5 layout foundation — arrangement engine + /display/layout + group placement
§6A layout, riding the Stages 1-3 registry with no protocol change: - vdisplay/layout.rs: pure arrangement engine — auto-row (left-to-right in acquire order, top-aligned) + manual (per-identity-slot offsets, auto-row fallback for unpinned members). Unit-tested. - Registry group model (Linux): group = backend (one desktop per compositor session). /display/state groups entries, orders by acquire (gen), and computes each member's position via the engine (pure `assemble_displays`, unit-tested). DisplayInfo carries group/display_index/position/identity_slot/topology. The backend reports its resolved slot via the new VirtualDisplay::last_identity_slot (KWin only), so the arrangement + state key on per-client identity. - Registry-driven position apply: new VirtualDisplay::apply_position(x,y) (default no-op; KWin drives kscreen-doctor). Right after create the registry computes the new display's position over its whole group (pure `position_for_new`, unit-tested) and applies it — one seam for BOTH deterministic auto-row AND manual placement. Guarded: the origin (0,0) is skipped, so a single-display / first-of-group session (and every non-KWin backend) issues no positioning — the historical single-display path is unchanged. On-glass-validation-pending. - PUT /api/v1/display/layout: persists the console's manual arrangement via the pure EffectivePolicy::with_manual_layout transform (locks current effective behavior into explicit Custom fields + sets a manual layout, so arranging is orthogonal to the other axes). OpenAPI regenerated. - /display/settings `enforced` now lists all five axes (keep_alive, topology, mode_conflict [Stage 4], identity [Stage 3], layout [Stage 5]) — was stale at keep_alive+topology; the console reads it to know which controls are live. Still Stage-5 TODO (design/display-management.md §11): Mutter/wlroots group-aware analogues, per-group topology restore, the web arrangement table, gamescope decline. cargo build/test/clippy/fmt green; OpenAPI in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a5dc3134de |
docs(display-management): handoff — mark Stages 0-4 done, Stage 5 started
Update the design doc for handoff: top-of-doc status, a Status/handoff block in §11 (per-stage state, validation boxes, key decisions), and per-stage [DONE]/[STARTED] markers. Records the decisions that diverged from the plan as written — the Windows admission default is reject (single-capturer IDD-push), reject is typed (QUIC 0x42), Stage 5's group-aware exclusive fixes a Stage-3 latent bug — and what's left in Stage 5 (Mutter/wlroots analogues, layout, /display/layout, per-group restore). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
eddcd91f48 |
feat(vdisplay/kwin): group-aware exclusive — never disable a sibling output (Stage 5 §6.1)
The critical latent bug Stage 3 introduced: per-slot output names mean a 2nd exclusive session's other_enabled_outputs() (which disabled 'everything not named Virtual-punktfunk') would black out the 1st session's Virtual-punktfunk-<id> output. Fix: recognise the whole managed group by the shared Virtual-punktfunk prefix — exclusive now disables only NON-managed outputs (bootstrap/physical), never a group sibling. Plus first-slot-wins for the group primary (a_managed_output_is_primary): a later session joins as a secondary monitor of the shared desktop instead of stealing the shell off the first. Unit-tested. Start of Stage 5 (§6A many-clients-one-desktop). Remaining: Mutter/wlroots group-aware analogues, layout (auto-row/manual + /display/layout + console), per-group topology restore, gamescope groups. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
23446fa177 |
fix(vdisplay): Windows admission default is reject, not join (single-capturer limit)
Two concurrent Windows sessions both drive the same pf-vdisplay monitor's single-capturer IDD-push channel (newest-delivery-wins), which freezes the live client and can wedge the driver (observed live: a concurrent-session test wedged .173 → Moonlight 'no video'; needed a reboot). True multi-session capture is §6.6/ Stage 7. So on Windows 'separate' (incl. the unconfigured default) now resolves to REJECT — a 2nd client gets a clean 503 and the live session is protected — instead of join (which would freeze it). join/steal stay explicit opt-ins; Linux keeps separate (real multi-view). Centralized as admission::effective_conflict(), shared by the native handshake + GameStream h_launch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
980939ed6b |
refactor(gamestream): extract + unit-test gamestream_admission (Stage 4)
Pull the GameStream mode-conflict decision out of h_launch into a pure gamestream_admission(live, req_fp, policy) -> GsDecision so the 503/join/take-over logic is unit-tested (no live session / same-client → Serve; different client → Reject/Join/Serve per policy; anonymous requester treated as different) — the GameStream path can't be driven without a Moonlight client, so this covers the logic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |