0868b6a3647029a9d13532d2c57e3112a42f14ef
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0868b6a364 |
fix(vdisplay): compositor availability follows the live session, not the env
GNOME/Mutter reported "Unavailable" on a host sitting in a live Mutter session — and, on the same request, "Default", because the two columns had different sources. available() asked each backend, and those probes read the process env (XDG_CURRENT_DESKTOP for Mutter, WAYLAND_DISPLAY for KWin's registry handshake, SWAYSOCK for sway) — env a host started outside the session (systemd --user, a TTY, ssh) never inherited. It is only retargeted at the live session on the connect path, so the answer also flipped depending on whether anyone had connected yet. Both columns now come from the same /proc scan detect() already used: the live session's compositor is usable by definition, as is an explicit operator pin, and the per-backend probe stays as the fallback for backends that are not the live session (gamescope, which spawns its own). A live KWin without the zkde_screencast grant now surfaces as available and fails at create with that probe's precise message, which beats "no usable compositor" on a box visibly running KDE. Mutter's env sniff stays deliberately narrow — one var, not three. XDG_CURRENT_DESKTOP is the one apply_session_env owns end to end (written per connect, scrubbed when nothing is live); sniffing DESKTOP_SESSION alongside would resurrect the bug that scrub exists to prevent, where a stale value after a gnome-shell crash routes the next client into a dead session. Non-Linux hosts now report no compositors at all rather than five Linux backends flagged unavailable with no default — on Windows the pf-vdisplay driver is the only backend and vdisplay::open ignores the argument, so the old list read as broken detection instead of "not applicable here". The console says so explicitly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit eb7ba3d6177552f5c3ed6a15439404084869c636) |
||
|
|
b08226dbf1 |
feat(vdisplay): mirror a pinned monitor on sway and Hyprland
P5 of design/per-monitor-portal-capture.md, and the end of a hole worth naming: the pin was accepted, persisted and offered in the console while `create` bailed on these two backends, so a sway or Hyprland host with a monitor selected failed EVERY session. Both already ship the mechanism — a managed portal-chooser config plus a per-session selection file (xdpw's `Monitor: NAME`, xdph's `[SELECTION]screen:NAME`) — so mirroring is that same chooser pointed at a physical connector instead of a headless output we created. The keepalive stops the cast and nothing else; the monitor is the compositor's. The selection write is now serialized against the handshake that reads it (SELECTION_LOCK, applied to the virtual-output paths too). That file is one per-user path: whoever writes last before the portal reads wins, and the loser does not fail — it silently captures the other session's output. `managed` is no longer grounds for refusal everywhere. It is conclusive only where the name is ours by construction (KWin's `Virtual-punktfunk`, Hyprland's `PF-N`); sway names EVERY headless output `HEADLESS-N`, its own included, so the old rule would have refused to mirror any output on a headless sway box — exactly the remote setup this feature serves. Found on-glass, and now a test. Verified on a two-output headless sway: asking for HEADLESS-1 yields 1280x720 frames and HEADLESS-2 yields 1920x1080, so the chooser really does steer per request. Hyprland is compile-only — no Hyprland box exists. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bf2f54a5fe |
feat(vdisplay): mirror a pinned monitor on Mutter, and scope the HDR probe
P3 of design/per-monitor-portal-capture.md. Mutter's mirror is the same private ScreenCast session the virtual path already drives, one call different — RecordMonitor instead of RecordVirtual — so it inherits what makes that path work headlessly: a direct D-Bus API that needs no interactive approval, unlike the xdg portal a background service could never answer. Deliberately not under TOPOLOGY_LOCK: that serializes operations which add or remove a monitor or apply a monitor configuration, and mirroring does neither. Nothing is created, no layout changes, and teardown is just Stop — the SIGSEGV-adjacent ordering the virtual path must observe cannot arise when no monitor is being removed. The GNOME HDR probe now honors the pin. Asking "is ANY monitor in BT.2100 mode" was fair while capture took whatever head it was handed; once the operator names one, an HDR TV on the next connector must not talk the host into offering PQ formats for the SDR panel it is actually streaming. A pin that names no live head reports SDR rather than falling back to "any" — the session is about to fail on that same missing monitor, and an over-claimed HDR offer would be a second, quieter wrong answer. ⚠️ RecordMonitor's exact signature is read from the interface, not yet confirmed against a live gnome-shell; no GNOME box was reachable. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
53c772702f |
feat(host): list-monitors CLI, and two bugs it caught on-glass
An operator configuring an unattended host has to learn the connector
names from somewhere, and "curl the management API before the host is
configured" is not it. `punktfunk-host list-monitors` prints them with
geometry and flags the pinned one.
Running it under a real sway immediately caught two defects the unit tests
could not:
- the query used the `swaymsg` command helper, which inserts `--`, so
`-t get_outputs` came back as "Unknown/invalid command '-t'". Queries
now go through a `swaymsg_query` helper that cannot make that mistake.
- compositors write the literal "Unknown" rather than leaving make/model
empty, so the picker label read "Unknown Unknown". One `describe`
helper now treats that as absent, for all four backends.
Verified on a headless two-output sway: names, modes, positions, scale and
the case-insensitive pin all resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|
|
95e3314d9a |
feat(vdisplay): enumerate the host's physical monitors
P0 of per-monitor capture (design/per-monitor-portal-capture.md): nothing in the tree could answer "what heads does this host have?", which both a monitor pin and a console picker need first. One read per compositor, each beside the code that already speaks that dialect: KWin's kde_output_device_v2 (the topology session now also records geometry + scale), Mutter's DisplayConfig.GetCurrentState, swaymsg get_outputs, hyprctl monitors. Logical geometry throughout, because x/y is what identifies a head — two monitors can share a size but never an origin. PUNKTFUNK_CAPTURE_MONITOR parses here and is reported at startup, loudly including that it is not yet enforced: a knob that is read but inert has to say so, or "it didn't work" reads as a bug. GET /display/monitors always answers 200 with an explained empty list, so a compositor-less host renders as "nothing to pick", not as a broken console. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
925130d1f9 |
feat(vdisplay): compositor-embedded pointer for sessions without the cursor channel (Phase B)
Since the cursor-channel work, every Linux virtual output was created in metadata pointer mode — making ALL sessions depend on host-side cursor compositing, including the ones that can never use the channel (Moonlight/ GameStream, legacy clients, capture-mode starts). Those sessions paid the blend bring-up per session and, whenever a visible cursor was composited, the loss of NVENC's stream-ordered submit — for a strictly worse cursor than the compositor's own. Mirror the Windows no-regression gate: the already-wired per-session set_hw_cursor(cursor_forward) now drives the Linux backends too. A cursor-channel session gets metadata (shapes forwarded, composite flip blends host-side — today's validated path, unchanged); every other session gets the pointer compositor-EMBEDDED at creation (KWin zkde pointer=2, Mutter cursor-mode=1, wlroots/hyprland portal CursorMode::Embedded — their pre-channel default; the portal pair also gains the Metadata arm for channel sessions, wired but untested on-glass). SessionPlan.cursor_blend narrows to cursor-forward sessions and rides into the direct-SDK NVENC open, which skips the Vulkan slot-blend bring-up entirely when off — embedded sessions ring on plain CUDA surfaces and pay zero cursor cost, per-session or per-frame. The keep-alive registry's reuse key grows the created pointer mode (new VirtualDisplay::hw_cursor getter): a kept embedded display has no cursor metadata for a channel session to forward, and a kept metadata display would leave a channel-less session with no pointer in its frames. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
315ffaf144 |
fix(linux/vdisplay): mutter virtual monitor ships the cursor as SPA_META_Cursor
RecordVirtual was created with cursor-mode EMBEDDED, which never delivers cursor metadata — the cursor channel had nothing to forward (client-drawn cursor invisible) and the always-built encoder blend had nothing to composite (host-drawn cursor invisible too). Metadata mode feeds both: non-forwarding sessions get the blended pointer (same pixels Mutter would have embedded), forwarding sessions strip the overlay and send shape/state. The capturer already negotiates the meta on every stream (meta_param). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
86d9f49473 |
style(pf-vdisplay): rustfmt under the pinned 1.96.0 toolchain
audit / bun-audit (push) Successful in 12s
windows-drivers / probe-and-proto (push) Successful in 31s
ci / web (push) Successful in 48s
apple / swift (push) Failing after 1m1s
ci / docs-site (push) Successful in 1m2s
decky / build-publish (push) Successful in 21s
windows-drivers / driver-build (push) Successful in 1m35s
windows-host / package (push) Failing after 16s
audit / cargo-audit (push) Successful in 2m46s
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 9s
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 8s
release / apple (push) Successful in 3m56s
apple / screenshots (push) Has been skipped
ci / bench (push) Successful in 6m26s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m0s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m16s
flatpak / build-publish (push) Failing after 8m9s
docker / deploy-docs (push) Successful in 24s
arch / build-publish (push) Successful in 13m9s
android / android (push) Successful in 13m11s
deb / build-publish (push) Successful in 14m12s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m8s
ci / rust (push) Successful in 19m29s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m10s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m1s
The W6.2 pf-vdisplay extraction (
|
||
|
|
27a5d8daac |
refactor(host/W6.2): extract virtual-display orchestration into the pf-vdisplay crate
vdisplay.rs + vdisplay/* (the per-compositor Linux backends — KWin zkde-screencast,
wlroots swaymsg, Mutter RemoteDesktop, Hyprland — and the Windows IddCx/pf-vdisplay
driver backend, behind one VirtualDisplay trait; the mode-conflict admission
registry, the display policy/identity/custom-preset state, and the session-env /
gamescope routing) move into crates/pf-vdisplay (plan §W6). The DDC/CI panel-power
control (used only here) and the KWin zkde protocol XML move with it. This
completes the host-crate decomposition: capture, encode, inject, and vdisplay are
now four subsystem crates over the shared leaves, and punktfunk-host is the
orchestrator (serve/supervisor + native + gamestream + mgmt).
Coupling breaks (all down-only, cargo-tree acyclic):
- capture::dxgi identity -> pf_frame::dxgi; win_display/monitor_devnode/
console_session_mismatch -> pf-win-display leaf; can_open_another_session ->
pf-encode (the NVENC session-budget admission gate — acyclic peer edge).
- The registry's DisplayCreated/DisplayReleased emits into the host SSE event bus
invert to a leaf hook: pf-vdisplay emits a neutral DisplayEvent to a
host-registered DISPLAY_EVENT_SINK, so it never reaches the orchestrator's
events module.
- The IddCx driver module is renamed pf_vdisplay -> driver (its old name collided
with the crate name through the host's `mod vdisplay` shim glob).
The host keeps `mod vdisplay { pub use pf_vdisplay::* }` so every crate::vdisplay::*
path (serve/mgmt/native/the capture FrameChannelSender seam) is unchanged; the
heavy deps (wayland/ashpd/tokio + the zkde protocol) moved with the crate.
Co-authored: a fail-closed IOCTL-reply-length security fix (reject short/zeroed
pf-vdisplay driver replies before trusting protocol_version/target_id/wudf_pid/luid,
security-review 2026-07-17) rides this commit in the moved driver module.
Verified: Linux clippy -D warnings (pf-vdisplay + host nvenc,vulkan-encode,pyrowave
--all-targets) + pf-vdisplay 63/63 + host 167/167 tests; Windows clippy -D warnings
(pf-vdisplay --all-targets + host nvenc,amf-qsv --all-targets) Finished exit 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|