A stopped display manager left the box unable to switch sessions, and a translated KWin refusal burned all 8 retries #331

Merged
enricobuehler merged 3 commits from worktree-kwin-vout-failed-permanent into main 2026-08-19 12:33:24 +00:00
6 changed files with 58 additions and 17 deletions
+10 -1
View File
@@ -268,7 +268,16 @@ impl VirtualDisplay for KwinDisplay {
.context("spawn KWin virtual-output thread")?;
match setup_rx.recv_timeout(OPENER_BUDGET) {
Ok(Ok(v)) => Ok((v, stop)),
Ok(Err(e)) => bail!("KWin virtual output failed: {e}"),
// KWin's reason is TRANSLATED into the session's language, so it is often
// unsearchable for the person reading the log. Say what it means once, here.
Ok(Err(e)) => bail!(
"KWin virtual output failed: {e} — KWin declined to create the output. It \
needs a Plasma WAYLAND session on KWin's DRM backend; a nested or \
`kwin_wayland --virtual` KWin can only do this since 6.5.6, and on KWin 6.6+ \
an output KWin creates but leaves DISABLED (stored \
~/.config/kwinoutputconfig.json, or a display config it refused to apply) \
reports the same. kwin_wayland's own journal says which"
),
Err(_) => {
// Nothing else will ever flip this `stop`: it is dropped with the error, and
// the `StopGuard` that normally owns it is only built on the success path. So
+9 -6
View File
@@ -2326,12 +2326,15 @@ pub unsafe extern "C" fn punktfunk_connect_ex10(
/// `audio_rate_hz` — `48000`, `96000`, or the 44.1 kHz family `44100` / `88200` / `176400` — and
/// `audio_bits` (`16` or `24`).
///
/// Passing anything other than `48000`/`16` sets `CLIENT_CAP_AUDIO_HIRES` in the `Hello` and asks
/// the host for the LOSSLESS `0xD3` plane — bit-exact PCM instead of Opus. That is an opt-in on
/// both ends, and it is meant to be: it costs **1.54.6 Mbps** taken off the top of the link
/// (audio rides QUIC datagrams outside the ABR loop, so ABR can neither see it nor reclaim it),
/// against the ~256 kbps Opus this replaces. Only call it with a non-default format when the
/// user turned the feature on AND this embedder can genuinely open an output device at it.
/// Passing a format AT ALL — any non-zero `audio_rate_hz`/`audio_bits`, `48000`/`16` included —
/// sets `CLIENT_CAP_AUDIO_HIRES` in the `Hello` and asks the host for the LOSSLESS `0xD3` plane,
/// bit-exact PCM instead of Opus. (This line once said "anything other than `48000`/`16`", which
/// was the rule until the cheapest rung turned out to be the one nobody could ask for; the ⚠ below
/// is the whole story.) That is an opt-in on both ends, and it is meant to be: it costs
/// **1.54.6 Mbps** taken off the top of the link (audio rides QUIC datagrams outside the ABR
/// loop, so ABR can neither see it nor reclaim it), against the ~256 kbps Opus this replaces. Only
/// pass a format when the user turned the feature on AND this embedder can genuinely open an
/// output device at it.
///
/// **The request is not the answer.** The host runs a five-condition gate
/// (`design/hi-res-audio.md` §8.4 — client asked, operator policy allows, stereo, the capture
+12 -3
View File
@@ -4611,9 +4611,14 @@ fn is_permanent_build_error(chain: &str) -> bool {
"virtual displays require linux",
"unknown punktfunk_compositor",
"could not detect compositor",
"could not find output", // KWin < 6.5.6: createVirtualOutput unsupported
"must be a node id", // PUNKTFUNK_GAMESCOPE_NODE not an integer
"is it installed", // gamescope / kscreen-doctor not on PATH
// KWin refused the virtual output. Its own reason arrives TRANSLATED (a field report read
// "Não foi possível encontrar saída" and burned all 8 retries), so match OUR English
// prefix, not KWin's payload. Every `failed` KWin sends on this path is a config/backend
// fact — unsupported compositing type, a backend without `createVirtualOutput`, an output
// the workspace declined to enable — none of which a retry 500 ms later changes.
"kwin virtual output failed",
"must be a node id", // PUNKTFUNK_GAMESCOPE_NODE not an integer
"is it installed", // gamescope / kscreen-doctor not on PATH
// 4:4:4 NVENC got a CUDA frame — should never happen now the Linux capturer honors gpu=false,
// but fail fast instead of 8× retry (~90 s) rather than wedge the session if it ever recurs.
"capture/encoder negotiation mismatch",
@@ -5329,6 +5334,10 @@ mod tests {
assert!(is_permanent_build_error(
"create virtual output: KWin virtual output failed: Could not find output"
));
// Same refusal from a localized KWin — the reason is translated, our prefix is not.
assert!(is_permanent_build_error(
"create virtual output: KWin virtual output failed: Não foi possível encontrar saída"
));
assert!(is_permanent_build_error(
"unknown PUNKTFUNK_COMPOSITOR 'foo' (kwin|wlroots|mutter|gamescope)"
));
+15
View File
@@ -144,6 +144,21 @@ The session unit brings up headless KWin; the host unit follows it and starts li
print **nothing at all**. A host binary carrying a Linux capability cannot be identified by KWin
and is never offered the protocol, however correctly its grant is installed — see
[GPU scheduling priority](/docs/running-as-a-service#gpu-scheduling-priority).
- **"KWin virtual output failed: Could not find output"** (the message arrives translated — a
Brazilian session reads *"Não foi possível encontrar saída"*): KWin got the request and refused
it, so the grant above is fine and only the output creation is not. Two shapes. Its **backend**
can't create one — a nested KWin, or `kwin_wayland --virtual` below 6.5.6; a normal Plasma
session on the DRM backend always can. Or, on **KWin 6.6+**, KWin created the output and then
left it **disabled**. That reports identically, and it is new: 6.6 put a
`workspace()->findOutput()` hop in front of the stream, so from 6.6 on the output must also be
*enabled and workspace-managed*, where 6.5 and earlier streamed it either way. Check
`punktfunk-host list-monitors` for a
`Virtual-punktfunk-*` marked *disabled*, a stale entry in `~/.config/kwinoutputconfig.json`, and
`journalctl --user -b -t kwin_wayland` for *"Applying output configuration failed!"* — KWin logs
that when it declines to enable one more output next to your current monitors, and keeps the old
configuration instead. Streaming a real monitor
([`PUNKTFUNK_CAPTURE_MONITOR`](/docs/configuration), or **Streamed screen** in the console) skips
virtual-output creation entirely and is the workaround while you sort the above out.
- **Black screen / no picture:** confirm you're on a Wayland session (not X11) and, on NVIDIA, that
the GL userspace is installed. More in [Troubleshooting](/docs/troubleshooting).
+3 -1
View File
@@ -376,7 +376,9 @@ an empty extension. Use **Primary** or **Exclusive** so your desktop actually la
**KWin can't create the virtual output.** On a normal Plasma session KWin runs its **DRM backend**,
which creates virtual outputs at any version. The 6.5.6 floor applies only to the **virtual backend**
(`kwin_wayland --virtual`, used for headless and test sessions) — below that the request fails with
"Could not find output". See [requirements](/docs/requirements).
"Could not find output". On **KWin 6.6+** that same message also covers an output KWin *did* create
and then left disabled; [KDE Plasma](/docs/kde#troubleshooting) walks that one. See
[requirements](/docs/requirements).
**Reconnecting into game mode reconnects cleanly now.** On a Steam Deck / Bazzite box, disconnecting
and reconnecting within game mode reuses the still-warm session (or cleanly recreates it) instead of
+9 -6
View File
@@ -2983,12 +2983,15 @@ PunktfunkConnection *punktfunk_connect_ex10(const char *host,
// `audio_rate_hz` — `48000`, `96000`, or the 44.1 kHz family `44100` / `88200` / `176400` — and
// `audio_bits` (`16` or `24`).
//
// Passing anything other than `48000`/`16` sets `CLIENT_CAP_AUDIO_HIRES` in the `Hello` and asks
// the host for the LOSSLESS `0xD3` plane — bit-exact PCM instead of Opus. That is an opt-in on
// both ends, and it is meant to be: it costs **1.54.6 Mbps** taken off the top of the link
// (audio rides QUIC datagrams outside the ABR loop, so ABR can neither see it nor reclaim it),
// against the ~256 kbps Opus this replaces. Only call it with a non-default format when the
// user turned the feature on AND this embedder can genuinely open an output device at it.
// Passing a format AT ALL — any non-zero `audio_rate_hz`/`audio_bits`, `48000`/`16` included —
// sets `CLIENT_CAP_AUDIO_HIRES` in the `Hello` and asks the host for the LOSSLESS `0xD3` plane,
// bit-exact PCM instead of Opus. (This line once said "anything other than `48000`/`16`", which
// was the rule until the cheapest rung turned out to be the one nobody could ask for; the ⚠ below
// is the whole story.) That is an opt-in on both ends, and it is meant to be: it costs
// **1.54.6 Mbps** taken off the top of the link (audio rides QUIC datagrams outside the ABR
// loop, so ABR can neither see it nor reclaim it), against the ~256 kbps Opus this replaces. Only
// pass a format when the user turned the feature on AND this embedder can genuinely open an
// output device at it.
//
// **The request is not the answer.** The host runs a five-condition gate
// (`design/hi-res-audio.md` §8.4 — client asked, operator policy allows, stereo, the capture