diff --git a/Cargo.lock b/Cargo.lock index a22f6744..ab6aab30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2194,7 +2194,7 @@ dependencies = [ [[package]] name = "latency-probe" -version = "0.19.1" +version = "0.19.2" [[package]] name = "lazy_static" @@ -2299,7 +2299,7 @@ dependencies = [ [[package]] name = "libvpl-sys" -version = "0.19.1" +version = "0.19.2" dependencies = [ "bindgen", "cmake", @@ -2334,7 +2334,7 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "loss-harness" -version = "0.19.1" +version = "0.19.2" dependencies = [ "punktfunk-core", ] @@ -2823,7 +2823,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pf-capture" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ashpd", @@ -2844,7 +2844,7 @@ dependencies = [ [[package]] name = "pf-client-core" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ash", @@ -2868,7 +2868,7 @@ dependencies = [ [[package]] name = "pf-clipboard" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ashpd", @@ -2886,7 +2886,7 @@ dependencies = [ [[package]] name = "pf-console-ui" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ash", @@ -2907,7 +2907,7 @@ dependencies = [ [[package]] name = "pf-encode" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ash", @@ -2931,7 +2931,7 @@ dependencies = [ [[package]] name = "pf-ffvk" -version = "0.19.1" +version = "0.19.2" dependencies = [ "ash", "bindgen", @@ -2940,7 +2940,7 @@ dependencies = [ [[package]] name = "pf-frame" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "libc", @@ -2952,7 +2952,7 @@ dependencies = [ [[package]] name = "pf-gpu" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "pf-host-config", @@ -2966,11 +2966,11 @@ dependencies = [ [[package]] name = "pf-host-config" -version = "0.19.1" +version = "0.19.2" [[package]] name = "pf-inject" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ashpd", @@ -2999,14 +2999,14 @@ dependencies = [ [[package]] name = "pf-paths" -version = "0.19.1" +version = "0.19.2" dependencies = [ "tracing", ] [[package]] name = "pf-presenter" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ash", @@ -3021,7 +3021,7 @@ dependencies = [ [[package]] name = "pf-vdisplay" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ashpd", @@ -3052,7 +3052,7 @@ dependencies = [ [[package]] name = "pf-win-display" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "pf-paths", @@ -3064,7 +3064,7 @@ dependencies = [ [[package]] name = "pf-zerocopy" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ash", @@ -3271,7 +3271,7 @@ dependencies = [ [[package]] name = "punktfunk-client-android" -version = "0.19.1" +version = "0.19.2" dependencies = [ "android_logger", "jni", @@ -3287,7 +3287,7 @@ dependencies = [ [[package]] name = "punktfunk-client-linux" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "async-channel", @@ -3303,7 +3303,7 @@ dependencies = [ [[package]] name = "punktfunk-client-session" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "pf-client-core", @@ -3318,7 +3318,7 @@ dependencies = [ [[package]] name = "punktfunk-client-windows" -version = "0.19.1" +version = "0.19.2" dependencies = [ "async-channel", "ffmpeg-next", @@ -3337,7 +3337,7 @@ dependencies = [ [[package]] name = "punktfunk-core" -version = "0.19.1" +version = "0.19.2" dependencies = [ "aes-gcm", "bytes", @@ -3369,7 +3369,7 @@ dependencies = [ [[package]] name = "punktfunk-host" -version = "0.19.1" +version = "0.19.2" dependencies = [ "aes", "aes-gcm", @@ -3453,7 +3453,7 @@ dependencies = [ [[package]] name = "punktfunk-probe" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "mdns-sd", @@ -3467,7 +3467,7 @@ dependencies = [ [[package]] name = "punktfunk-tray" -version = "0.19.1" +version = "0.19.2" dependencies = [ "anyhow", "ksni", @@ -3490,7 +3490,7 @@ checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" [[package]] name = "pyrowave-sys" -version = "0.19.1" +version = "0.19.2" dependencies = [ "bindgen", "cmake", diff --git a/Cargo.toml b/Cargo.toml index 25bcb77a..15f55cea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ exclude = [ ndk = { path = "clients/android/native/vendor/ndk" } [workspace.package] -version = "0.19.1" +version = "0.19.2" edition = "2021" rust-version = "1.82" license = "MIT OR Apache-2.0" diff --git a/docs/releases/v0.19.2.md b/docs/releases/v0.19.2.md new file mode 100644 index 00000000..d5694d76 --- /dev/null +++ b/docs/releases/v0.19.2.md @@ -0,0 +1,46 @@ +A small fixes update, all on the machine you stream *from* — your apps don't change, you can update whenever, and everything you've already paired stays paired. + +This one cleans up a handful of things that went wrong on specific setups. On a **KDE Plasma** desktop, streaming now properly takes over the screen — switching off the real monitors and handing you the streamed display — and comes up in a couple of seconds instead of stalling, even on machines where KDE's own display tools had stopped responding. On a **Linux** machine you switch into **Steam's Game Mode**, that switch no longer risks leaving the machine stuck on a black screen, and you can now switch back to the desktop from inside the stream. And on **Windows**, giving the stream its own dedicated display is reliable again and can no longer leave the real desktop dark — and pen, touch and absolute-position mouse now land where you point when a second monitor is in play. + +## Fixed: streaming from a KDE Plasma desktop takes over the screen — and comes up fast + +On some KDE Plasma machines — a Nobara media PC among them — connecting used to *look* like it worked but leave the real monitors on and never actually hand you the streamed display, after a long wait of around half a minute. The cause was KDE's own display tooling hanging on that machine, with Punktfunk waiting on it. + +Punktfunk now sets the machine's displays up by talking to KDE directly instead of going through that tool: + +- **The streamed display actually becomes the desktop.** The physical monitors switch off for the session and come back when you disconnect — reliably, and typically in a couple of seconds instead of half a minute. +- **It works even when KDE's display settings tool is unresponsive** — the exact case that used to break connecting entirely. +- **High refresh rates work on these machines too.** The 90/120 Hz path no longer leans on that same tool. (Machines where the tool was healthy already worked; this just removes the dependency.) + +## Fixed: switching a Linux machine into Steam's Game Mode no longer breaks it + +Switching a Linux desktop into Steam's Game Mode mid-stream could leave the whole machine on a **permanent black screen** — on desktops that don't use the same sign-in manager as a Steam Deck (a Nobara home-theatre PC was the report). Steam Deck-style machines were never affected. + +The switch is safe now: Punktfunk hands the screen to Game Mode without taking down the machine's sign-in manager, and puts everything back when you disconnect. On a machine where it doesn't have permission to do that cleanly, it mirrors whatever Game Mode is already showing rather than risk the screen — so you always get a picture instead of a black one. + +## New: switch back to the desktop from inside the stream + +On a Linux machine that Punktfunk put into Game Mode for you, choosing **Switch to Desktop** from Steam's menu used to do nothing — it just dropped you straight back into Game Mode. It now actually returns the machine to its desktop, and the stream follows it there. + +## Fixed: giving the stream its own display on Windows is reliable again — and never goes dark + +The **experimental option that isolates the stream onto its own display** on Windows (switching off the physical monitors) could fail outright on some monitor layouts, and a failed attempt could leave the real desktop on a black screen. + +- **It works across monitor layouts now**, not only when a physical monitor happened to sit in the top-left corner of the desktop. +- **The desktop is never left dark.** If restoring your original layout can't light a screen back up, Punktfunk falls back to a normal extended-desktop arrangement, so you're never stranded looking at black. + +## Fixed: pen, touch and absolute mouse land where you point on a multi-monitor Windows machine + +When you stream from a Windows machine that keeps a **physical monitor on alongside** the streamed display, pen, touch, and absolute-position mouse input landed in the **wrong spot** — shifted across and scaled wrong, so a stylus drew where you weren't pointing. A single-display setup was never affected; the pen showed it first, because a stylus is pure point-where-you-touch with nothing to correct it. + +Input now maps onto the streamed display itself, so **the pen lands exactly where you touch** — and it keeps up if you rearrange the machine's monitors mid-session. + +## Under the hood (for developers) + +- **Nothing changed on the wire or at the API boundary.** The streaming protocol stays at version **2** and the embeddable core library stays at C ABI **13** — no embedder rebuild is required, and 0.18/0.19 hosts and clients still mix freely. The Windows virtual-display driver protocol is unchanged. +- **KWin display topology now runs in-process over `kde_output_management_v2` / `kde_output_device_v2`, not `kscreen-doctor`.** `kscreen-doctor` drives libkscreen, which waits on the kscreen KDED module over D-Bus; when that layer wedges it blocks in its own connect and never returns, so all five topology queries hit their 5 s budget and are killed — the physicals never disable and the virtual output never becomes the desktop (`also_disabled=[]`, ~26 s bring-up) even though the compositor's own Wayland is fully responsive. The host now resolves its output by stable device UUID (supersede-robust), takes primary, disables the physical/bootstrap outputs (capturing their modes to re-enable on teardown) and positions — all over bounded Wayland round-trips (management v19 bound + outputs enumerated in ~2.4 ms on KWin 6.6.4, ~3400× faster than the hang). Every wait is time-bounded, so a genuinely wedged compositor degrades to `handled = false` and the old `kscreen-doctor` path still runs. +- **>60 Hz custom modes install in-process too**, so the whole KWin path is kscreen-free on modern KWin. `set_custom_mode` builds a one-entry `kde_mode_list_v2` and applies it via `kde_output_configuration_v2.set_custom_modes` (since v18), waits for KWin to generate the mode (its CVT generator may align the width down — matched with the same height-exact / width-within-8 / refresh-within-1 Hz gate as before), then selects it, which drives the sacrificial-birth stream renegotiation. `set_custom_modes` *replaces* the custom list, so reconnects are idempotent — no more one-custom-mode-per-connect growth of the display's mode list — and a >60 Hz session no longer eats a 5 s kscreen resolve timeout on a box where the tool wedges. Pre-6.6 KWin without `set_custom_modes` falls back to `set_custom_refresh`. +- **The gamescope managed takeover is display-manager-flavor-aware.** Masking the box's `gamescope-session-plus` unit made plasmalogin's `Relogin=true` fail its Exec repeatedly and trip systemd's start limit within ~1 s — the DM dies, and the restore verb (unmask + user start) cannot bring a seatless gamescope back; only `reset-failed` + `restart` recovers. SDDM (Bazzite/SteamOS) keeps the proven mask + SIGKILL path. plasmalogin / unknown DMs are never masked: with privilege (root or a scoped operator polkit rule on the DM unit — documented) the host stops the DM for the stream and restores it with `reset-failed` + `restart` (recorded in the persisted takeover state so a host crash still restores); without privilege the managed takeover degrades to attach and mirrors the live Game Mode. Companion fixes from the same triage: an attach-only rebuild-probe guard on `ensure_box_gamescope_mode` (which also no longer re-modes a box that drives a physical display), a 100 s capture-loss rebuild budget for gamescope (the 40 s budget expired inside Steam's first ~45 s cold-start attempt), a once-per-capture-loss WARN when a `PUNKTFUNK_COMPOSITOR` pin no longer matches the live session, and a managed session that took nothing over is stopped on disconnect instead of orphaned. +- **In-stream "Switch to Desktop" is honored under the managed takeover.** Steam's session-select is a silent no-op while the DM is stopped (its config-write branches all require the DM running), so it merely relaunched Game Mode. The managed launch now baselines the `~/.config/steamos-session-select` sentinel's mtime; a capture loss with the sentinel advanced is read as the switch request and replayed with the DM up — restore the DM, run the distro's own `os-session-select` (its internal pkexec is authorized `allow_any` by the distro policy, so it works from the host's sessionless context), then stop the autologin game-mode unit so Relogin lands in the newly selected desktop. A 120 s post-honor grace keeps the rebuild loop from racing the booting desktop back into Game Mode (superseded early if the box's own game-mode unit reappears, so the desktop→game leg stays fast). The baseline is re-recorded on crash-restore so a pre-existing sentinel never reads as a fresh request. +- **Windows absolute input maps over the streamed output's desktop rect, not the whole virtual desktop.** Pen, touch and absolute mouse arrive normalized to the streamed display's frame, but `pointer_windows::to_screen` and sendinput's `MouseMoveAbs` mapped them across the entire virtual desktop — correct only when the virtual display is the sole active display (Exclusive topology). In Extend — a physical kept on beside the virtual output, or an Exclusive isolate degraded to the `0x57` keep-physicals fallback — the streamed output sits at a non-zero origin, so every sample landed shifted and mis-scaled, and the strictly-absolute pen (no closed-loop correction onto the target) exposed it first. New `pf-inject::stream_target` publishes the streamed output's CCD target id at capture bring-up (one central site — `capture_virtual_output` covers the native and GameStream planes); mapping sites resolve its current desktop rect through `pf-win-display`'s `source_desktop_rect` — the same resolver the cursor-readback poller uses, so inject and readback always agree — TTL-cached at 250 ms because a group-layout re-arrange moves a live output's origin mid-session. No target resolved falls back to the whole virtual desktop (still right for Exclusive topology). The same fix closes the identical latent absolute-mouse offset. +- **Windows display isolation anchors the kept sources at the desktop origin.** A committable CCD config must contain a primary pinned at exactly (0,0); deactivating the display that held the origin while the kept virtual stays at its EXTEND offset supplies an origin-less desktop, which Windows rejects wholesale (`0x57`) — on every shape, doomed-path-carried and keep-only escalation alike. The kept sources are now translated rigidly so the top-left-most lands on the origin (sets already covering (0,0) stay byte-identical, so plain re-commits don't churn). `restore_displays_ccd` also guarantees the desktop is never left all-dark: an unappliable snapshot (`0x64a`) or one that applies cleanly yet re-lights nothing falls back to the database EXTEND preset when no external physical is active afterwards (internal panels don't count — a closed lid isn't forced back on), and the final isolate-failure diagnostic now names the surviving targets rather than asserting a non-virtual display stayed active.