diff --git a/docs-site/content/docs/bazzite.md b/docs-site/content/docs/bazzite.md index 0350664f..7932f898 100644 --- a/docs-site/content/docs/bazzite.md +++ b/docs-site/content/docs/bazzite.md @@ -85,11 +85,10 @@ cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env The template is deliberately minimal — it does **not** force a compositor, because the host auto-detects Gaming Mode (gamescope) vs Desktop (KWin) on every connect and follows the switch -mid-stream. The only settings that matter are the session anchors (GPU zero-copy is on by default): +mid-stream. No session anchors are needed either (a user service inherits the right runtime dir). +The only settings that matter (GPU zero-copy is on by default): ```sh -XDG_RUNTIME_DIR=/run/user/1000 -DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus PUNKTFUNK_VIDEO_SOURCE=virtual # GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU. PUNKTFUNK_GAMESCOPE_ATTACH=1 # Gaming Mode = attach to the box's own session (see below) @@ -99,12 +98,13 @@ PUNKTFUNK_GAMESCOPE_ATTACH=1 # Gaming Mode = attach to the box's own session For Gaming Mode there are two models (pick one; the shipped default is **attach**): -- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`, the default) — the **box** owns its gamescope session, - the host attaches to whatever's live and never tears it down, and the streamed game-mode resolution - is the box's own gamescope mode. Switching Desktop ↔ Game is rock-solid. -- **Managed** (`PUNKTFUNK_GAMESCOPE_MANAGED=1`, and remove the attach line) — the host launches its - **own** gamescope at the *client's* exact resolution and refresh. Client-mode-following, but there - must be no physical gaming session already running. +- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`, the template's default) — the **box** owns its + gamescope session on its own display, and the host attaches to whatever's live without ever + tearing it down (a box-owned autologin session is restarted at the client's resolution on a + mismatch). Switching Desktop ↔ Game is rock-solid. +- **Managed** (`PUNKTFUNK_GAMESCOPE_MANAGED=1`, and remove the attach line) — the host takes the + box's gamescope over and relaunches it **headless** at the *client's* exact resolution and + refresh — Game Mode on the virtual screen — restoring the box on idle. Full treatment: [Steam / gamescope → Attach vs managed](/docs/gamescope#attach-vs-managed). diff --git a/docs-site/content/docs/configuration.md b/docs-site/content/docs/configuration.md index 7eddd74a..73b1ea84 100644 --- a/docs-site/content/docs/configuration.md +++ b/docs-site/content/docs/configuration.md @@ -4,7 +4,8 @@ description: Every host.env setting and PUNKTFUNK_* environment variable — com --- The host reads its settings from **`~/.config/punktfunk/host.env`** (a simple `KEY=value` file, `#` -starts a comment). On Windows the service reads **`%ProgramData%\punktfunk\host.env`** instead. Your +starts a comment; keys are **case-sensitive** — `punktfunk_compositor` sets nothing, use the exact +uppercase names). On Windows the service reads **`%ProgramData%\punktfunk\host.env`** instead. Your [setup guide](/docs/requirements) gives you a starting `host.env` for your desktop; this page is the full reference for every setting. @@ -16,25 +17,24 @@ full reference for every setting. ## Session anchors -These tell the host which desktop session to attach to. Your setup guide sets them for you; they're -required when the host runs outside your interactive session (e.g. as a service). +**Leave these unset on a normal setup.** Running as a `systemctl --user` service the host inherits +the correct `XDG_RUNTIME_DIR` from systemd, derives the session bus from it, and **rewrites +`WAYLAND_DISPLAY` / `XDG_CURRENT_DESKTOP` / `XDG_RUNTIME_DIR` / `DBUS_SESSION_BUS_ADDRESS` on every +connect** to follow the active session (Gaming ↔ Desktop) — a value written here can only be +redundant or stale. -| Setting | What it does | +| Setting | When to set it | |---|---| -| `XDG_RUNTIME_DIR` | Your session's runtime dir (e.g. `/run/user/1000`). Always needed for a service. | -| `DBUS_SESSION_BUS_ADDRESS` | Your session bus (e.g. `unix:path=/run/user/1000/bus`). Always needed for a service. | -| `WAYLAND_DISPLAY` | The Wayland socket of your session (`wayland-0` for a normal desktop, `wayland-kde` for the headless-KDE unit). | -| `XDG_CURRENT_DESKTOP` | Your desktop (`GNOME`, `KDE`). | - -On Linux the host **rewrites `WAYLAND_DISPLAY` / `XDG_CURRENT_DESKTOP` / `XDG_RUNTIME_DIR` / -`DBUS_SESSION_BUS_ADDRESS` on every connect** to follow the active session (Gaming ↔ Desktop). Only -`XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` need to be pinned as trustworthy anchors. +| `XDG_RUNTIME_DIR` | Only when the host runs **outside** a user service (ssh, cron): `/run/user/` — check `id -u`. A copy-pasted `1000` on a box where that isn't your uid points the host at another user's (nonexistent) PipeWire/D-Bus, and **everything** fails (audio `Creation failed`, no capture, clients report the host unreachable). | +| `DBUS_SESSION_BUS_ADDRESS` | Same cases only: `unix:path=/run/user//bus`. Otherwise derived automatically. | +| `WAYLAND_DISPLAY` | Only the dedicated [headless-KDE appliance](/docs/kde#headless-session) (`wayland-kde`, set by its shipped `host.env.kde`). | +| `XDG_CURRENT_DESKTOP` | Same — appliance-only. | ## Core | Setting | Values | Meaning | |---|---|---| -| `PUNKTFUNK_COMPOSITOR` | `kwin` · `mutter` · `gamescope` · `wlroots` · `hyprland` (aliases: `kde`/`plasma`, `gnome`, `sway`/`wlr`) | Which backend creates the virtual display. `wlroots` is sway/River; `hyprland` is its own backend. **Leave unset to auto-detect;** set only to force one. | +| `PUNKTFUNK_COMPOSITOR` | `kwin` · `mutter` · `gamescope` · `wlroots` · `hyprland` (aliases: `kde`/`plasma`, `gnome`, `sway`/`wlr`) | Which backend creates the virtual display. `wlroots` is sway/River; `hyprland` is its own backend. **Leave unset.** Setting it **pins** the backend and turns session-following **off** — per connect *and* mid-stream, so a Desktop ↔ Gaming switch kills the stream instead of being followed. For CI/tests and dedicated single-session appliances only. | | `PUNKTFUNK_VIDEO_SOURCE` | `virtual` · `portal` | `virtual` creates a per-client display at the client's exact mode (the normal choice). `portal` captures an existing monitor instead. | | `PUNKTFUNK_ZEROCOPY` | `1` · `0` *(default on)* | GPU zero-copy capture→encode (dmabuf → CUDA → NVENC, or D3D11 on Windows). **On by default** — no need to set it; it falls back to a CPU path automatically. Set `0` to force the CPU path. One exception: Windows **Intel/QSV** keeps the CPU path by default until zero-copy is validated on Intel hardware — set `1` to try it there. | | `PUNKTFUNK_INPUT_BACKEND` | `libei` · `gamescope` · `wlr` · `uinput` | How input is injected. `libei` for GNOME/KDE, `gamescope` for Bazzite/gamescope, `wlr` for Sway/wlroots **and Hyprland**. Auto-detected with the compositor. | @@ -53,8 +53,8 @@ the full picture (and [Bazzite](/docs/bazzite) for that distro's specifics). | Setting | Values | Meaning | |---|---|---| -| `PUNKTFUNK_GAMESCOPE_ATTACH` | `1` | **Attach** model: the box owns its gamescope session (you switch Gaming ↔ Desktop with the Steam UI); the host just captures whatever's live and never tears it down. Rock-solid; streamed resolution is the box's gamescope mode. | -| `PUNKTFUNK_GAMESCOPE_MANAGED` | `1` | **Managed** model: the host tears the box's gamescope down on connect and launches its **own** at the *client's* exact resolution, restoring on idle. Client-mode-following, but doesn't coexist with a box-owned game-mode session. | +| `PUNKTFUNK_GAMESCOPE_ATTACH` | `1` | **Attach** model: the box owns its gamescope session on its own display (you switch Gaming ↔ Desktop with the Steam UI); the host just captures whatever's live and never tears it down. A box-owned autologin session is restarted at the client's resolution on a mismatch; a foreign/bare gamescope streams at its own mode. | +| `PUNKTFUNK_GAMESCOPE_MANAGED` | `1` | **Managed** model (the default where session infra is detected): the host takes the box's gamescope over and relaunches it **headless** at the *client's* exact resolution — Game Mode on the virtual screen — restoring the box on idle. | | `PUNKTFUNK_GAMESCOPE_SESSION` | `steam` | The host owns a `gamescope-session-plus` (Steam) session at the client's mode (headless appliance; no physical session running). | | `PUNKTFUNK_GAMESCOPE_NODE` | `auto` · node id | Discover + capture a **running** gamescope's PipeWire node at a fixed mode. Do **not** combine with `SESSION`. | | `PUNKTFUNK_GAMESCOPE_APP` | command | For an ad-hoc bare-gamescope session, the nested command to run (e.g. `vkcube`). | diff --git a/docs-site/content/docs/gamescope.md b/docs-site/content/docs/gamescope.md index 7d086ef7..5ffbd560 100644 --- a/docs-site/content/docs/gamescope.md +++ b/docs-site/content/docs/gamescope.md @@ -17,18 +17,20 @@ from the install guide for your OS: [Bazzite](/docs/bazzite) or [SteamOS (Host)] ## Attach vs managed -There are two mutually-exclusive models for a gamescope box; pick one. The shipped default is -**attach**. +There are two mutually-exclusive models for a gamescope box; pick one. With **nothing set**, a box +that has gamescope session infrastructure (Bazzite, SteamOS, Nobara) gets **managed**; the +[Bazzite template](/docs/bazzite) ships with **attach** chosen instead. -- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`, the default) — the **box** owns its gamescope session - and decides Gaming vs Desktop via the normal Steam UI. The host just attaches to whatever's live - and never tears it down, so switching Desktop ↔ Game is rock-solid and disconnecting leaves the box - where it was. The streamed game-mode resolution is the box's gamescope mode - (`SCREEN_WIDTH/HEIGHT` in `/etc/gamescope-session-plus/sessions.d/steam`), not the client's. -- **Managed** (`PUNKTFUNK_GAMESCOPE_MANAGED=1`, and remove the attach line) — the host tears the - box's gamescope down on connect and launches its **own** at the *client's* exact resolution and - refresh, restoring on idle. Client-mode-following, but it can't coexist with a box-owned game-mode - session, and there must be **no physical gaming session already running**. +- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`) — the **box** owns its gamescope session and decides + Gaming vs Desktop via the normal Steam UI. Game Mode stays on the box's own (physical) display; + the host attaches to whatever's live and never tears it down, so switching Desktop ↔ Game is + rock-solid and disconnecting leaves the box where it was. When the session is the box's own + autologin unit, the host restarts it at the **client's** resolution on a mismatch; a foreign or + bare gamescope is streamed at its own mode. +- **Managed** (the infra-detected default; force with `PUNKTFUNK_GAMESCOPE_MANAGED=1`) — the host + takes the box's gamescope session over and relaunches it **headless** at the *client's* exact + resolution and refresh — Game Mode runs on the virtual screen, physical displays drop out of it — + restoring the box on idle after disconnect. ## Session following @@ -40,8 +42,8 @@ over its own compositor, and re-targets whichever is live on each switch. ## Start the host On an appliance box (Bazzite, SteamOS) the install guide already enables the host service for you. On -any other distro running a gamescope session, start it from your session — the default attach model -just latches onto whatever gamescope session is live: +any other distro running a gamescope session, just start it — the host auto-detects the live +gamescope session and picks the model for it: ```sh systemctl --user enable --now punktfunk-host @@ -56,8 +58,8 @@ a model. See the full [Configuration reference](/docs/configuration) for every o | Setting | Values | Meaning | |---|---|---| -| `PUNKTFUNK_GAMESCOPE_ATTACH` | `1` | **Attach** model: the box owns its gamescope session; the host captures whatever's live and never tears it down. Streamed resolution is the box's gamescope mode. The default. | -| `PUNKTFUNK_GAMESCOPE_MANAGED` | `1` | **Managed** model: the host tears the box's gamescope down on connect and launches its own at the client's exact mode, restoring on idle. Doesn't coexist with a box-owned game-mode session. | +| `PUNKTFUNK_GAMESCOPE_ATTACH` | `1` | **Attach** model: the box owns its gamescope session (on its own display); the host captures whatever's live and never tears it down. A box-owned autologin session is restarted at the client's resolution on a mismatch; a foreign/bare gamescope streams at its own mode. | +| `PUNKTFUNK_GAMESCOPE_MANAGED` | `1` | **Managed** model (the default where session infra is detected): the host takes the box's gamescope over and relaunches it headless at the client's exact mode, restoring on idle. | | `PUNKTFUNK_GAMESCOPE_SESSION` | `steam` | The host owns a `gamescope-session-plus` (Steam) session at the client's mode — a headless appliance with no physical session running. | | `PUNKTFUNK_GAMESCOPE_NODE` | `auto` · node id | Discover and capture a **running** gamescope's PipeWire node at a fixed mode. Do **not** combine with `SESSION`. | | `PUNKTFUNK_GAMESCOPE_APP` | command | For an ad-hoc bare-gamescope session, the nested command to run (e.g. `vkcube`). | diff --git a/docs-site/content/docs/gnome.md b/docs-site/content/docs/gnome.md index 1f6a69cd..96c3d404 100644 --- a/docs-site/content/docs/gnome.md +++ b/docs-site/content/docs/gnome.md @@ -12,19 +12,20 @@ installed — see [Ubuntu](/docs/ubuntu), [Fedora](/docs/fedora), or [Arch](/doc ## host.env -Write `~/.config/punktfunk/host.env` with the GNOME settings. The host auto-detects the compositor -from your session, so the explicit `PUNKTFUNK_COMPOSITOR` is belt-and-braces: +The host auto-detects the compositor from your live session on every connect, so the starter +`~/.config/punktfunk/host.env` is one line: ```ini -# ~/.config/punktfunk/host.env -WAYLAND_DISPLAY=wayland-0 -XDG_CURRENT_DESKTOP=GNOME -PUNKTFUNK_COMPOSITOR=mutter +# ~/.config/punktfunk/host.env (keys are case-sensitive) PUNKTFUNK_VIDEO_SOURCE=virtual # GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU. -PUNKTFUNK_INPUT_BACKEND=libei ``` +> **Don't set `PUNKTFUNK_COMPOSITOR`, `WAYLAND_DISPLAY`, or `XDG_CURRENT_DESKTOP` here.** Pinning +> the compositor turns auto-detection **off** — per connect *and* mid-stream — so the host stops +> following session switches, and stale session values point it at dead sockets. Forcing a backend +> is a CI / dedicated-appliance posture, not desktop configuration. + You must be on a **Wayland** session (not X11), and Mutter must be **≥ 48**. See the [Configuration reference](/docs/configuration) for every option. diff --git a/docs-site/content/docs/hyprland.md b/docs-site/content/docs/hyprland.md index d58c8182..2efe8d13 100644 --- a/docs-site/content/docs/hyprland.md +++ b/docs-site/content/docs/hyprland.md @@ -19,14 +19,19 @@ or [Fedora](/docs/fedora). ## host.env -The host auto-detects a Hyprland session, so you usually need nothing here. To force the backend, set -these in `~/.config/punktfunk/host.env`: +The host auto-detects a Hyprland session, so the starter `~/.config/punktfunk/host.env` is one line: + +```ini +PUNKTFUNK_VIDEO_SOURCE=virtual +# GPU zero-copy capture→encode is ON by default; auto-falls back to CPU. Set PUNKTFUNK_ZEROCOPY=0 to force CPU. +``` + +To force the backend (CI/testing — note that pinning turns live-session auto-detection **off**, so +the host stops following session switches): ```ini PUNKTFUNK_COMPOSITOR=hyprland PUNKTFUNK_INPUT_BACKEND=wlr -PUNKTFUNK_VIDEO_SOURCE=virtual -# GPU zero-copy capture→encode is ON by default; auto-falls back to CPU. Set PUNKTFUNK_ZEROCOPY=0 to force CPU. ``` See [Configuration](/docs/configuration) for the full reference. diff --git a/docs-site/content/docs/kde.md b/docs-site/content/docs/kde.md index 6996574a..e57f31e5 100644 --- a/docs-site/content/docs/kde.md +++ b/docs-site/content/docs/kde.md @@ -13,20 +13,30 @@ installed — see [Ubuntu](/docs/ubuntu), [Fedora](/docs/fedora), [Arch](/docs/a ## host.env -A KDE starter `~/.config/punktfunk/host.env`: +The host auto-detects your KWin session on every connect — including a box that switches between +the Plasma desktop and Steam Game Mode — so the starter `~/.config/punktfunk/host.env` is one line: ```ini -WAYLAND_DISPLAY=wayland-0 -XDG_CURRENT_DESKTOP=KDE -PUNKTFUNK_COMPOSITOR=kwin +# ~/.config/punktfunk/host.env (keys are case-sensitive) PUNKTFUNK_VIDEO_SOURCE=virtual # GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU. -PUNKTFUNK_INPUT_BACKEND=libei ``` -The host auto-detects the running compositor on every connect, so most of this is optional — the -values above are just what it resolves to on a KWin session. See the -[Configuration reference](/docs/configuration) for every option. +> **Don't set `PUNKTFUNK_COMPOSITOR`, `WAYLAND_DISPLAY`, or `XDG_CURRENT_DESKTOP` here.** Pinning +> the compositor turns auto-detection **off** — per connect *and* mid-stream — so a switch to Game +> Mode then kills the stream instead of being followed, and stale session values point the host at +> dead sockets. Forcing a backend is for CI and dedicated appliances (the +> [headless session](#headless-session) below ships a `host.env.kde` that pins on purpose). + +If the box switches between the desktop and Game Mode, also enable lingering — the host is a user +service, and without linger the logout moment of a session switch tears it (and PipeWire) down +mid-stream: + +```sh +sudo loginctl enable-linger "$USER" +``` + +See the [Configuration reference](/docs/configuration) for every option. ## Use a Wayland session diff --git a/docs-site/content/docs/sway.md b/docs-site/content/docs/sway.md index d41f8c71..83e6af86 100644 --- a/docs-site/content/docs/sway.md +++ b/docs-site/content/docs/sway.md @@ -23,16 +23,21 @@ or [Fedora](/docs/fedora). ## host.env -The host auto-detects a wlroots session, so you usually need nothing here. To force the backend, set -these in `~/.config/punktfunk/host.env`: +The host auto-detects a wlroots session, so the starter `~/.config/punktfunk/host.env` is one line: ```ini -PUNKTFUNK_COMPOSITOR=wlroots # aliases: sway, wlr, hyprland (all the wlroots family; the exact backend is auto-detected) -PUNKTFUNK_INPUT_BACKEND=wlr PUNKTFUNK_VIDEO_SOURCE=virtual # GPU zero-copy capture→encode is ON by default; auto-falls back to CPU. Set PUNKTFUNK_ZEROCOPY=0 to force CPU. ``` +To force the backend (CI/testing — note that pinning turns live-session auto-detection **off**, so +the host stops following session switches): + +```ini +PUNKTFUNK_COMPOSITOR=wlroots # aliases: sway, wlr (the wlroots-proper family) +PUNKTFUNK_INPUT_BACKEND=wlr +``` + See [Configuration](/docs/configuration) for the full reference. ## How it works diff --git a/docs-site/content/docs/troubleshooting.md b/docs-site/content/docs/troubleshooting.md index b5bf7a89..5e89201f 100644 --- a/docs-site/content/docs/troubleshooting.md +++ b/docs-site/content/docs/troubleshooting.md @@ -103,7 +103,22 @@ See [GNOME](/docs/gnome) for the GL/EGL userspace details. - KWin must be **≥ 6.5.6** (`kwin_wayland --version`); GNOME **≥ 48**; gamescope **≥ 3.16.22**. See [KDE](/docs/kde) for the KWin/Wayland requirement and [gamescope](/docs/gamescope) for the gamescope one. -- Confirm `PUNKTFUNK_COMPOSITOR` in [`host.env`](/docs/configuration) matches your desktop. +- If [`host.env`](/docs/configuration) sets `PUNKTFUNK_COMPOSITOR`, **remove it** — the host + auto-detects the live compositor, and the pin points it at one backend even when a different + session is live (it also disables Gaming ↔ Desktop following). + +## Session fails right after editing host.env + +- Keys are **case-sensitive**: `punktfunk_gamescope_attach=1` sets nothing — use the exact + uppercase names. +- Hardcoded session anchors with the wrong uid (`XDG_RUNTIME_DIR=/run/user/1000` when `id -u` + isn't 1000) point the host at another user's PipeWire/D-Bus: audio errors like + `pw audio connect … Creation failed`, no capture, and clients reporting the host as + unreachable or asleep. **Delete both anchor lines** — a `systemctl --user` service doesn't need + them — or fix the uid. +- `PUNKTFUNK_COMPOSITOR` pins the backend and disables Gaming ↔ Desktop following — remove it on + any box that switches sessions. +- The env file is read at service start: `systemctl --user restart punktfunk-host` after edits. ## Capture fails: "Session creation inhibited" (GNOME) diff --git a/packaging/bazzite/README.md b/packaging/bazzite/README.md index e5a63b3d..c503861b 100644 --- a/packaging/bazzite/README.md +++ b/packaging/bazzite/README.md @@ -234,33 +234,25 @@ cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env The Bazzite template (`packaging/bazzite/host.env`) contains: ```sh -XDG_RUNTIME_DIR=/run/user/1000 -DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus - -# gamescope backend: spawned per session, no compositor login required. -PUNKTFUNK_COMPOSITOR=gamescope PUNKTFUNK_VIDEO_SOURCE=virtual -PUNKTFUNK_GAMESCOPE_APP=steam -gamepadui - -# gamescope hosts its own EIS input socket — input lands in the nested session. -PUNKTFUNK_INPUT_BACKEND=gamescope # GPU zero-copy capture (dmabuf -> CUDA -> NVENC) is ON by default and auto-falls back to CPU if # unavailable. No need to set it. Set to 0 only to force the CPU path. # PUNKTFUNK_ZEROCOPY=0 #RUST_LOG=info + +# Gaming Mode = ATTACH: the box owns its gamescope session; the host captures + follows it. +PUNKTFUNK_GAMESCOPE_ATTACH=1 ``` **What each knob means and why these are the Bazzite defaults:** | Knob | Value | Meaning | |---|---|---| -| `XDG_RUNTIME_DIR` / `DBUS_SESSION_BUS_ADDRESS` | `…/user/1000` | Session bus / runtime dir. **`1000` assumes your user is UID 1000** — change both if `id -u` says otherwise. | -| `PUNKTFUNK_COMPOSITOR` | `gamescope` | **The Bazzite default.** The host spawns a **headless gamescope per session** at the client's exact resolution/refresh and captures its PipeWire node — so you need **no graphical desktop login** to stream. Bazzite ships gamescope, so this "just works." | +| *(no compositor / no anchors)* | — | The host **auto-detects** the live session per connect (Gaming Mode gamescope vs the KDE desktop) and follows switches mid-stream; a `systemctl --user` service inherits the right `XDG_RUNTIME_DIR` and the host derives the bus itself. Pinning `PUNKTFUNK_COMPOSITOR` or hardcoding uid-1000 anchors only breaks this — leave them out. | | `PUNKTFUNK_VIDEO_SOURCE` | `virtual` | Create a per-client virtual output at the client's exact WxH@Hz (the flagship "native resolution, no scaling" mode), vs. `portal` which captures an existing monitor. | -| `PUNKTFUNK_GAMESCOPE_APP` | `steam -gamepadui` | The command launched **inside** the nested gamescope — here, a SteamOS-style couch UI. Set it to whatever you want the session to run. | -| `PUNKTFUNK_INPUT_BACKEND` | `gamescope` | Inject mouse/keyboard/gamepad into the nested gamescope via its own EIS socket. | +| `PUNKTFUNK_GAMESCOPE_ATTACH` | `1` | Gaming Mode model: the **box** owns its gamescope session; the host attaches to whatever's live and never tears it down. Swap for `PUNKTFUNK_GAMESCOPE_MANAGED=1` to have the host relaunch the gaming session headless at the **client's** exact mode instead (see the template's comments). | | `PUNKTFUNK_ZEROCOPY` | `on` *(default)* | GPU zero-copy capture (dmabuf → CUDA → NVENC), on by default. Falls back to CPU automatically if unavailable; set `0` to force the CPU path. | | `RUST_LOG` | (commented) | Uncomment `RUST_LOG=info` for verbose logs while debugging. | @@ -268,16 +260,14 @@ PUNKTFUNK_INPUT_BACKEND=gamescope games a virtual Sony DualSense (lightbar, adaptive triggers, touchpad, motion) instead of the default X-Box-360 pad. The feedback flows back to a real DualSense on the client. -**Alternative — drive the full Plasma/GNOME desktop** instead of a nested gamescope (per the -template's footer comment): switch to `PUNKTFUNK_COMPOSITOR=kwin` and -`PUNKTFUNK_INPUT_BACKEND=libei`, and run the host **inside** a KDE session with `WAYLAND_DISPLAY` / -`XDG_CURRENT_DESKTOP` set. The full knob list (FEC %, per-stage timing, etc.) is in -`scripts/host.env.example` / `/usr/share/punktfunk/host.env.example`. +**The Plasma desktop needs no extra config:** the same auto-detection streams the KDE Desktop +session whenever that's what's live — no compositor pin, no `WAYLAND_DISPLAY` / +`XDG_CURRENT_DESKTOP` (the host retargets those per connect). The full knob list (FEC %, per-stage +timing, etc.) is in `scripts/host.env.example` / `/usr/share/punktfunk/host.env.example`. -> The gamescope default is what makes Bazzite the easy path: it's a **headless, per-session** -> compositor — no desktop login, no display manager, no `--drm` scanout. You don't need any of the -> headless-KDE bring-up scripts (`scripts/headless/run-headless-kde.sh`) on Bazzite unless you -> deliberately switch to the KWin backend. +> Auto-detection is what makes Bazzite the easy path: the host follows the box between Gaming Mode +> and the Desktop — even mid-stream — with a one-line config. You don't need any of the +> headless-KDE bring-up scripts (`scripts/headless/run-headless-kde.sh`) on Bazzite. --- @@ -474,8 +464,11 @@ desktop viewer. NVIDIA driver. The code falls back to CPU automatically; check the log for the fallback line and verify the `-nvidia` image / driver is healthy. -- **Wrong UID in `host.env`.** `XDG_RUNTIME_DIR=/run/user/1000` and the bus path assume UID 1000. Run - `id -u`; if it's different, fix both lines or the host can't reach your session's PipeWire/D-Bus. +- **Session anchors in `host.env`.** The template no longer sets `XDG_RUNTIME_DIR` / + `DBUS_SESSION_BUS_ADDRESS` — a `systemctl --user` service inherits the right values. If an older + config hardcodes them with the wrong uid (`/run/user/1000` when `id -u` isn't 1000), the host + points at another user's PipeWire/D-Bus and everything fails (`pw audio connect … Creation + failed`, no capture). Delete both lines, or fix the uid. - **Service `ExecStart` points at a missing path in `$HOME`.** The dev unit references `%h/punktfunk/target/release/...`. The RPM binary is `/usr/bin/punktfunk-host`. Override diff --git a/packaging/bazzite/host.env b/packaging/bazzite/host.env index 97e4cde4..42699449 100644 --- a/packaging/bazzite/host.env +++ b/packaging/bazzite/host.env @@ -3,10 +3,9 @@ # The compositor + input backend are AUTO-DETECTED per connect from the ACTIVE session: the host # follows the box as you flip between Steam Gaming Mode (gamescope — a managed session at the # CLIENT's resolution) and a KDE/GNOME Desktop (KWin/Mutter virtual output at the client's mode). -# So nothing here forces a backend — only the trustworthy anchors stay. - -XDG_RUNTIME_DIR=/run/user/1000 -DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus +# So nothing here forces a backend, and no session anchors are needed: a `systemctl --user` +# service inherits the correct XDG_RUNTIME_DIR and the host derives the bus from it. (Keys are +# CASE-SENSITIVE — use the exact uppercase names.) PUNKTFUNK_VIDEO_SOURCE=virtual @@ -23,10 +22,11 @@ PUNKTFUNK_VIDEO_SOURCE=virtual # # GAME MODE = ATTACH (the box owns its session; the host follows). The box decides whether it's in # Steam Gaming Mode or a Desktop — you switch with the normal Steam UI / "Switch to Desktop". The -# host just ATTACHES to whatever's live and captures it; it never tears the session down or relaunches -# it. So switching Desktop<->Game is rock-solid, and when you disconnect the box STAYS in its current -# mode — reconnecting drops you right back where you were. The streamed resolution in game mode is the -# box's gamescope mode (see SCREEN_WIDTH/HEIGHT in /etc/gamescope-session-plus/sessions.d/steam). +# host just ATTACHES to whatever's live and captures it; it never tears the session down. So +# switching Desktop<->Game is rock-solid, and when you disconnect the box STAYS in its current +# mode — reconnecting drops you right back where you were. On a resolution mismatch the host +# restarts the box's own game-mode session at the CLIENT's resolution (a foreign/bare gamescope +# instead streams at its own mode). PUNKTFUNK_GAMESCOPE_ATTACH=1 # # Opt OUT to the MANAGED model instead (host tears the box's gamescope down on connect and launches diff --git a/packaging/kde/host.env b/packaging/kde/host.env index c9ba2b7d..0cedeece 100644 --- a/packaging/kde/host.env +++ b/packaging/kde/host.env @@ -1,4 +1,11 @@ # punktfunk host config for a Fedora/Ubuntu KDE Plasma appliance (kwin backend). +# +# APPLIANCE-ONLY: this file deliberately PINS the backend (PUNKTFUNK_COMPOSITOR) and the session +# env (WAYLAND_DISPLAY/XDG_CURRENT_DESKTOP) at the dedicated headless KWin session — which also +# turns OFF the host's live-session auto-detection and Desktop<->Game following. On a normal +# desktop (or any box that switches to Steam Game Mode) do NOT use this file; start from +# host.env.example instead, whose defaults auto-detect and follow the live session. +# # Copy to ~/.config/punktfunk/host.env. Pairs with punktfunk-kde-session.service, which brings # up a headless `kwin --virtual` on wayland-kde (with KWIN_WAYLAND_NO_PERMISSION_CHECKS=1 so the # host can bind KWin's privileged zkde_screencast protocol — an interactive Plasma session will diff --git a/packaging/nix/README.md b/packaging/nix/README.md index 59ec69ad..22763cc7 100644 --- a/packaging/nix/README.md +++ b/packaging/nix/README.md @@ -168,14 +168,16 @@ Everything the RPM's `%install` + `%post` do, declaratively: ### Headless / appliance -Set `autoStart = true`, enable lingering, and pick a backend in `settings`: +Set `autoStart = true`, enable lingering, and — for a **dedicated single-session appliance** — +pin a backend in `settings` (pinning `PUNKTFUNK_COMPOSITOR` disables live-session auto-detection, +so leave it out on any box that switches between a desktop and Game Mode): ```nix services.punktfunk.host = { enable = true; autoStart = true; users = [ "streamer" ]; - settings = { PUNKTFUNK_COMPOSITOR = "gamescope"; }; # or kwin/mutter/wlroots + settings = { PUNKTFUNK_COMPOSITOR = "gamescope"; }; # appliance-only; omit to auto-detect }; users.users.streamer.linger = true; # For the gamescope/KWin backends extend the service PATH, e.g.: diff --git a/scripts/host.env.example b/scripts/host.env.example index ad80e377..32551269 100644 --- a/scripts/host.env.example +++ b/scripts/host.env.example @@ -1,16 +1,17 @@ # punktfunk host configuration (~/.config/punktfunk/host.env) — consumed by punktfunk-host.service. # -# The compositor + input backend are AUTO-DETECTED per connect from the live session (the host -# probes which compositor is actually running and retargets WAYLAND_DISPLAY/XDG_CURRENT_DESKTOP/ -# DBUS at it), so a box that flips between Steam Gaming Mode and a KDE/GNOME desktop is followed -# automatically. The blocks below are OPTIONAL OVERRIDES — uncomment one only to force a backend -# (this also skips the per-connect env retargeting). The anchors XDG_RUNTIME_DIR + DBUS stay. - -# Session / compositor environment (headless KWin example). -XDG_RUNTIME_DIR=/run/user/1000 -DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus -WAYLAND_DISPLAY=wayland-kde -XDG_CURRENT_DESKTOP=KDE +# YOU BARELY NEED THIS FILE. The host AUTO-DETECTS the live session per connect — which compositor +# is running (KWin / Mutter / sway / Hyprland / gamescope), its Wayland socket, session bus, and the +# matching input backend — and FOLLOWS the box when it switches between a desktop and Steam Gaming +# Mode, even mid-stream. Everything below except PUNKTFUNK_VIDEO_SOURCE is an optional override. +# +# Two rules that save debugging sessions: +# * Keys are CASE-SENSITIVE. `punktfunk_gamescope_attach=1` sets nothing — use the exact +# uppercase names. +# * On a desktop you actually use, do NOT set PUNKTFUNK_COMPOSITOR / WAYLAND_DISPLAY / +# XDG_CURRENT_DESKTOP. Pinning the compositor DISABLES session-following (a switch to Game +# Mode mid-stream then kills the stream instead of being followed), and stale session vars +# point detection at dead sockets. Those knobs are for CI and dedicated appliances (below). # Video source: `virtual` creates a per-client virtual output at the client's exact # resolution+refresh (the flagship mode); `portal` captures an existing monitor. @@ -20,33 +21,41 @@ PUNKTFUNK_VIDEO_SOURCE=virtual # CPU automatically. No need to set it. Set to 0 only to force the CPU path. # PUNKTFUNK_ZEROCOPY=0 -# --- Bazzite / SteamOS-like host: host-managed Steam-Deck-UI session ----------------------- -# The host LAUNCHES gamescope-session-plus headless AT THE CLIENT'S mode (so games see the -# client's exact resolution + refresh, not the box's TV), and relaunches it when the mode -# changes. Requires the headless-appliance prereqs (linger + multi-user.target — see -# punktfunk-steam-session.service header) and NO physical gaming session running. -#PUNKTFUNK_COMPOSITOR=gamescope -#PUNKTFUNK_GAMESCOPE_SESSION=steam # host owns a gamescope-session-plus session at the client mode -#PUNKTFUNK_INPUT_BACKEND=gamescope -# Mutually exclusive with the above: ATTACH to a gamescope session something ELSE owns (fixed mode): -#PUNKTFUNK_GAMESCOPE_NODE=auto # discover + capture a running gamescope (do NOT combine with SESSION) +# --- Session anchors (rarely needed) ------------------------------------------------------- +# As a `systemctl --user` service the host inherits the correct XDG_RUNTIME_DIR from systemd and +# derives the bus (`unix:path=$XDG_RUNTIME_DIR/bus`) itself. Set these ONLY when running the host +# outside a user service (ssh, cron) — and with YOUR uid (`id -u`), never a copy-pasted 1000: a +# wrong uid points the host at another user's (nonexistent) PipeWire/D-Bus, and every session +# fails with errors like "pw audio connect … Creation failed". +#XDG_RUNTIME_DIR=/run/user/ +#DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user//bus -# --- GNOME / Mutter host (e.g. an Ubuntu desktop) ----------------------------------------- -# Attach to a running GNOME (Wayland) session — its default socket is wayland-0, not wayland-kde. -# Mutter creates the per-client virtual output via its `RecordVirtual` D-Bus API (a virtual -# monitor alongside any real one), and input goes through the RemoteDesktop portal (libei). On a -# real desktop the host runs as the logged-in user; headless GNOME also works (gnome-shell -# --headless). Needs GNOME ≥ 48 for the zero-copy RecordVirtual path. -#WAYLAND_DISPLAY=wayland-0 -#XDG_CURRENT_DESKTOP=GNOME -#PUNKTFUNK_COMPOSITOR=mutter -#PUNKTFUNK_VIDEO_SOURCE=virtual -#PUNKTFUNK_INPUT_BACKEND=libei +# --- Steam Gaming Mode (Linux boxes with gamescope session infra: Bazzite/SteamOS/Nobara) --- +# Game Mode is auto-handled; two models decide WHERE it runs when a client streams: +# * MANAGED (the default where session infra is detected) — the host relaunches the gaming +# session HEADLESS at the CLIENT's exact mode ("game mode on the virtual screen"); physical +# displays drop out of it, and the box is restored on a debounced idle after disconnect. +# * ATTACH — the BOX owns its session: Game Mode stays on the physical screen and the host +# captures/follows it, never tearing it down. Reconnects land wherever the box is. +#PUNKTFUNK_GAMESCOPE_ATTACH=1 # pick the ATTACH model +#PUNKTFUNK_GAMESCOPE_MANAGED=1 # force MANAGED even where infra detection wouldn't pick it +#PUNKTFUNK_GAMESCOPE_SESSION=steam # host owns a gamescope-session-plus session at the client mode +#PUNKTFUNK_GAMESCOPE_NODE=auto # raw attach: discover + capture a running gamescope's node +# # (do NOT combine with SESSION) +#PUNKTFUNK_GAMESCOPE_APP=vkcube # nested command for ad-hoc bare-gamescope sessions +#PUNKTFUNK_SESSION_WATCH=0 # disable mid-stream Desktop<->Game following (on by default +# # on gamescope-infra boxes) + +# --- Force a backend (CI / tests / dedicated single-session appliances ONLY) --------------- +# PINS the backend: the host stops following the live session entirely — per connect AND +# mid-stream. Fine for a dedicated headless appliance (punktfunk-kde-session.service, a pure +# gamescope box) or a CI run; wrong for any box that switches sessions. +#PUNKTFUNK_COMPOSITOR=kwin # kwin | mutter | gamescope | wlroots | hyprland +#PUNKTFUNK_INPUT_BACKEND=libei # wlr | libei | gamescope | uinput (auto-routed per connect) +#WAYLAND_DISPLAY=wayland-kde # headless-KDE appliance socket; retargeted per connect otherwise +#XDG_CURRENT_DESKTOP=KDE # Optional overrides (apps.json is the primary mechanism for per-app settings): -#PUNKTFUNK_COMPOSITOR=kwin # kwin | mutter | gamescope | wlroots -#PUNKTFUNK_GAMESCOPE_APP=vkcube # nested command for ad-hoc bare-gamescope sessions -#PUNKTFUNK_INPUT_BACKEND=libei # wlr | libei | gamescope | uinput #PUNKTFUNK_FEC_PCT=20 # video FEC overhead percent #PUNKTFUNK_PERF=1 # per-stage timing logs #PUNKTFUNK_MDNS=0 # disable the mDNS adverts (native + GameStream) — for multicast- diff --git a/scripts/punktfunk-host.service b/scripts/punktfunk-host.service index edcb5a74..d1148b9d 100644 --- a/scripts/punktfunk-host.service +++ b/scripts/punktfunk-host.service @@ -2,15 +2,18 @@ # GameStream/Moonlight-compat planes). For a SECURE native-only host (no plain-HTTP pairing / legacy # GCM nonce reuse — security-review #5/#9; native clients only), drop `--gamestream` from ExecStart. # -# Install (against an already-running compositor session): +# Install (against an already-running compositor session — the host auto-detects and follows it, +# so host.env needs no backend config): # mkdir -p ~/.config/systemd/user && cp scripts/punktfunk-host.service ~/.config/systemd/user/ -# cp scripts/host.env.example ~/.config/punktfunk/host.env # then edit for your backend +# cp scripts/host.env.example ~/.config/punktfunk/host.env # defaults are right for a desktop # systemctl --user daemon-reload && systemctl --user enable --now punktfunk-host # -# Self-contained boot appliance (no login, no manual steps after boot): +# Self-contained boot appliance (no login, no manual steps after boot). These routes PIN the +# backend via PUNKTFUNK_COMPOSITOR — correct for a dedicated single-session box, but it turns off +# live-session auto-detection, so never do it on a desktop that switches sessions (Game Mode etc.): # - kwin backend (stream the Plasma desktop): also install + enable -# punktfunk-kde-session.service (it brings up the headless KWin session this After=s), and set -# PUNKTFUNK_COMPOSITOR=kwin + WAYLAND_DISPLAY=wayland-kde in host.env. +# punktfunk-kde-session.service (it brings up the headless KWin session this After=s), and use +# the shipped packaging/kde/host.env (pins kwin + WAYLAND_DISPLAY=wayland-kde on purpose). # - gamescope backend (stream a nested app, no desktop): set PUNKTFUNK_COMPOSITOR=gamescope in # host.env — the host spawns gamescope per session, so no kde-session unit is needed. # Then `sudo loginctl enable-linger "$USER"` so user units start at boot, and reboot.