From 468a60c88aa5f11a6765099b0af484d79d696ebe Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 5 Jul 2026 17:50:32 +0000 Subject: [PATCH] =?UTF-8?q?docs(display-management):=20Stage=208=20consoli?= =?UTF-8?q?dation=20=E2=80=94=20docs=20page,=20README,=20CLAUDE.md,=20host?= =?UTF-8?q?.env?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- README.md | 5 ++ docs-site/content/docs/virtual-displays.md | 56 ++++++++++++++-------- scripts/host.env.example | 9 ++++ 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 519f8c1..d118007 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,11 @@ protocol, FEC, and crypto, linked into the host and every client over a stable C - **Your device's exact mode.** For each client that connects, the host spins up a virtual display sized to that device — 1080p60 to a laptop, 1440p120 to a desktop, 4K to a TV, all at once. No letterboxing, no scaling, no rearranging your real monitors. +- **Displays you configure, not just create.** Keep a game's display (and the game) alive across + disconnects so a reconnect drops straight back in; make the stream your sole desktop or extend + alongside your monitors; let several devices become monitors of one desktop; keep each client's + scaling. One-click presets in the console — a dedicated couch box, a shared desktop, a multi-monitor + workstation. See [Virtual displays](docs-site/content/docs/virtual-displays.md). - **A real virtual display on Windows, too.** On Linux the host uses per-compositor virtual outputs; on Windows you get the same on-the-fly virtual display — at the client's exact mode, no physical monitor or dummy HDMI plug, even on the secure desktop (UAC / lock screen). It also has **its own diff --git a/docs-site/content/docs/virtual-displays.md b/docs-site/content/docs/virtual-displays.md index 5d708f9..a56e277 100644 --- a/docs-site/content/docs/virtual-displays.md +++ b/docs-site/content/docs/virtual-displays.md @@ -18,11 +18,12 @@ opened on. > Reach for a preset when you want a specific experience — a dedicated couch/gaming box, a desktop > you also use in person, or a multi-monitor workstation. -> **What's live today:** this release wires **keep-alive** (linger duration) and **topology** -> (extend / primary / exclusive). The other options below — conflict handling, identity/scaling -> persistence on Linux, and multi-monitor layout — are **stored but not yet enforced**; they arrive -> in following releases. The console marks them accordingly. Windows already persists per-client -> scaling (see [Persistent scaling](#persistent-scaling)). +> **What's live today:** **keep-alive** (linger, or **forever**), **topology** (extend / primary / +> exclusive), **conflict handling**, **per-client identity + persistent scaling** (Windows *and* +> KDE/KWin), and **multi-monitor layout** (several clients as monitors of one desktop) are all +> enforced. A reconnect always resumes the kept display — even a fast one — instead of spawning a +> second. The remaining gaps are noted inline: the Linux `primary` physical-keep *effect*, Sway +> `exclusive`, and multi-display for a *single* client (that last is the next stage). ## Pick a preset @@ -32,7 +33,7 @@ the individual options documented further down. | Preset | What it's for | |---|---| | **Default** | Today's behavior. A short linger absorbs reconnects, the streamed output becomes the sole desktop, and extra clients each get their own view. | -| **Gaming rig** | A dedicated couch/headless box. The game and its display survive disconnects indefinitely, and whoever connects takes the box over. *(Arrives with the keep-alive stage.)* | +| **Gaming rig** | A dedicated couch/headless box. The game and its display survive disconnects indefinitely (keep-alive **forever**), and whoever connects takes the box over. Release it from the console when you're done. | | **Shared desktop** | A desktop you also use in person. punktfunk never blanks your real monitors and never leaves a ghost display behind; concurrent viewers each get a view. | | **Hot-desk** | One user at a time with fast reattach — roaming between your own devices. A second user is told the box is busy, and each device+resolution keeps its own scaling. | | **Workstation** | The multi-monitor daily driver. Your displays come back exactly where you arranged them, with per-client identity and an exclusive desktop. | @@ -49,12 +50,19 @@ this also keeps the **game itself running** so you can reconnect straight back i - **Off** — tear the display down at session end (nothing lingers). - **A duration** (seconds) — keep it for that long; a reconnect inside the window drops you straight back in, with no re-negotiation and no desktop reshuffle. -- **Forever** — keep it until you stop the host or release it from the console. *(Arrives with the - keep-alive lifecycle stage; the console won't let you save it before then.)* +- **Forever** — keep it until you stop the host or **release it** from the console (Host → *Virtual + displays* → *Release*). This is the gaming-rig model. Default: **10 seconds**. Windows has always lingered 10 s; the Linux backends previously tore down immediately — a short linger makes reconnects smoother on both. +**A reconnect always resumes the kept display** — the host recognises your device and hands back the +same display, even if you reconnect a second or two after dropping (before it has noticed you left). +**Deliberately quitting** (closing the client, not a network drop) tears the display down at once, +skipping the linger, so you don't leave a ghost behind. How quickly a *dropped* client is noticed is +the QUIC idle timeout — 8 s by default, tunable with `PUNKTFUNK_IDLE_TIMEOUT_MS` (see +[Legacy environment knobs](#legacy-environment-knobs)) if you want kept displays freed sooner. + > **Keep-alive + Exclusive keeps your physical monitors dark after you disconnect**, until the > linger expires or you release the display. That's intentional for a dedicated gaming box, but > don't set a long/forever keep-alive together with Exclusive on a machine whose monitors you also @@ -78,23 +86,25 @@ Per-backend support: | | KWin | Mutter/GNOME | Sway/wlroots | Windows | |---|---|---|---|---| | Extend | ✅ | ✅ | ✅ | ✅ | -| Primary | ✅ | ✅ | ⚠️ treated as Extend | ✅ *(following release)* | -| Exclusive | ✅ | ✅ | ✅ *(following release)* | ✅ | +| Primary | ✅ | ✅ | ⚠️ treated as Extend | ✅ | +| Exclusive | ✅ | ✅ | ⏳ following release | ✅ | ### Conflict handling · identity · layout -These are **stored but not yet enforced** — they're documented here so you know what's coming and -can set them ahead of the release that turns them on: - - **Conflict handling** — what happens when a *different* client connects while one is already streaming and asks for a different resolution: give it its own display (**separate**), take the box over (**steal**), share the existing display at its current mode (**join**), or refuse it - (**reject**). + (**reject**). On Linux, `separate` gives each client its own display on the shared desktop. On + **Windows** a second client is **rejected** (a clean "host busy") even under `separate` — two + clients can't yet share one virtual display's capture there (that's a later stage), so the live + session is protected instead. A same-client *reconnect* never conflicts — it resumes. - **Identity** — whether each client gets a **stable display identity** so your desktop environment - remembers its settings (see below): one shared identity, one **per client**, or one **per client + - resolution**. -- **Layout / max displays** — how multiple virtual displays are arranged (for multi-monitor), and an - upper bound on how many can be live at once. + remembers its settings (see [Persistent scaling](#persistent-scaling)): one shared identity, one + **per client**, or one **per client + resolution**. +- **Layout / max displays** — when several clients each become a monitor of one desktop, this places + them side by side (**auto**) or exactly where you arrange them in the console (**manual**, keyed to + each client), up to **max displays**. Arrange them under Host → *Virtual displays* once two or more + are streaming. ## Persistent scaling @@ -104,7 +114,7 @@ client a *stable display identity*, so your desktop environment keys its per-mon | Host | Supported | How | |---|---|---| | **Windows** | ✅ today | Connect, set scaling in Settings while streaming — Windows remembers it per client. | -| **KDE / KWin** | ⏳ following release | A stable per-client output name lets KWin persist scale/mode per client. | +| **KDE / KWin** | ✅ today | Set scaling in System Settings while streaming; KWin keys it to a stable per-client output name and reapplies it on reconnect. Validated live (150 %/125 % survive a full disconnect + reconnect). | | **GNOME / Mutter** | ❌ | GNOME's virtual-monitor API exposes no stable identity to key config on. | | **Sway / wlroots** | ❌ | Headless outputs can't carry a stable identity; pin scale in your sway config instead. | @@ -119,6 +129,14 @@ them — when a settings file exists, it wins. | `PUNKTFUNK_NO_ISOLATE` | **Topology** → Extend *(Windows)* | | `PUNKTFUNK_KWIN_VIRTUAL_PRIMARY` / `PUNKTFUNK_MUTTER_VIRTUAL_PRIMARY` | **Topology** → Exclusive (when set) / Extend (when `0`) | +One knob has no console equivalent — it's a transport tuning, not display policy: + +- **`PUNKTFUNK_IDLE_TIMEOUT_MS`** (host, default `8000`) — how long the host waits before declaring a + *dropped* client gone, which is when a kept display starts its linger (or is freed). Lower it (e.g. + `3000`) to reclaim kept displays sooner after an ungraceful drop; it's clamped to ≥1 s and its + keep-alive ping scales with it, so a live session never false-disconnects. A deliberate quit is + instant regardless. Also `--idle-timeout-ms` on `punktfunk1-host`. + ## Troubleshooting **My physical monitors stayed off after I disconnected.** You have keep-alive set together with diff --git a/scripts/host.env.example b/scripts/host.env.example index 63ba9c1..9d26691 100644 --- a/scripts/host.env.example +++ b/scripts/host.env.example @@ -48,6 +48,15 @@ PUNKTFUNK_ZEROCOPY=1 #PUNKTFUNK_INPUT_BACKEND=libei # wlr | libei | gamescope | uinput #PUNKTFUNK_FEC_PCT=20 # video FEC overhead percent #PUNKTFUNK_PERF=1 # per-stage timing logs +# Display-management policy (keep-alive · topology · conflict · identity · layout · max) is set in the +# web console (Host → Virtual displays) → ~/.config/punktfunk/display-settings.json, NOT here; a +# settings file supersedes the legacy PUNKTFUNK_MONITOR_LINGER_MS / _NO_ISOLATE / *_VIRTUAL_PRIMARY +# knobs. One transport knob has no console equivalent: +#PUNKTFUNK_IDLE_TIMEOUT_MS=8000 # disconnect-detection latency: how long before a DROPPED client is + # declared gone (a kept display then starts its linger, or frees). + # Lower (e.g. 3000) to reclaim kept displays sooner after an + # ungraceful drop; clamped ≥1s, keep-alive ping scales with it so a + # live session never false-disconnects. A deliberate quit is instant. # Full-chroma 4:4:4 (HEVC Range Extensions) — sharper text/desktop, no chroma loss. Honored only on # the punktfunk/1 native path when the client advertises 4:4:4 AND the GPU supports it (probed; else # the session stays 4:2:0). HEVC-only; independent of 10-bit. NVENC (NVIDIA) is the validated path;