diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73cbb429b..0e0301850 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,7 +112,8 @@ dense. When a change touches a user-facing fact, update the docs-site page that PR. CI enforces the cheap half of this (`scripts/ci/check-docs-drift.sh` and `check-docs-links.sh`): -the OpenAPI snapshot must match `api/openapi.json`, every `PUNKTFUNK_*` variable the docs mention +the OpenAPI snapshot must match `api/openapi.json`, the docs-site copy of `data/platforms.json` must +match the canonical one, every `PUNKTFUNK_*` variable the docs mention must still exist in the tree, the counts of undocumented `PUNKTFUNK_*` variables and undocumented `punktfunk-host` subcommands may never grow (document the new knob, or consciously raise the baseline in the script), and internal docs links must resolve. diff --git a/data/platforms.json b/data/platforms.json index d4cc8ac56..b3636ed95 100644 --- a/data/platforms.json +++ b/data/platforms.json @@ -1,14 +1,20 @@ { - "$comment": "Single source for the install/port facts that used to drift across four surfaces (docs-and-onboarding-overhaul WP1). Consumers: docs-site install pages (WP2), the website download page (WP3), the guided install script (WP4). A port number, repo URL or install command lives HERE and nowhere else — pages quote it, they don't restate it. Validated by scripts/ci/check-docs-drift.sh (parse only; ponytail: cross-check against code literals when a consumer exists).", + "$comment": "Single source for the install/port facts that used to drift across four surfaces (docs-and-onboarding-overhaul WP1). Consumers: docs-site install pages ( and in docs-site/src/components/platforms.tsx, reading the byte-identical snapshot docs-site/src/data/platforms.json that check-docs-drift.sh gates), the website download page (WP3), the guided install script (WP4). A port number, repo URL or install command lives HERE and nowhere else — pages quote it, they don't restate it. `install` is the shell snippet that puts the package on the box (lines, in order); `url` is a download/store link where there is no command. Validated by scripts/ci/check-docs-drift.sh (parse + snapshot sync; ponytail: cross-check against code literals when a consumer exists).", "ports": { "mgmt": { "port": 47990, "proto": "tcp", - "what": "management REST API + web console endpoint", + "what": "management REST API (HTTPS + token; read-only status/library off loopback)", "env": "PUNKTFUNK_MGMT_BIND", "conflict": "Sunshine / Apollo / Vibeshine serve their web UI on 47990 too — the one port still shared with them once GameStream compat is off. Move it via PUNKTFUNK_MGMT_BIND; clients relearn it from discovery." }, + "web": { + "port": 47992, + "proto": "tcp", + "what": "web console (HTTPS, login-gated); plugin interfaces on 47993", + "also": [47993] + }, "native": { "port": 9777, "proto": "udp", @@ -18,7 +24,7 @@ "data": { "port": null, "proto": "udp", - "what": "per-session video data plane — random port + hole-punch by default, pinnable", + "what": "per-session video data plane — ephemeral port the client hole-punches; nothing fixed to open", "env": "PUNKTFUNK_DATA_PORT" }, "mdns": { @@ -29,26 +35,36 @@ "gamestream": { "tcp": [47984, 47989, 48010], "udp": [47998, 47999, 48000], - "what": "GameStream/Moonlight-compat planes (opt-in, `serve --gamestream`)", + "what": "GameStream/Moonlight-compat planes (opt-in, PUNKTFUNK_GAMESTREAM=1)", "conflict": "Sunshine / Apollo / Vibeshine bind these same fixed ports and advertise the same mDNS name — run only one GameStream host at a time, or keep punktfunk native-only." } }, + "firewall": { + "$comment": "Service/profile names the Linux packages install for firewalld and ufw — the packages never open a port themselves.", + "native": "punktfunk-native", + "gamestream": "punktfunk-gamestream", + "web": "punktfunk-web" + }, + "conflicts": { "hosts": ["Sunshine", "Apollo", "Vibeshine"], "detect": "punktfunk-host detect-conflicts", "detectExit": "1 only when a conflicting host runs or will start on its own; dormant leftovers print but exit 0", - "docs": "/docs/troubleshooting#another-streaming-host-sunshine-apollo--is-installed" + "docs": "/docs/switching-from-sunshine" }, "platforms": [ { "id": "debian", - "name": "Debian 13 / Ubuntu 24.04+", + "name": "Debian 13+ / Ubuntu 26.04+", "installs": "host", "packageManager": "apt", "docs": "/docs/debian", + "repo": "https://git.unom.io/api/packages/unom/debian", "install": [ + "sudo install -d -m 0755 /etc/apt/keyrings", + "curl -fsSL https://git.unom.io/api/packages/unom/debian/repository.key | sudo tee /etc/apt/keyrings/punktfunk.asc >/dev/null", "echo \"deb [signed-by=/etc/apt/keyrings/punktfunk.asc] https://git.unom.io/api/packages/unom/debian stable main\" | sudo tee /etc/apt/sources.list.d/punktfunk.list", "sudo apt update", "sudo apt install punktfunk-host" @@ -60,11 +76,12 @@ "installs": "host", "packageManager": "pacman", "docs": "/docs/arch", + "repo": "https://git.unom.io/api/packages/unom/arch", "install": [ "curl -fsS https://git.unom.io/api/packages/unom/arch/repository.key | sudo pacman-key --add -", "sudo pacman-key --lsign-key E0CA04465C99C936E0B0C6510A317015A34DDD69", - "printf '\\n[punktfunk]\\nServer = https://git.unom.io/api/packages/unom/arch/$repo/$arch\\n' | sudo tee -a /etc/pacman.conf >/dev/null", - "sudo pacman -Sy punktfunk-host" + "grep -q '^\\[punktfunk\\]' /etc/pacman.conf || printf '\\n[punktfunk]\\nServer = https://git.unom.io/api/packages/unom/arch/$repo/$arch\\n' | sudo tee -a /etc/pacman.conf >/dev/null", + "sudo pacman -Syu punktfunk-host" ] }, { @@ -75,8 +92,18 @@ "docs": "/docs/fedora", "repo": "https://git.unom.io/api/packages/unom/rpm/fedora-44", "install": [ - "sudo dnf install punktfunk", - "sudo usermod -aG input \"$USER\"" + "sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO'", + "[punktfunk]", + "name=punktfunk", + "# fedora-44 on Fedora 44; bazzite on Fedora 43 (a plain Fedora 43 build of the same package)", + "baseurl=https://git.unom.io/api/packages/unom/rpm/fedora-44", + "enabled=1", + "gpgcheck=1", + "repo_gpgcheck=1", + "gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key", + " https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk", + "REPO", + "sudo dnf install punktfunk" ] }, { @@ -95,20 +122,32 @@ "name": "NixOS", "installs": "host", "packageManager": "nix", - "docs": "/docs/install", + "docs": "/docs/nixos", "install": [ "# flake input: inputs.punktfunk.url = \"git+https://git.unom.io/unom/punktfunk\";", "# then: imports = [ punktfunk.nixosModules.default ]; services.punktfunk.host.enable = true;" ] }, + { + "id": "steamos", + "name": "SteamOS (Steam Deck as host)", + "installs": "host", + "packageManager": "script", + "docs": "/docs/steamos-host", + "install": [ + "git clone https://git.unom.io/unom/punktfunk ~/punktfunk", + "bash ~/punktfunk/scripts/steamdeck/install.sh" + ] + }, { "id": "windows", - "name": "Windows 10/11", + "name": "Windows 11 (22H2+)", "installs": "host", "packageManager": "winget", "docs": "/docs/windows-host", "install": [ - "winget install unom.PunktfunkHost --interactive" + "winget source add -n punktfunk https://winget.punktfunk.unom.io -t Microsoft.Rest", + "winget install unom.PunktfunkHost" ] }, { @@ -116,11 +155,52 @@ "name": "Linux client (any distro)", "installs": "client", "packageManager": "flatpak", - "docs": "/docs/install-client", + "docs": "/docs/install-client#linux-desktop-flatpak", "install": [ "flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.flatpakref" ] + }, + { + "id": "steam-deck-client", + "name": "Steam Deck (Gaming Mode)", + "installs": "client", + "packageManager": "decky", + "docs": "/docs/steam-deck" + }, + { + "id": "windows-client", + "name": "Windows client", + "installs": "client", + "packageManager": "msix", + "docs": "/docs/install-client#windows", + "install": [ + "curl.exe -LO https://git.unom.io/api/packages/unom/generic/punktfunk-client-windows/latest/punktfunk-client-windows_x64.msix", + "Add-AppxPackage .\\punktfunk-client-windows_x64.msix" + ] + }, + { + "id": "macos-client", + "name": "macOS", + "installs": "client", + "packageManager": "dmg", + "docs": "/docs/install-client#macos", + "url": "https://git.unom.io/unom/punktfunk/releases" + }, + { + "id": "apple-client", + "name": "iPhone, iPad, Apple TV", + "installs": "client", + "packageManager": "testflight", + "docs": "/docs/install-client#ios-ipados-apple-tv", + "url": "https://testflight.apple.com/join/Qr7uSemk" + }, + { + "id": "android-client", + "name": "Android / Android TV", + "installs": "client", + "packageManager": "play", + "docs": "/docs/install-client#android", + "url": "https://play.google.com/store/apps/details?id=io.unom.punktfunk" } ] - } diff --git a/docs-site/README.md b/docs-site/README.md index 8cfbdffb4..58578f2fc 100644 --- a/docs-site/README.md +++ b/docs-site/README.md @@ -29,6 +29,19 @@ of `api/openapi.json`, and the `rust` job regenerates the spec and diffs it agai one — so a management-API change can't publish stale API docs any more, it fails CI until you run the two commands above. +## Install commands and ports + +`src/data/platforms.json` is a byte-identical snapshot of the repo-root +[`data/platforms.json`](../data/platforms.json) — the single source for install commands, repo +URLs, port facts and the Sunshine/Apollo/Vibeshine conflict facts. The `` +and `` MDX components (`src/components/platforms.tsx`) render from it, so no page restates +a command or a port. It's a snapshot for the same reason as `openapi.json` (the Docker build context +is this directory alone), and the same `docs-drift` job fails unless it matches: + +```sh +cp data/platforms.json docs-site/src/data/platforms.json # from the repo root, after editing the canonical file +``` + ## Develop ```sh diff --git a/docs-site/content/docs/access-levels.md b/docs-site/content/docs/access-levels.md index 6b2c5e5f9..430ad1ce2 100644 --- a/docs-site/content/docs/access-levels.md +++ b/docs-site/content/docs/access-levels.md @@ -3,15 +3,14 @@ title: Access levels description: What each paired device may do, and for how long — the three presets, the advanced toggles, temporary access that expires on its own, and what access control honestly does not cover. --- -Pairing used to be all-or-nothing: a paired device had full control of the host, forever. **Access** -changes that. Every paired device carries an **access level** — what it may send to the host — and -optionally an expiry — how long that lasts. A friend's phone can be a second controller for the -evening and nothing more; the living-room TV can watch and play but never type into your desktop; -a spectator can see and hear without sending anything. +Pairing used to be all-or-nothing: a paired device had full control of the host, forever. Now every +paired device carries an **access level** — what it may send to the host — and optionally an +expiry: a friend's phone as a second controller for the evening, a TV that can play but never type, +a spectator who only watches. Access is **enforced by the host**. A client's UI reflects its access as a courtesy, but the host -drops anything a device isn't granted regardless of what the client sends — nothing a client can -send widens its own access. +drops anything a device isn't granted regardless of what the client sends — nothing a client sends +can widen its own access. You manage access from the host's [web console](/docs/web-console): when you [approve a device or arm pairing](/docs/pairing#choosing-access-when-you-admit-a-device), and any @@ -22,16 +21,16 @@ countdown if it expires) and an edit sheet. | Access level | What the device can do | |---|---| -| **Full control** | Everything — keyboard, mouse, controllers, clipboard, microphone, launching games. This is what pairing has always meant, and it stays the default: every device paired before access levels existed keeps full control, and so does a plain **Approve**. | +| **Full control** | Everything — keyboard, mouse, controllers, clipboard, microphone, launching games. What pairing has always meant, and still the default: every device paired before access levels existed keeps full control, and so does a plain **Approve**. | | **Controller only** | Gamepad input only — the guest and co-play preset. The device's pads show up as additional controllers (with rumble and pad audio), but it cannot type, move the mouse, read the clipboard, use the mic, or launch anything. | | **View only** | See and hear the stream, send nothing. The spectator preset. | -The preset label is derived from the underlying toggles, so a hand-tuned combination simply shows -as **Custom** — there is no separate thing to keep in sync. +The preset label is derived from the underlying toggles, so a hand-tuned combination shows as +**Custom** — there is no separate thing to keep in sync. ## The advanced toggles -Each preset is a bundle of six independent grants, exposed under **Advanced** in the edit sheet: +Each preset is a bundle of six independent grants, under **Advanced** in the edit sheet: | Toggle | Covers | |---|---| @@ -40,13 +39,13 @@ Each preset is a bundle of six independent grants, exposed under **Advanced** in | **Keyboard** | Key presses. | | **Clipboard** | The [shared clipboard](/docs/clipboard). Both switches still apply: the host operator's clipboard policy *and* this grant have to allow it — the grant can only narrow, never widen, what the operator permits. An ungranted device gets a clean "not permitted" instead of a toggle that silently does nothing. | | **Microphone** | Sending the client's microphone to the host. Without it, the session never attaches to the host's mic service at all. | -| **Launch** | Starting a game from the host's [library](/docs/game-library) when connecting. Without it, a connect that asks to launch is refused with a clear error rather than being dropped onto the bare desktop. The library remains *visible* — this governs launching, not browsing. | +| **Launch** | Starting a game from the host's [library](/docs/game-library) when connecting. Without it, a connect that asks to launch is refused with a clear error rather than dropped onto the bare desktop. The library stays *visible* — this governs launching, not browsing. | -**Controller only deliberately does not include Launch**: in co-play the owner drives what runs. If -you want a guest picking games from the couch, that's one Advanced toggle away. +**Controller only deliberately does not include Launch**: in co-play the owner drives what runs. +Want a guest picking games? Turn on that one Advanced toggle. A session's quality controls — resolution, bitrate, keyframe requests — are *not* governed. They -only shape that device's own stream, so restricting them would cost usability and buy no security. +only shape that device's own stream; restricting them would cost usability and buy no security. ## Temporary access @@ -54,7 +53,7 @@ Any grant can carry an expiry, picked when you approve the device or set later i **1 h / 4 h / 8 h / custom / forever**. - Expiry is **wall-clock time on the host** — "4 hours" means four hours from now by the host's - clock, matching the mental model of "until tonight". + clock. - A device streaming when its access runs out gets **warnings at 5 minutes and 1 minute** before the deadline, then its session ends with an explicit reason: *"Your access to this host has expired."* Only that device's sessions end — yours is untouched. @@ -65,43 +64,42 @@ Any grant can carry an expiry, picked when you approve the device or set later i extending re-arms the running session's deadline, and Expire now ends it with the same clean "access expired" message — no lingering stream. -Edits other than expiry are just as immediate: changing a device's access level while it streams -takes effect within moments, and removing the device ends its sessions. Access is per *device*, -not per session — two sessions from the same device share one grant. +Other edits are just as immediate: changing a device's access level while it streams takes effect +within moments, and removing the device ends its sessions. Access is per *device*, not per session +— two sessions from the same device share one grant. ## What this does not cover -Be honest with yourself about three limits before relying on access levels: +Three limits before relying on access levels: > **A view-only guest still sees your whole desktop.** On the shared-desktop backends every > session shows the *same* desktop — access levels govern what a device can send *in*, not what it > sees going *out*. A view-only or controller-only guest watches and hears everything you do, -> notifications included. Don't read email with a spectator attached. +> notifications included. -- **Moonlight / GameStream devices are not governed yet.** Access levels currently apply to the - native Punktfunk protocol. A device paired via [Moonlight](/docs/moonlight) has full control and - shows an honest **Full (ungoverned)** chip in the console — not a fake editor. When enforcement - reaches the GameStream plane, it will be *silent* from the client's side: the GameStream - protocol has no way to tell a Moonlight client about its access, so an ungranted keyboard will - simply be inert, with the explanation visible only in the console. +- **Moonlight / GameStream devices are not governed yet.** Access levels apply to the native + Punktfunk protocol. A device paired via [Moonlight](/docs/moonlight) has full control and shows + an honest **Full (ungoverned)** chip in the console — not a fake editor. When enforcement reaches + the GameStream plane it will be *silent* from the client's side: the protocol has no way to tell + a Moonlight client about its access, so an ungranted keyboard will simply be inert, with the + explanation visible only in the console. - **Older Punktfunk clients are enforced, but can't explain it.** The host enforces access - identically for every client version. A client from before this feature just lacks the chrome: + identically for every client version; a client from before this feature just lacks the chrome: no "Controller only · ends in 2 h" chip, no expiry warnings, a generic disconnect instead of - "access expired" — and an ungranted keyboard is silently inert rather than never captured in the - first place. If a guest reports "my keyboard does nothing", check their access level in the - console first, then whether their client is current. + "access expired" — and an ungranted keyboard is silently inert rather than never captured. If a + guest reports "my keyboard does nothing", check their access level in the console first, then + whether their client is current. -Up-to-date native clients do get the chrome: they stop capturing what can't land (no keyboard grab +Up-to-date native clients get the chrome: they stop capturing what can't land (no keyboard grab without the Keyboard grant), hide the clipboard and mic controls when ungranted, show a small overlay chip naming the session's access and time remaining, and surface the expiry warnings as toasts. ## Where enforcement happens -For the security-minded: the host checks every input event against the device's grants before -injecting it, refuses ungranted planes at session setup (no Gamepad grant means the virtual pads -are never created; no Microphone grant means the mic plane never attaches), and re-pairing a -device **preserves** its existing access — the only way to widen a grant is the console's own -dialogs, behind the console login. Dropped traffic is logged once per session and category, not -per event, so a misbehaving client can't flood the log. See [Security & Safe -Use](/docs/security) for the wider picture. +The host checks every input event against the device's grants before injecting it, refuses +ungranted planes at session setup (no Gamepad grant means the virtual pads are never created; no +Microphone grant means the mic plane never attaches), and re-pairing a device **preserves** its +existing access — the only way to widen a grant is the console's own dialogs, behind the console +login. Dropped traffic is logged once per session and category, not per event, so a misbehaving +client can't flood the log. See [Security & Safe Use](/docs/security) for the wider picture. diff --git a/docs-site/content/docs/arch.md b/docs-site/content/docs/arch.md deleted file mode 100644 index 0cd0a8116..000000000 --- a/docs-site/content/docs/arch.md +++ /dev/null @@ -1,214 +0,0 @@ ---- -title: Arch Linux -description: Install a Punktfunk host on Arch (and Arch-derived distros) from the signed pacman binary repo. ---- - -Set up a Punktfunk host on **Arch Linux** (or an Arch-derived distro like CachyOS/EndeavourOS). The -host installs from a **signed pacman binary repo**, so it updates with `pacman -Syu` like the rest -of your system — no building required. Host encode is **NVENC on NVIDIA**; on **AMD/Intel** HEVC -and AV1 go through **Vulkan Video**, with **VAAPI** for H.264 and as the fallback -(`PUNKTFUNK_ENCODER=auto` picks per GPU). - -> New here? Read [Security & Safe Use](/docs/security) first — a streaming host is remote control of -> the machine, so keep it on a trusted LAN or VPN and require pairing. - -> Prefer to build it yourself? A split `PKGBUILD` (host + client + optional web console) is in the -> repo at `packaging/arch/` — see the [appendix](#appendix--build-from-source-pkgbuild). The binary -> repo below is the supported path. - -## 1. GPU prerequisites - -- **NVIDIA:** `sudo pacman -S --needed nvidia-utils` (provides NVENC + the EGL/CUDA zero-copy path). - Arch's stock `ffmpeg` already has NVENC built in — no RPM-Fusion-style swap like Fedora needs. -- **AMD / Intel:** the Mesa stack. HEVC/AV1 encode goes through **Vulkan Video** by default, so - install the Vulkan driver — `vulkan-radeon` (AMD) or `vulkan-intel` (Intel) — alongside the VAAPI - drivers (`libva-mesa-driver` for AMD, `intel-media-driver` for Intel), which carry H.264 and the - fallback path. Both are usually already installed on a desktop. - -## 2. Add the signed repo - -The registry **signs its database and every package**, so first trust its key once (after this, -packages install signature-verified): - -```sh -# Trust the registry signing key. -curl -fsS https://git.unom.io/api/packages/unom/arch/repository.key \ - | sudo pacman-key --add - -sudo pacman-key --lsign-key E0CA04465C99C936E0B0C6510A317015A34DDD69 - -# Add the repo (append to /etc/pacman.conf). No SigLevel line needed — pacman's default -# verifies signed packages against the key you just trusted. (printf, not a heredoc, so this -# works in fish too — CachyOS's default shell has no `</dev/null -``` - -> **Stable vs canary.** `[punktfunk]` is the **stable** channel — it moves only when a `vX.Y.Z` -> release is cut. For the latest `main` build, use `[punktfunk-canary]` instead (same `Server` line, -> just the repo name). Enable exactly one. See [Release Channels](/docs/channels). - -## 3. Install the host - -```sh -sudo pacman -Syu punktfunk-host # the streaming host -sudo pacman -Syu punktfunk-web # optional: the browser management console (pairing + status) -sudo pacman -Syu punktfunk-gamescope # optional: HDR (10-bit BT.2020 PQ) off gamescope sessions -sudo pacman -Syu punktfunk-scripting # optional: the plugin/script runner (see below) -sudo usermod -aG input "$USER" # /dev/uinput access for virtual gamepads (re-login to apply) -``` - -Also join `punktfunk` if **either** applies — you want the **virtual Steam Deck controller** -(paddles, trackpads, gyro — it reaches games as a real USB pad, which is why Steam Input adopts -it), or this box autologins into Steam **Gaming Mode** and you want the host to take that session -over at the client's resolution: - -```sh -sudo usermod -aG punktfunk "$USER" # usbip/vhci + display-manager takeover (re-login to apply) -``` - -That is a second group on purpose. It grants write access to the usbip `attach` file, which -materialises an arbitrary emulated USB device — so it stays off the `input` group everyone is -routinely told to join. Join it only on a machine you trust. On a plain desktop host, everything -else still works without it and the pad simply arrives as an ordinary Xbox 360 controller; on a -Gaming Mode box the takeover silently degrades to mirroring the box's own screen — see -[gamescope](/docs/gamescope#nobara-and-other-autologin-display-managers). - -Each install is a **full** `-Syu`, on purpose: our packages are built against current Arch -sonames, and `pacman -Sy ` would drop one onto a system whose other packages are still old — -the classic partial upgrade that breaks Arch boxes. To take several in one go, name them on a -single line: `sudo pacman -Syu punktfunk-host punktfunk-web punktfunk-gamescope`. - -`punktfunk-scripting` is the runner behind [Plugins](/docs/plugins); it isn't started for you — -`systemctl --user enable --now punktfunk-scripting` when you want it. `punktfunk-client` (the native -GTK4 Linux client) is in the same repo if this box is also a client. The host package ships the -systemd **user** units, the udev rule, the UDP socket-buffer sysctl tuning, and example configs. - -Updates later are a normal `sudo pacman -Syu`, then `systemctl --user restart punktfunk-host` so the -running host picks up the new binary. A `-Syu` moves every Punktfunk package you installed, so -restart `punktfunk-web` the same way if you run the console. The web console can run the update for -you — see [Updating the Host](/docs/updating); on Arch that button additionally needs -`PACMAN_FULL_SYSUPGRADE=1` in `/etc/punktfunk/update.conf`, because the only pacman update we will -run is a full one. - -## 4. Configure and run - -The host runs as a systemd **`--user`** service — it needs your session's PipeWire and D-Bus. Copy a -starting config: - -```sh -mkdir -p ~/.config/punktfunk -cp /usr/share/punktfunk/host.env.example ~/.config/punktfunk/host.env -``` - -How the host creates its virtual display and injects input depends on your desktop, not your distro — -edit `host.env` for the desktop you run, following its page for the exact settings and any quirks: - -- [KDE Plasma (KWin)](/docs/kde) -- [GNOME (Mutter)](/docs/gnome) -- [Steam / gamescope](/docs/gamescope) -- [Hyprland](/docs/hyprland) -- [Sway / wlroots](/docs/sway) - -Then enable the service and turn on linger so it starts at boot without a login: - -```sh -systemctl --user daemon-reload -systemctl --user enable --now punktfunk-host -sudo loginctl enable-linger "$USER" -``` - -Check it came up: - -```sh -systemctl --user status punktfunk-host # active -journalctl --user -u punktfunk-host -f # watch a client connect -``` - -Enable the browser console, find your login password, and arm PIN pairing from -[The Web Console](/docs/web-console). For a headless KWin appliance that streams at boot with no -graphical login, see [KDE → Headless session](/docs/kde#headless-session). Full reference: -[Configuration](/docs/configuration) · [Running as a Service](/docs/running-as-a-service). - -## 5. Open the firewall (if you have one) - -**Stock Arch ships no firewall** — every port is already open, so you can skip this. But **CachyOS -enables `ufw` by default** (firewalld is not installed), and some other spins (e.g. EndeavourOS) -enable **`firewalld`** — an Arch package never opens ports for you, so on those the host is -unreachable until you allow it. - -The `punktfunk-host` package installs openers for **both**, so it's a one-liner whichever you run. -The unit you enabled in step 4 runs `serve --gamestream` — the package installs it as it ships and -only rewrites the binary path — so that host serves **both** the native `punktfunk/1` plane and -stock [Moonlight](/docs/moonlight) clients, and needs **both** openers: - -```sh -# ufw — CachyOS (and Ubuntu, once you enable ufw): -sudo ufw allow punktfunk-native - -# firewalld — Fedora-like spins (EndeavourOS, …): -sudo firewall-cmd --reload # load the installed definitions -sudo firewall-cmd --permanent --add-service=punktfunk-native -sudo firewall-cmd --reload -``` - -Enabled **GameStream/Moonlight compat** (`PUNKTFUNK_GAMESTREAM=1` in `host.env` — see -[What the unit starts](/docs/running-as-a-service#what-the-unit-starts)), or you pass -`--gamestream` by hand? Then also open its service: - -```sh -sudo ufw allow punktfunk-gamestream # ufw -sudo firewall-cmd --permanent --add-service=punktfunk-gamestream && sudo firewall-cmd --reload -``` - -`punktfunk-native` opens the QUIC control port (UDP 9777), mDNS discovery and the mgmt/library API -(TCP 47990); `punktfunk-gamestream` opens the fixed Moonlight ports — TCP 47984, 47989 and 48010, -UDP 47998–48000 — plus the same mDNS. -The media **data plane** uses an *ephemeral* UDP port that the client opens with a hole-punch — the -host streams back out through the path the client opened, so there's **nothing fixed to open** as -long as the firewall allows outbound UDP (the default for both ufw and firewalld). - -Enabled the **web console** (`punktfunk-web`, above) and want to reach it from your phone or another -machine? It's not opened by the streaming rules — open its port too, the same one-liner way: - -```sh -sudo ufw allow punktfunk-web # ufw -sudo firewall-cmd --permanent --add-service=punktfunk-web && sudo firewall-cmd --reload # firewalld -``` - -That opens **TCP 47992** (HTTPS, login-gated). The mgmt API (47990) is opened for paired clients by the -`punktfunk-native` profile (game-library browsing over mTLS); off-loopback it serves only read-only -status/library, and every admin action stays loopback-only. Full port lists (`nftables`, explicit ports) are in -[`packaging/arch/README.md`](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md#firewall). - -## 6. Connect a client - -From any [client](/docs/clients), `--discover` finds the host on the LAN. On first connect, complete -the **PIN pairing**: arm it from [The Web Console](/docs/web-console#arm-pairing), which displays a -4-digit PIN to type into the client. (Pairing is required by default; pass `serve --open` only if -you deliberately want to disable it.) See [Clients](/docs/clients) for per-platform setup. - -## Next steps - -- **Keep it current** — [Updating the Host](/docs/updating). -- **Remove it again** — [Uninstalling](/docs/uninstall). -- **Something not working?** — [Troubleshooting](/docs/troubleshooting). - -## Appendix — build from source (PKGBUILD) - -To build instead of using the binary repo, use the split `PKGBUILD` in `packaging/arch/` (produces -`punktfunk-host` + `punktfunk-client`; set `PF_WITH_WEB=1` to also build `punktfunk-web` and -`PF_WITH_SCRIPTING=1` to also build `punktfunk-scripting` — both need `bun`): - -```sh -git clone https://git.unom.io/unom/punktfunk.git && cd punktfunk/packaging/arch -# Build the working tree (no git fetch): -PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver -sudo pacman -U punktfunk-host-*.pkg.tar.zst -``` - -NVENC/EGL come from the NVIDIA driver (`nvidia-utils`); on a GPU-less builder, symlink the CUDA -stub into the link path first (the `PKGBUILD` header documents this). Full details, the -Fedora→Arch dependency map, and the systemd-sysext mechanism are in -[`packaging/arch/README.md`](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md). -(For a **SteamOS host**, use the [on-device installer](/docs/steamos-host) instead — it builds -the host and the HDR gamescope against the running OS.) diff --git a/docs-site/content/docs/arch.mdx b/docs-site/content/docs/arch.mdx new file mode 100644 index 000000000..8a900f660 --- /dev/null +++ b/docs-site/content/docs/arch.mdx @@ -0,0 +1,81 @@ +--- +title: Arch Linux +description: Install the Punktfunk host on Arch, CachyOS or EndeavourOS from the signed pacman repo — four steps. +--- + +For **Arch Linux** and Arch-based distros (CachyOS, EndeavourOS, …). The host comes from a signed +binary repo and updates with `pacman -Syu` like everything else. SteamOS is different — it has +[its own page](/docs/steamos-host). + +## 1. GPU driver + +- **NVIDIA:** `sudo pacman -S --needed nvidia-utils` (NVENC and the zero-copy path; Arch's `ffmpeg` + already has NVENC built in). +- **AMD / Intel:** the Mesa stack you already have — `vulkan-radeon` / `vulkan-intel` for Vulkan + Video, `libva-mesa-driver` / `intel-media-driver` for VAAPI. Usually all installed on a desktop. + +## 2. Install the host + +Trust the repo key once, add the repo, install. Every install and update here is a **full** +`-Syu` on purpose — our packages are built against current Arch sonames, and `pacman -Sy ` is +the partial upgrade that breaks Arch boxes: + + + +The browser console is **optional** on Arch, so name it yourself — same line, full upgrade: +`sudo pacman -Syu punktfunk-web`. (Also in the repo: `punktfunk-gamescope` for HDR off gamescope, +`punktfunk-scripting` for [plugins](/docs/plugins), `punktfunk-client` if this box is also a client.) + +From then on a normal `sudo pacman -Syu` moves every Punktfunk package; restart the host afterwards +(`systemctl --user restart punktfunk-host`) — or let the [console do it](/docs/updating). + +## 3. Let it use your controllers + +Join the `input` group (virtual gamepads go through `/dev/uinput`), then **log out and back in**: + +```sh +sudo usermod -aG input "$USER" +``` + +Want the **virtual Steam Deck controller** (paddles, trackpads, gyro), or does this PC boot into Steam +**Gaming Mode**? Also join the `punktfunk` group — +[what it gates and why it's separate](/docs/gamescope#nobara-and-other-autologin-display-managers). + +## 4. Start it + +From a terminal inside your desktop session: + +```sh +systemctl --user enable --now punktfunk-host punktfunk-web +systemctl --user enable --now punktfunk-scripting # if you installed the plugin runner — other distros start it for you, Arch doesn't +``` + +**Firewall:** stock Arch has none. **CachyOS enables `ufw`**, EndeavourOS enables **firewalld** — +on those the host is unreachable until you allow it (the package installed the profiles): + +```sh +sudo ufw allow punktfunk-native && sudo ufw allow punktfunk-web # CachyOS (ufw) +sudo firewall-cmd --reload && sudo firewall-cmd --permanent --add-service=punktfunk-native --add-service=punktfunk-web && sudo firewall-cmd --reload # firewalld +``` + +[Ports & firewall](/docs/ports) has every port, and the GameStream profile if you turn Moonlight +compat on. + +**That's the install.** Continue with the [Quick Start from step 3](/docs/quickstart#3-open-the-web-console) +— open the console, pair a client, stream. + +## When you want more + +- **pacman says `database already registered`?** The repo got added twice — + [the one-line fix](/docs/troubleshooting#pacman-error-could-not-register-punktfunk-database-database-already-registered). + **`unable to satisfy dependency 'libavcodec.so=…'`?** FFmpeg major mismatch — + [what to do](/docs/troubleshooting#pacman-unable-to-satisfy-dependency-libavcodecso). +- `punktfunk-host detect-conflicts` tells you if Sunshine or Apollo is also running; + [Troubleshooting](/docs/troubleshooting) starts from the symptom. +- Your desktop's particulars — [KDE](/docs/kde), [GNOME](/docs/gnome), [gamescope](/docs/gamescope), + [Hyprland](/docs/hyprland), [Sway](/docs/sway). +- Stream with nobody logged in — [Running as a service](/docs/running-as-a-service) (`sudo loginctl + enable-linger "$USER"` is the one extra line). +- Track `main` instead of releases (`[punktfunk-canary]`, same `Server` line — enable exactly one) — + [Release channels](/docs/channels). Build it yourself with the split `PKGBUILD` — + [Build from source](/docs/build-from-source#arch-pkgbuild). diff --git a/docs-site/content/docs/automation.md b/docs-site/content/docs/automation.md index 32ff014e5..d0224281b 100644 --- a/docs-site/content/docs/automation.md +++ b/docs-site/content/docs/automation.md @@ -8,15 +8,15 @@ disconnects, a stream starts or stops, a pairing request arrives, a virtual disp the library changes, the host starts or shuts down. Two ways to consume them: - **Hooks** — zero-code: entries in `~/.config/punktfunk/hooks.json` run a **command** or POST a - **webhook** when a matching event fires. This covers the common automation: Do-Not-Disturb - during a stream, a phone notification on a pairing request, pausing downloads while playing. + **webhook** when a matching event fires. Covers the common automation: Do-Not-Disturb during a + stream, a phone notification on a pairing request, pausing downloads while playing. - **The event stream** — code: `GET /api/v1/events` on the management API is a standard [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) stream of the same events, for scripts and integrations that want to *decide* things (e.g. auto-approve pairing from a known subnet by calling the approve endpoint). Hooks **observe** — they can never veto or delay a connection, a stream, or a pairing decision, -and nothing you configure here runs anywhere near the streaming path. +and nothing configured here runs anywhere near the streaming path. ## The events @@ -38,7 +38,7 @@ and nothing you configure here runs anywhere near the streaming path. | `host.started` / `host.stopping` | the serve planes come up / wind down | version, whether GameStream is enabled | Every event is a small JSON document with a monotonic `seq`, a `ts_ms` timestamp, a `schema` -version (additive-only — fields get added, never renamed), and the fields above. Example: +version (additive-only — fields get added, never renamed), and the fields above: ```json { "seq": 42, "ts_ms": 1784227449526, "schema": 1, @@ -81,13 +81,12 @@ Each entry: ### What the host refuses The document is validated as a whole, and **one bad entry disables every hook** — the host logs -`hooks.json invalid — hooks disabled until fixed` and runs none of them until you correct it. The -rules: +`hooks.json invalid — hooks disabled until fixed` and runs none until you correct it. The rules: - An entry needs a non-empty `on`, plus `run` and/or `webhook`. - `webhook` must be an `http(s)://` URL, and must **not** point at loopback, `localhost` or a - link-local address (which is also what blocks the cloud metadata endpoint). A receiver on this - same machine is what a `run` command is for. Ordinary LAN addresses — `192.168.x.x`, a ULA, a + link-local address (which also blocks the cloud metadata endpoint). A receiver on this same + machine is what a `run` command is for. Ordinary LAN addresses — `192.168.x.x`, a ULA, a hostname — are fine, so Home Assistant on another box on your network works as written. - `timeout_s` must be 1–600. - If `hmac_secret_file` is set but unreadable, the host **skips** that POST rather than sending it @@ -107,10 +106,10 @@ A `run` command's shell one-liner vocabulary — the event flattened to env, val [ "$PF_EVENT_KIND" = stream.started ] && makoctl mode -a do-not-disturb ``` -Richer payloads (and the full document) are on stdin — `jq` away. On a Windows host running as -the service, the command runs **in your interactive session** (never as SYSTEM); that path can't -carry per-process env or stdin, so the event JSON's path is appended as the command's last -argument instead. +Richer payloads (and the full document) are on stdin for `jq`. On a Windows host running as the +service, the command runs **in your interactive session** (never as SYSTEM); that path can't carry +per-process env or stdin, so the event JSON's path is appended as the command's last argument +instead. Verify a signed webhook (Python): @@ -120,15 +119,15 @@ expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest() ok = hmac.compare_digest(request.headers["X-Punktfunk-Signature"], expected) ``` -**Rules of the road:** hooks are fire-and-forget and bounded — at most 8 in flight (extra -firings are dropped with a log line, never queued), and a command that outlives its timeout is -killed. Hook commands run as the host user, so `hooks.json` is operator-privileged config. On -Linux, when a command starts with an **absolute path** to a script, the host checks that file is -owned by you (or root) and not group/world-writable, and refuses to run it — loudly, in the log — -if it isn't. Write the full path (`/home/me/.config/punktfunk/scripts/on-stream.sh`, not `~/…`) if -you want that check: the shell expands `~` and looks up PATH names like `makoctl` only afterwards, -so those are never checked. On Windows there is no per-script check — the ACL on the config -directory is the boundary. +**Rules of the road:** hooks are fire-and-forget and bounded — at most 8 in flight (extra firings +are dropped with a log line, never queued), and a command that outlives its timeout is killed. +Hook commands run as the host user, so `hooks.json` is operator-privileged config. On Linux, when a +command starts with an **absolute path** to a script, the host checks that file is owned by you (or +root) and not group/world-writable, and refuses to run it — loudly, in the log — if it isn't. Write +the full path (`/home/me/.config/punktfunk/scripts/on-stream.sh`, not `~/…`) if you want that +check: the shell expands `~` and looks up PATH names like `makoctl` only afterwards, so those are +never checked. On Windows there is no per-script check — the ACL on the config directory is the +boundary. The two simplest cases also exist as plain [host.env](/docs/configuration) settings, no `hooks.json` needed: `PUNKTFUNK_ON_CONNECT_CMD` and `PUNKTFUNK_ON_DISCONNECT_CMD`. @@ -137,9 +136,8 @@ The two simplest cases also exist as plain [host.env](/docs/configuration) setti For per-title setup (HDR toggle, MangoHud, a VRR tweak), attach `prep` steps to a GameStream `apps.json` entry or to a [custom library entry](/docs/game-library#adding-a-game-by-hand) — each -`do` runs **before** the title launches -(synchronously — the launch waits), each `undo` runs at session end in **reverse order**, -best-effort, even if the session crashed: +`do` runs **before** the title launches (synchronously — the launch waits), each `undo` runs at +session end in **reverse order**, best-effort, even if the session crashed: ```json { "id": 2, "title": "Steam", "compositor": "gamescope", "cmd": "steam -gamepadui", @@ -153,11 +151,9 @@ A `do` that fails logs, keeps going, and its own `undo` is skipped (it never too ## Reacting to a game, not a stream -`stream.stopped` tells you the *stream* ended; `game.exited` tells you the *game* did. They are -often the same moment, but not always — a desktop stream has no game at all, and a stream can -outlive its game if you turned off "end the session when the game exits". - -If you have been polling the host to work out when a game finished, you don't need to any more: +`stream.stopped` tells you the *stream* ended; `game.exited` tells you the *game* did. Often the +same moment, but not always — a desktop stream has no game at all, and a stream can outlive its +game if you turned off "end the session when the game exits". No polling needed: ```json { "hooks": [ @@ -170,8 +166,8 @@ Both carry the title in `PF_EVENT_GAME_TITLE` / `PF_EVENT_GAME_APP`, and `game.e `PF_EVENT_REASON` so a script can tell "the player quit" (`exited`) from "the host closed it" (`terminated`) — worth checking before you, say, power the TV off. -Ending the session yourself when a game exits needs no script at all: it is the default behavior, -on the console's **Virtual displays** page under +Ending the session when a game exits needs no script: it is the default, on the console's +**Virtual displays** page under [When a game or a session ends](/docs/virtual-displays#when-a-game-ends-and-when-a-session-does). ## The event stream (`GET /api/v1/events`) @@ -227,22 +223,21 @@ The canonical "decide, don't just observe" pattern — approve pairing from your ## Recipe: full controller passthrough (VirtualHere) To get a controller's *native* features on the host — DualSense gyro, touchpad, adaptive -triggers, USB rumble — or to use a device no emulation can stand in for, like a racing wheel or a -HOTAS, hand the physical device from the couch to the host over +triggers, USB rumble — or to use a device no emulation can stand in for (a racing wheel, a HOTAS), +hand the physical device from the couch to the host over [VirtualHere](https://www.virtualhere.com/) (USB-over-IP) while you play. -**Use the plugin.** [VirtualHere passthrough](/docs/plugins#virtualhere-usb-passthrough) does all of -this for you: it finds the device by name (so it survives the couch rebooting), brackets it around -the session, gives it back if anything crashes, and tells you which half of the setup is broken when -it isn't working. That is the supported route, and the rest of this section is only for people who -would rather not install a plugin. +**Use the plugin.** [VirtualHere passthrough](/docs/plugins#virtualhere-usb-passthrough) finds the +device by name (so it survives the couch rebooting), brackets it around the session, gives it back +if anything crashes, and tells you which half of the setup is broken. That is the supported route; +the rest of this section is for people who would rather not install a plugin. -**Turn off controller forwarding on the couch.** Whatever route you take below, the client that -hands the device over should stop *also* forwarding it: Settings → **Forward controllers**, off -([Client settings](/docs/client-settings#input)). Otherwise the host ends up with two controllers -for one pair of hands and games read both. On Linux and Windows it matters twice over — while the -client has the pad open it has *claimed* the device node, and VirtualHere cannot bind a device -somebody else is holding. +**Turn off controller forwarding on the couch.** Whatever route you take, the client that hands the +device over should stop *also* forwarding it: Settings → **Forward controllers**, off +([Client settings](/docs/client-settings#input)). Otherwise the host gets two controllers for one +pair of hands and games read both. On Linux and Windows it matters twice over — while the client +has the pad open it has *claimed* the device node, and VirtualHere cannot bind a device somebody +else is holding. **The two sides.** VirtualHere is a server/client pair, and you run both: the **server on the couch** (where the device is plugged in) shares it, and the **client on the host** mounts it. The client's @@ -265,12 +260,11 @@ Bracket it on the stream with two [hooks](#hooks-hooksjson): `couch-deck.11` is the device's address from `vhclientx86_64 -t LIST`. -Know what this trades away, because the plugin exists to fix exactly these: the address is -hard-coded, so it breaks when the couch reboots or the device moves port; and if the stream ends -abnormally the `stream.stopped` hook never fires, leaving the device stranded on the host until -somebody notices. There is also a +The trade-offs the plugin exists to fix: the address is hard-coded, so it breaks when the couch +reboots or the device moves port; and if the stream ends abnormally the `stream.stopped` hook never +fires, leaving the device stranded on the host until somebody notices. There is also a [`virtualhere-dualsense.ts`](https://git.unom.io/unom/punktfunk/src/branch/main/sdk/examples/virtualhere-dualsense.ts) -SDK example if you want a worked script to build your own on. +SDK example to build your own script on. > VirtualHere is a commercial product, sold separately by VirtualHere Pty. Ltd. — free for one > shared device, licensed beyond that. Punktfunk is not affiliated with it. diff --git a/docs-site/content/docs/bazzite.md b/docs-site/content/docs/bazzite.md deleted file mode 100644 index cc4a365a9..000000000 --- a/docs-site/content/docs/bazzite.md +++ /dev/null @@ -1,252 +0,0 @@ ---- -title: Bazzite -description: Set up a Punktfunk host on Bazzite — it follows the box between Steam Gaming Mode (gamescope) and the KDE Plasma desktop automatically. ---- - -[Bazzite](https://bazzite.gg/) already ships everything a Punktfunk host needs — the NVIDIA driver, -NVENC, PipeWire, **gamescope**, and the **KDE Plasma desktop**. So a Bazzite host is the most -"appliance-like" setup, and it streams **both** of Bazzite's faces: - -- **Steam Gaming Mode** (gamescope) — the couch/handheld game UI. -- **The KDE Plasma desktop** — the full desktop you get from "Switch to Desktop". - -The host **auto-detects which one is live and follows the box across the switch** — including -mid-stream. You flip between Gaming Mode and Desktop with Bazzite's normal Steam UI / -"Switch to Desktop"; the host just re-targets whatever's running and keeps streaming. Nothing in -`host.env` forces a mode. - -> Ideal for a dedicated game-streaming box that you also occasionally want as a remote desktop. For a -> pure desktop machine, install on [Ubuntu](/docs/ubuntu) or [Fedora](/docs/fedora) and configure the -> [KDE](/docs/kde) or [GNOME](/docs/gnome) desktop directly — simpler. - -> New here? Read [Security & Safe Use](/docs/security) first — a streaming host is remote control of -> the machine, so keep it on a trusted LAN or VPN and require pairing. - -## Install - -The host installs as a **systemd system extension (sysext)** — no `rpm-ostree` layering. The -Bazzite docs treat layering as a last resort (layered packages slow every OS update and can block -upgrades until removed); a sysext never enters an rpm-ostree transaction: it overlays `/usr` -read-only from `/var/lib/extensions/`, survives OS updates, installs and updates **without a -reboot**, and is removable in one command. This is the same mechanism the Fedora Atomic -maintainers ship via the [fedora-sysexts](https://fedora-sysexts.github.io/) project. - -```sh -# One-time bootstrap (afterwards the updater is on PATH as `punktfunk-sysext`): -curl -fsSLO https://git.unom.io/unom/punktfunk/raw/branch/main/packaging/bazzite/punktfunk-sysext.sh -sudo bash punktfunk-sysext.sh install # add `--channel canary` for rolling builds -``` - -That downloads the newest image — host + tray + web console + the plugin runner -(`punktfunk-scripting`), plus the HDR `punktfunk-gamescope` build — merges it, and applies the -udev/sysctl setup on the spot; the host is usable immediately, no reboot. The feed's checksum -manifest is OpenPGP-signed by packages@unom.io (key `AF245C506F4E4763`, the same one that signs our -RPMs), and `punktfunk-sysext` checks that signature against a key baked into the script before it -trusts a single checksum — so it needs `gpg` on the box, and it refuses a feed it can't verify. - -The plugin runner rides along in the image and is **started for you** — the image bakes in its -`default.target.wants` symlink, because the game-library scanners ship as -[plugins](/docs/plugins). To turn it off: `systemctl --user mask punktfunk-scripting` (`mask`, not -`disable` — a plain disable cannot remove a symlink that lives in `/usr`). - -From then on: - -```sh -sudo punktfunk-sysext update # fetch + merge the newest build -sudo punktfunk-sysext status # channel, installed vs latest version -sudo punktfunk-sysext remove # unmerge and delete the image (~/.config/punktfunk is kept) -``` - -After an update, restart the host so it runs the new binary (the updater prints this reminder too). -The image carries the console as well, so restart that first if you enabled it: - -```sh -systemctl --user restart punktfunk-web # only if you run the console -systemctl --user restart punktfunk-host -``` - -To **switch channel** later, re-run the install: `sudo punktfunk-sysext install --channel canary` -(or `--channel stable`). `update` takes no channel flag — it follows whatever the last install wrote -to `/etc/punktfunk-sysext.conf`. To be able to **go back** to a build that worked, keep a copy of the -image before you update, and re-install that file afterwards: - -```sh -sudo cp /var/lib/extensions/punktfunk.raw ~/punktfunk-known-good.raw # before updating -sudo punktfunk-sysext install --from-file ~/punktfunk-known-good.raw # to go back to it -``` - -The web console can also run the update for you — see [Updating the Host](/docs/updating), which -needs the one-time `sudo usermod -aG punktfunk-update $USER`. - -`remove` deletes the image and the `/etc` files it seeded (the tray autostart entry, and the -gamescope session drop-in unless you've edited it), but three things it created outside `/usr` stay -behind. To clear those too — services first, because once the image unmerges their binaries are -gone and the units just keep failing: - -```sh -systemctl --user disable --now punktfunk-host punktfunk-web -sudo punktfunk-sysext remove -sudo rm -f /etc/modules-load.d/punktfunk.conf /etc/udev/rules.d/60-punktfunk.rules -sudo groupdel punktfunk-update # the (empty) group for web-console updates -``` - -[Uninstalling](/docs/uninstall) has the same walkthrough for the other install methods, and for the -clients. - -Three things to know: - -- **After a Bazzite major rebase** (Fedora 43 → 44) the old image **refuses to load** rather than - run against mismatched system libraries — run `sudo punktfunk-sysext update` once and it fetches - the image built for the new base. -- **Already layering Punktfunk?** Install the sysext (it shadows the layered copy immediately), - then drop the layer so it stops slowing your updates: - `sudo rpm-ostree uninstall punktfunk punktfunk-web && systemctl reboot`. -- **If it refuses the feed.** `refusing to install from an unsigned feed` means that Fedora major's - feed predates signing; it gets sealed on the next publish. To install from it anyway, accepting - that the images are unauthenticated, run - `sudo env PUNKTFUNK_SYSEXT_ALLOW_UNSIGNED=1 bash punktfunk-sysext.sh install`. The other message, - `the feed's SHA256SUMS is NOT signed by packages@unom.io`, is not the same thing — don't install; - re-download the script and try again. - -For a fully baked appliance image there's also a **bootc** Containerfile that installs the RPMs -from the registry at image-build time — see `packaging/bootc/` in the repo. Plain `rpm-ostree` -layering from the [RPM registry](https://git.unom.io/unom/-/packages) keeps working too: add the -repo exactly as on [Fedora](/docs/fedora), with the `baseurl` group matching your Fedora base, then -`sudo rpm-ostree install punktfunk punktfunk-web` and reboot. The sysext is still the supported -default. Building from source also works (Bazzite is Fedora Atomic underneath — same steps as -[Fedora](/docs/fedora)). - -## Allow controller input - -Gamepad and DualSense input needs your user in the `input` group. On Bazzite, don't use -`usermod` — the base is immutable and the group is managed by a recipe. Use: - -```sh -ujust add-user-to-input-group -``` - -Then **log out and back in**. (A controller that's "detected but does nothing" is almost always this -permission, not a client problem.) - -Then join `punktfunk` — `usermod` is fine here, because unlike `input` this group is ours and the -sysext creates it on merge: - -```sh -sudo usermod -aG punktfunk "$USER" # then log out and back in -``` - -This box **is** a Gaming Mode box, so that group is not optional in practice: it authorizes the -helper the host uses to stop the display manager when it takes the Gaming Mode session over at your -client's resolution, and it gates the usbip `attach` file the **virtual Steam Deck controller** -(paddles, trackpads, gyro) attaches through. It is a separate group on purpose — writing that file -can materialise arbitrary emulated USB hardware, so it is not folded into the group everyone is -told to join for gamepads. Without it the pad arrives as an ordinary Xbox 360 controller, and the -takeover degrades to mirroring the box's own screen — see -[gamescope](/docs/gamescope#nobara-and-other-autologin-display-managers). - -## Configure - -The RPM ships a Bazzite-tuned config you can copy as your starting point: - -```sh -mkdir -p ~/.config/punktfunk -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. 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 -PUNKTFUNK_VIDEO_SOURCE=virtual -# GPU zero-copy (dmabuf → CUDA → NVENC) is ON by default; auto-falls back to CPU. Set =0 to force CPU. -``` - -### Gaming Mode: attach vs managed - -For Gaming Mode there are two models. The template forces **neither** — the host picks per connect, -and on Bazzite (which ships `gamescope-session-plus`) that is **managed**: - -- **Managed** (what you get by default here) — 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. This is the model that gives the client a display of - its **own**, and the only one under which a game launched from a client's library gets a - dedicated session. It needs the [`punktfunk` group](#allow-controller-input): the takeover stops - the display manager for the length of the stream, and without that grant it cannot. -- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`) — the **box** owns its gamescope session on its own - display, and the host attaches to whatever's live without ever tearing it down (on a headless - box, a box-owned autologin session is restarted at the client's resolution on a mismatch; with a - display connected it streams at the box's own mode). Switching Desktop ↔ Game is rock-solid, and - the cost is that a box with a screen attached serves the client a **mirror** of that screen - rather than its own display. Setting it also outranks a dedicated game session. - -`=0` turns the attach override off, the same as removing the line. - -Full treatment: [Steam / gamescope → How the host gets a -gamescope](/docs/gamescope#how-the-host-gets-a-gamescope). - -Mid-stream Gaming ↔ Desktop following (`PUNKTFUNK_SESSION_WATCH`) is **on by default** on -Bazzite/SteamOS. See [Configuration](/docs/configuration) for the full list of knobs. - -### Streaming the KDE Plasma desktop - -The **virtual output** (video) for the Desktop session needs no config — the host package ships an -`io.unom.Punktfunk.Host.desktop` file whose `X-KDE-Wayland-Interfaces` grants the host KWin's -restricted screencast protocol on a normal interactive Plasma session (background: -[KDE Plasma](/docs/kde)). After a **fresh host install, log out and back into the Desktop session -once** so KWin re-reads that grant. - -The one thing a normal KDE login lacks is the RemoteDesktop grant for headless **input** injection. -Seed it once (as the streaming user, no root) so the host auto-approves instead of popping an -un-answerable dialog: - -```sh -bash /usr/share/punktfunk/bazzite/kde-desktop-setup.sh -``` - -Gaming Mode needs none of this — it auto-attaches. - -## Run as an always-on host - -Bazzite hosts are typically headless. Enable the host service and linger so it starts at boot — see -[Running as a Service](/docs/running-as-a-service). One host service covers both Gaming Mode and the -Desktop; it follows whichever the box is in. - -```sh -systemctl --user enable --now punktfunk-host -systemctl --user enable --now punktfunk-web # web console: pairing + status -sudo loginctl enable-linger "$USER" # start at boot with nobody logged in -``` - -Without that last line the `--user` units don't start until someone logs in — which on a headless box -never happens. - -Then open [The Web Console](/docs/web-console) for the login password and to -[arm pairing](/docs/web-console#arm-pairing). - -## Good to know - -These apply to the **Gaming Mode (gamescope)** path; the KDE Desktop path is unaffected: - -- **gamescope 3.16.22 or newer is required; 3.16.23 or newer for the Steam overlay.** Below 3.16.22 - headless capture can deadlock; between the two, capture works but the Steam overlay (Shift+Tab / - the Quick Access Menu) is never painted into the captured node. Bazzite's current gamescope is - past both; this only bites if you've pinned an old one. -- **Forcing attach costs you the cursor, HDR and your own display.** The sysext ships the - `punktfunk-gamescope` build, but it only reaches a session the host starts itself — under - `PUNKTFUNK_GAMESCOPE_ATTACH=1` the live session is Bazzite's own stock gamescope. The managed - default gets you the compositor-drawn pointer, real HDR and a display of the client's own. If you - deliberately stay on attach, also set `PUNKTFUNK_GAMESCOPE_HDR=0` and - `PUNKTFUNK_GAMESCOPE_BIN=/usr/bin/gamescope`. Why each half breaks: - [gamescope → Known limits](/docs/gamescope#known-limits) for the cursor, - [HDR → Linux + gamescope](/docs/hdr#linux--gamescope) for the failed connect. - ⚠ Older templates set `PUNKTFUNK_GAMESCOPE_ATTACH=1` for you — if you copied one, delete that - line from `~/.config/punktfunk/host.env`, because an upgrade never rewrites a file you already - have. - -Those are the two that bite on Bazzite. The full set — touch, mouse modes, the clipboard — is on -[gamescope → Known limits](/docs/gamescope#known-limits). - -Then [connect a client](/docs/clients) — Moonlight works great for couch gaming, and the Apple app for -Apple TV / iPad. Trouble? See [Troubleshooting](/docs/troubleshooting). diff --git a/docs-site/content/docs/bazzite.mdx b/docs-site/content/docs/bazzite.mdx new file mode 100644 index 000000000..49f084a8a --- /dev/null +++ b/docs-site/content/docs/bazzite.mdx @@ -0,0 +1,94 @@ +--- +title: Bazzite +description: Install the Punktfunk host on Bazzite (or any Fedora Atomic spin) as a sysext — no layering, no reboot — and stream both Gaming Mode and the desktop. +--- + +Bazzite already ships everything a host needs — the NVIDIA driver, NVENC, PipeWire, gamescope, KDE +Plasma — so this is the most appliance-like setup. One host streams **both** of Bazzite's faces, +Steam **Gaming Mode** and the **KDE desktop**, and follows the box when you switch, even mid-stream. +Nothing in the config picks a mode. + +## 1. Install the host + +The host installs as a **systemd system extension** — it overlays `/usr` from +`/var/lib/extensions/`, survives OS updates, and installs, updates and uninstalls **without a +reboot** (no `rpm-ostree` layering, which Bazzite's docs treat as a last resort). The feed is +signed; the installer refuses one it can't verify. + + + +That fetches the newest image — host, web console, tray, the plugin runner (started for you, because +the game-library scanners are [plugins](/docs/plugins)) and the HDR `punktfunk-gamescope` build — and +applies the udev/sysctl setup on the spot. From then on `sudo punktfunk-sysext update` (or the +[console's update button](/docs/updating)) moves you to the newest build; `status` shows where you +are. After an update restart the host: `systemctl --user restart punktfunk-web punktfunk-host`. + +## 2. Let it use your controllers + +On Bazzite the `input` group is managed by a recipe, so don't `usermod` it — use the helper. The +`punktfunk` group is ours (the sysext creates it), and on a Gaming Mode box it isn't optional: it +authorizes the host to take the Gaming Mode session over at your client's resolution, and gates the +virtual Steam Deck controller. Then **log out and back in**: + +```sh +ujust add-user-to-input-group +sudo usermod -aG punktfunk "$USER" +``` + +([Why it's a separate group](/docs/gamescope#nobara-and-other-autologin-display-managers).) + +## 3. Start it + +Bazzite hosts are usually headless, so enable the services **and** linger, from a terminal in the +desktop session — without linger the `--user` units wait for a login that never comes: + +```sh +systemctl --user enable --now punktfunk-host punktfunk-web +sudo loginctl enable-linger "$USER" +``` + +Two one-time steps for streaming the **KDE desktop** (Gaming Mode needs neither): log out and back +into the Desktop session once so KWin re-reads the screencast grant the package installed, and seed +the input grant so the host auto-approves instead of popping a dialog nobody can answer: + +```sh +bash /usr/share/punktfunk/bazzite/kde-desktop-setup.sh +``` + +## 4. Open the firewall + +Bazzite runs **firewalld**, and a package never opens ports for you — until you allow it, no client +can reach the host. The image installed the service definitions; enable them once: + +```sh +sudo firewall-cmd --reload # load the definitions the package installed +sudo firewall-cmd --permanent --add-service=punktfunk-native --add-service=punktfunk-web +sudo firewall-cmd --reload +``` + +(`punktfunk-web` is only needed to reach the console from another device; Moonlight compat needs +`punktfunk-gamestream` too — [Ports & firewall](/docs/ports).) + +**That's the install.** Continue with the [Quick Start from step 3](/docs/quickstart#3-open-the-web-console) +— open the console, pair a client, stream. Moonlight works great for couch gaming here, the Apple +app for Apple TV and iPad. + +## When you want more + +- **Gaming Mode: what the client gets.** By default the host relaunches Gaming Mode *headless* at the + client's exact resolution (a display of its own, real HDR, the compositor's cursor) and restores + the box on idle. `PUNKTFUNK_GAMESCOPE_ATTACH=1` makes it attach to the box's own session instead — + rock-solid switching, but a box with a screen serves a *mirror* of it, and loses HDR and the cursor. + [How the host gets a gamescope](/docs/gamescope#how-the-host-gets-a-gamescope) has the whole + model; the Bazzite template (`/usr/share/punktfunk/host.env.bazzite`) forces neither. +- **Stream lags, then freezes, with a DualSense-type client pad** — an SELinux storm, with a shipped + fix: [Troubleshooting](/docs/troubleshooting#stream-lags-then-freezes-with-a-dualsense-pad-bazzite-selinux). +- **Channels, rollback, after a Bazzite major rebase** — + [Updating → Bazzite sysext](/docs/updating#bazzite-sysext-channels-rollback-and-rebases). + Removing it — [Uninstall](/docs/uninstall#bazzite--fedora-atomic-systemd-sysext). +- **Other ways in.** Plain `rpm-ostree` layering from the [RPM repo](/docs/fedora#2-install-the-host) + (use the `bazzite` group) still works, and `packaging/bootc/` in the repo bakes an appliance image; + the sysext is the supported default. Already layering? Install the sysext (it shadows the layer at + once), then `sudo rpm-ostree uninstall punktfunk punktfunk-web && systemctl reboot`. +- Everything Gaming-Mode-specific — overlay, touch, HDR, the two gamescope version floors — + [Steam / gamescope](/docs/gamescope); the desktop — [KDE](/docs/kde). diff --git a/docs-site/content/docs/build-from-source.md b/docs-site/content/docs/build-from-source.md new file mode 100644 index 000000000..14f097e5f --- /dev/null +++ b/docs-site/content/docs/build-from-source.md @@ -0,0 +1,90 @@ +--- +title: Build from source +description: Compile the Linux host yourself — on Ubuntu/Debian, Fedora, or with the Arch PKGBUILD — when no package fits your release or you want to track main. +--- + +The package repos are the supported path ([Install the Host](/docs/install)). Build from source when +your release is older than a package supports (Ubuntu before 26.04, Debian 12, a Fedora without a +repo group), or to hack on it. A source build gets **no packaged units and no clean updates** — you +wire the service up by hand ([Running as a service](/docs/running-as-a-service) shows the unit). + +Two build features matter on every distro: `punktfunk-host/nvenc` (direct NVENC on NVIDIA) and +`punktfunk-host/vulkan-encode` (Vulkan Video on AMD/Intel). They're what the packaged builds use; +without them the host falls back to the slower libav backends. Rust comes from +[rustup](https://rustup.rs) if you don't have it: + +```sh +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +## Ubuntu / Debian + +The packaged host is built against **FFmpeg 8**. Ubuntu 26.04's and Debian 13's `libavcodec-dev` +are new enough; Ubuntu 24.04's is FFmpeg 6.1 — build FFmpeg 8 yourself first there (what +`ci/rust-ci-noble.Dockerfile` does), or stick with the packaged host. + +```sh +sudo apt install build-essential pkg-config cmake clang libclang-dev nasm git curl \ + pipewire pipewire-pulse wireplumber libpipewire-0.3-dev libspa-0.2-dev \ + libwayland-dev wayland-protocols libxkbcommon-dev libopus-dev \ + libdrm-dev libgbm-dev libgl-dev libegl-dev libgles-dev mesa-common-dev libva-dev \ + ffmpeg libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev libavdevice-dev \ + libnvidia-egl-wayland1 libnvidia-egl-gbm1 libei-dev +git clone https://git.unom.io/unom/punktfunk.git && cd punktfunk +cargo build --release --locked \ + --features punktfunk-host/nvenc,punktfunk-host/vulkan-encode \ + -p punktfunk-host +``` + +## Fedora + +```sh +sudo dnf install gcc gcc-c++ make cmake clang clang-devel nasm git pkgconf-pkg-config \ + pipewire-devel wayland-devel wayland-protocols-devel libxkbcommon-devel opus-devel \ + libdrm-devel mesa-libgbm-devel mesa-libGL-devel mesa-libEGL-devel mesa-libGLES-devel libva-devel \ + ffmpeg-devel libei-devel +git clone https://git.unom.io/unom/punktfunk.git && cd punktfunk +cargo build --release --locked \ + --features punktfunk-host/nvenc,punktfunk-host/vulkan-encode \ + -p punktfunk-host +``` + +`ffmpeg-devel` must be RPM Fusion's (with NVENC), not `ffmpeg-free-devel`. `mesa-libGL-devel` isn't +optional — the zero-copy GPU path links `libGL`, and without it the build fails at link time with +`cannot find -lGL`. To build an RPM instead, use the same toolchain CI does: +`docker build --build-arg FEDORA_VERSION=NN -f ci/fedora-rpm.Dockerfile -t pf-rpm ci`, then run +`packaging/rpm/build-rpm.sh` inside it. + +## Arch (PKGBUILD) + +The split `PKGBUILD` in `packaging/arch/` produces `punktfunk-host` and `punktfunk-client`; set +`PF_WITH_WEB=1` to also build `punktfunk-web` and `PF_WITH_SCRIPTING=1` for `punktfunk-scripting` +(both need `bun`): + +```sh +git clone https://git.unom.io/unom/punktfunk.git && cd punktfunk/packaging/arch +PF_SRCDIR="$(git rev-parse --show-toplevel)" makepkg -f --holdver # builds the working tree, no git fetch +sudo pacman -U punktfunk-host-*.pkg.tar.zst +``` + +NVENC/EGL come from `nvidia-utils`; on a GPU-less builder, symlink the CUDA stub into the link path +first (the `PKGBUILD` header documents this). Packager notes, the Fedora→Arch dependency map and the +sysext mechanism: [packaging/arch](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md). +For a **SteamOS** host don't use the PKGBUILD — the [on-device installer](/docs/steamos-host) builds +ABI-matched to the running OS. + +## Running what you built + +The binary lands at `target/release/punktfunk-host`. Run it from inside your desktop session — it +auto-detects the compositor: + +```sh +target/release/punktfunk-host serve # secure native-only host +target/release/punktfunk-host serve --gamestream # + Moonlight compat (trusted LAN only) +``` + +To run it as a user service, copy `scripts/punktfunk-host.service` to +`~/.config/systemd/user/` (it already points at `%h/punktfunk/target/release/punktfunk-host`), then +`systemctl --user daemon-reload && systemctl --user enable --now punktfunk-host`. The other +workspace members (`punktfunk-web`, `punktfunk-scripting`, the client) build the same way — the +root [README](https://git.unom.io/unom/punktfunk#build--test-from-source) covers the dev loop. diff --git a/docs-site/content/docs/client-settings.md b/docs-site/content/docs/client-settings.md index 14ac25e60..1ed4fcf04 100644 --- a/docs-site/content/docs/client-settings.md +++ b/docs-site/content/docs/client-settings.md @@ -6,21 +6,21 @@ description: Every setting a Punktfunk client stores — what it does, what it d The host has [its own settings reference](/docs/configuration). This page is the other half: the settings each **client** keeps, which together decide what a session looks like. -Most of them are a *request*. The client asks, the host answers, and the answer comes back in the -handshake — so a setting the host can't honor is usually a quiet downgrade rather than an error. +Most of them are a *request*. The client asks, the host answers in the handshake — so a setting the +host can't honor is usually a quiet downgrade rather than an error. ## Where the settings live The Linux, Windows, Mac, iPhone/iPad and Android apps group settings the same way — **General**, **Display**, **Input**, **Audio**, **Controllers** — under *Preferences* on Linux and *Settings* -elsewhere. The Apple TV app shows one scrolling list instead, and so does any client's settings -screen reached with a controller. A controller-driven launch (Steam Deck Gaming Mode) opens the -client's **console home**, whose settings screen is one steppable list of sections — **Stream**, -**Video**, **Presentation**, **Audio**, **Controller**, **Touchscreen**, **Interface**, -**Profiles**. On a Steam Deck that list *is* the settings surface: the -[Decky plugin](/docs/steam-deck) is a launcher and keeps no settings of its own, and its **Open -Punktfunk** button puts the console home one tap from the Quick Access Menu. The console home is -part of the client — it is not the host's [web console](/docs/web-console). +elsewhere. The Apple TV app shows one scrolling list instead, as does any client's settings screen +reached with a controller. A controller-driven launch (Steam Deck Gaming Mode) opens the client's +**console home**, whose settings screen is one steppable list of sections — **Stream**, **Video**, +**Presentation**, **Audio**, **Controller**, **Touchscreen**, **Interface**, **Profiles**. On a +Steam Deck that list *is* the settings surface: the [Decky plugin](/docs/steam-deck) is a launcher +and keeps no settings of its own, and its **Open Punktfunk** button puts the console home one tap +from the Quick Access Menu. The console home is part of the client — it is not the host's +[web console](/docs/web-console). Linux stores them in `~/.config/punktfunk/client-gtk-settings.json`, the same file the console home writes, so a change in either shows up in the other. Windows uses @@ -30,8 +30,8 @@ Changes apply to the **next** session — a running stream keeps what it started is the exception in effect, not in reading: it too is read at connect, but once a session is running with it on, every window resize renegotiates the mode.) -Not every client offers every setting, and the wording on screen varies a little between them — the -names below are the ones the Linux app uses. The differences that matter are noted per setting. +Not every client offers every setting; the names below are the Linux app's, and differences that +matter are noted per setting. ## Video @@ -73,8 +73,8 @@ link. The stops run 0.5× to 4×. The result is floored to an even size and capp **Video codec** — *default: Automatic.* A soft preference: the host emits your choice when it can also produce it, otherwise the best codec you both speak, in the order HEVC → AV1 → H.264. -**PyroWave** is never auto-picked — pick it explicitly on Linux, Windows, the console home, or -an Apple device whose decode probe passes; anywhere else it isn't offered, and asking for it lands on +**PyroWave** is never auto-picked — pick it explicitly on Linux, Windows, the console home, or an +Apple device whose decode probe passes; anywhere else it isn't offered, and asking for it lands on that same order. See [PyroWave](/docs/pyrowave). The Android and Apple apps hide AV1 unless the device has a hardware AV1 decoder; Android never offers PyroWave. @@ -91,24 +91,23 @@ decode probe to pass). The console home offers the toggle; Android doesn't. **Prioritize** — *default: Lowest latency.* What the client optimizes for when a decoded frame is ready. **Lowest latency** shows every frame the moment the display can take it, so a network hiccup -becomes an occasional repeated or skipped frame. **Smoothness** holds a small buffer that evens -those hiccups out, at that buffer's worth of added delay. Linux and Windows apps and the console -home; the Apple and Android apps have carried the same setting for a while, and it is stored -under the same name, so a [profile](/docs/profiles-and-links) means the same thing on every device. +becomes an occasional repeated or skipped frame. **Smoothness** holds a small buffer that evens those +hiccups out, at that buffer's worth of added delay. Linux and Windows apps and the console home; the +Apple and Android apps have it too, stored under the same name, so a +[profile](/docs/profiles-and-links) means the same thing on every device. **Smoothness buffer** — *default: Automatic (two frames).* How many frames are held back before showing. Each frame absorbs roughly one screen refresh of network hiccup and costs one refresh of delay — so on a 120 Hz screen, two frames is about 17 ms of extra delay bought against 17 ms of jitter. If you never see stutter, you don't need this. The row appears wherever **Prioritize** is -offered, and only once you have picked **Smoothness** — under Lowest latency there are no held -frames for it to count, so it isn't shown at all. +offered, and only once you have picked **Smoothness**. -**V-Sync** — *default: on.* Tear-free presentation. Turning it off asks the GPU to show each frame -the instant it's ready instead of waiting for the screen's next refresh: the lowest delay a display -can give you, at the cost of visible tearing on fast motion. It is **best-effort** — not every -driver or compositor offers a tearing mode, and where none is available the stream stays tear-free. -The Detailed [stats overlay](/docs/stats) names the mode actually in use, so you can tell "off" -from "off but unavailable". Linux and Windows apps and the console home. +**V-Sync** — *default: on.* Tear-free presentation. Off asks the GPU to show each frame the instant +it's ready instead of waiting for the screen's next refresh: the lowest delay a display can give +you, at the cost of visible tearing on fast motion. It is **best-effort** — not every driver or +compositor offers a tearing mode, and where none is available the stream stays tear-free. The +Detailed [stats overlay](/docs/stats) names the mode actually in use, so you can tell "off" from +"off but unavailable". Linux and Windows apps and the console home. **Follow variable refresh rate** — *default: on.* On a VRR / FreeSync / G-Sync screen, let the panel refresh in step with the stream rather than on a fixed cadence — which removes the wait between a @@ -132,21 +131,20 @@ claims a sink advertising exactly that many channels, so applications produce re **Windows** host loopback-captures your current output endpoint and lets Windows convert it — so 5.1 from a stereo endpoint is an upmix, not new channels. Offered everywhere. -**Microphone** — *default: off on Linux, Windows, Android and the console home; on in the -Apple app.* Sends this device's microphone to the host's virtual mic. On Linux and Windows the -row is spelled *Stream microphone*, and **Ctrl+Alt+Shift+V** mutes it mid-stream without ending -anything — see [Muting your microphone](/docs/input#muting-your-microphone). +**Microphone** — *default: off on Linux, Windows, Android and the console home; on in the Apple +app.* Sends this device's microphone to the host's virtual mic. On Linux and Windows the row is +spelled *Stream microphone*, and **Ctrl+Alt+Shift+V** mutes it mid-stream without ending anything — +see [Muting your microphone](/docs/input#muting-your-microphone). -**Echo cancellation** — *default: on.* Stops the host's audio, playing out of this device's -speakers, from being picked up by the microphone and sent straight back. It hands the microphone -to the system's own canceller rather than doing the work itself: on **Linux** that means capturing -from an echo-cancelled PipeWire source when your desktop provides one, on **Windows** asking WASAPI -for the Communications stream category so the endpoint's processing engages, and on **Apple** and +**Echo cancellation** — *default: on.* Stops the host's audio, playing out of this device's speakers, +from being picked up by the microphone and sent straight back. It hands the microphone to the +system's own canceller rather than doing the work itself: on **Linux** that means capturing from an +echo-cancelled PipeWire source when your desktop provides one, on **Windows** asking WASAPI for the +Communications stream category so the endpoint's processing engages, and on **Apple** and **Android** the platform's voice-processing mode. Turn it off if your microphone already runs its own processing, or if the canceller makes your voice sound thin. The row sits under the microphone toggle and greys out while the microphone is off. Offered by the Linux, Windows, Apple, Android and -console-home clients. What it can and can't fix is in -[Why do I hear myself](/docs/echo). +console-home clients. What it can and can't fix is in [Why do I hear myself](/docs/echo). **Speaker** and **Microphone** device pickers — *default: System default.* Which endpoint stream audio plays out of, and which input feeds the uplink. Only the Linux app (PipeWire nodes) and the @@ -164,41 +162,39 @@ more settings are worth naming here. **Forward controllers** — *default: on*, on every client. Off, the controllers connected to *this* device are not sent to the host at all. That is what you want when your controller already reaches -the host by some other route — [USB passthrough](/docs/automation#recipe-full-controller-passthrough-virtualhere) -such as VirtualHere, or simply a pad plugged into the host itself. Leaving forwarding on in that -situation hands the host two controllers for one pair of hands, and games read both: a stick drifts -because the second pad is centred, or a menu takes every input twice. +the host by some other route — +[USB passthrough](/docs/automation#recipe-full-controller-passthrough-virtualhere) such as +VirtualHere, or a pad plugged into the host itself. Leaving forwarding on there hands the host two +controllers for one pair of hands, and games read both: a stick drifts because the second pad is +centred, or a menu takes every input twice. -On Linux and Windows it does more than stay quiet. Opening a controller is what *claims* it — the -client's SDL takes the device node — and a claimed device is one a passthrough tool cannot bind. So -with this off the session never opens the controller at all, which is precisely what leaves it free -for VirtualHere to hand over. The consequence to know: the +On Linux and Windows, opening a controller is what *claims* it — the client's SDL takes the device +node — and a passthrough tool cannot bind a claimed device; with this off the session never opens +the controller at all, leaving it free for VirtualHere to hand over. The consequence: the [controller escape chord](/docs/input#leaving-with-a-controller) is read off forwarded pads, so it is unavailable on those two while this is off — leave a stream with the keyboard chord or the client's own UI. The Apple and Android apps claim nothing, so their chords keep working either way; the -Android app does stop its DualSense and Steam Controller 2 USB captures, which *do* claim the -device. +Android app does stop its DualSense and Steam Controller 2 USB captures, which *do* claim the device. The rows below it — which pad, and what type — have nothing to act on while this is off, and every client greys them out to say so. **Gamepad type** (*Controller type* on Apple, Android and the console home) — *default: Automatic*, which matches each physical controller. The pickers offer Xbox 360, Xbox One, DualSense and -DualShock 4 everywhere, plus Steam Deck on Linux, Android and the console home. Your client -declares a type per pad as it connects — Automatic declares what that controller really is, an -explicit choice declares your choice — and the host builds each virtual pad from that. A type the -host has no backend for degrades to an Xbox 360 pad rather than failing: Xbox One on a Windows host, -for instance, or any Sony pad on a Linux host that can't open `/dev/uhid`. +DualShock 4 everywhere, plus Steam Deck on Linux, Android and the console home. Your client declares +a type per pad as it connects — Automatic declares what that controller really is, an explicit +choice declares your choice — and the host builds each virtual pad from that. A type the host has no +backend for degrades to an Xbox 360 pad rather than failing: Xbox One on a Windows host, for +instance, or any Sony pad on a Linux host that can't open `/dev/uhid`. -That degrade is the one thing worth knowing about **motion**. An Xbox-class virtual pad has no -gyroscope in its HID contract, so a session that ends up on one throws every motion sample away — -your controller's gyro simply does nothing, which from the couch is indistinguishable from a broken -sensor. Automatic lands there for any controller punktfunk doesn't recognise as Sony or Valve (an -8BitDo with a gyro, say), and so does a Switch Pro streaming to a Windows host, which has no -Nintendo backend to build. **If you want motion, pick a DualSense-class type** — DualSense, -DualSense Edge, DualShock 4, Switch Pro or Steam Deck all carry a motion plane. The clients detect -this case and say so on-screen for a few seconds when it happens; the setting applies from the next -session, not the one you are in. +That degrade matters for **motion**. An Xbox-class virtual pad has no gyroscope in its HID +contract, so a session that ends up on one throws every motion sample away — your controller's gyro +does nothing. Automatic lands there for any controller punktfunk doesn't recognise as Sony or Valve (an 8BitDo +with a gyro, say), and so does a Switch Pro streaming to a Windows host, which has no Nintendo +backend to build. **If you want motion, pick a DualSense-class type** — DualSense, DualSense Edge, +DualShock 4, Switch Pro or Steam Deck all carry a motion plane. The clients detect this case and say +so on-screen for a few seconds when it happens; the setting applies from the next session, not the +one you are in. On a **Steam Deck as the client**, motion also needs Steam Input switched off for punktfunk — with it on, Steam hands the app its own virtual Xbox pad, which has no gyro to forward no matter which @@ -229,9 +225,8 @@ Windows apps, *off* on Android. The two halves of [controller audio](/docs/contr DualSense's voice-coil haptics, and the little speaker in the middle of the pad. Both need a **wired** DualSense or DualSense Edge — over Bluetooth a controller exposes no audio device at all, and both settings quietly do nothing. Neither costs anything without a host that sends them: the -plane is negotiated, and silence is never encoded or transmitted, so leaving haptics on is free even -on a pad that never gets any. Turn **Controller speaker** off if you would rather all game audio came -out of your speakers or headset. +plane is negotiated, and silence is never encoded or transmitted. Turn **Controller speaker** off if +you would rather all game audio came out of your speakers or headset. Offered by the Linux, Windows and Android apps. On Linux, the client also switches the controller's sound card to Pro Audio while it needs the voice coils, and puts it back afterwards — see @@ -240,13 +235,13 @@ for why that is necessary and how to turn it off. **Capture system shortcuts** — *default: on.* Offered by the Linux, Windows and macOS apps and the console home; Windows spells the row out as *Capture system shortcuts (Alt+Tab, Win, …)*. On a Deck it -matters only for a keyboard you attached yourself, for the reason the paragraph below gives: Gaming -Mode is gamescope, which has nothing to hold back. On, Alt+Tab and the Windows key -(Super on Linux) reach the host while the stream has input captured. Off, they act on this machine -instead — what you want when the stream shares a screen with local work. Either way the chords come -back the moment you release capture with **Ctrl+Alt+Shift+Q**, the window loses focus, or the stream -ends, and [Desktop mouse mode](/docs/input#mouse-modes) never takes them at all. Leaving this on does -mean **Ctrl+Alt+Shift+Q is your way out** of a captured stream, since Alt+Tab no longer is. +matters only for a keyboard you attached yourself: Gaming Mode is gamescope, which has nothing to +hold back. On, Alt+Tab and the Windows key (Super on Linux) +reach the host while the stream has input captured. Off, they act on this machine instead — what you +want when the stream shares a screen with local work. Either way the chords come back the moment you +release capture with **Ctrl+Alt+Shift+Q**, the window loses focus, or the stream ends, and +[Desktop mouse mode](/docs/input#mouse-modes) never takes them at all. Leaving this on does mean +**Ctrl+Alt+Shift+Q is your way out** of a captured stream, since Alt+Tab no longer is. On macOS the chords in question are the **⌘** ones — ⌘Q above all, which reaches the host as Super+Q, one of the most-bound chords on a Linux desktop. On, ⌘Q, ⌘W, ⌘H and the rest go to the host instead @@ -270,7 +265,7 @@ learned. Turn it off for hosts you reach over a VPN, where "offline" usually mea broadcast" and the wake only adds a delay. The Linux, Windows, Apple and Android apps have this toggle, as does the console home — and on a Steam Deck it governs the [Decky plugin's](/docs/steam-deck) launches too, because the plugin starts every stream through the -client, which reads this setting like any other connect. The console home also offers wake as an +client. The console home also offers wake as an explicit action on an offline host, whatever the toggle says. See [Wake-on-LAN](/docs/wake-on-lan). @@ -282,14 +277,13 @@ offered on any paired host. See [Game library](/docs/game-library). **Start streams in fullscreen** — *default: on.* On Linux and Windows, F11 or Alt+Enter leaves fullscreen live. On a Mac the setting is **Fullscreen while streaming**, and the window comes back when you return to the host list. The console home carries the row for the desktop client that -shares the store — a Gaming-Mode launch is fullscreen whatever it says. iPhone, iPad, Apple TV -and Android have no equivalent. +shares the store — a Gaming-Mode launch is fullscreen whatever it says. iPhone, iPad, Apple TV and +Android have no equivalent. ## Interface -These change how the client itself looks and behaves. None of them touches a stream, so none of them -can live in a [profile](/docs/profiles-and-links) — they are decisions about the device in front of -you. +These change how the client itself looks and behaves. None touches a stream, so none can live in a +[profile](/docs/profiles-and-links) — they are decisions about the device in front of you. **Gamepad-optimized browsing** — *default: on.* Swaps the touch or desktop home for the controller-optimized one: the host carousel, larger focus targets, a swipeable cover browser, and @@ -299,22 +293,20 @@ controller-optimized home is a separate entry point rather than a switch, so the turn off. An Android TV is always in this mode — its remote is the only input it has. **Show it** — *default: With a controller.* Only shown while the switch above is on, and it decides -*when* that switch takes effect. **With a controller** is the long-standing behaviour: the -controller-optimized home appears as a pad connects and the touch interface returns when the last one -disconnects. **Always** keeps the controller-optimized home either way — for a phone or tablet that -lives docked to a TV, where the pad isn't always awake but the couch layout is always the one you -want. Apple and Android. (An Android TV is in that mode regardless, so the choice changes nothing +*when* that switch takes effect. **With a controller**: the controller-optimized home appears as a +pad connects and the touch interface returns when the last one disconnects. **Always** keeps the +controller-optimized home either way — for a phone or tablet docked to a TV, where the pad isn't +always awake. Apple and Android. (An Android TV is in that mode regardless, so the choice changes nothing there.) **Background** — *default: Violet.* The colour family the controller-optimized home's living backdrop drifts through. Thirteen of them: seven dark fields — **Violet**, **OLED**, **Nebula**, **Abyss**, **Ember**, **Moss**, **Graphite** — then six pale ones, **Holo**, **Sunset**, **Bloom**, **Dawn**, **Mint** and **Opal**, which flip the whole interface to dark text on a light field. The backdrop -recolours as you step the row, so pick by looking. **OLED** is the one with a practical point rather -than a decorative one: it is true black — most of the frame is pixels switched off, which on an OLED -or AMOLED panel means no glow and no power drawn, with only a faint violet ember left in one corner. -Stored under the same name on every client, so a phone, a Deck and a desktop set to Mint all look -alike. Appearance only — nothing about a stream depends on it. +recolours as you step the row, so pick by looking. **OLED** is the one with a practical point: it is +true black — most of the frame is pixels switched off, which on an OLED or AMOLED panel means no +glow and no power drawn, with only a faint violet ember left in one corner. Stored under the same +name on every client, so a phone, a Deck and a desktop set to Mint all look alike. The row lives in the controller-optimized settings themselves — the screen you reach with **X** from the controller-optimized home — on every platform that has one, which includes the Steam Deck and the @@ -329,8 +321,7 @@ superset of the one before. This setting only picks the tier a session *starts* them live in-stream, with a shortcut that differs by platform. The Apple app additionally lets you choose which corner the overlay sits in (Top Left, Top Right, Bottom Left, Bottom Right). The console home has the tier picker too, as **Statistics overlay** under **Interface**. The shortcuts, -and every number in the overlay, are in -[Understanding the stats overlay](/docs/stats). +and every number in the overlay, are in [Understanding the stats overlay](/docs/stats). ## Settings that are facts about your device @@ -352,9 +343,7 @@ stay global and **cannot be put in a settings profile**: - **Auto-wake on connect**, and **Show game library** where it still exists (the Apple and Android apps) — decisions about this device and this network, not about how a given host is streamed. - Everything under **Interface** — **Gamepad-optimized browsing**, **Show it** and **Background**. - How this client looks and which layout it wears has nothing to do with how a host streams to it, - so binding them to a host would only make the same device change appearance depending on what it - connected to. + How this client looks has nothing to do with how a host streams to it. One switch you might expect here isn't in Settings at all: **Share clipboard** lives in a saved host's own edit sheet, because handing a machine your clipboard is a decision about that one host — diff --git a/docs-site/content/docs/clients.md b/docs-site/content/docs/clients.md index 082a2977f..25b805888 100644 --- a/docs-site/content/docs/clients.md +++ b/docs-site/content/docs/clients.md @@ -5,15 +5,13 @@ description: The ways to connect to a Punktfunk host — the Apple app, Moonligh A Punktfunk host accepts clients over its own `punktfunk/1` protocol (the macOS, Linux, Windows, and Android apps) and over GameStream (Moonlight). Pick whichever fits the device you're streaming *to*. -Ready to install? -**[Install a Client](/docs/install-client)** has the step-by-step for every device — plus how to +**[Install a Client](/docs/install-client)** has the step-by-step for every device, plus how to [update](/docs/install-client#keeping-a-client-up-to-date) and [remove](/docs/install-client#removing-a-client) each one. -Two things apply to every app, whichever you pick: -[profiles and `punktfunk://` links](#profiles-and-links-every-app), and the keys and chords that -work [while you're streaming](#while-youre-streaming). What each one lets you change — resolution, -bitrate, codec, HDR, audio, controllers — is catalogued in +Two things apply to every app: [profiles and `punktfunk://` links](#profiles-and-links-every-app), +and the keys and chords that work [while you're streaming](#while-youre-streaming). What each app +lets you change — resolution, bitrate, codec, HDR, audio, controllers — is catalogued in [Client settings](/docs/client-settings). ## Apple app (Mac, iPhone, iPad, Apple TV) @@ -29,9 +27,9 @@ protocol — the lowest-latency, most resilient path, with the full feature set: - A live **stats overlay** (resolution, fps, bitrate, latency) and a built-in **network speed test** to pick a bitrate for your link. - **Widgets, Live Activities and Shortcuts** — a hosts widget and a game-library widget for the - home screen (the library one opens a host you pick straight into its library), a Live Activity - while a session runs, and App Intents so Siri and the Shortcuts app can start a stream or jump - into a host's game library. + home screen (the library one opens a picked host's library), a Live Activity while a session + runs, and App Intents so Siri and the Shortcuts app can start a stream or jump into a host's game + library. Open the app, pick your host, [pair](/docs/pairing) once, and stream. It builds from the `clients/apple` directory in the repo (Swift / VideoToolbox / Metal). @@ -40,11 +38,9 @@ Open the app, pick your host, [pair](/docs/pairing) once, and stream. It builds Punktfunk also speaks the **GameStream** protocol, so any [Moonlight](https://moonlight-stream.org/) client — a browser, a smart TV, an old phone, a games console — connects with no punktfunk-specific -software. (Most platforms also have a native Punktfunk app below — Moonlight is the catch-all.) See -[Connect with Moonlight](/docs/moonlight). - -This is the broadest-compatibility option and great for couch gaming. It doesn't use the native -protocol's FEC/encryption extensions, but for a healthy LAN that rarely matters. +software; it's the catch-all where no native Punktfunk app exists. See +[Connect with Moonlight](/docs/moonlight). It doesn't use the native protocol's FEC/encryption +extensions, but on a healthy LAN that rarely matters. ## Linux desktop client (GTK4) @@ -52,26 +48,26 @@ protocol's FEC/encryption extensions, but for a healthy LAN that rarely matters. `punktfunk/1` directly, with vendor-ordered hardware decode (**Vulkan Video first on NVIDIA and AMD**, **VAAPI dmabuf first on Intel**; whichever isn't first is the fallback, and software decode is last), PipeWire audio, and SDL3 controllers (rumble, lightbar, DualSense touchpad/motion). The -decoders are Punktfunk's own — the client links no FFmpeg at all, and talks to your GPU's Vulkan -and VAAPI drivers directly. To force one, pick it in *Preferences → Display → Video decoder* or -set `PUNKTFUNK_DECODER=native-vulkan|native-vaapi|software`. Like the Apple app it discovers hosts -on your network automatically, does PIN pairing, pins reconnects, and browses the host's -**game library** (with cover art) so you can launch a title straight into the stream. +decoders are Punktfunk's own — the client links no FFmpeg and talks to your GPU's Vulkan and VAAPI +drivers directly. To force one, pick it in *Preferences → Display → Video decoder* or set +`PUNKTFUNK_DECODER=native-vulkan|native-vaapi|software`. Like the Apple app it discovers hosts +automatically, does PIN pairing, pins reconnects, and browses the host's **game library** (with +cover art) to launch a title straight into the stream. -It ships as a real package, not just a source build — full steps in +It ships as real packages — full steps in [Install a Client](/docs/install-client#linux-desktop-flatpak): -- **Any Flatpak distro (recommended)** — one command from the hosted `flatpak.unom.io` repo; the - guide linked above has the exact command and how updates work. It's also the client the +- **Any Flatpak distro (recommended)** — one command from the hosted `flatpak.unom.io` repo (exact + command and update flow in the guide above). It's also the client the [Decky plugin](/docs/steam-deck) uses by default, though the plugin drives a native `punktfunk-client` just as well. - **Ubuntu 26.04 or newer** — `apt install punktfunk-client` from the Punktfunk apt registry. The client package needs SDL3 and GTK4 ≥ 4.20, which Ubuntu 24.04 LTS doesn't ship — on 24.04 use the - Flatpak above. + Flatpak. - **Fedora** — `sudo dnf install punktfunk-client` from the Gitea RPM registry (add the repo as in the [Fedora guide](/docs/fedora)). -- **Fedora Atomic / Bazzite** — use the Flatpak above. `rpm-ostree install punktfunk-client` works, - but layering slows every OS update, so it's a last resort on an image-based system (see +- **Fedora Atomic / Bazzite** — use the Flatpak. `rpm-ostree install punktfunk-client` works, but + layering slows every OS update, so it's a last resort on an image-based system (see [Bazzite](/docs/bazzite)). - **Arch** — `sudo pacman -Syu punktfunk-client` from the signed binary repo (see [Arch Linux](/docs/arch)). @@ -88,7 +84,7 @@ The client also updates itself (`punktfunk-client --check-update` / `--apply-upd ## Android app (phone + Android TV) -The native Android app speaks `punktfunk/1` directly, on both phones and Android TV. It does hardware +The native Android app speaks `punktfunk/1` directly, on phones and Android TV. It does hardware HEVC decode (including [HDR10](/docs/hdr#per-client)), Opus audio with a mic uplink, game controllers with rumble and DualSense feedback, automatic host discovery, PIN pairing with pinned reconnects, the host's **game library** with cover art, and a live stats overlay — with D-pad and @@ -96,29 +92,28 @@ game-controller focus navigation for the couch. It builds from the `clients/andr (Kotlin + a shared Rust core). **Controllers.** Plug a **DualSense**, **DualSense Edge** or **DualShock 4** into the phone or tablet -by USB and grant the USB permission Android asks for when it attaches — Punktfunk then drives the pad -itself instead of taking what Android's gamepad layer exposes, so the host gets rumble, adaptive -triggers, the lightbar and gyro. The app's **Controllers** screen lists attached pads and their -capture state, and the switch that turns this off is *DualSense / DualShock passthrough (USB)* in -Settings. Over **Bluetooth** the pad still works as an ordinary gamepad, but adaptive triggers and -the lightbar need the USB connection. +by USB and grant the USB permission Android asks for — Punktfunk then drives the pad itself instead +of taking what Android's gamepad layer exposes, so the host gets rumble, adaptive triggers, the +lightbar and gyro. The app's **Controllers** screen lists attached pads and their capture state; the +switch that turns this off is *DualSense / DualShock passthrough (USB)* in Settings. Over +**Bluetooth** the pad still works as an ordinary gamepad, but adaptive triggers and the lightbar +need USB. The app is on **[Google Play](https://play.google.com/store/apps/details?id=io.unom.punktfunk)** as a -public listing — no invite — or you can sideload the public APK instead (see +public listing — no invite — or sideload the public APK (see [Install a Client](/docs/install-client#android)); canary builds ride a separate, invite-only Play -Internal testing track. Then open the app, pick your host, [pair](/docs/pairing) once, and stream. +Internal testing track. Open the app, pick your host, [pair](/docs/pairing) once, and stream. ## Windows desktop client -`punktfunk-client` for Windows (`clients/windows`) is the native graphical client for Windows — pure -Rust, the same `punktfunk/1` core as the Apple, Linux, and Android apps, with a **WinUI 3** UI (host -list, settings, PIN pairing); the stream itself runs in Punktfunk's Vulkan presenter. Its decoder -order is per-vendor: **Vulkan Video, then D3D11VA, then software** on NVIDIA and AMD, and -**D3D11VA first** on Intel and other GPUs (Intel's driver advertises Vulkan Video, but DXVA is the -proven path there), with [10-bit/HDR present](/docs/hdr#per-client), WASAPI audio + mic, -SDL3 controllers (rumble, lightbar, DualSense), network discovery, the host's **game library** with -cover art, and the full PIN-pairing trust surface. It builds for both `x86_64` and `aarch64` and -ships as a **signed MSIX**. Launch it and pick a host from the list, just like the other native apps. +`punktfunk-client` for Windows (`clients/windows`) is the native graphical client — pure Rust, the +same `punktfunk/1` core as the Apple, Linux, and Android apps, with a **WinUI 3** UI (host list, +settings, PIN pairing); the stream itself runs in Punktfunk's Vulkan presenter. Decoder order is +per-vendor: **Vulkan Video, then D3D11VA, then software** on NVIDIA and AMD, and **D3D11VA first** +on Intel and other GPUs (Intel's driver advertises Vulkan Video, but DXVA is the proven path there). +It has [10-bit/HDR present](/docs/hdr#per-client), WASAPI audio + mic, SDL3 controllers (rumble, +lightbar, DualSense), network discovery, the host's **game library** with cover art, and the full +PIN-pairing trust surface. It builds for `x86_64` and `aarch64` and ships as a **signed MSIX**. The package installs **two** Start-menu entries — **Punktfunk**, the desktop window, and **Punktfunk Console**, a controller-driven fullscreen interface for a TV or HTPC (host list, pairing, @@ -130,7 +125,7 @@ settings and game library, all navigable with a pad) — plus the headless > is a proven alternative for Windows. For scripting, prefer the [`punktfunk` CLI](#scripting-the-punktfunk-cli). The window binary's own -headless flags stay supported too: +headless flags stay supported: ```sh punktfunk-client # open the WinUI 3 window (host list / settings) @@ -138,12 +133,10 @@ punktfunk-client --discover # list hosts on the punktfunk-client --headless --speed-test --connect :9777 # no window: probe the link, print measured/recommended bitrate ``` -Prefer the broadest compatibility, or no install? **Moonlight** also streams to Windows (see below). - ## webOS (LG TV) — community [`pf-webos`](https://github.com/dyptan-io/pf-webos) is a native client for LG webOS TVs, built and -maintained by the community ([dyptan-io](https://github.com/dyptan-io)) on top of Punktfunk's +maintained by the community ([dyptan-io](https://github.com/dyptan-io)) on Punktfunk's `punktfunk/1` protocol and core. It's not an official Punktfunk app, but it speaks the real protocol directly (not Moonlight/GameStream) — LAN discovery or add-by-IP, PIN pairing with pinned reconnects, hardware video decode via webOS's NDL DirectMedia API, and a browsable game library with cover art, @@ -178,21 +171,21 @@ named, **6** it needs a person (pairing, or an unknown host). Under the Flatpak, run it as `flatpak run --command=punktfunk io.unom.Punktfunk `. -> The older headless flags stay supported for existing scripts — `punktfunk-client --connect`, -> `--discover` and `--headless --speed-test` on both Linux and Windows. `punktfunk` is the surface -> to build new things on: it wakes a sleeping host before connecting, which those never did. +> The older `punktfunk-client --connect`, `--discover` and `--headless --speed-test` flags stay +> supported on Linux and Windows for existing scripts, but build new things on `punktfunk`: it wakes +> a sleeping host before connecting, which those never did. > -> `punktfunk-probe` is a different thing again — an in-repo protocol test and latency-measurement -> tool for development. It isn't shipped in any package; you build it from source. +> `punktfunk-probe` is different again — an in-repo protocol test and latency-measurement tool for +> development, not shipped in any package; you build it from source. ## Profiles and links (every app) Two things work the same in the Apple, Linux, Windows and Android apps. **Settings profiles** are named sets of stream overrides — bitrate, resolution, codec, HDR and the rest — that you bind to a -host or pick for a single connect, with every field you didn't touch still following your defaults. -And a **`punktfunk://` link** starts a stream from a browser, a desktop shortcut, a home-automation -rule or `punktfunk open`, carrying only *references* to things that already exist on your device — -never a setting, and never a trust decision. +host or pick for a single connect; every field you didn't touch still follows your defaults. A +**`punktfunk://` link** starts a stream from a browser, a desktop shortcut, a home-automation rule +or `punktfunk open`, carrying only *references* to things that already exist on your device — never +a setting, never a trust decision. [Profiles and links](/docs/profiles-and-links) has both in full: the link grammar, where each app puts **Copy link** and **Create shortcut…**, and what a link is refused for. From a script, @@ -203,14 +196,14 @@ puts **Copy link** and **Create shortcut…**, and what a link is refused for. F Click the stream and the desktop clients **capture** your keyboard and mouse — everything goes to the host until you let go. **Ctrl+Alt+Shift+Q** (⌃⌥⇧Q or ⌘⎋ on a Mac) gives it back. -That chord, the three others a stream reserves, the controller chord that works with no keyboard in -reach, which app honours which of them, the two mouse modes, the three touch modes and stylus input -are all on [Mouse, touch and pen](/docs/input#getting-your-input-back). +That chord, the three others a stream reserves, the controller chord that needs no keyboard, which +app honours which, the two mouse modes, the three touch modes and stylus input are all on +[Mouse, touch and pen](/docs/input#getting-your-input-back). Copying between the two machines is a separate opt-in: the host operator allows it in `host.env` -and you turn it on for that one host in your client. Content crosses today from the macOS, iOS, -iPadOS, Windows and Android apps — the Linux client has the switch but no bridge behind it yet, and -tvOS has no pasteboard to share. See [Shared clipboard](/docs/clipboard). +and you turn it on per host in your client. Content crosses today from the macOS, iOS, iPadOS, +Windows and Android apps — the Linux client has the switch but no bridge behind it yet, and tvOS +has no pasteboard to share. See [Shared clipboard](/docs/clipboard). ## Which should I use? @@ -226,5 +219,4 @@ tvOS has no pasteboard to share. See [Shared clipboard](/docs/clipboard). | Scripts, plugins, home automation | The headless **[`punktfunk`](#scripting-the-punktfunk-cli)** CLI | | Protocol development / latency measurement | **`punktfunk-probe`** (source build only) | -Whichever you choose, the first connection needs a one-time [pairing](/docs/pairing), and -[Install a Client](/docs/install-client) covers installing, updating and removing it. +Whichever you choose, the first connection needs a one-time [pairing](/docs/pairing). diff --git a/docs-site/content/docs/clipboard.md b/docs-site/content/docs/clipboard.md index ec9e3d90b..67f327ca1 100644 --- a/docs-site/content/docs/clipboard.md +++ b/docs-site/content/docs/clipboard.md @@ -4,17 +4,16 @@ description: Copy on one machine and paste on the other — the two switches tha --- Punktfunk can share the clipboard between the machine you are sitting at and the host you are -streaming. Copy a URL on your laptop, paste it into a browser on the host. Copy an error message on -the host, paste it into a chat app on your laptop. +streaming, in both directions — copy a URL on your laptop, paste it on the host, and back. **Two separate switches have to be on:** -1. The **host** operator has to allow it, with a line in `host.env` and a host restart. This one is - off by default. -2. **You** have to turn it on for that one host, in that host's edit sheet on your client. This one - is off by default on the macOS, Windows and Linux clients — but **on by default on Android**. +1. The **host** operator has to allow it, with a line in `host.env` and a host restart. Off by + default. +2. **You** have to turn it on for that one host, in that host's edit sheet on your client. Off by + default on the macOS, Windows and Linux clients — **on by default on Android**. -Flipping one and not the other looks exactly like the feature not existing. So check both. +Flipping one and not the other looks exactly like the feature not existing. Check both. ## 1. Allow it on the host @@ -52,14 +51,14 @@ punktfunk-host service restart See [Configuration](/docs/configuration) for the rest of `host.env`. > **About the file mode.** No client shipping today asks for file transfer, and no host clipboard -> backend offers file formats yet. `on` and `text-only` therefore behave the same in practice — -> `text-only` is how you make that explicit and keep it that way. +> backend offers file formats yet, so `on` and `text-only` behave the same in practice — `text-only` +> makes that explicit and keeps it that way. ## 2. Turn it on for that host, in your client -The client switch is **per saved host**, not global: handing a machine your clipboard is a decision -about *that* machine. You set it in the host's edit sheet, and it is deliberately not something a -[settings profile can carry](/docs/profiles-and-links#what-a-profile-cant-change). +The client switch is **per saved host**, not global — handing a machine your clipboard is a decision +about *that* machine — so it lives in the host's edit sheet, deliberately not in a +[settings profile](/docs/profiles-and-links#what-a-profile-cant-change). | Client | Where the switch is | Label | Default | |---|---|---|---| @@ -69,39 +68,38 @@ about *that* machine. You set it in the host's edit sheet, and it is deliberatel | Linux (GTK) | Host card menu → **Edit…** | **Share clipboard** | Off | | Android (touch) | Host card menu → **Edit…** | **Shared clipboard** | **On** | -On Android the switch is only in the touch edit dialog. The controller/TV interface — what you get -on Android TV, and on a phone when a controller is attached — has its own **Edit Host** screen with -no clipboard row, so there is nowhere to change it there. It stays on, which is the Android default. +On Android the switch is only in the touch edit dialog. The controller/TV interface — Android TV, +and a phone with a controller attached — has its own **Edit Host** screen with no clipboard row, so +it stays on, the Android default. The setting is read when a session starts, so if you change it while streaming, reconnect. macOS can also flip it mid-session: **Stream ▸ Share Clipboard** (⌃⌥⇧C), which becomes **Stop Sharing Clipboard** once the host has acknowledged it. On an iPad with a hardware keyboard the same combo works, though there is no menu bar to show it in — and only while the pointer is released, as -a captured session sends the keys to the host instead. +a captured session sends the keys to the host. tvOS and a Steam Deck in Gaming Mode have no clipboard switch — the Apple TV has no pasteboard to -share at all, and neither the Decky panel nor the client's console home has a host edit sheet — see +share, and neither the Decky panel nor the client's console home has a host edit sheet — see [what each client does](#which-hosts-and-clients-support-it) below. ## Nothing crosses until something pastes A copy costs nothing. When you copy, your machine announces only the **list of formats** it now -holds — no bytes. The bytes are pulled across on a separate transfer, and only when an application -on the other end actually pastes. Copying a large image and never pasting it transfers nothing. +holds — no bytes. The bytes are pulled across on a separate transfer, only when an application on +the other end actually pastes. Copying a large image and never pasting it transfers nothing. That holds for everything you copy on your own machine, and for both directions on the host. It does **not** hold for a host copy arriving at a Windows or Android client: those two fetch the -content straight away and put it on your local clipboard, whether or not you ever paste. On Windows -that is because the lazy path needs Windows delayed rendering, which the client doesn't implement -yet; on Android there is no way to satisfy a paste from the network at all. The macOS and iOS -clients are lazy in both directions. +content straight away and put it on your local clipboard, whether or not you ever paste — on +Windows because the lazy path needs Windows delayed rendering, which the client doesn't implement +yet; on Android because there is no way to satisfy a paste from the network at all. The macOS and +iOS clients are lazy in both directions. -On iOS there is one deliberate exception. Backgrounding the app ends the session, and a promise -nobody can answer is worse than no promise at all — so if the host copied something and you have not -pasted it yet, those bytes are pulled across as the session ends, up to 8 MiB. That is what makes -"copy on the host, switch to Safari, paste" work on an iPad. Nothing is fetched if you never leave -the app, or if you already pasted. +iOS has one deliberate exception. Backgrounding the app ends the session, so if the host copied +something you have not pasted yet, those bytes are pulled across as the session ends, up to 8 MiB. That is what makes "copy on the host, +switch to Safari, paste" work on an iPad. Nothing is fetched if you never leave the app, or if you +already pasted. A single transfer is capped at 64 MiB. Nothing else limits size, so a very large host-side copy can cross to a Windows or Android client for a paste that never happens. @@ -119,9 +117,7 @@ from a Punktfunk client. A Moonlight client has no clipboard. ## Which hosts and clients support it **Hosts.** The host runs on Linux and Windows, and both have a clipboard backend — but on Linux it -depends on the desktop session. - -On Linux the host needs one of two mechanisms in the session it is streaming: +depends on the desktop session, which needs one of two mechanisms: - `ext-data-control-v1` — KWin, wlroots/Sway and Hyprland. Tried first. - GNOME's own `org.gnome.Mutter.RemoteDesktop.Session` clipboard, used directly. Tried second. @@ -154,8 +150,8 @@ registered `PNG` clipboard format. Many Windows apps publish only a bitmap, and announced yet. The other direction is fine: an image copied on the host reaches the Windows client either way. -The host side is richer than any client: it can offer and accept text, HTML, RTF, PNG, JPEG and -GIF. What you get is therefore whatever your client supports. +The host side is richer than any client: it can offer and accept text, HTML, RTF, PNG, JPEG and GIF. +What you get is whatever your client supports. ## Why the toggle does nothing (or is greyed out) @@ -164,18 +160,18 @@ connected host did not advertise a clipboard. On the other clients there is noth the per-host switch always looks available, and a host that can't do it simply does nothing. Work through these in order: -- **The host has it off.** The default. Nothing was added to `host.env`, or the value is `off`, - `0`, `false` or empty. Fix it with step 1 above. +- **The host has it off.** The default. Nothing was added to `host.env`, or the value is `off`, `0`, + `false` or empty. Fix it with step 1 above. - **`host.env` was edited but the host wasn't restarted.** The file is read once, at startup. - **The switch is off for this host in your client.** It is per saved host, and off by default everywhere except Android. Check the host's **Edit…** sheet — step 2 above. - **The host's session has no supported backend.** The host allows the clipboard, so it still - advertises the capability, but it has nothing to read the desktop's clipboard with. This is a - gamescope session, a compositor with only the old `zwlr-data-control-unstable-v1`, or a GNOME - session whose Mutter doesn't expose the direct RemoteDesktop clipboard. Nothing on screen tells - you this apart — the host log does. + advertises the capability, but has nothing to read the desktop's clipboard with: a gamescope + session, a compositor with only the old `zwlr-data-control-unstable-v1`, or a GNOME session whose + Mutter doesn't expose the direct RemoteDesktop clipboard. Nothing on screen tells you this apart — + the host log does. - **The host is older than the feature.** A host from before clipboard sync never advertises it. -- **Your client doesn't implement it** — Linux, Steam Deck, iOS, iPadOS or tvOS. Nothing crosses +- **Your client doesn't implement it** — Linux (GTK), Steam Deck or tvOS. Nothing crosses regardless of what the host allows. - **You changed the switch while connected.** Reconnect, or use ⌃⌥⇧C on macOS. - **The copy was a secret, or a format nobody handles.** Concealed content is skipped on purpose on diff --git a/docs-site/content/docs/controller-audio.md b/docs-site/content/docs/controller-audio.md index 432af8ca7..d9527b09d 100644 --- a/docs-site/content/docs/controller-audio.md +++ b/docs-site/content/docs/controller-audio.md @@ -3,50 +3,46 @@ title: Controller speaker and haptics description: DualSense voice-coil haptics and the pad's built-in speaker, streamed from the host to the controller in your hands — what to enable, and what "set it to Pro Audio" means on a Linux host. --- -A DualSense is partly an audio device. Its little speaker and its two voice-coil motors — the -actuators that make a PS5 pad feel like sand, rain or a bowstring instead of a buzzing phone — -are all driven by a four-channel audio stream, not by rumble commands. Games that support them -write PCM into "the controller's audio device". +A DualSense is partly an audio device. Its speaker and its two voice-coil motors are driven by a +four-channel audio stream, not by rumble commands. Games that support them write PCM into "the +controller's audio device". -Punktfunk gives that device to the game on the host, captures what the game writes, and streams -it to the controller physically in your hands, on its own low-latency plane. Channels 1–2 are the -pad's speaker, channels 3–4 are the voice coils. +Punktfunk gives that device to the game on the host, captures what the game writes, and streams it +to the controller in your hands on its own low-latency plane. Channels 1–2 are the pad's speaker, +channels 3–4 the voice coils. ## What you need - **A DualSense or DualSense Edge plugged in over USB** on the client. Bluetooth pads expose no - audio interface at all, so they fall back to ordinary rumble — this is a limit of the - controller, not of Punktfunk. + audio interface, so they fall back to ordinary rumble — a limit of the controller, not of Punktfunk. - On the client, **Controller haptics** is on by default. So is **Controller speaker** on the Linux - and Windows apps — turn it off in [client settings](/docs/client-settings#input) if you would - rather all game audio came out of your speakers. On Android the speaker is opt-in. -- On a **Linux host**, a game that speaks DualSense — which in practice means running it under - **GE-Proton 11-5 or newer**. Stock Proton does not route controller audio. + and Windows apps — turn it off in [client settings](/docs/client-settings#input) if you'd rather + all game audio came out of your speakers. On Android the speaker is opt-in. +- On a **Linux host**, a game that speaks DualSense — in practice, running under **GE-Proton 11-5 or + newer**. Stock Proton does not route controller audio. - On the host, controller audio is on by default (`PUNKTFUNK_PAD_AUDIO`). Nothing is sent while the pad is quiet, so leaving it on costs nothing. ## "Set the controller audio to Pro Audio" — you don't have to -If you have looked into DualSense haptics on Linux before, you have probably run into this -advice: plug the pad into the Linux box, open your sound settings, find *DualSense wireless -controller (PS5)*, and switch its **Profile** to **Pro Audio**. That advice is real and it is -correct — for a pad plugged directly into the host. +The usual advice for DualSense haptics on Linux — plug the pad into the Linux box, open your sound +settings, find *DualSense wireless controller (PS5)*, switch its **Profile** to **Pro Audio** — is +correct for a pad plugged directly into the host. The reason is channel layout. A pad's other profiles present it as a mono speaker, a stereo headphone jack, or a positioned four-channel "surround" device. Games write their haptics as four -*unpositioned* channels, so on any of those profiles the audio system helpfully re-mixes them into -the speaker pair and the voice-coil channels are folded away. You feel nothing. Pro Audio is the -one profile that hands the four channels through untouched, in order. +*unpositioned* channels, so on any of those profiles the audio system re-mixes them into the speaker +pair and the voice-coil channels are folded away. Pro Audio is the one profile that hands the four +channels through untouched, in order. -**Punktfunk's controller audio device is already in that shape.** It is created as four raw -channels with no re-mixing, which is exactly what Pro Audio produces — so there is nothing to -switch, and no switch to make. +**Punktfunk's controller audio device is already in that shape** — four raw channels with no +re-mixing, exactly what Pro Audio produces — so there is nothing to switch. That is also why it looks different in your sound settings. A real pad is a USB sound card, so it -gets a **Profile** dropdown; Punktfunk's is a software device, so it has no card and no dropdown. -Seeing **Wireless Controller** with a volume slider and no profile selector is what a correctly -minted controller-audio device looks like. It is not a sign that something is missing. +gets a **Profile** dropdown; Punktfunk's is a software device with no card and no dropdown. Seeing +**Wireless Controller** with a volume slider and no profile selector is what a correctly minted +controller-audio device looks like, not a sign that something is missing. ## Checking it is working @@ -68,9 +64,9 @@ When a game actually starts driving the actuators, the pad's own driver reports DS5 title asserted haptics-select (audio haptics) pad=0 ``` -That last line is the one that matters: it means a title recognised the controller as an audio -device and switched the pad out of plain rumble. If you see it and still feel nothing, the problem -is downstream — on the client or the pad. If you never see it, the game never found the device. +That last line is the one that matters: a title recognised the controller as an audio device and +switched the pad out of plain rumble. If you see it and still feel nothing, the problem is +downstream — on the client or the pad. If you never see it, the game never found the device. You can also look at the device directly: @@ -78,22 +74,22 @@ You can also look at the device directly: pactl list sinks | grep -A25 Speaker__sink ``` -The line to check is `audio.position = "AUX0,AUX1,AUX2,AUX3"` — four unpositioned channels is the -layout that reaches the voice coils. Anything positioned (`FL,FR,RL,RR`) would not. +Check for `audio.position = "AUX0,AUX1,AUX2,AUX3"` — four unpositioned channels is the layout that +reaches the voice coils. Anything positioned (`FL,FR,RL,RR`) would not. ## If a game does not find it Games identify the controller's audio device by name and by USB ids, and different titles check -different things. GE-Proton has several routes to the pad, and a couple of them are opt-in per -game. Add these as launch options if a title is not cooperating: +different things. GE-Proton has several routes to the pad, a couple of them opt-in per game. Add +these as launch options if a title is not cooperating: ``` PROTON_DUALSENSE_HAPTICS_PREFER_NON_EVENT=1 %command% ``` -This forces GE onto its most direct route — it opens Punktfunk's controller-audio device by name -and writes the four channels straight into it, with no re-mixing anywhere in between. It is the -first thing to try. +This forces GE onto its most direct route — it opens Punktfunk's controller-audio device by name and +writes the four channels straight into it, with no re-mixing in between. It is the first thing to +try. Some titles additionally want: @@ -112,55 +108,52 @@ To see which route GE took, launch the game with `WINEDEBUG=+pulse` and look for ## On a Linux client, the pad's own profile matters too -Everything above is about the host, where the controller-audio device is one Punktfunk mints. On a -Linux **client** the pad is real, and the same channel-layout problem shows up from the other side: -the voice coils are physically channels 3 and 4 of the controller's USB sound card, and a -controller almost never presents as a four-channel device on its own. Depending on your distribution -it appears as a stereo output, or as a mono *Speaker* plus a stereo *Headphones* pair. Playing into -any of those puts the haptics in the headphone jack and folds the coil channels away — audio that -looks perfectly healthy, felt as nothing at all. +On a Linux **client** the pad is real, and the same channel-layout problem shows up from the other +side: the voice coils are physically channels 3 and 4 of the controller's USB sound card, and a +controller almost never presents as a four-channel device on its own. Depending on your +distribution it appears as a stereo output, or as a mono *Speaker* plus a stereo *Headphones* pair. +Playing into any of those puts the haptics in the headphone jack and folds the coil channels away — +audio that looks healthy, felt as nothing. **Punktfunk handles this for you.** When it needs the coils and the pad is not already presenting four channels, it switches the controller's card to **Pro Audio** for the length of the session and -puts your setting back afterwards. You will see the profile change in your sound settings while you -are streaming; that is expected. It is never saved as the card's remembered profile. +puts your setting back afterwards. You will see the profile change in your sound settings while +streaming; that is expected. It is never saved as the card's remembered profile. If you would rather manage the card yourself, set `PUNKTFUNK_PAD_AUDIO_PROFILE=0` on the client. Then Punktfunk uses a four-channel profile if you have already selected one and logs what it needs if you have not. -Many systems never reach the switch at all. On **SteamOS** a DualSense already exposes its four -channels behind a combined speaker-and-haptics output, and Punktfunk finds them there. That is a -Valve addition, though, not something every up-to-date system has: `alsa-ucm-conf` upstream — and -so Fedora, Bazzite and Arch — describes the pad as a *mono speaker plus stereo headphones* and -nothing else, which is precisely the shape that folds the coils away. The switch is the fallback -for those. **If you run the client as a Flatpak**, your audio manager may not let a sandboxed app -change a card's profile; if the log says so, switch the controller to Pro Audio yourself, which is -the same fix. +Many systems never reach the switch. On **SteamOS** a DualSense already exposes its four channels +behind a combined speaker-and-haptics output, and Punktfunk finds them there. That is a Valve +addition, not something every up-to-date system has: `alsa-ucm-conf` upstream — and so Fedora, +Bazzite and Arch — describes the pad as a *mono speaker plus stereo headphones* and nothing else, +precisely the shape that folds the coils away. The switch is the fallback for those. **If you run +the client as a Flatpak**, your audio manager may not let a sandboxed app change a card's profile; if +the log says so, switch the controller to Pro Audio yourself, which is the same fix. -Punktfunk's **host** packages (rpm, deb, Arch, and the Bazzite sysext) close that gap at the -source: they install a small ALSA profile for the DualSense that adds the combined -speaker-and-haptics output SteamOS has, and give it priority over the mono one. It adds files -rather than replacing any your distribution owns, so it upgrades cleanly and can be removed by -uninstalling Punktfunk. A pad plugged into the host then presents four channels on its own, with -no profile switching by anyone — and, because the lone mono output stops existing, games that -crashed when they opened it stop crashing. A card reads its profile once, when it appears, so -replug the pad after installing (or restart PipeWire) rather than expecting a pad that was -already plugged in to pick it up. +Punktfunk's **host** packages (rpm, deb, Arch, and the Bazzite sysext) close that gap at the source: +they install a small ALSA profile for the DualSense that adds the combined speaker-and-haptics +output SteamOS has, and give it priority over the mono one. It adds files rather than replacing any +your distribution owns, so it upgrades cleanly and can be removed by uninstalling Punktfunk. A pad +plugged into the host then presents four channels on its own, with no profile switching by anyone — +and, because the lone mono output stops existing, games that crashed when they opened it stop +crashing. A card reads its profile once, when it appears, so replug the pad after installing (or +restart PipeWire) rather than expecting an already-plugged pad to pick it up. ### Checking the client side without a host -The client can test the whole path on its own — no host, no game, no pairing. Plug in the -DualSense and run: +The client can test the whole path on its own — no host, no game, no pairing. Plug in the DualSense +and run: ```sh punktfunk-session --pad-audio-test ``` -It prints every DualSense object it can see in your audio graph, says which one it chose, and then -plays a tone into the voice coils for three seconds. **If the pad buzzes, the client side is -working** and any remaining silence is coming from the host or the game. Add `--speaker` to test -the pad's speaker instead, and `--seconds N` for a longer run. +It prints every DualSense object it can see in your audio graph, says which one it chose, and plays +a tone into the voice coils for three seconds. **If the pad buzzes, the client side is working** and +any remaining silence is coming from the host or the game. Add `--speaker` to test the pad's speaker +instead, and `--seconds N` for a longer run. On the Steam Deck and other flatpak installs, run it inside the sandbox: @@ -172,30 +165,29 @@ flatpak run --command=punktfunk-session io.unom.Punktfunk --pad-audio-test The controller's speaker and its headphone jack **share a channel**. Channel 1 of the pad's audio device is the headphone jack's right channel *and* the built-in speaker, and the controller decides -which one actually sounds. It powers up pointing at the jack — so with nothing plugged in, a -perfectly routed speaker stream is heard by nobody. +which one sounds. It powers up pointing at the jack — so with nothing plugged in, a perfectly routed +speaker stream is heard by nobody. Punktfunk points the pad at its own speaker when **Controller speaker** is on. The voice coils are -different channels and are not affected by that choice, which is why haptics work as soon as the -audio is routed correctly and the speaker needs this extra step. A game that drives the pad's audio -settings itself still overrides it. If your pad's speaker stays quiet, `PUNKTFUNK_PAD_SPEAKER_PATH` -and `PUNKTFUNK_PAD_SPEAKER_VOLUME` let you bisect it without a rebuild. +different channels and unaffected by that choice. A game that drives the pad's audio settings +itself still overrides it. If your pad's speaker stays quiet, `PUNKTFUNK_PAD_SPEAKER_PATH` and +`PUNKTFUNK_PAD_SPEAKER_VOLUME` let you bisect it without a rebuild. ## Known limits - **Bluetooth client pads get rumble, not haptics.** No audio interface exists over BT. -- **Titles that match the controller by container ID** — a Windows notion of "these devices are - the same physical thing" — will not recognise the pairing on a Linux host, because the virtual - pad has no USB device behind it to derive one from. Titles that match by name or by USB ids are - unaffected, which is most of them. +- **Titles that match the controller by container ID** — a Windows notion of "these devices are the + same physical thing" — will not recognise the pairing on a Linux host, because the virtual pad has + no USB device behind it to derive one from. Titles that match by name or by USB ids — most of them + — are unaffected. - **A pad plugged into the host itself can steal the audio.** If a real DualSense is connected to - the host while you are streaming to a different one, some titles will find the local pad's sound - card first. Unplug it, or stream from a host that has no pad attached. + the host while you stream to a different one, some titles find the local pad's sound card first. + Unplug it, or stream from a host that has no pad attached. - **The Pro Audio switch on a Linux client renames the pad's microphone too.** Switching a sound card's profile re-creates all of its inputs and outputs, so if you had picked the DualSense's own microphone as your [mic](/docs/client-settings#audio), that session falls back to your default - one. Pick a different microphone, or set `PUNKTFUNK_PAD_AUDIO_PROFILE=0` and select a - four-channel profile on the card yourself. -- **A client killed mid-stream leaves the pad on Pro Audio.** The profile is restored when a - session ends normally and is never written to your saved settings, so anything that reloads the - card — unplugging it, logging out, a reboot — brings your own profile back. + one. Pick a different microphone, or set `PUNKTFUNK_PAD_AUDIO_PROFILE=0` and select a four-channel + profile on the card yourself. +- **A client killed mid-stream leaves the pad on Pro Audio.** The profile is restored when a session + ends normally and is never written to your saved settings, so anything that reloads the card — + unplugging it, logging out, a reboot — brings your own profile back. diff --git a/docs-site/content/docs/debian.md b/docs-site/content/docs/debian.md deleted file mode 100644 index 54a39c986..000000000 --- a/docs-site/content/docs/debian.md +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: Debian -description: Install the Punktfunk host on Debian 13 with apt — including LMDE and Linux Mint. ---- - -Install a Punktfunk host on **Debian 13 ("trixie") or newer** from the apt registry. This page -covers the distro-level setup — GPU driver, package, gamepad access. How the host creates its -virtual display and injects input is desktop-specific, so pick your desktop on the -[configure pages](#configure-your-desktop) afterward rather than here. - -> New here? Read [Security & Safe Use](/docs/security) first — a streaming host is remote control of -> the machine, so keep it on a trusted LAN or VPN and require pairing. - -> **Which releases.** The host package needs **glibc 2.39 or newer**; Debian 13 has 2.41, so it -> installs and runs there. **Debian 12 (bookworm) has glibc 2.36 and cannot install it** — build -> from source ([Ubuntu appendix](/docs/ubuntu#appendix--build-from-source), which applies here too) -> or upgrade. Check yours with `ldd --version`. - -> **The desktop client is not packaged for Debian yet.** `punktfunk-client` is built on Ubuntu 26.04 -> and floors at `libc6 >= 2.43` (Debian 13 has 2.41), on top of needing GTK4 ≥ 4.20. On a Debian -> box, stream *to* it with a [different client](/docs/install-client) — the Flatpak, or a build from -> source. The **host**, the **web console** and the **plugin runner** all install normally. - -## What works on Debian 13 - -| Package | Debian 13 | What it is | -|---|---|---| -| `punktfunk-host` | ✅ | The streaming host | -| `punktfunk-web` | ✅ | The browser management console | -| `punktfunk-scripting` | ✅ | The plugin/script runner | -| `punktfunk-gamescope` | ✅ | The patched gamescope (HDR + cursor + real refresh) | -| `punktfunk-client` | ❌ | Desktop client — `libc6 >= 2.43`, see above | - -## 1. GPU driver - -On **NVIDIA**, the driver lives in Debian's `contrib` / `non-free` / `non-free-firmware` -components, which a default install does not enable. Debian 13 keeps its sources in the deb822 -format, so add them there and refresh: - -```sh -sudo sed -i 's/^Components: .*/Components: main contrib non-free non-free-firmware/' \ - /etc/apt/sources.list.d/debian.sources -sudo apt update -sudo apt install nvidia-driver firmware-misc-nonfree -``` - -Debian 13 ships driver 550, comfortably above the [535 floor](/docs/requirements). - -Reboot, then confirm the driver and KMS modeset — Wayland on NVIDIA needs `modeset=1`: - -```sh -nvidia-smi -cat /sys/module/nvidia_drm/parameters/modeset # should print Y -``` - -If modeset is not `Y`: - -```sh -echo 'options nvidia-drm modeset=1' | sudo tee /etc/modprobe.d/nvidia-drm.conf -sudo update-initramfs -u && sudo reboot -``` - -> **Secure Boot:** with Secure Boot enabled, Debian's DKMS-built NVIDIA module must be signed and -> its key enrolled before it will load. If `nvidia-smi` can't talk to the driver, enrol the MOK -> (`sudo mokutil --import /var/lib/dkms/mok.pub`, reboot, choose **Enrol MOK**) or disable Secure -> Boot in firmware. - -On **AMD/Intel** none of the NVIDIA steps apply. Encode runs on the Mesa stack: **Vulkan Video** for -HEVC and AV1 (`mesa-vulkan-drivers`), with **VAAPI** for H.264 and as the fallback — -`mesa-va-drivers` on AMD, `intel-media-va-driver` on Intel (the latter is in `non-free`). - -## 2. Install the host (apt) - -The registry is public — no auth needed, just trust its signing key: - -```sh -sudo install -d -m 0755 /etc/apt/keyrings -curl -fsSL https://git.unom.io/api/packages/unom/debian/repository.key \ - | sudo tee /etc/apt/keyrings/punktfunk.asc >/dev/null - -echo "deb [signed-by=/etc/apt/keyrings/punktfunk.asc] https://git.unom.io/api/packages/unom/debian stable main" \ - | sudo tee /etc/apt/sources.list.d/punktfunk.list - -sudo apt update -sudo apt install punktfunk-host -``` - -`punktfunk-host` `Recommends` the browser console (`punktfunk-web`), so apt pulls it in by default. -The NVIDIA driver is **not** a dependency — you installed it out of band in step 1. Later updates -are `sudo apt update && sudo apt upgrade`; restart the running host afterwards so it picks up the -new binary: - -```sh -systemctl --user restart punktfunk-host -``` - -The `stable` component above is the stable channel. To track pre-release builds instead, see -[Release Channels](/docs/channels). - -## 3. Grant gamepad access - -Virtual gamepads inject through `/dev/uinput`, gated by the `input` group. Add yourself and re-login: - -```sh -sudo usermod -aG input "$USER" # re-login to apply -``` - -Also join `punktfunk` if you want the **virtual Steam Deck controller** (paddles, trackpads, gyro) — -it reaches games as a real USB device over usbip, which is what makes Steam Input adopt it. Join it -only on a machine you trust: writing the usbip `attach` file can materialise arbitrary emulated USB -hardware. - -```sh -sudo usermod -aG punktfunk "$USER" # re-login to apply -``` - -## 4. Check it installed - -```sh -punktfunk-host --version # the binary is on PATH -punktfunk-host detect-conflicts # exits 1 if Sunshine/Apollo is also installed -``` - -Two hosts on one machine is the most common reason a clean install never streams — see -[Troubleshooting](/docs/troubleshooting#another-streaming-host-sunshine-apollo--is-installed). - -## 5. Open the firewall (if you have one) - -**Debian ships no firewall enabled by default**, so out of the box there is nothing to open. If you -run one, the package installs the openers: - -```sh -# ufw: -sudo ufw allow punktfunk-native - -# firewalld: -sudo firewall-cmd --reload # load the installed definitions -sudo firewall-cmd --permanent --add-service=punktfunk-native -sudo firewall-cmd --reload -``` - -Add `punktfunk-gamestream` for Moonlight compat and `punktfunk-web` (TCP 47992) to reach the console -from another device. Full port lists are in -[`packaging/debian/README.md`](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md#firewall). - -## Cinnamon, Linux Mint and LMDE - -**A Cinnamon desktop cannot host a virtual display, and no setting changes that.** Punktfunk gives -each client its own screen at that device's exact resolution by asking the compositor to create a -virtual output. Cinnamon's compositor, **Muffin**, has no such API: it forked from Mutter 3.36, and -its `org.cinnamon.Muffin.ScreenCast` interface offers only `RecordMonitor` and `RecordWindow` — -never the `RecordVirtual` that Mutter gained in 42. Its portal backend -(`xdg-desktop-portal-xapp`) implements no ScreenCast either, so the route that serves Sway and -Hyprland is closed too. This is upstream's to fix, not a Punktfunk setting. - -**Which Mint you run decides whether there is any route at all:** - -| Edition | Base | Can it host? | -|---|---|---| -| **LMDE 7 "Gigi"** | Debian 13 | ✅ Yes — via gamescope (below) | -| **Linux Mint 22.x** ("Wilma"…"Zena") | Ubuntu 24.04 | ❌ No — see [below](#linux-mint-22x-cannot-host-yet) | -| **Linux Mint 23** | Ubuntu 26.04 | ✅ Expected — due December 2026 | - -On **LMDE 7**, what works is **gamescope**: the host starts its own headless gamescope for each -connecting client and runs the game inside it, so it needs no desktop compositor at all. Your -Cinnamon session keeps running untouched; the stream is the game, not the desktop. - -```sh -sudo apt install punktfunk-gamescope # LMDE 7 / Debian 13 — not available on Mint 22.x -echo 'PUNKTFUNK_COMPOSITOR=gamescope' >> ~/.config/punktfunk/host.env -systemctl --user restart punktfunk-host -``` - -The pin is required: auto-detection reads the live session, finds Cinnamon, and stops with an error -rather than guessing. Set a game to launch with -[`PUNKTFUNK_GAMESCOPE_APP`](/docs/gamescope) or per-session launch commands, then see -[Steam / gamescope](/docs/gamescope) for the rest. - -> **Install `punktfunk-gamescope`, not Debian's.** Debian ships **no** `gamescope` package at all, -> and the patched build is what gives the stream HDR, a visible cursor, and the client's real -> refresh rate instead of a hardcoded 60 Hz. - -If you want to stream the **desktop** from an LMDE box, the answer today is to log into a GNOME or -Sway session instead — Debian 13 ships GNOME 48.7 and sway 1.10, both above the -[floors](/docs/requirements). (Debian 13's KDE is KWin **6.3.6**, below the 6.5.6 floor, so Plasma -is not an option there yet.) - -### Linux Mint 22.x cannot host yet - -**On Linux Mint 22.x — the current mainstream release, and every version until Mint 23 in December -2026 — there is no working configuration.** `punktfunk-host` will install, which makes this easy to -miss, but nothing on the box can produce a stream: - -- **Cinnamon** cannot host a virtual display (above). -- **gamescope is not available and cannot be made available.** Ubuntu 24.04 packages no gamescope, - and the patched `punktfunk-gamescope` cannot run there either: 24.04 is short of what the build - needs on *five* libraries — wayland 1.22.0 (needs ≥ 1.23.1), libinput 1.25 (≥ 1.26), libavif - 1.0.4 (≥ 1.2.1), pixman 0.42 (≥ 0.44), and no `libdisplay-info2` or `libxcb-errors0` at all. -- **Switching desktop does not rescue it.** Ubuntu 24.04 ships KWin **5.27** (floor 6.5.6) and GNOME - Shell **46** (floor 48). Only `sway` 1.9 is even a candidate, and that means giving up Cinnamon. - -If you want to run a host on Mint hardware today, use **LMDE 7** — it is the same desktop on a -Debian 13 base, where gamescope works. Otherwise wait for **Mint 23** (Ubuntu 26.04 base), where -both the patched gamescope and the newer compositors are available. - -## Configure your desktop - -How the host creates its virtual display and injects input depends on your desktop, not your distro: - -- [KDE Plasma (KWin)](/docs/kde) -- [GNOME (Mutter)](/docs/gnome) -- [Steam / gamescope](/docs/gamescope) -- [Hyprland](/docs/hyprland) -- [Sway / wlroots](/docs/sway) - -Then bring up [The Web Console](/docs/web-console) to arm pairing and connect your first -[client](/docs/clients). To run the host at boot — including fully **headless** — see -[Running as a Service](/docs/running-as-a-service). - -## Next steps - -- **Keep it current** — [Updating the Host](/docs/updating). -- **Remove it again** — [Uninstalling](/docs/uninstall). -- **Something not working?** — [Troubleshooting](/docs/troubleshooting). -- **Build from source** (Debian 12, or tracking `main`) — the - [Ubuntu appendix](/docs/ubuntu#appendix--build-from-source) applies unchanged; Debian 13's - `libavcodec-dev` is new enough to build against. diff --git a/docs-site/content/docs/debian.mdx b/docs-site/content/docs/debian.mdx new file mode 100644 index 000000000..618638d43 --- /dev/null +++ b/docs-site/content/docs/debian.mdx @@ -0,0 +1,77 @@ +--- +title: Debian +description: Install the Punktfunk host on Debian 13 or newer with apt — including LMDE 7. +--- + +For **Debian 13 ("trixie") or newer**, and **LMDE 7**. Debian 12 is too old (glibc 2.36, the +package needs 2.39) — [build from source](/docs/build-from-source) there, or upgrade. + +> **Desktop matters more than distro here.** Debian 13's GNOME (48) and Sway (1.10) can host; its +> KDE (KWin 6.3) is below the floor, and a Cinnamon desktop (Linux Mint, LMDE) can only host through +> gamescope. [What each desktop can do, and what Linux Mint 22 can't](/docs/requirements#cinnamon-linux-mint-and-lmde). + +## 1. GPU driver + +- **NVIDIA:** the driver lives in `non-free`, which a default install doesn't enable. Enable it, + install, reboot: + + ```sh + sudo sed -i 's/^Components: .*/Components: main contrib non-free non-free-firmware/' /etc/apt/sources.list.d/debian.sources + sudo apt update && sudo apt install nvidia-driver firmware-misc-nonfree + ``` + + If `nvidia-smi` can't talk to the driver afterwards, Secure Boot is in the way — see + [Troubleshooting](/docs/troubleshooting#nvidia-smi-says-it-cant-communicate-with-the-driver). +- **AMD / Intel:** nothing to install — Mesa's Vulkan and VAAPI drivers are already there (Intel's + `intel-media-va-driver` is in `non-free`; the host package recommends it). + +## 2. Install the host + +The repo is public and signed — the `debian` in the URL is the package format, it's the same repo +Ubuntu uses. The browser console, `punktfunk-web`, comes along automatically: + + + +Updates ride along with `sudo apt upgrade`; restart the host afterwards +(`systemctl --user restart punktfunk-host`) — or let the [console do it](/docs/updating). + +## 3. Let it use your controllers + +Join the `input` group (virtual gamepads go through `/dev/uinput`), then **log out and back in**: + +```sh +sudo usermod -aG input "$USER" +``` + +Want the **virtual Steam Deck controller** (paddles, trackpads, gyro), or does this PC boot into Steam +**Gaming Mode**? Also join the `punktfunk` group — +[what it gates and why it's separate](/docs/gamescope#nobara-and-other-autologin-display-managers). + +## 4. Start it + +From a terminal inside your desktop session: + +```sh +systemctl --user enable --now punktfunk-host punktfunk-web +``` + +Debian ships no firewall enabled, so there is nothing to open. If you run one, the package installed +profiles for ufw and firewalld — [Ports & firewall](/docs/ports). + +**Cinnamon (LMDE 7):** the host can't stream the Cinnamon desktop itself; it streams games through +gamescope instead. Add `sudo apt install punktfunk-gamescope` and put +`PUNKTFUNK_COMPOSITOR=gamescope` in `~/.config/punktfunk/host.env`, then restart the host — +[Steam / gamescope](/docs/gamescope) takes it from there. + +**That's the install.** Continue with the [Quick Start from step 3](/docs/quickstart#3-open-the-web-console) +— open the console, pair a client, stream. + +## When you want more + +- `punktfunk-host detect-conflicts` tells you if Sunshine or Apollo is also running; + [Troubleshooting](/docs/troubleshooting) starts from the symptom. +- Your desktop's particulars — [GNOME](/docs/gnome), [Sway](/docs/sway), [gamescope](/docs/gamescope). +- Stream with nobody logged in — [Running as a service](/docs/running-as-a-service). +- The Linux **client** isn't packaged for Debian (it needs a newer glibc) — use the + [Flatpak](/docs/install-client#linux-desktop-flatpak) to stream *to* a Debian box. +- Track `main` instead of releases — [Release channels](/docs/channels). diff --git a/docs-site/content/docs/fedora.md b/docs-site/content/docs/fedora.md deleted file mode 100644 index 5d0d4e1ca..000000000 --- a/docs-site/content/docs/fedora.md +++ /dev/null @@ -1,240 +0,0 @@ ---- -title: Fedora -description: Install the Punktfunk host on Fedora from the RPM registry. ---- - -Install a Punktfunk host on **Fedora** from the self-hosted RPM registry. The host installs as an -RPM-managed systemd **`--user`** service and updates with `dnf upgrade` like the rest of your -system — no building required. It works with either **KDE Plasma** or **GNOME**; the -desktop-specific setup (which compositor captures, headless sessions, quirks) lives on the -[desktop configure pages](#5-configure-your-desktop). Host encode is **NVENC on NVIDIA**; on -**AMD/Intel** HEVC and AV1 go through **Vulkan Video**, with **VAAPI** for H.264 and as the fallback -(`PUNKTFUNK_ENCODER=auto` picks per GPU). - -> New here? Read [Security & Safe Use](/docs/security) first — a streaming host is remote control of -> the machine, so keep it on a trusted LAN or VPN and require pairing. - -Install is two parts: **GPU driver** → **host RPM**. Then open the firewall and point the host at -your desktop from the [desktop configure pages](#5-configure-your-desktop). - -## 1. NVIDIA driver (RPM Fusion akmod) - -Enable RPM Fusion (free + nonfree), then install the akmod driver + CUDA. RPM Fusion's nonfree -NVIDIA repo is sometimes pre-enabled on some spins; the full free/nonfree repos below are still -needed (they carry the NVENC ffmpeg in the next step). - -```sh -sudo dnf install \ - https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \ - https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm -sudo dnf install akmod-nvidia xorg-x11-drv-nvidia-cuda -``` - -**NVENC ffmpeg.** Fedora ships `ffmpeg-free`, which is built **without** NVENC — the host can't -encode with it. Swap to RPM Fusion's ffmpeg: - -```sh -sudo dnf install --allowerasing ffmpeg ffmpeg-libs -ffmpeg -hide_banner -encoders | grep nvenc # expect hevc_nvenc / av1_nvenc / h264_nvenc -``` - -**Secure Boot.** If `mokutil --sb-state` says *enabled*, the akmod module is signed with a -locally-generated key that must be enrolled once: - -```sh -sudo akmods --force # build + sign the module -sudo mokutil --import /etc/pki/akmods/certs/public_key.der # set a one-time password -sudo reboot -``` - -On the next boot a blue **MOK Manager** screen appears **on the machine's console** (not over -SSH): *Enroll MOK → Continue → Yes → (the password) → Reboot*. Then verify: - -```sh -nvidia-smi # driver loads -ffmpeg -hide_banner -encoders | grep nvenc -``` - -(Or disable Secure Boot in firmware to skip the MOK step — fine for a dedicated test box.) - -**AMD / Intel.** No akmod needed — the Mesa stack carries both encode paths. HEVC and AV1 go through -**Vulkan Video** by default (the Mesa Vulkan driver, present on any normal Fedora desktop), and -**VAAPI** is the H.264 path and the fallback. Install the freeworld VAAPI drivers for full codec -support (`mesa-va-drivers-freeworld` for AMD from RPM Fusion, `intel-media-driver` for Intel); on a -desktop these are usually already present. - -## 2. Install the host (RPM) - -The host is published to the self-hosted Gitea RPM registry, in a per-release group (an RPM is -soname-coupled to its base, so each Fedora release gets its own group). Pick the one matching your -release — `rpm -E %fedora` prints the number you're on: - -- **Fedora 44** → `fedora-44` -- **Fedora 43** → `bazzite` — that group is a plain Fedora 43 build of the same `punktfunk` package, - so it's the right one for a regular Fedora 43 box too - -Put your group in the `baseurl` below, then add the repo and install: - -```sh -sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO' -[punktfunk] -name=punktfunk -# The group for your release: fedora-44 on Fedora 44, bazzite on Fedora 43. -baseurl=https://git.unom.io/api/packages/unom/rpm/fedora-44 -enabled=1 -# Packages are GPG-signed (gpgcheck=1) AND the repo metadata is Gitea-signed (repo_gpgcheck=1). -gpgcheck=1 -repo_gpgcheck=1 -gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key - https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk -REPO - -sudo dnf install punktfunk -sudo usermod -aG input "$USER" # /dev/uinput access for virtual gamepads (re-login to apply) -``` - -Also join `punktfunk` if **either** applies — you want the **virtual Steam Deck controller** -(paddles, trackpads, gyro — it reaches games as a real USB pad, which is why Steam Input adopts -it), or this box autologins into Steam **Gaming Mode** (Nobara and friends) and you want the host -to take that session over at the client's resolution: - -```sh -sudo usermod -aG punktfunk "$USER" # usbip/vhci + display-manager takeover (re-login to apply) -``` - -That is a second group on purpose: it grants write access to the usbip `attach` file, which -materialises an arbitrary emulated USB device, so it stays off the `input` group everyone is -routinely told to join. Join it only on a machine you trust. Skip it on a plain desktop host and -the pad simply arrives as an ordinary Xbox 360 controller; skip it on a Gaming Mode box and the -takeover silently degrades to mirroring the box's own screen — see -[gamescope](/docs/gamescope#nobara-and-other-autologin-display-managers). - -Updates later are just `sudo dnf upgrade punktfunk`, followed by -`systemctl --user restart punktfunk-host` so the running host picks up the new binary. The package -ships the systemd user units, the udev rule, the UDP socket-buffer sysctl tuning, and example -configs. - -The group you picked above is the **stable** channel. For the latest `main` build, point `baseurl` at -`fedora-44-canary` (or `bazzite-canary`) instead — see [Release Channels](/docs/channels). Updating -in general, including the opt-in one-click button in the web console, is covered in -[Updating the Host](/docs/updating). - -> `fedora-44` and `bazzite` are the only stable groups published, so on Fedora 42 or older — or on a -> release newer than 44 — there's nothing matching yet. Build one with the same toolchain CI uses — -> `docker build --build-arg FEDORA_VERSION=NN -f ci/fedora-rpm.Dockerfile -t pf-rpm ci` then run -> `packaging/rpm/build-rpm.sh` inside it — or build from source (appendix below). - -## 3. Check it installed - -Before moving on, confirm the binary is there and nothing else is competing for the same job: - -```sh -punktfunk-host --version # the binary is on PATH -punktfunk-host detect-conflicts # exits 1 if Sunshine/Apollo is also installed -``` - -If `detect-conflicts` reports another streaming host, remove it before going further — two hosts on -one machine is the most common reason a clean install never streams. See -[Troubleshooting → another streaming host is installed](/docs/troubleshooting#another-streaming-host-sunshine-apollo--is-installed). - -Once you've enabled the service on your desktop page below, these are how you watch it: - -```sh -systemctl --user status punktfunk-host # active -journalctl --user -u punktfunk-host -f # watch a client connect -``` - -## 4. Open the firewall - -Fedora runs **firewalld** by default and the package never edits your firewall, so the host stays -unreachable until you allow it. The RPM installs the service definitions — enable them once. - -The packaged unit runs `serve --gamestream` — the RPM installs it as it ships and only rewrites the -binary path — so a host you enabled with `systemctl --user enable --now punktfunk-host` serves -**both** the native `punktfunk/1` plane and stock [Moonlight](/docs/moonlight) clients, and needs -**both** services: - -```sh -sudo firewall-cmd --reload # load the installed definitions -sudo firewall-cmd --permanent --add-service=punktfunk-native -sudo firewall-cmd --reload -``` - -Enabled **GameStream/Moonlight compat** (`PUNKTFUNK_GAMESTREAM=1` in `host.env` — see -[What the unit starts](/docs/running-as-a-service#what-the-unit-starts))? Then also: - -```sh -sudo firewall-cmd --permanent --add-service=punktfunk-gamestream && sudo firewall-cmd --reload -``` - -`punktfunk-native` opens UDP 9777 (QUIC control), UDP 5353 (mDNS discovery) and TCP 47990 (the -mgmt/library API — HTTPS + mTLS, read-only off loopback). `punktfunk-gamestream` opens the fixed -Moonlight ports — TCP 47984, 47989 and 48010, UDP 47998–48000 — plus the same mDNS. The media -**data plane** uses an ephemeral UDP port the client opens with a hole-punch, so there is nothing -fixed to open for video. - -And if you want the web console reachable from another device, open **TCP 47992**: - -```sh -sudo firewall-cmd --permanent --add-service=punktfunk-web && sudo firewall-cmd --reload -``` - -## 5. Configure your desktop - -How the host creates its virtual display and injects input depends on your desktop, not your distro. -Continue on the page for the desktop you run — it covers your `host.env`, any compositor quirks, and -starting the host: - -- [KDE Plasma (KWin)](/docs/kde) -- [GNOME (Mutter)](/docs/gnome) -- [Steam / gamescope](/docs/gamescope) -- [Hyprland](/docs/hyprland) -- [Sway / wlroots](/docs/sway) - -Enable the browser management console (status, paired devices, arm pairing) — see -[Web Console](/docs/web-console). - -For a headless KWin appliance that streams at boot with no graphical login, see -[KDE → Headless session](/docs/kde#headless-session). - -Full config reference: [Configuration](/docs/configuration). Service model: -[Running as a Service](/docs/running-as-a-service). - -## 6. Connect a client - -From any [client](/docs/clients), `--discover` finds the host on the LAN. On first connect, complete -the **PIN pairing** — arm it from the host's [web console](/docs/web-console#arm-pairing), which -displays a 4-digit PIN to type into the client. See [Clients](/docs/clients) and -[Pairing](/docs/pairing). - -## Next steps - -- **Keep it current** — [Updating the Host](/docs/updating). -- **Remove it again** — [Uninstalling](/docs/uninstall). -- **Something not working?** — [Troubleshooting](/docs/troubleshooting). - -## Appendix — build from source - -If there's no RPM for your Fedora release and you don't want to build one, compile the host directly -(no clean updates / no packaged units — you wire those up by hand): - -```sh -sudo dnf install gcc gcc-c++ make cmake clang clang-devel nasm git pkgconf-pkg-config \ - pipewire-devel wayland-devel wayland-protocols-devel libxkbcommon-devel opus-devel \ - libdrm-devel mesa-libgbm-devel mesa-libGL-devel mesa-libEGL-devel mesa-libGLES-devel libva-devel \ - ffmpeg-devel libei-devel -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -git clone https://git.unom.io/unom/punktfunk.git && cd punktfunk -cargo build --release --locked \ - --features punktfunk-host/nvenc,punktfunk-host/vulkan-encode \ - -p punktfunk-host -``` - -`mesa-libGL-devel` isn't optional — the zero-copy GPU path links `libGL`, and without it the build -fails at the link step with `cannot find -lGL`. The two `--features` are what the packaged builds -use: leave them off and the host has no direct NVENC (NVIDIA) and no Vulkan Video encode -(AMD/Intel), and quietly falls back to the slower libav backends. - -Then write `~/.config/punktfunk/host.env` (as in `/usr/share/punktfunk/host.env.kde`, but the host -binary is `target/release/punktfunk-host`) and run it inside your desktop session — for a headless -KWin appliance see [KDE → Headless session](/docs/kde#headless-session). diff --git a/docs-site/content/docs/fedora.mdx b/docs-site/content/docs/fedora.mdx new file mode 100644 index 000000000..cd237d33c --- /dev/null +++ b/docs-site/content/docs/fedora.mdx @@ -0,0 +1,86 @@ +--- +title: Fedora +description: Install the Punktfunk host on Fedora 43 or newer from the RPM repo — four steps. +--- + +For **Fedora 43 or newer** (Workstation or KDE). Bazzite and other Fedora Atomic spins have +[their own page](/docs/bazzite). + +## 1. GPU driver + +- **NVIDIA:** the driver and an NVENC-capable FFmpeg both come from **RPM Fusion** — Fedora's own + `ffmpeg-free` has no NVENC and the host cannot encode with it. Enable RPM Fusion, install, reboot: + + ```sh + sudo dnf install \ + https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm \ + https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm + sudo dnf install akmod-nvidia xorg-x11-drv-nvidia-cuda + sudo dnf install --allowerasing ffmpeg ffmpeg-libs + ``` + + With **Secure Boot** on, the module won't load until you enrol its key — `nvidia-smi` will say it + can't talk to the driver; the fix is in + [Troubleshooting](/docs/troubleshooting#nvidia-smi-says-it-cant-communicate-with-the-driver). +- **AMD / Intel:** nothing to install — Mesa carries Vulkan Video and VAAPI. For full codec coverage + add RPM Fusion's `mesa-va-drivers-freeworld` (AMD) or `intel-media-driver` (Intel). + +## 2. Install the host + +The RPM repo has one group per Fedora release: **`fedora-44`** on Fedora 44, **`bazzite`** on +Fedora 43 (it's a plain Fedora 43 build of the same package). `rpm -E %fedora` prints your number — +set `baseurl` to match, then install. The browser console, `punktfunk-web`, comes along +automatically: + + + +Updates ride along with `sudo dnf upgrade`; restart the host afterwards +(`systemctl --user restart punktfunk-host`) — or let the [console do it](/docs/updating). + +## 3. Let it use your controllers + +Join the `input` group (virtual gamepads go through `/dev/uinput`), then **log out and back in**: + +```sh +sudo usermod -aG input "$USER" +``` + +Want the **virtual Steam Deck controller** (paddles, trackpads, gyro), or does this PC boot into Steam +**Gaming Mode** (Nobara and friends)? Also join the `punktfunk` group — +[what it gates and why it's separate](/docs/gamescope#nobara-and-other-autologin-display-managers). + +## 4. Start it, open the firewall + +From a terminal inside your desktop session: + +```sh +systemctl --user enable --now punktfunk-host punktfunk-web +``` + +Fedora runs **firewalld**, and a package never opens ports for you — so until you allow it, no client +can reach the host. The RPM installed the service definitions; enable them once: + +```sh +sudo firewall-cmd --reload # load the definitions the package installed +sudo firewall-cmd --permanent --add-service=punktfunk-native --add-service=punktfunk-web +sudo firewall-cmd --reload +``` + +(`punktfunk-web` is only needed to reach the console from another device. Turning on Moonlight +compat later? Add `punktfunk-gamestream` too — [Ports & firewall](/docs/ports).) + +**That's the install.** Continue with the [Quick Start from step 3](/docs/quickstart#3-open-the-web-console) +— open the console, pair a client, stream. + +## When you want more + +- **No video on NVIDIA?** `ffmpeg-libs` is only a *recommended* dependency — without RPM Fusion's + build, NVENC fails at runtime. Re-run step 1. + [More in Troubleshooting](/docs/troubleshooting#no-video-on-fedora-nvenc-fails-ffmpeg-libs-is-missing). +- `punktfunk-host detect-conflicts` tells you if Sunshine or Apollo is also running; + [Troubleshooting](/docs/troubleshooting) starts from the symptom. +- Your desktop's particulars — [KDE](/docs/kde), [GNOME](/docs/gnome), [gamescope](/docs/gamescope). +- Stream with nobody logged in, or a headless KDE appliance — + [Running as a service](/docs/running-as-a-service), [KDE → Headless session](/docs/kde#headless-session). +- Track `main` instead of releases (`fedora-44-canary` / `bazzite-canary`) — + [Release channels](/docs/channels). No group for your release — [Build from source](/docs/build-from-source). diff --git a/docs-site/content/docs/game-library.md b/docs-site/content/docs/game-library.md index 785f6ab0a..7e508a445 100644 --- a/docs-site/content/docs/game-library.md +++ b/docs-site/content/docs/game-library.md @@ -3,23 +3,22 @@ title: Your game library description: How Punktfunk finds your installed games, how to add one by hand, and how to launch a title from a client, from Moonlight, or from the command line. --- -Every Punktfunk host keeps one **game library** — a single list of titles that every surface reads -from. It has two sources: the [plugins](/docs/plugins) you install for the launchers you actually -use, and entries you add by hand in the [web console](/docs/web-console). +A Punktfunk host keeps one **game library** that every surface reads from. It has two sources: the +[plugins](/docs/plugins) you install for the launchers you use, and entries you add by hand in the +[web console](/docs/web-console). -Whichever source a title came from, it looks the same everywhere: a poster, a name, and a stable id -like `steam:570` or `custom:9f2a1c…`. Pick one on a client and the host launches it into the stream. +Whatever its source, a title looks the same everywhere: a poster, a name, and a stable id like +`steam:570` or `custom:9f2a1c…`. Pick one on a client and the host launches it into the stream. ## Where your games come from **Install a plugin for each launcher you want in the library.** A fresh host holds no games until -you do — go to the console's **Library** page, open **Game sources**, and install the ones you use. -It takes a click each. +you do — on the console's **Library** page, open **Game sources** and install the ones you use, a +click each. -Each plugin reads that launcher's **own local files** on the host. There are no accounts to connect -and no API keys — nothing leaves the machine to build the list. A launcher that isn't installed -contributes nothing, so installing a plugin you turn out not to need costs you an empty source and -nothing else. +Each plugin reads that launcher's **own local files** on the host — no accounts to connect, no API +keys, nothing leaves the machine to build the list. A launcher that isn't installed contributes +nothing, so an unneeded plugin costs you an empty source and nothing else. | Plugin | Linux host | Windows host | What it reads | |---|---|---|---| @@ -31,31 +30,30 @@ nothing else. | **Playnite** | — | ✅ | Your Playnite library, whichever stores it aggregates | | **ROM Manager** | ✅ | ✅ | Your ROM folders, matched against a metadata source | -> Through v0.27.x six of these scanners were built into the host itself and ran whether you wanted -> them or not. From **v0.28.0** they are plugins like any other. If you were already running the -> plugin for a launcher, nothing changes — the ids, art and app ids are identical by design. If you -> were relying on the built-in scanner, install that launcher's plugin once and your grid comes back -> exactly as it was, including anything you had switched off or hidden. +> Through v0.27.x six of these scanners were built into the host and always on; from **v0.28.0** +> they are plugins like any other. Already running the plugin for a launcher? Nothing changes — ids, +> art and app ids are identical by design. Relied on the built-in scanner? Install that launcher's +> plugin once and your grid comes back exactly as it was, including anything you had switched off or +> hidden. -A few things are deliberately left out. Steam's tooling — Proton, the Steam Linux Runtimes, Steamworks -Common Redistributables, SteamVR — is filtered out, so your grid holds games rather than plumbing. A -non-Steam shortcut you have hidden inside Steam stays hidden here too. +Deliberately left out: Steam's tooling — Proton, the Steam Linux Runtimes, Steamworks Common +Redistributables, SteamVR — so your grid holds games rather than plumbing, and any non-Steam +shortcut you have hidden inside Steam. -To see exactly what the host resolved, run [`punktfunk-host library`](/docs/host-cli) on the host: it -prints the whole library as JSON. That answers "does the host see my games?" without involving a -client. +[`punktfunk-host library`](/docs/host-cli), run on the host, prints the whole library as JSON — +"does the host see my games?" without involving a client. ## Turning a source off -The console's **Library** page has a **Game sources** card with one chip per source this host has. -A chip is highlighted when that source is contributing titles; click it to turn the source off. +On the console's **Library** page, the **Game sources** card shows one chip per source this host +has; a highlighted chip is contributing titles, and clicking it turns the source off. -Turning a source off hides its titles from **everywhere at once** — the console grid, every native -client, the Moonlight app list, and launching. Nothing is deleted and the change needs no restart: -the plugin keeps its titles, they simply stop being shown, and turning the source back on brings -them straight back on the next read. (To remove a source's titles for good, uninstall its plugin.) +That hides its titles **everywhere at once** — the console grid, every native client, the Moonlight +app list, and launching — with nothing deleted and no restart: the plugin keeps its titles, and +turning the source back on brings them straight back on the next read. (To remove a source's titles +for good, uninstall its plugin.) -Your hand-added entries are not a source and have no chip — they are always shown. +Hand-added entries are not a source and have no chip — they are always shown. The choice is stored per host in `library-scanners.json`, next to the rest of the host config (`~/.config/punktfunk/` on Linux, `%ProgramData%\punktfunk\` on Windows). Only the sources you turned @@ -64,10 +62,10 @@ The choice is stored per host in `library-scanners.json`, next to the rest of th ## Adding a game by hand Anything your launchers don't know about — an emulator, a ROM, a DRM-free build, a tool you want on -the couch — goes in by hand. On the console's **Library** page, click **Add custom game**. +the couch — goes in by hand: on the console's **Library** page, click **Add custom game**. -**Title** is the only required field. **Launch command** is the command the host runs for this title; -leave it empty and the entry is a poster the host has nothing to launch from. +**Title** is the only required field. **Launch command** is what the host runs for this title; leave +it empty and the entry is a poster with nothing to launch. Under **Details (optional)** a title can carry: @@ -90,8 +88,8 @@ runs, so it is locked down to the host user (0600 on Linux, a SYSTEM+Administrat treat what you type there as operator-level configuration. > **Editing replaces the whole entry.** The console form re-sends every field it knows about, so -> nothing you can see is lost. Fields the form has no input for — prep/undo steps in particular — are -> **cleared** when you save an entry through the form. +> nothing you can see is lost — but fields the form has no input for (prep/undo steps in particular) +> are **cleared** when you save through the form. ### Cover art @@ -105,9 +103,9 @@ host can see is fine. A plain Linux path like `/home/me/cover.jpg` is **not** re Scanned titles need no art. Steam covers come from your local Steam cache, falling back to Steam's public CDN. On a Windows host, GOG and Xbox covers are the one thing the library looks up over the -network: a background pass asks GOG's and Microsoft's public catalogs for them when the host starts, -and repeats every five minutes for any title it hasn't resolved yet. Neither needs an account or a -key, the answer is cached on the host, and a lookup that fails just leaves a title-only tile. +network: a background pass asks GOG's and Microsoft's public catalogs when the host starts and +repeats every five minutes for any title still unresolved. Neither needs an account or a key, the +answer is cached on the host, and a failed lookup just leaves a title-only tile. ## Games from a plugin @@ -116,39 +114,40 @@ Manager and Playnite plugins get your collection into the grid, box art and all. A library plugin can also publish a **launcher tile** — an entry that opens Steam Big Picture, Heroic, Lutris or Playnite itself rather than a game, so you can install or fix something from the -couch. Clients group those into their own row above your titles, and each one draws its launcher's -logo. A launcher tile you don't want is a switch in that plugin's settings. +couch. Clients group those into their own row above your titles, each drawing its launcher's logo. +A launcher tile you don't want is a switch in that plugin's settings. -Entries a plugin owns are read-only to you. The host refuses a hand edit or a delete of one, because +Entries a plugin owns are read-only to you. The host refuses a hand edit or delete of one, because the next sync would overwrite it anyway — change the title at its source and let the plugin sync again. Only the plugin can remove its own entries, and it removes every one of them at once. Your hand-added entries are never touched by a sync. The console grid can't tell you which entries those are: a plugin's titles carry the same **Custom** -badge as your own and still show **Edit** and **Delete** on hover. The form and the delete -confirmation open as usual, but the host refuses the change and the entry stays exactly as it was. +badge as your own and still show **Edit** and **Delete** on hover — the form and the delete +confirmation open as usual, but the host refuses the change and the entry stays as it was. ## Launching a game -Whatever the surface, the client sends only an **id**. The host looks that id up in its own library -and runs what it already knows about the title, so a client can never hand the host a command to run. +Whatever the surface, the client sends only an **id**. The host looks it up in its own library and +runs what it already knows about the title, so a client can never hand the host a command to run. -- **Native clients** — the browser needs a **paired** host, and that is the only condition: a paired - host's card offers **Browse library…** (**Browse Library…** on Apple) with nothing to switch on - first. Pick a title and the stream starts with the host launching it. The Apple and Android apps +- **Native clients** — a **paired** host's card offers **Browse library…** (**Browse Library…** on + Apple) with nothing to switch on first; pairing is the only condition. Pick a title and the stream + starts with the host launching it. The Apple and Android apps keep a **Show game library** switch, on by default, for turning it off. See [Client settings](/docs/client-settings). - **Android** — the library lives only in the controller-optimized home, which a TV always uses and a phone or tablet switches to when a controller is connected. Press **Y** on a saved host, or open its options and choose **Library**. - **Steam Deck (Decky)** — the panel is a launcher and browses nothing itself: tap **Open - Punktfunk**, which opens the client's console home, and a paired host's **Library** button is - right there — full-screen covers, gamepad-navigable, and a press starts the stream with the title - launching. See [Steam Deck](/docs/steam-deck). -- **Moonlight** — when the host runs with `--gamestream`, your library appears in Moonlight's app - list beside `Desktop`, with covers served by the host. A title keeps the same app id across host - restarts, so Moonlight's cached tiles stay correct. Titles with no launch recipe are left out. - See [Moonlight](/docs/moonlight). + Punktfunk**, which opens the client's console home, where a paired host's **Library** button is — + full-screen covers, gamepad-navigable, and a press starts the stream with the title launching. See + [Steam Deck](/docs/steam-deck). +- **Moonlight** — when the host runs GameStream compat (opt-in: `PUNKTFUNK_GAMESTREAM=1` in + `host.env`, or `serve --gamestream` — see [Moonlight](/docs/moonlight)), your library appears in + Moonlight's app list beside `Desktop`, with covers served by the host. A title keeps the same app id + across host restarts, so Moonlight's cached tiles stay correct. Titles with no launch recipe are + left out. - **A link** — a [`punktfunk://` link](/docs/profiles-and-links) carries the id in a `launch=` parameter, so a desktop shortcut, a browser bookmark or a home-automation rule starts the stream with the title already launching: `punktfunk://connect/couch-pc?launch=steam:570`. On the Apple diff --git a/docs-site/content/docs/hdr.md b/docs-site/content/docs/hdr.md index 9b188d24f..90bb6babb 100644 --- a/docs-site/content/docs/hdr.md +++ b/docs-site/content/docs/hdr.md @@ -4,23 +4,23 @@ description: How an HDR10 stream is decided end to end — the four things that --- An HDR session carries a **10-bit BT.2020 PQ (HDR10)** picture from the host's display to your -screen. It is on by default wherever it works, and a session that can't be HDR streams 8-bit BT.709 -SDR instead. Which one you get is decided **before the first frame**: the host resolves every gate -below, then tells the client what it is really going to send. Nothing on this page takes effect -mid-stream, so reconnect after changing any of it. +screen. It is on by default wherever it works; otherwise the session streams 8-bit BT.709 SDR. The +host decides **before the first frame** — it resolves every gate below and tells the client what it +will really send — so nothing on this page takes effect mid-stream; reconnect after changing any of +it. ## The chain -Four things must all be true. If your stream is SDR when you expected HDR, one of these is why. +Four things must all be true. If you got SDR when you expected HDR, one of these is why. -1. **The source.** What the host captures must hand it 10-bit PQ pixels. This is the link that fails - most often, and it is entirely a host-side question — see [Per host](#per-host). -2. **The encoder.** The host GPU must encode 10-bit for the codec the session picked. The host - probes this by opening a tiny real encoder once per GPU and codec, and believes the answer. - (PyroWave skips the probe — it has its own rule, below.) +1. **The source.** What the host captures must hand it 10-bit PQ pixels. This link fails most often + and is entirely host-side — see [Per host](#per-host). +2. **The encoder.** The host GPU must encode 10-bit for the session's codec. The host probes by + opening a tiny real encoder once per GPU and codec, and believes the answer. (PyroWave skips the + probe — its own rule is below.) 3. **The codec.** Only HEVC, AV1 and PyroWave have a 10-bit path — see [Codec rules](#codec-rules). -4. **The client.** Your client must advertise 10-bit and HDR (its **HDR** setting), and be able to - present or tone-map PQ. +4. **The client.** It must advertise 10-bit and HDR (its **HDR** setting) and be able to present or + tone-map PQ. The host allows 10-bit by default (`PUNKTFUNK_10BIT`). It only ever *allows* — the client's setting is the real per-session switch. @@ -29,78 +29,75 @@ is the real per-session switch. ### Windows -The Windows host creates a virtual display for your session and **turns HDR on for that display -itself** when the session negotiated 10-bit. You do not have to enable "Use HDR" in Windows Settings -first: Punktfunk enables advanced colour at capture open, waits for it to settle, then composes in -FP16 and encodes P010 BT.2020 PQ. The reverse is enforced too — a session that negotiated **SDR** -forces advanced colour **off** on that display, so a client that asked for 8-bit is never handed PQ. -If enabling it fails, the host logs a loud error and encodes 8-bit anyway: the client was already -told HDR, so that is the one place a Punktfunk label can outrun the picture. The log says so. - -Two details worth knowing: +The Windows host **turns HDR on for the session's virtual display itself** when 10-bit was +negotiated — you don't have to enable "Use HDR" in Windows Settings. It enables advanced colour at +capture open, waits for it to settle, then composes in FP16 and encodes P010 BT.2020 PQ. The +reverse holds too: an **SDR** session forces advanced colour **off** on that display, so a client +that asked for 8-bit is never handed PQ. If enabling it fails, the host logs a loud error and encodes +8-bit anyway — the client was already told HDR, so that is the one place a Punktfunk label can +outrun the picture. - **HDR and 4:4:4 compose on Windows, not on Linux.** A **Windows** host carries both: the capture path writes full-resolution 10-bit chroma and NVENC encodes HEVC Main 4:4:4 10, so - [full chroma](/docs/client-settings) costs you nothing on an HDR desktop. - [PyroWave](/docs/pyrowave) does the same there, in 16-bit planes. On **Linux** the 4:4:4 route is - 8-bit, so a session that negotiates both resolves back down to SDR — full chroma wins. AV1 never - carries 4:4:4 anywhere: Range Extensions are HEVC-only. + [full chroma](/docs/client-settings) costs nothing on an HDR desktop; [PyroWave](/docs/pyrowave) + does the same there, in 16-bit planes. On **Linux** the 4:4:4 route is 8-bit, so a session that + negotiates both resolves back down to SDR — full chroma wins. AV1 never carries 4:4:4 anywhere: + Range Extensions are HEVC-only. - **Vulkan games need the bundled layer.** NVIDIA and AMD Vulkan drivers refuse to advertise any HDR colour space for a surface on an indirect (virtual) display, so Vulkan games decide the device - "does not support HDR" — even though the driver happily presents an HDR swapchain there. The host + "does not support HDR" — though the driver happily presents an HDR swapchain there. The host installer ships an implicit Vulkan layer, `VK_LAYER_PUNKTFUNK_hdr_inject`, that adds those formats back (installer task **Install the HDR Vulkan layer**, ticked by default). It self-gates on the - monitor's live advanced-colour state, so it does nothing on an SDR session, and it already skips a - built-in list of kernel-anti-cheat titles. `DISABLE_PF_VKHDR=1` in a game's environment switches it - off for that process; `PF_VKHDR_EXCLUDE=foo.exe,bar.exe` skips further executables by name. - D3D11/D3D12 games need none of this. + monitor's live advanced-colour state, so it does nothing on an SDR session, and it skips a built-in + list of kernel-anti-cheat titles. `DISABLE_PF_VKHDR=1` in a game's environment switches it off for + that process; `PF_VKHDR_EXCLUDE=foo.exe,bar.exe` skips further executables by name. D3D11/D3D12 + games need none of this. ### Linux + gamescope -A stock gamescope tone-maps its composite down to 8 bits before handing it over, so its capture -output is SDR no matter what the game rendered. Real HDR needs **`punktfunk-gamescope`**, a build -carrying a patch that adds the 10-bit PQ formats to its PipeWire node. It installs beside your system -gamescope rather than replacing it; [HDR on gamescope](/docs/gamescope#hdr-on-gamescope) has the -package for each distro. +Stock gamescope tone-maps its composite down to 8 bits before handing it over, so its capture output +is SDR whatever the game rendered. Real HDR needs **`punktfunk-gamescope`**, a build carrying a patch +that adds the 10-bit PQ formats to its PipeWire node. It installs beside your system gamescope rather +than replacing it; [HDR on gamescope](/docs/gamescope#hdr-on-gamescope) has the package for each +distro. -The host settles two facts before spawning anything: the gamescope binary it will run carries the +Before spawning anything the host settles two facts: the gamescope binary it will run carries the patch (its `--version` banner contains `+pfhdr`), and this host is the one **starting** the session rather than attaching to a node someone else started. **Attach mode is the trap.** The patched build only reaches sessions the host spawns itself — managed, `PUNKTFUNK_GAMESCOPE_SESSION`, or a bare spawn. A session started by your display manager -runs the distro's own gamescope, which offers neither the 10-bit formats nor the in-node cursor. The -host cannot tell that from the outside unless you pinned `PUNKTFUNK_GAMESCOPE_NODE`: with +runs the distro's own gamescope, which offers neither the 10-bit formats nor the in-node cursor, and +the host cannot tell that from the outside unless you pinned `PUNKTFUNK_GAMESCOPE_NODE`. With `PUNKTFUNK_GAMESCOPE_ATTACH=1` and the patched build installed it reads the binary, believes HDR is -available, and offers it. The attached session can't answer that negotiation, so the connect fails +available, and offers it; the attached session can't answer that negotiation, so the connect fails with no picture, the host latches an SDR downgrade for the rest of its life, and the next connect streams — in SDR. -That combination bites on [Bazzite](/docs/bazzite), where the sysext installs `punktfunk-gamescope` -alongside a stock session gamescope. No template pins attach any more, so the managed default gets -you HDR and the compositor-drawn cursor — but an older template did, and an upgrade never rewrites a -`host.env` you already have, so check yours for `PUNKTFUNK_GAMESCOPE_ATTACH=1` and delete the line. -If you deliberately stay on attach, set `PUNKTFUNK_GAMESCOPE_HDR=0` so the failed attempt never -happens. Staying on attach also leaves the stream with no cursor; -[HDR on gamescope](/docs/gamescope#hdr-on-gamescope) has the fix for that half. +That bites on [Bazzite](/docs/bazzite), where the sysext installs `punktfunk-gamescope` alongside a +stock session gamescope. No template pins attach any more, so the managed default gets you HDR and +the compositor-drawn cursor — but an older template did, and an upgrade never rewrites a `host.env` +you already have: check yours for `PUNKTFUNK_GAMESCOPE_ATTACH=1` and delete the line. If you +deliberately stay on attach, set `PUNKTFUNK_GAMESCOPE_HDR=0` so the failed attempt never happens. +Attach also leaves the stream with no cursor; [HDR on gamescope](/docs/gamescope#hdr-on-gamescope) +has the fix for that half. -SDR content rides the same PQ container — the desktop, the Steam overlay, an SDR game — mapped in at -`PUNKTFUNK_GAMESCOPE_SDR_NITS`, which defaults to **203 nits**. That is BT.2408 reference white, and -it is the level our clients decode against, so the two ends agree out of the box. gamescope's own -default is 400, nearly a stop brighter; hosts that let it float showed a glaring, over-saturated -Steam UI and washed-out HDR game content on the same stream. Move the knob if you want a brighter or -dimmer desktop, but be aware that moving it re-opens that gap. +SDR content — the desktop, the Steam overlay, an SDR game — rides the same PQ container, mapped in +at `PUNKTFUNK_GAMESCOPE_SDR_NITS`, default **203 nits**. That is BT.2408 reference white and the +level our clients decode against, so the two ends agree out of the box. gamescope's own default is +400, nearly a stop brighter; hosts that let it float showed a glaring, over-saturated Steam UI and +washed-out HDR game content on the same stream. Moving the knob re-opens that gap. ### Linux + GNOME -A Punktfunk host serves [two protocols](/docs/how-it-works#two-protocols): its own `punktfunk/1`, -which the Linux, Windows, Apple and Android apps speak, and GameStream, which -[Moonlight](/docs/moonlight) speaks. GNOME HDR is available on the GameStream side only. +A host serves [two protocols](/docs/how-it-works#two-protocols): its own `punktfunk/1` (the Linux, +Windows, Apple and Android apps) and GameStream ([Moonlight](/docs/moonlight)). GNOME HDR is +available on the GameStream side only. GNOME 50 added HDR screencast for **real monitors** only, so this route mirrors a monitor instead of creating a virtual display: set `PUNKTFUNK_VIDEO_SOURCE=portal`, put the monitor in HDR mode in **Settings → Displays**, and connect an HDR-capable client. `PUNKTFUNK_CAPTURE_MONITOR=` -pins which head, and when it is set the host checks *that* monitor's colour mode rather than asking +pins which head; when it is set the host checks *that* monitor's colour mode rather than asking whether any monitor is in HDR. If none is, the session degrades to 8-bit SDR and says so in the log. A Punktfunk app connecting to a GNOME host over `punktfunk/1` gets SDR. On that protocol the only @@ -109,10 +106,10 @@ Linux HDR source is the gamescope virtual output. ### Linux virtual displays on KWin, Mutter and wlroots **These are SDR.** Mutter's `RecordVirtual` streams and the KWin and wlroots virtual outputs are -8-bit upstream, so there is nothing for the host to capture in 10 bits — no setting changes this. -Streaming a *physical* monitor with the [Streamed screen](/docs/virtual-displays) setting is SDR to -a Punktfunk app too, HDR panel or not; the GNOME/GameStream route above is the only Linux monitor -mirror that can be HDR. +8-bit upstream, so there is nothing to capture in 10 bits — no setting changes this. Streaming a +*physical* monitor with the [Streamed screen](/docs/virtual-displays) setting is SDR to a Punktfunk +app too, HDR panel or not; the GNOME/GameStream route above is the only Linux monitor mirror that can +be HDR. ## Per client @@ -125,12 +122,12 @@ mirror that can be HDR. | **Moonlight** | Its own HDR toggle, which appears only when the host advertises a 10-bit codec | — | The Linux and Windows clients are deliberately looser: they advertise HDR whenever the setting is on -and let the presenter sort out the display side — HDR10 swapchain where the compositor offers one, +and let the presenter sort out the display — HDR10 swapchain where the compositor offers one, tone-mapped to SDR where it doesn't. The stats overlay says which happened: `HDR` versus `HDR→SDR`. One exception: frames from **software decode** never take the HDR10 swapchain, whatever the surface -offers. On a client with no hardware HEVC decode an HDR stream is therefore presented on the SDR -swapchain without a tone-map, which looks washed out. Turn the client's HDR setting off there. The +offers, so a client with no hardware HEVC decode presents an HDR stream on the SDR swapchain without +a tone-map — washed out. Turn the client's HDR setting off there. The [Steam Deck plugin](/docs/steam-deck) streams through this same client. ## Codec rules @@ -139,15 +136,15 @@ swapchain without a tone-map, which looks washed out. Turn the client's HDR sett - **AV1** — 10-bit, where the GPU encodes it. Advertised separately from HEVC, so a box that does one and not the other tells the truth about each. - **H.264** — never. High10 is not an encode mode on the hardware Punktfunk targets, so negotiation - never even asks. Pinning H.264 in your client settings pins the session to SDR. + never asks. Pinning H.264 in your client settings pins the session to SDR. - **[PyroWave](/docs/pyrowave)** — carries HDR in 16-bit planes, but **only from a Windows host**. The Linux PyroWave capture path has no HDR colour conversion, so a Linux-hosted PyroWave session is SDR. Use HEVC or AV1 for HDR from Linux. -One more rule if you also use full chroma: a **Linux** host encodes 4:4:4 at 8 bits, so a session -that negotiates both resolves back down to SDR before the stream starts — on Linux, 4:4:4 wins. A -**Windows** host has no such trade: it carries HDR and full chroma at once. Full chroma is off until -you turn it on, so this only bites if you did. +With full chroma: a **Linux** host encodes 4:4:4 at 8 bits, so a session that negotiates both +resolves back down to SDR before the stream starts — on Linux, 4:4:4 wins. A **Windows** host +carries HDR and full chroma at once. Full chroma is off until you turn it on, so this only bites if +you did. ## Check it @@ -170,8 +167,8 @@ set -a; . ~/.config/punktfunk/host.env; set +a punktfunk-host hdr-probe ``` -There is no `hdr-probe` on Windows. What Windows has instead is a GPU colour self-test for the -capture conversion, which needs no display or session: +There is no `hdr-probe` on Windows. Windows has instead a GPU colour self-test for the capture +conversion, which needs no display or session: ```powershell punktfunk-host hdr-p010-selftest 1920x1080 nvidia @@ -193,13 +190,12 @@ Host, in [`host.env`](/docs/configuration): |---|---|---| | `PUNKTFUNK_10BIT` | **on** | Allow 10-bit (HEVC Main10 / AV1 10-bit) at all. `0`, `false`, `off` or `no` forces every session to 8-bit SDR. | | `PUNKTFUNK_GAMESCOPE_HDR` | **on** | Allow HDR on the gamescope backend. It only decides whether HDR is *attempted* — a host without `punktfunk-gamescope` stays SDR either way. `0` is the escape hatch that puts the gamescope backend back on the old SDR path, spawn flags included. | -| `PUNKTFUNK_GAMESCOPE_SDR_NITS` | gamescope's own (400) | How bright SDR content is inside the PQ container of an HDR gamescope session. | -| `PUNKTFUNK_VIDEO_SOURCE=portal` | unset | Required for the GNOME 50+ monitor-mirror route. GameStream/Moonlight only — it has no effect on `punktfunk/1` sessions. | +| `PUNKTFUNK_GAMESCOPE_SDR_NITS` | **203** | How bright SDR content is inside the PQ container of an HDR gamescope session. | +| `PUNKTFUNK_VIDEO_SOURCE=portal` | unset | Required for the GNOME 50+ monitor-mirror route. GameStream/Moonlight only — no effect on `punktfunk/1` sessions. | Client: one toggle, in Settings under **Quality** with [the rest of the video settings](/docs/client-settings#video) — **10-bit HDR** on the Linux, macOS, iOS, iPadOS and tvOS apps, **HDR (10-bit, BT.2020 PQ)** on Windows, **HDR** on Android. It is **on -by default** on all of them. Turning it off means "never send me 10-bit", and the host then never -upgrades the session. Like the other video settings it can be set per -[profile](/docs/profiles-and-links), so a Work profile can prefer 4:4:4 while a Couch profile -prefers HDR. +by default** on all of them. Off means "never send me 10-bit", and the host then never upgrades the +session. Like the other video settings it can be set per [profile](/docs/profiles-and-links), so a +Work profile can prefer 4:4:4 while a Couch profile prefers HDR. diff --git a/docs-site/content/docs/hyprland.md b/docs-site/content/docs/hyprland.md index 8b6af6884..cbe3f772f 100644 --- a/docs-site/content/docs/hyprland.md +++ b/docs-site/content/docs/hyprland.md @@ -150,9 +150,8 @@ systemctl --user enable --now punktfunk-host journalctl --user -u punktfunk-host -f ``` -This unit runs `serve --gamestream`, so it serves stock [Moonlight](/docs/moonlight) clients as well -as the native ones. For a native-only host, see -[What the unit starts](/docs/running-as-a-service#what-the-unit-starts). +This unit runs the secure native-only host; to serve stock [Moonlight](/docs/moonlight) clients as +well, see [What the unit starts](/docs/running-as-a-service#what-the-unit-starts). ## Bring up the console and pair diff --git a/docs-site/content/docs/index.mdx b/docs-site/content/docs/index.mdx index f5ade04b8..eb3749cf3 100644 --- a/docs-site/content/docs/index.mdx +++ b/docs-site/content/docs/index.mdx @@ -36,7 +36,8 @@ stream and links out to the detail as you need it. The rest of these are for whe - + + diff --git a/docs-site/content/docs/input.md b/docs-site/content/docs/input.md index adb8ace67..eb2ae9ac0 100644 --- a/docs-site/content/docs/input.md +++ b/docs-site/content/docs/input.md @@ -5,15 +5,15 @@ description: The in-stream keyboard shortcuts that give your mouse back, the two A stream takes your mouse and keyboard the moment you click into it. This page starts with how to get them back, then covers driving the host with a mouse, a touchscreen and a pen. The rows that -pick these modes sit in your client's **Input** settings; the toggles that share that page with -them are in [Client settings](/docs/client-settings#input). +pick these modes sit in your client's **Input** settings; the toggles that share that page are in +[Client settings](/docs/client-settings#input). ## Getting your input back On the Linux and Windows clients the stream runs in its own session window. Input is **captured** when the stream starts and whenever you click the video: your local cursor disappears and keys go to -the host instead of your desktop. In the default mouse mode the pointer is also locked to the -window — see [Mouse modes](#mouse-modes) below. +the host. In the default mouse mode the pointer is also locked to the window — see +[Mouse modes](#mouse-modes). | Shortcut | What it does | |---|---| @@ -31,45 +31,41 @@ Click the stream to capture input · Ctrl+Alt+Shift+Q releases · Ctrl+Alt+Shift Ctrl+Alt+Shift+D disconnects · Ctrl+Alt+Shift+S stats ``` -With a controller in use the same hint names the controller chord instead of the mouse-mode and -stats entries. The full list is always available without a stream running — see below. +With a controller in use the hint names the controller chord instead of the mouse-mode and stats +entries. The full list is always available without a stream running — see below. ### Muting your microphone -**Ctrl+Alt+Shift+V** stops sending your microphone to the host, and pressing it again resumes. -The uplink itself keeps running underneath, so unmuting is instant rather than a second of the -device warming back up. +**Ctrl+Alt+Shift+V** stops sending your microphone to the host; pressing it again resumes. The +uplink keeps running underneath, so unmuting is instant. -While you are muted a **Microphone muted** badge sits in the top-right corner of the stream. It -is deliberately separate from the [stats overlay](/docs/stats): it shows even with stats off, -because "am I still muted?" is a question you ask ten minutes later. +While muted, a **Microphone muted** badge sits in the top-right corner of the stream — separate +from the [stats overlay](/docs/stats), so it shows even with stats off. The mute lasts for that stream only — the next session starts unmuted, and nothing is written to your settings. If the stream isn't sending a microphone at all (**Stream microphone** off in -[client settings](/docs/client-settings#audio)) the shortcut does nothing and no badge appears, -rather than pretending to mute something. +[client settings](/docs/client-settings#audio)) the shortcut does nothing and no badge appears. -This is on the **Linux and Windows** clients — including a Steam Deck stream, which is the Linux -client, so an attached keyboard gets the chord. The Apple and Android clients have no mute shortcut -yet; turn **Stream microphone** off in their settings instead. +**Linux and Windows** clients only — a Steam Deck stream is the Linux client, so an attached +keyboard gets the chord. The Apple and Android clients have no mute shortcut yet; turn **Stream +microphone** off in their settings instead. Alt-Tabbing away releases input on its own and takes it back when you return. A release you asked for with the chord stays released until you opt back in. Either way, keys and buttons you were holding are released on the host, so nothing sticks down. -You can look the shortcuts up again without a stream running: the Linux client has **Keyboard -Shortcuts** in its main menu, and the Windows client has a **Shortcuts** screen reached from its -host list. Both list the microphone mute; the in-stream hint over the video doesn't, to keep it -to one readable line. +Without a stream running, the Linux client lists the shortcuts under **Keyboard Shortcuts** in its +main menu, and the Windows client on a **Shortcuts** screen reached from its host list. Both list the +microphone mute; the in-stream hint over the video doesn't, to stay one readable line. ### On the other clients - **macOS** honours the release, mouse-mode, disconnect and stats combos, written - **⌃⌥⇧Q / M / D / S** — but not the microphone mute. **⌘⎋** also toggles capture, - **⌃⌘F** toggles fullscreen, and **⌃⌥⇧C** starts or stops [clipboard sharing](/docs/clipboard). The - **Stream** menu lists them all except the mouse-mode combo, which works but has no menu item. - Every *other* ⌘ chord goes to the host while input is captured — ⌘Q reaches the host's compositor - rather than quitting the app — unless you turn **Capture system shortcuts** off in + **⌃⌥⇧Q / M / D / S** — but not the microphone mute. **⌘⎋** also toggles capture, **⌃⌘F** toggles + fullscreen, and **⌃⌥⇧C** starts or stops [clipboard sharing](/docs/clipboard). The **Stream** menu + lists them all except the mouse-mode combo, which works but has no menu item. Every *other* ⌘ + chord goes to the host while input is captured — ⌘Q reaches the host's compositor rather than + quitting the app — unless you turn **Capture system shortcuts** off in [client settings](/docs/client-settings#input). ⌘⎋ and ⌃⌘F are held back either way, so there is always a way out. - **iPhone and iPad** with a hardware keyboard: **⌃⌥⇧Q** releases input while it is captured, and @@ -91,32 +87,30 @@ Every client reserves one controller chord: **L1 + R1 + Start + Select** (LB + R an Xbox pad), held on any connected pad. - **Linux, Windows** — a press releases captured input, and leaves fullscreen if you didn't start - fullscreen. Keep it held about 1.5 seconds and it disconnects. + fullscreen. Hold about 1.5 seconds and it disconnects. - **Steam Deck** — a press releases captured input only. The Decky plugin always launches the client fullscreen, and a stream that started fullscreen stays that way. Holding disconnects, as above. -- **macOS, iPhone/iPad, Apple TV** — holding it about 1.5 seconds disconnects. There is no - quick-press step. -- **Android** — holding it about a second disconnects. A quick press does nothing; the moment the - chord completes a **Hold to quit…** cue appears so you know it registered. +- **macOS, iPhone/iPad, Apple TV** — holding about 1.5 seconds disconnects. There is no quick-press + step. +- **Android** — holding about a second disconnects. A quick press does nothing; the moment the chord + completes a **Hold to quit…** cue appears so you know it registered. The chord is read off the pads a client forwards, so turning [**Forward controllers**](/docs/client-settings#input) off takes it away on **Linux and Windows** — -there the client stops opening the controller at all, which is the point of the setting. Use +there the client stops opening the controller at all. Use **Ctrl+Alt+Shift+D** or the client's own UI to leave instead. The Apple and Android apps keep watching for the chord either way. ### Statistics with a controller The **Apple** apps reserve a second chord: **Select + X**, which cycles the -[stats overlay](/docs/stats) one level each time you complete it. It is for the moment your hands -are on a controller and the usual routes aren't — no keyboard for **⌃⌥⇧S**, no free screen for the -three-finger tap — and on **Apple TV** it is the only way there with a pad. X is deliberately none -of the four leave-chord buttons, so reaching for one chord never trips the other. Both buttons -still reach the game; only the overlay changes locally. +[stats overlay](/docs/stats) one level each time you complete it. For when you have a pad but no +keyboard for **⌃⌥⇧S** and no free screen for the three-finger tap; on **Apple TV** it is the only +way there with a pad. X is deliberately none of the four leave-chord buttons, so reaching for one +chord never trips the other. Both buttons still reach the game; only the overlay changes locally. -On the **Siri Remote**, **hold Play/Pause** for about half a second instead. A quick tap of that -button is still a right click — the click is simply sent when you let go, so the hold has -something to be. +On the **Siri Remote**, **hold Play/Pause** for about half a second instead. A quick tap is still a +right click, sent when you let go. ### The guide button (Xbox / PS / Steam) and Quick Access @@ -126,30 +120,30 @@ devices want that button for themselves, so every client also carries a gesture everywhere: **Hold Select (Back / View) on its own for about a third of a second.** The host sees its guide -button go down, and it stays down for as long as you hold — so keeping it held reads as a long -press on the host, which is how SteamOS opens the **Quick Access Menu** for a regular pad. A quick -tap of Select still reaches the game, delivered when you let go (a beat late). Select pressed as -part of a combo — including the leave chord above — passes through untouched. +button held down for as long as you hold — a long press, which is how SteamOS opens the **Quick +Access Menu** for a regular pad. A quick tap of Select still reaches the game, delivered when you +let go (a beat late). Select pressed as part of a combo — including the leave chord above — passes +through untouched. What the raw button does, per client: -- **Linux & Windows desktop, macOS, Android** — the guide press is forwarded to the host. If - Steam Big Picture or the Xbox Game Bar is also watching for it *on the device in your hands*, - both may react — that's a local setting on that device, not something the stream can suppress. +- **Linux & Windows desktop, macOS, Android** — the guide press is forwarded to the host. If Steam + Big Picture or the Xbox Game Bar is also watching for it *on the device in your hands*, both may + react — that's a local setting on that device, not something the stream can suppress. - **Steam Deck / Gaming Mode** — the **Steam** and **`…`** buttons stay with the Deck by default: - SteamOS always opens its own menus for them, so forwarding the raw press as well opened BOTH - menus at once, the Deck's on top of the stream. Reach the host's menus with **hold-Select**, or - with the Punktfunk panel's **Host menus** buttons ([Steam Deck page](/docs/steam-deck)). The - old behavior is one setting away: **Steam / guide button → Send to host**. + SteamOS always opens its own menus for them, so forwarding the raw press as well opened BOTH menus + at once, the Deck's on top of the stream. Reach the host's menus with **hold-Select**, or with the + Punktfunk panel's **Host menus** buttons ([Steam Deck page](/docs/steam-deck)). The old behavior + is one setting away: **Steam / guide button → Send to host**. - **iPhone / iPad** — iOS reserves the Home press for its own Game Overlay, so hold-Select is the - reliable route to the host's overlay. On iOS 27 or later you can also hand the button to the - app yourself, in the system's per-controller Home-button setting. + reliable route to the host's overlay. On iOS 27 or later you can also hand the button to the app + yourself, in the system's per-controller Home-button setting. - **Apple TV** — tvOS never delivers the Home press to apps; hold-Select is the only route. Both halves are [settings](/docs/client-settings#input), per profile like everything else: **Steam / guide button** (Automatic / Send to host / This device) and **Hold Select for guide** -(Automatic / On / Off). Automatic picks the behavior above for each platform — the gesture stays -off where the raw button already works, so games that use a *held* Select keep it. +(Automatic / On / Off). Automatic picks the behavior above for each platform — the gesture stays off +where the raw button already works, so games that use a *held* Select keep it. ## Mouse modes @@ -159,14 +153,13 @@ There are two, and they are a per-client setting called **Mouse input**: cursor you see is the host's. This is what mouse-look in a game needs. The session window also grabs the keyboard here, so Alt+Tab and the Windows key (Super on Linux) reach the host rather than your own desktop — on macOS that is the ⌘ chords, ⌘Q included, with ⌘⎋ kept back as the way out. - Turn **Capture system shortcuts** off in - [client settings](/docs/client-settings#input) to keep them local. + Turn **Capture system shortcuts** off in [client settings](/docs/client-settings#input) to keep + them local. - **Desktop (absolute)** — the pointer is not locked. It moves in and out of the stream freely and its position is sent as an absolute point — what you want for remote desktop work. Your local cursor is hidden over the stream; the one you see there is the host's. -**Capture is the default** on the Linux, Windows and macOS clients. **Android defaults to Desktop** -— a phone or TV is more often driven by touch or a pad than by a locked mouse. +**Capture is the default** on the Linux, Windows and macOS clients. **Android defaults to Desktop**. Switch live with **Ctrl+Alt+Shift+M** (**⌃⌥⇧M** on macOS), whether input is captured or not. On Android, Ctrl+Alt+Shift+Q flips the capture instead. The picker is macOS-only among the Apple apps; @@ -217,7 +210,7 @@ The trackpad and pointer models are unaffected by all of this: they send ordinar A stylus is not treated as a finger. Punktfunk carries **position, tip pressure, tilt angle and tilt direction, barrel roll, hover distance, the eraser end, and two barrel buttons** on their own input -plane, so drawing and handwriting behave the way they do locally. +plane. **Clients that send pen input:** @@ -241,8 +234,8 @@ The Linux, Windows, macOS and Apple TV clients do not send stylus input. tilt, rotation, the barrel button and the eraser. This needs **Windows 10 1809 or newer**. **Before it can work on Linux**, the host needs access to `/dev/uinput` — the same `input` group step -the virtual gamepads need, covered under [After installing](/docs/install#after-installing). Without -it the host never offers pen at all. +the virtual gamepads need, step 3 of your [install guide](/docs/install). Without it the host never +offers pen at all. **If the host is too old, or pen is switched off**, nothing breaks: the client keeps folding the stylus into its ordinary touch or pointer path. You can still draw — just without pressure and tilt. diff --git a/docs-site/content/docs/install-client.md b/docs-site/content/docs/install-client.md index 8bb023b12..6ef912a5b 100644 --- a/docs-site/content/docs/install-client.md +++ b/docs-site/content/docs/install-client.md @@ -5,9 +5,9 @@ description: Install the Punktfunk client for the device you're streaming to — This page is the **install path for each client device**. For what each client *is* and which to pick, see [Clients](/docs/clients); to install the **host**, see [Install the Host](/docs/install). -Whichever client you install, the first connection needs a one-time [pairing](/docs/pairing). If the -app installs but your host doesn't appear in its list, start at [Troubleshooting → The host isn't -found on the network](/docs/troubleshooting#the-host-isnt-found-on-the-network). +Every client needs a one-time [pairing](/docs/pairing) on its first connection. If the app installs +but your host doesn't appear in its list, start at [Troubleshooting → The host isn't found on the +network](/docs/troubleshooting#the-host-isnt-found-on-the-network). Already installed? Skip to [Keeping a client up to date](#keeping-a-client-up-to-date) or [Removing a client](#removing-a-client). @@ -31,9 +31,8 @@ Already installed? Skip to [Keeping a client up to date](#keeping-a-client-up-to ## Linux desktop (Flatpak) -The **recommended** path on any Flatpak distro — install once, then `flatpak update` tracks new -builds. One command adds the signed `unom` remote, pulls the GNOME runtime from Flathub -automatically, and installs the client: +The **recommended** path on any Flatpak distro. One command adds the signed `unom` remote, pulls the +GNOME runtime from Flathub automatically, and installs the client: ```sh flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.flatpakref @@ -47,10 +46,9 @@ touches the *system* scope and silently skips it): flatpak update # or: flatpak update --user io.unom.Punktfunk ``` -Prefer your native package manager? The client also ships as real packages (add the repo once — -see the linked guide — then it tracks updates with your normal `apt upgrade` / `dnf upgrade` / -`pacman -Syu`; a *layered* Atomic install needs one extra step, under -[Keeping a client up to date](#keeping-a-client-up-to-date)): +Prefer your native package manager? Add the repo once (see the linked guide); updates then ride your +normal `apt upgrade` / `dnf upgrade` / `pacman -Syu` (a *layered* Atomic install needs +[one extra step](#keeping-a-client-up-to-date)): | Distro | Install | Guide | |--------|---------|-------| @@ -60,9 +58,9 @@ see the linked guide — then it tracks updates with your normal `apt upgrade` / | **Arch** | `sudo pacman -Syu punktfunk-client` (signed binary repo) | [Arch Linux](/docs/arch) | > **The client `.deb` needs SDL3 and GTK4 ≥ 4.20**, which Ubuntu 24.04 LTS doesn't ship, so -> `apt install punktfunk-client` can't satisfy its dependencies there. On 24.04 (or any older -> distro) use the **Flatpak above** — it carries its own libadwaita and SDL3. This limit is the -> *client's* alone: the host `.deb` is built separately and installs on 24.04 LTS through 26.04. +> `apt install punktfunk-client` can't resolve there. On 24.04 (or any older distro) use the +> **Flatpak above** — it carries its own libadwaita and SDL3. The limit is the *client's* alone: the +> host `.deb` is built separately and installs on 24.04 LTS through 26.04. Then launch it, pick your host from the list, and stream. Every one of these packages — Flatpak included — also installs the headless **`punktfunk`** command for scripts: @@ -78,18 +76,17 @@ list: [Clients → the `punktfunk` CLI](/docs/clients#scripting-the-punktfunk-cl ## Steam Deck -Most Deck users want **Gaming Mode**: install the **[Decky plugin](/docs/steam-deck)** and a -**Punktfunk** panel lands in the Quick Access Menu, so you can find a host, get let in (a PIN, or a -request the host's operator approves), and stream **without dropping to the desktop**. Everything -else — settings, the game library, adding a host by address — is one tap away in the client's own -gamepad UI. Follow the **[Steam Deck (Decky) guide](/docs/steam-deck)** — it walks through Decky +Most Deck users want **Gaming Mode**: the **[Decky plugin](/docs/steam-deck)** puts a **Punktfunk** +panel in the Quick Access Menu — find a host, get let in (a PIN, or a request the host's operator +approves), and stream **without dropping to the desktop**; settings, the game library and adding a +host by address are one tap away in the client's own gamepad UI. That guide walks through Decky Loader, the plugin, and the one-time client install. > The plugin doesn't decode video itself — it drives whichever `punktfunk-client` is installed on > the Deck. The Flatpak below is the tested default; a native package or a sysext works too. If your > client isn't one the plugin can update for you (a sysext, a nix profile, a source build), the panel > shows you the update command instead of an **Update** button. The Gaming Mode panel comes from the -> plugin, so a client on its own won't add it. The Decky guide covers installing both, so start there. +> plugin, so a client on its own won't add it; the Decky guide covers installing both. For **Desktop Mode** (or to add the client to Game Mode as a non-Steam app yourself), install the Flatpak exactly as [above](#linux-desktop-flatpak) — it carries its own libadwaita + SDL3 and @@ -104,7 +101,7 @@ See [packaging/flatpak](https://git.unom.io/unom/punktfunk/src/branch/main/packa ## Windows The Windows client ships as a **signed MSIX** in the package registry, signed with a publicly -trusted certificate — so there is nothing to import and nothing to trust by hand. Download, install. +trusted certificate — nothing to import or trust by hand. 1. Download the package. Each channel keeps one fixed URL, so this line always fetches the current build — in PowerShell: @@ -115,7 +112,7 @@ trusted certificate — so there is nothing to import and nothing to trust by ha Swap `_x64` for `_arm64` on an Arm device, and `latest` for `canary` to track `main`. The same file is attached to every [release](https://git.unom.io/unom/punktfunk/releases), and every - build is also kept under its own version on the + build is kept under its own version on the [packages page](https://git.unom.io/unom/-/packages) (generic group, `punktfunk-client-windows`). 2. Install it: @@ -129,8 +126,8 @@ trusted certificate — so there is nothing to import and nothing to trust by ha (the MSIX depends on `Microsoft.WindowsAppRuntime.2`), then re-run `Add-AppxPackage`. Install from a signed-in desktop session. Over a remote, non-interactive session (SSH, an RMM - tool) `Add-AppxPackage` can fail with `0x80070005` when the Windows App Runtime it depends on is - in use and Windows can't restart the apps holding it. + tool) `Add-AppxPackage` can fail with `0x80070005` when the Windows App Runtime is in use and + Windows can't restart the apps holding it. 3. Launch **Punktfunk** from the Start menu and pick your host. The package also adds a second entry, **Punktfunk Console** — the same client as a controller-driven fullscreen interface for a @@ -142,11 +139,10 @@ trusted certificate — so there is nothing to import and nothing to trust by ha ## macOS -Download the notarized disk image from the [releases page](https://git.unom.io/unom/punktfunk/releases) -— `Punktfunk-.dmg`. It's Developer-ID signed, notarized, and stapled, so Gatekeeper opens -it without warnings: +Download `Punktfunk-.dmg` from the [releases page](https://git.unom.io/unom/punktfunk/releases). +It's Developer-ID signed, notarized, and stapled, so Gatekeeper opens it without warnings: -1. Open `Punktfunk-.dmg` and drag **Punktfunk** to **Applications**. +1. Open the `.dmg` and drag **Punktfunk** to **Applications**. 2. Launch it, pick your host from *On this network*, and [pair](/docs/pairing). The Mac app is also in the [TestFlight beta](https://testflight.apple.com/join/Qr7uSemk); the DMG @@ -159,19 +155,19 @@ Mac. Install Apple's [TestFlight](https://apps.apple.com/app/testflight/id899247 **[Join the Punktfunk beta on TestFlight →](https://testflight.apple.com/join/Qr7uSemk)** -Open the app, and your hosts appear automatically under *On this network*. +Open the app; your hosts appear automatically under *On this network*. ## Android -The Android client (phone + Android TV — one package, the TV layout is the same app in leanback -mode) is published on **Google Play**. It's a public listing: no invite, no tester list. +The Android client (phone + Android TV — one package; the TV layout is the same app in leanback +mode) is on **Google Play** as a public listing: no invite, no tester list. **[Get Punktfunk on Google Play →](https://play.google.com/store/apps/details?id=io.unom.punktfunk)** Install it, open the app, and pick your host. -**Prefer not to go through Play?** The signed APK is published publicly on every build, so you can -sideload it instead — no Play account needed: +**Prefer not to go through Play?** The signed APK is published publicly on every build — sideload it +instead, no Play account needed: ```text https://git.unom.io/api/packages/unom/generic/punktfunk-android/latest/punktfunk-android.apk @@ -195,13 +191,13 @@ LG's webOS doesn't allow apps outside the LG Content Store without sideloading, **Developer Mode** and the **Homebrew Channel** once: 1. Enable Developer Mode on the TV and install the [Homebrew Channel](https://www.webosbrew.org/) — - follow its [install guide](https://www.webosbrew.org/guide/getting-started.html) if you haven't - done this before. + its [install guide](https://www.webosbrew.org/guide/getting-started.html) covers it if you + haven't done this before. 2. Grab the latest `.ipk` from the [pf-webos releases page](https://github.com/dyptan-io/pf-webos/releases/latest). -3. Install it: either sideload with `ares-install` / the project's `task deploy TV_HOST=root@` - (see the repo's README), or side-copy the `.ipk` onto the TV and install it from the Homebrew - Channel's app. +3. Install it: sideload with `ares-install` / the project's `task deploy TV_HOST=root@` (see + the repo's README), or copy the `.ipk` onto the TV and install it from the Homebrew Channel's + app. 4. Launch **Punktfunk** from the TV's launcher, discover your host over LAN (or add it by IP), and [pair](/docs/pairing) with a PIN. @@ -230,9 +226,9 @@ but keeping them close is the least surprising. (Updating the **host** is its ow | **LG webOS** | install the newer `.ipk` the same way you installed the first one | **Fedora Atomic, if you layered the client.** `rpm-ostree upgrade` upgrades the *base image* and -only re-resolves layered packages when that base actually changes — so on a base that sits still it -keeps reporting no updates while a newer `punktfunk-client` waits in the repo. Force a re-resolve of -just that layer, in one transaction, then reboot to activate it: +only re-resolves layered packages when that base changes — on a base that sits still it keeps +reporting no updates while a newer `punktfunk-client` waits in the repo. Force a re-resolve of just +that layer, in one transaction, then reboot to activate it: ```sh sudo rpm-ostree refresh-md --force @@ -240,27 +236,27 @@ sudo rpm-ostree update --uninstall punktfunk-client --install punktfunk-client systemctl reboot ``` -The client's own updater below runs exactly that dance for you, if you'd rather not remember it. (A -layered **host** has the same trap — [Updating](/docs/updating) covers it.) +The client's own updater below runs exactly that for you. (A layered **host** has the same trap — +[Updating](/docs/updating) covers it.) **Windows, coming from 0.28.1 or earlier — uninstall first.** Those builds were signed with our own self-signed certificate. The move to a publicly trusted one changes the package's *publisher*, and an MSIX's identity is its name **plus** its publisher — so Windows treats the new package as a different -app rather than an update, and installing it leaves you with two **Punktfunk** entries. Remove the -old one first, then install the new `.msix` as above: +app, not an update, and installing it leaves you with two **Punktfunk** entries. Remove the old one +first, then install the new `.msix` as above: ```powershell Get-AppxPackage *Punktfunk* | Remove-AppxPackage ``` -This is one-time; releases after that upgrade in place. Note that a packaged app's settings live -*inside* its package, so removing the old one also removes this client's identity and its paired -hosts — expect to [pair](/docs/pairing) again once. Nothing on the host side is affected. +This is one-time; releases after that upgrade in place. A packaged app's settings live *inside* its +package, so removing the old one also removes this client's identity and its paired hosts — expect +to [pair](/docs/pairing) again once. Nothing on the host side is affected. ### The Linux client can update itself -The native Linux client checks its own channel and can apply the update in place, so you don't have -to remember which package manager installed it: +The native Linux client checks its own channel and can apply the update in place, whichever package +manager installed it: ```sh punktfunk-client --check-update # prints installed vs available for this box's channel @@ -272,16 +268,16 @@ available, and **1** when it couldn't tell (offline, or the check is disabled) deliberately not "up to date". Add `--json` to either for machine-readable output. Applying an update needs root, so it's **opt-in**: join the `punktfunk-update` group once, and a -packaged root helper does the install (the same group and the same grant as the host's one-click -updating, described in [Updating](/docs/updating)). +packaged root helper does the install (the same group and grant as the host's one-click updating, +described in [Updating](/docs/updating)). ```sh sudo usermod -aG punktfunk-update $USER ``` -Membership is re-read on every check, so there's no need to log out and back in. Without it, +Membership is re-read on every check, so no need to log out and back in. Without it, `--check-update` prints the opt-in line and the plain package-manager command instead. On a Flatpak -install `--apply-update` isn't used at all — the client tells you to run `flatpak update`. +install `--apply-update` isn't used — the client tells you to run `flatpak update`. ## Removing a client diff --git a/docs-site/content/docs/install.md b/docs-site/content/docs/install.md deleted file mode 100644 index 5418af9bc..000000000 --- a/docs-site/content/docs/install.md +++ /dev/null @@ -1,276 +0,0 @@ ---- -title: Install the Host -description: Install the Punktfunk host — on Linux from its package registry, or on Windows from a signed installer. ---- - -On Linux, the package registries are the real distribution channel. Pick your distro, add the repo, and -install with your native package manager. Each row links to the full per-distro guide (add the repo, -first-run steps, the web console) — those are the source of truth, so this page doesn't duplicate them. -On **Windows**, the host ships as a signed installer instead — see [Windows](#windows). - -> **First, read [Security & Safe Use](/docs/security).** A streaming host is remote control of the -> machine. It's built for trusted local networks — don't expose it to the internet, and be thoughtful -> about which machine you host on (especially on Windows). - -## Pick your distro - -| Distro | Package manager | One-command happy path | Guide | -|--------|-----------------|------------------------|-------| -| **Ubuntu 26.04+** ¹ | apt | `sudo apt install punktfunk-host` | [Ubuntu](/docs/ubuntu) · [packaging/debian](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md) | -| **Debian 13+** (incl. LMDE) | apt | `sudo apt install punktfunk-host` | [Debian](/docs/debian) · [packaging/debian](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md) | -| **Bazzite / Fedora Atomic** | systemd-sysext | `curl -fsSLO https://git.unom.io/unom/punktfunk/raw/branch/main/packaging/bazzite/punktfunk-sysext.sh && sudo bash punktfunk-sysext.sh install` (no layering, no reboot) | [Bazzite](/docs/bazzite) · [packaging/bazzite](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/bazzite/README.md) | -| **Fedora (dnf)** | dnf / rpm-ostree | `sudo dnf install punktfunk` | [Fedora](/docs/fedora) · [packaging/rpm](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/rpm/README.md) | -| **Arch** | pacman | `sudo pacman -Syu punktfunk-host` (binary repo — always a full `-Syu`, never `-Sy`) | [Arch Linux](/docs/arch) · [packaging/arch](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md) | -| **SteamOS (host)** | on-device script | clone the repo, then `bash ~/punktfunk/scripts/steamdeck/install.sh` (builds on-device) | [SteamOS (Host)](/docs/steamos-host) | -| **NixOS / Nix** | nix flake | `nix run git+https://git.unom.io/unom/punktfunk#punktfunk-host -- serve --gamestream` | [NixOS](#nixos) · [packaging/nix](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/nix/README.md) | - -> ¹ **Ubuntu 24.04 LTS installs the package but cannot host.** It ships no compositor that meets -> the [version floors](/docs/requirements) — KWin 5.27 against 6.5.6, GNOME Shell 46 against 48 — -> and no `gamescope`. Use 26.04 or newer. This is also why -> [Linux Mint 22.x cannot host](/docs/debian#linux-mint-22x-cannot-host-yet); LMDE 7 (Debian 13) -> can. - -Each registry is public — no auth, you just trust the repo's signing key. Adding the repo is a -one-time step covered in the linked guide; after that, normal `apt upgrade` / `dnf upgrade` / -`pacman -Syu` (or `sudo punktfunk-sysext update` on Bazzite) tracks new builds. On **NixOS** you add -the flake as an input and enable its module rather than adding a package repo — but do add the -[binary cache](#nixos), or every build compiles from source. - -> **Stable vs canary.** The repos in the per-distro guides are the **stable** channel — it only -> moves when a `vX.Y.Z` release is cut. For the latest `main` build (fast, possibly broken), point -> at the **canary** channel instead (`canary` apt distribution / `*-canary` rpm group). See -> [Release Channels](/docs/channels). - -## Windows - -Punktfunk also runs as a native host on **Windows 11 22H2+ (x64)**, shipped as a signed -installer — see [Windows Host](/docs/windows-host) for what it includes and its limitations. - -For hardware encode you need a GPU — NVIDIA (NVENC), AMD (AMF), or Intel (QSV); there's a software -fallback without one. More detail — including the CLI `punktfunk-host service install` path — is in -[Running as a Service → Windows](/docs/running-as-a-service#windows). - -### winget (recommended) - -In an **admin** PowerShell, register the Punktfunk source once, then install: - -```powershell -winget source add -n punktfunk https://winget.punktfunk.unom.io -t Microsoft.Rest -winget install unom.PunktfunkHost -``` - -Later, `winget upgrade unom.PunktfunkHost` updates it in place. Add `--interactive` to get the full -wizard instead (the optional task checkboxes, the web-console password page). winget carries -**stable** releases only — canary builds are not published there. - -### Manual download - -Download `punktfunk-host-setup-.exe` and run it elevated. The full procedure — where to get it, -everything the installer puts on the machine, its optional tasks, the console password, and the -`/VERYSILENT` unattended switch — lives on one page: [Windows Host → Install](/docs/windows-host#install). -This is also the path for **canary** builds, which winget doesn't carry — see -[Release Channels](/docs/channels) for that download. - -> **About signing.** The installer is signed with a publicly trusted certificate, so Windows shows -> the publisher by name at the UAC prompt — there is no Unknown Publisher warning and nothing to -> import. The winget route is no different: it downloads and runs that same installer. -> -> SmartScreen is a separate mechanism that builds reputation per publisher, so shortly after a new -> signing certificate starts being used it can still show *"Windows protected your PC"* on the first -> downloads — **More info → Run anyway**. It settles as installs accumulate. -> -> The bundled **drivers** are a separate matter — they carry their own certificate, and the installer -> imports that one for you. [Windows Host](/docs/windows-host#about-the-signatures) has the detail. -> -> Releases **0.28.1 and earlier** were signed with our own self-signed certificate, and older docs -> told you to import it. Nothing needs it any more: if you imported -> `punktfunk-host-windows_.cer` back then, you can remove it from `Cert:\LocalMachine\Root` and -> `Cert:\LocalMachine\TrustedPublisher` (look for the certificate issued to **unom**, thumbprint -> `CD1EFDEEEC9743AFC38F56C5AF30C5A3009BE941`). - -## NixOS - -The repo's `flake.nix` is a supported install path: it builds `punktfunk-host`, `punktfunk-client`, -`punktfunk-web` and `punktfunk-scripting`, and ships a NixOS module. **`x86_64-linux` only**, and -NixOS **24.11 or newer**. - -**Add the binary cache first.** Without it, a build compiles the whole Rust workspace *and* -gamescope from source — about an hour. With it you get prebuilt binaries: - -```nix -nix.settings = { - substituters = [ "https://nix.unom.io" ]; - trusted-public-keys = [ "punktfunk-cache-1:yhOJmHxzg6tzXpxSFzlYn6Pc6r0jHprsWqt8MZC654o=" ]; # curl https://nix.unom.io/punktfunk-cache.pub -}; -``` - -Off NixOS, put the same two values in `/etc/nix/nix.conf` as `extra-substituters` / -`extra-trusted-public-keys`. One caveat worth knowing before you copy a flake snippet from -elsewhere: setting `inputs.punktfunk.inputs.nixpkgs.follows = "nixpkgs"` changes every store path -and so misses the cache entirely — details in -[packaging/nix](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/nix/README.md#binary-cache-do-this-before-your-first-build). - -You can run it straight from the flake without NixOS (on other distros, wrap it in -[nixGL](https://github.com/nix-community/nixGL) so the GPU drivers resolve): - -```sh -nix run git+https://git.unom.io/unom/punktfunk#punktfunk-host -- serve --gamestream -``` - -On NixOS, add the flake as an input, add `punktfunk.nixosModules.default` to your system's modules, -and enable the host: - -```nix -services.punktfunk.host = { - enable = true; - users = [ "alice" ]; # added to the `input` group, for virtual gamepads - openFirewall = true; - desktopSession = true; # on a machine you log into — see below - settings = { RUST_LOG = "info"; }; # these become host.env -}; -``` - -The module does declaratively what the deb/RPM scriptlets do — the systemd user service, udev rules, -kernel modules, sysctl tuning, the firewall ports and `input` group membership — and brings in the -web console alongside the host. Because `settings` writes the environment file for you, skip the -`host.env` step in [After installing](#after-installing). - -**Set `desktopSession = true` on any machine somebody logs into.** It ties the host to -`graphical-session.target`, so restarting Plasma or GNOME restarts the host with it. Without it the -host keeps running against a compositor that no longer exists — still listening, still answering, -and failing at capture on every session after that. Leave it off for the headless appliance route -(a pinned compositor or a gamescope box), which may never reach that target. Same reasoning, and -the same caveats for Sway and Hyprland, as [Restart the host with your -desktop](/docs/running-as-a-service#restart-the-host-with-your-desktop). - -The host and console user services are defined but not started (set `autoStart = true` for an -appliance), so from your graphical session enable them: - -```sh -systemctl --user enable --now punktfunk-host punktfunk-web -``` - -The plugin runner needs no such step — like the deb and RPM, the module starts it for you, because -the game-library scanners ship as plugins. Opt out with -`services.punktfunk.scripting.autoStart = false;`. - -The full option reference (client, console and scripting options, GPU driver notes, headless -appliance setup) is in -[packaging/nix](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/nix/README.md). To -update, run `nix flake update punktfunk` in your flake directory, then `sudo nixos-rebuild switch`. - -## What the packages are - -- **`punktfunk-host`** — the streaming host. Install this on your Linux gaming machine. -- **`punktfunk-web`** — the browser management console (pairing + status). Recommended alongside the - host. On apt and RPM the host package *recommends* it, so your package manager pulls it in by - default, and the Bazzite sysext image already contains it. On **Arch** it's an optional - dependency, so name it yourself: `sudo pacman -Syu punktfunk-web`. -- **`punktfunk-client`** — the GTK4 desktop client, for streaming *to* a Linux box (shipped via - apt / RPM / Arch, and as a Flatpak). On a **Steam Deck** take the Flatpak instead — SteamOS's - `/usr` is read-only, so the native package isn't the path there: - - ```sh - flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.flatpakref - ``` - - For Gaming Mode, add the [Decky plugin](/docs/steam-deck) on top of it. Full client instructions - for every device: [Install a Client](/docs/install-client). - -- **`punktfunk-scripting`** — the plugin/script runner, behind [plugins](/docs/plugins) and - [automation](/docs/automation). The game-library scanners ship as plugins, so a host without the - runner can come up with an empty library — which is why **apt, dnf, the Bazzite sysext and the - NixOS module all start it for you**. On **Arch** and source installs it is not started, so enable - it yourself: - - ```sh - systemctl --user enable --now punktfunk-scripting - ``` - - To opt out where it *is* on: `systemctl --user mask punktfunk-scripting` (`mask`, not `disable` — - a plain disable cannot remove a symlink that lives in `/etc` or `/usr`), or on NixOS - `services.punktfunk.scripting.autoStart = false;`. - -## After installing - -These three steps are for the **Linux packages**. On Windows the installer does the equivalent for -you; on NixOS the module does steps 1 and 2, and [NixOS](#nixos) above has the units to enable. - -1. Add yourself to the `input` group — virtual gamepads and [pen - input](/docs/input#pen-and-stylus) both need `/dev/uinput` — then re-login. The exact - command differs per distro — see your guide (`usermod -aG input "$USER"`, or `ujust - add-user-to-input-group` on Bazzite). - - Also join `punktfunk` — `sudo usermod -aG punktfunk "$USER"`, then log out and back in — if - **either** of these is true: you want the **virtual Steam Deck controller** (paddles, - trackpads, gyro), or this box autologins into Steam **Gaming Mode** and you want the host to - take that session over at your client's resolution - ([gamescope](/docs/gamescope#nobara-and-other-autologin-display-managers)). Your package created - that group at install time and left it empty. It gates the usbip nodes the pad attaches through - *and* the helper that stops the display manager for a takeover, and it is separate from `input` - on purpose, because writing those nodes can present arbitrary emulated USB hardware — so join it - only on a machine you trust. On a plain desktop host that streams no Gaming Mode, skipping it - costs you nothing but that one pad type. -2. Put your `host.env` in place, then start the host. Every Linux package ships a systemd **user** - unit, so you don't run the host by hand — but that unit reads `~/.config/punktfunk/host.env` and - won't start until the file exists. Each package ships a template to copy; your distro and desktop - guides say which one to pick (on Bazzite it's `host.env.bazzite`): - - ```sh - mkdir -p ~/.config/punktfunk - # /usr/share/punktfunk/ on Fedora/Arch/Bazzite, /usr/share/punktfunk-host/ on Ubuntu - cp /usr/share/punktfunk/host.env.example ~/.config/punktfunk/host.env - systemctl --user enable --now punktfunk-host - ``` - - The shipped unit runs `serve --gamestream` — the native `punktfunk/1` plane **plus** the - GameStream/Moonlight-compatible planes, so stock [Moonlight](/docs/moonlight) clients work out of - the box. Those extra planes are only appropriate on a trusted LAN. To run native-only, drop the - flag with a drop-in (`systemctl --user edit punktfunk-host`): - - ```ini - [Service] - ExecStart= - ExecStart=/usr/bin/punktfunk-host serve - ``` - - The empty `ExecStart=` is required — without it systemd adds a second command instead of - replacing the first — and the binary path has to match your install (`systemctl --user cat - punktfunk-host` shows it; the distro packages use `/usr/bin`). Save the drop-in, then - `systemctl --user restart punktfunk-host`. For what each mode starts, see - [Host CLI → `serve`](/docs/host-cli#serve). - -3. Enable the web console: - - ```sh - systemctl --user enable --now punktfunk-web - ``` - - Then open `https://:47992`. Reading its [login password](/docs/web-console#login-password) - and [arming PIN pairing](/docs/web-console#arm-pairing) are covered in - [The Web Console](/docs/web-console). - -### Configure your desktop - -How the virtual display and input work depends on your desktop — see [KDE](/docs/kde), -[GNOME](/docs/gnome), [Steam / gamescope](/docs/gamescope), [Hyprland](/docs/hyprland), or -[Sway](/docs/sway) for the compositor-specific setup. - -From there, follow the [Quick Start](/docs/quickstart) to pair your first client. To run the host -automatically at boot, see [Running as a Service](/docs/running-as-a-service). If something doesn't -come up, [Troubleshooting](/docs/troubleshooting) starts from the symptom. - -## Updating and removing - -The web console's **Host → Updates** card tells you when a newer host is out and shows the exact -command for the way you installed — the full list, plus one-click updating and how to turn the check -off, is on [Updating the Host](/docs/updating). - -To take it back off, see [Uninstall](/docs/uninstall) — it covers every install method and what is -deliberately left behind (your `~/.config/punktfunk` — identity certificate, paired devices, console -password — survives package removal). - -## Building from source - -If no package exists for your platform, you can build from source — see the repository README. Source -builds are a fallback; the registries are the supported path. diff --git a/docs-site/content/docs/install.mdx b/docs-site/content/docs/install.mdx new file mode 100644 index 000000000..d67b04926 --- /dev/null +++ b/docs-site/content/docs/install.mdx @@ -0,0 +1,39 @@ +--- +title: Install the Host +description: Pick the system you stream from — each guide is one page with the install command, the group to join, and how to start the host. +--- + +import { Cards, Card } from 'fumadocs-ui/components/card' + +The **host** is the program on the PC you stream *from*. It installs from Punktfunk's own package +repositories on Linux and from a signed installer on Windows — pick your system: + + + + + + + + + + + + +Streaming **to** a device instead? That's a client — see [Install a Client](/docs/install-client). + +## Good to know + +- **Read [Security & Safe Use](/docs/security) once.** A streaming host is remote control of the + machine: keep it on your LAN or a VPN, and keep pairing on (the default). +- **Already running Sunshine, Apollo or Vibeshine?** They can coexist, with one port moved — see + [Switching from Sunshine](/docs/switching-from-sunshine) before you install. +- **What gets installed.** `punktfunk-host` is the host; `punktfunk-web` is the browser console + (pairing, status, library — apt and RPM pull it in automatically, on Arch you name it yourself, + the Bazzite image contains it); `punktfunk-scripting` is the plugin runner that brings your + game launchers into the [library](/docs/game-library) (started for you everywhere except Arch and + source builds — `systemctl --user enable --now punktfunk-scripting` there). +- **Stable by default.** Every guide points at the **stable** channel, which moves on `vX.Y.Z` + releases. The latest `main` build is the **canary** channel — [Release Channels](/docs/channels). +- **Updating and removing** are one page each: [Updating the Host](/docs/updating) (the console + tells you when a newer build is out) and [Uninstall](/docs/uninstall). +- **No package for your platform?** [Build from source](/docs/build-from-source). diff --git a/docs-site/content/docs/kde.md b/docs-site/content/docs/kde.md index 8c5c1b2b9..cc46bc3de 100644 --- a/docs-site/content/docs/kde.md +++ b/docs-site/content/docs/kde.md @@ -95,9 +95,8 @@ systemctl --user enable --now punktfunk-host journalctl --user -u punktfunk-host -f # watch it come up and print its identity fingerprint ``` -This unit runs `serve --gamestream`, so it serves stock [Moonlight](/docs/moonlight) clients as well -as the native ones. For a native-only host, see -[What the unit starts](/docs/running-as-a-service#what-the-unit-starts). +This unit runs the secure native-only host; to serve stock [Moonlight](/docs/moonlight) clients as +well, see [What the unit starts](/docs/running-as-a-service#what-the-unit-starts). A desktop-login host should also follow your session's lifetime, or restarting Plasma leaves the host wired to a compositor that is gone — it keeps answering, and every session after that fails at diff --git a/docs-site/content/docs/meta.json b/docs-site/content/docs/meta.json index 5c42dd38c..650a49fe3 100644 --- a/docs-site/content/docs/meta.json +++ b/docs-site/content/docs/meta.json @@ -1,13 +1,9 @@ { "title": "Documentation", "pages": [ + "---Get started---", "index", - "how-it-works", - "security", "quickstart", - "support-matrix", - "---Install the host---", - "requirements", "install", "ubuntu", "debian", @@ -15,48 +11,55 @@ "arch", "bazzite", "steamos-host", + "nixos", "windows-host", + "install-client", + "pairing", + "---Guides---", "web-console", + "clients", + "steam-deck", + "moonlight", + "game-library", + "plugins", + "virtual-displays", + "hdr", + "pyrowave", + "input", + "controller-audio", + "clipboard", + "client-settings", + "profiles-and-links", + "wake-on-lan", + "access-levels", + "automation", + "running-as-a-service", "updating", "uninstall", - "---Configure your desktop---", + "---Switching from Sunshine---", + "switching-from-sunshine", + "---Reference---", + "requirements", + "support-matrix", + "how-it-works", + "security", "configuration", + "host-cli", + "ports", "kde", "gnome", "gamescope", "hyprland", "sway", - "running-as-a-service", - "virtual-displays", - "hdr", - "pyrowave", - "host-cli", - "automation", - "plugins", - "---Connecting---", - "clients", - "install-client", - "pairing", - "access-levels", - "steam-deck", - "moonlight", - "---Using Punktfunk---", - "input", - "controller-audio", - "client-settings", - "profiles-and-links", - "game-library", - "clipboard", - "wake-on-lan", + "build-from-source", + "channels", + "roadmap", + "[API Reference](/api)", "---Troubleshooting---", "troubleshooting", "echo", "stats", "forgot-password", - "---Project---", - "roadmap", - "channels", - "---Reference---", - "[API Reference](/api)" + "status" ] } diff --git a/docs-site/content/docs/moonlight.md b/docs-site/content/docs/moonlight.md index 9bbd42bf6..6c1aaf469 100644 --- a/docs-site/content/docs/moonlight.md +++ b/docs-site/content/docs/moonlight.md @@ -4,8 +4,8 @@ description: Stream from a Punktfunk host using any Moonlight client. --- Punktfunk speaks the **GameStream** protocol, so [Moonlight](https://moonlight-stream.org/) connects -to it like it would to any GameStream host — no punktfunk-specific app needed. It's a great option for -a browser, a smart TV, or any device without a native client. +to it like any GameStream host — no punktfunk-specific app needed. A good option for a browser, a +smart TV, or any device without a native client. > Many platforms also have a **native Punktfunk client** with lower latency and built-in > discovery/pairing — including **Windows** and **Android** (phone and Android TV). See @@ -41,15 +41,15 @@ the secure native-only host): ``` (Bare `serve` is the secure native-only default and stock Moonlight clients can't connect to it; the -native plane is always on, and `--gamestream` adds the Moonlight-compat surface.) GameStream pairs over -plain HTTP and its legacy control encryption is weaker than the native plane's, so only enable it on a -**trusted LAN**. See [Running as a Service](/docs/running-as-a-service) for the bundled unit. The host -advertises itself on the network, so Moonlight usually finds it on its own. +native plane is always on, and `--gamestream` adds the Moonlight-compat surface.) GameStream pairs +over plain HTTP and its legacy control encryption is weaker than the native plane's, so only enable +it on a **trusted LAN**. See [Running as a Service](/docs/running-as-a-service) for the bundled +unit. The host advertises itself on the network, so Moonlight usually finds it on its own. ## 2. Add the host in Moonlight -Open Moonlight. Your host should appear automatically on the same network. If it doesn't, use **Add -Host manually** and enter the host machine's IP address. +Open Moonlight. Your host should appear automatically on the same network. If not, use **Add Host +manually** and enter the host machine's IP address. Still nothing? Two causes account for almost all of it: @@ -74,7 +74,7 @@ Both are covered in more detail in [Troubleshooting](/docs/troubleshooting). ## 3. Pair Moonlight's PIN is typed in on the **host** side, so you need the host's -[web console](/docs/web-console) running — it's the only UI where a Moonlight PIN can be entered. +[web console](/docs/web-console) running — the only UI where a Moonlight PIN can be entered. 1. Open the console at `https://:47992` and go to **Pairing**. 2. In Moonlight, select the host and choose **Pair** — it shows a 4-digit PIN. diff --git a/docs-site/content/docs/nixos.mdx b/docs-site/content/docs/nixos.mdx new file mode 100644 index 000000000..8b12f489f --- /dev/null +++ b/docs-site/content/docs/nixos.mdx @@ -0,0 +1,76 @@ +--- +title: NixOS +description: Install the Punktfunk host on NixOS from the repo's flake — add the binary cache, import the module, enable the host. +--- + +The repo's `flake.nix` builds `punktfunk-host`, `punktfunk-client`, `punktfunk-web` and +`punktfunk-scripting` and ships a NixOS module. **`x86_64-linux` only**, NixOS **24.11 or newer**. + +## 1. Add the binary cache + +Without it a build compiles the whole Rust workspace *and* gamescope from source — about an hour. +With it you get prebuilt binaries: + +```nix +nix.settings = { + substituters = [ "https://nix.unom.io" ]; + trusted-public-keys = [ "punktfunk-cache-1:yhOJmHxzg6tzXpxSFzlYn6Pc6r0jHprsWqt8MZC654o=" ]; # curl https://nix.unom.io/punktfunk-cache.pub +}; +``` + +Off NixOS, put the same two values in `/etc/nix/nix.conf` as `extra-substituters` / +`extra-trusted-public-keys`. One trap: setting `inputs.punktfunk.inputs.nixpkgs.follows = "nixpkgs"` +changes every store path and misses the cache entirely — +[packaging/nix](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/nix/README.md#binary-cache-do-this-before-your-first-build). + +## 2. Import the module and enable the host + + + +A typical host block: + +```nix +services.punktfunk.host = { + enable = true; + users = [ "alice" ]; # added to the `input` group, for virtual gamepads + openFirewall = true; + desktopSession = true; # on a machine you log into — see below + settings = { RUST_LOG = "info"; }; # these become host.env +}; +``` + +The module does declaratively what the deb/RPM scriptlets do — the systemd user services, udev +rules, kernel modules, sysctl tuning, the firewall ports and `input` group membership — and brings +the web console in alongside the host. `settings` writes `host.env` for you. + +**Set `desktopSession = true` on any machine somebody logs into.** It ties the host to +`graphical-session.target`, so restarting Plasma or GNOME restarts the host with it; without it the +host keeps running against a compositor that no longer exists and fails capture on every later +session. Leave it off for the headless appliance route (a pinned compositor or a gamescope box), +which may never reach that target — same reasoning as +[Restart the host with your desktop](/docs/running-as-a-service#restart-the-host-with-your-desktop). + +## 3. Start it + +The host and console user services are defined but not started (set `autoStart = true` for an +appliance). From your graphical session: + +```sh +systemctl --user enable --now punktfunk-host punktfunk-web +``` + +The plugin runner needs no such step — the module starts it, because the game-library scanners ship +as plugins (`services.punktfunk.scripting.autoStart = false;` to opt out). + +**That's the install.** Continue with the [Quick Start from step 3](/docs/quickstart#3-open-the-web-console) +— open the console, pair a client, stream. + +## When you want more + +- **Run it without NixOS** (other distros, wrapped in [nixGL](https://github.com/nix-community/nixGL) + so the GPU drivers resolve): `nix run git+https://git.unom.io/unom/punktfunk#punktfunk-host -- serve`. +- The full option reference — client, console and scripting options, GPU driver notes, headless + appliance setup — is in + [packaging/nix](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/nix/README.md). +- Updating: `nix flake update punktfunk` in your flake directory, then `sudo nixos-rebuild switch` + ([Updating](/docs/updating)). Removing: [Uninstall](/docs/uninstall#nixos). diff --git a/docs-site/content/docs/pairing.md b/docs-site/content/docs/pairing.md index e79fab08f..d44c9154b 100644 --- a/docs-site/content/docs/pairing.md +++ b/docs-site/content/docs/pairing.md @@ -1,165 +1,65 @@ --- title: Pairing & Trust -description: How a client and host establish trust — PIN pairing once, pinned reconnects after. +description: Admit a new device once — approve it from the web console or type a PIN — and it reconnects on its own from then on. --- -Punktfunk has no accounts and no cloud. Trust is established directly between a client and a host, -on your network, with a one-time pairing — either an **approval click in the host's -[web console](/docs/web-console)** or a **PIN ceremony**. After that, the device reconnects -automatically on a pinned cryptographic identity. +Punktfunk has no accounts and no cloud. A new device is let in **once**, by you, on your network; +after that it reconnects automatically on a pinned identity, and the host lists it until you remove +it. There are two ways to let a device in. -## How it works +## Approve it from the console (no PIN) -- Each host has a stable **identity** (a certificate). Clients remember its fingerprint, so they know - they're talking to the same host next time. -- The first time a client connects, you **pair** it: with the native protocol the **host** shows a - short **4-digit PIN** and you type it into the client. (With Moonlight it runs the other way round - — Moonlight shows the PIN and you type it into the host's console.) Either way a secure exchange - (SPAKE2) binds the two identities, and an attacker who doesn't know the PIN gets a single online - guess — no offline cracking. -- After pairing, the host stores the client's identity in its allow-list, and the client stores the - host's fingerprint. Reconnects are automatic — no PIN. Having seen the host on your network while - it was awake also teaches the client its MAC address, so a later connect can - [wake it from sleep](/docs/wake-on-lan). +The fastest way: just **connect** from the new device. The attempt shows up in the +[web console](/docs/web-console) under **Pairing → Waiting for approval**, with the device's name +and fingerprint. Click **Approve** — optionally give it a label like "Living Room TV" — and it's +paired on the spot; its next connect goes straight through. -## Approving a device from the console (no PIN) +**Deny** only dismisses the request (it can knock again; it isn't a blocklist). Requests expire on +their own after 10 minutes. -The fastest way to admit a new device: just **try to connect** from it. On a pairing-required host, -the attempt shows up in the web console's Pairing page under **Waiting for approval** — with the -device's name and identity fingerprint. Click **Approve** (and optionally give it a label like -"Living Room TV"), and the device is paired on the spot: its next connect goes straight through. No -PIN to read or type. +## Pair with a PIN -**Deny** just dismisses the request (the device can knock again later — it's "not now", not a -blocklist). Requests expire on their own after **10 minutes**. +When you're at the device and the console isn't handy, or for the very first device: in the console +open **Pairing** and click **Pair a device**. The host shows a **4-digit PIN** and counts down a +2-minute window — type the PIN into the client: -This works because approval happens on the host's authenticated management surface — only someone -with console access can admit a device. +- **Native apps (Apple, Linux, Windows, Android):** select the host, or *Pair with PIN…* from its + menu, and enter it. +- **Steam Deck (Decky plugin):** pick the host in the Quick Access panel — an unpaired one offers + **Request access** (the console approval above) or **Use a PIN instead**. +- **Moonlight:** it runs the other way round — Moonlight shows a PIN, and you type it into the + console's **Moonlight (GameStream) pairing** card. Arming doesn't apply. (Moonlight needs + [GameStream compat on](/docs/moonlight) first.) + +If the window lapses, arm it again. A `punktfunk://` link can't pair for you — it only starts a +stream on a host this device already trusts. ## Choosing access when you admit a device -Admitting a device and deciding what it may do are **one dialog**, not two trips. The Approve -dialog carries, alongside the device's name: +Approving and deciding what the device may do are one dialog. Alongside the name: -- **Access level** — **Full control**, **Controller only**, or **View only**, with an **Advanced** - expander for the individual toggles. See [Access levels](/docs/access-levels) for exactly what - each covers. -- **Expires** — **Forever**, or **1 h / 4 h / 8 h / custom**. +- **Access level** — **Full control**, **Controller only**, or **View only** (an **Advanced** + expander has the individual toggles — [Access levels](/docs/access-levels)). +- **Expires** — **Forever**, or 1 h / 4 h / 8 h / custom. -The defaults are **Full control · Forever** — approving your *own* new laptop is the common case, -and it should behave like pairing always has. The guest case gets its own button instead: a -distinct secondary action, **"Approve as guest"**, admits the device as **Controller only for -4 hours** in one click. The whole co-play flow is: your friend installs a client, connects, the -knock appears in your console, you tap **Approve as guest** — and their pad lights up as -controller #2. When the evening's over, the access expires on its own. - -The same two controls appear on the **arm pairing** card for the PIN path: whatever access and -expiry you choose while arming applies to whichever device completes the PIN ceremony in that -window. - -Everything here can be changed later from the Paired devices table — see -[Managing paired devices](#managing-paired-devices) below. - -## Pairing with a PIN - -PIN pairing is the **default and required** path for any new host: unless the host has explicitly -opted into trust-on-first-use (see below), a client connecting to an unknown host must complete the -PIN ceremony — or be approved from the console, as above — before it can stream. It's the right path -for the *first* device (before the console has admitted anything) or when you're at the client and -the console isn't handy. - -Pairing has to be **armed** on the host before a client can pair (so a random device can't pair -itself). On the production host (`serve`), this is done from the **web console**: open the -host's management console, click to arm pairing, and the host displays a 4-digit PIN along with the -list of paired devices. This works on a headless host over the network — there is no command-line flag -to arm pairing on `serve`. - -The armed window lasts **2 minutes** — the console counts it down under the PIN and offers a -**Cancel** button. Arm it once you're standing at the device; if it lapses, just arm it again. - -Pairing from the console needs the console running. On Linux that's the separate `punktfunk-web` -systemd user unit, which you enable once — see [The Web Console](/docs/web-console). - -Then, on the client: - -- **[Native clients](/docs/clients) (Apple, Linux, Windows, Android):** select the host (or use - *Pair with PIN…* from its menu) and enter the PIN the host displays. -- **[Steam Deck](/docs/steam-deck) (the Decky plugin):** open Punktfunk from the Quick Access menu - and pick the host — an unpaired one opens a sheet offering **Request access** (no PIN: somebody - approves the Deck at the host) or **Use a PIN instead**, which opens the 4-digit pad. -- **[Moonlight](/docs/moonlight):** choose **Pair**; Moonlight shows a 4-digit PIN, and you type - that PIN into the console's **Moonlight (GameStream) pairing** card and press **Submit PIN**. - (This direction is the reverse of the native flow, and arming doesn't apply to it.) - -A link can't stand in for any of this. A -[`punktfunk://` link](/docs/profiles-and-links#what-a-link-can-and-cant-do) starts a stream on a -host this device already trusts; `punktfunk://pair/…` is refused outright, and a link naming a host -you've never paired with can at most open the app's own trust prompt. - -### Pairing from a terminal - -On Linux the client package also installs `punktfunk`, a headless CLI. Arm pairing in the console, -read the PIN, then run: - -```sh -punktfunk pair 192.168.1.50 --pin 1234 --name "Living Room" -``` - -It prints `paired : fp=` and saves the host in the same store the desktop -client uses, so later connects are silent. `--name` is the label the host files this device under -(default: this machine's name), and the port defaults to **9777** — write `host:port` to use another -one. Without `--pin` the command asks for one; in a script with no terminal it exits **6** rather -than hanging, and exit **3** means the host refused or the PIN was wrong. - -The GTK client can do the same thing without opening a window: - -```sh -punktfunk-client --connect 192.168.1.50:9777 --pair 1234 --name "Living Room" -``` - -## Requiring pairing (the default) - -By default, the native host **requires** pairing — only devices that have paired can stream. This is -the right setting on a shared network: a device has to complete the PIN ceremony once before it can -connect. - -If you're on a fully trusted single-user network and want to skip pairing, run the host open with -`serve --open` — it then advertises `pair=optional` and accepts unpaired clients. Requiring pairing -is strongly recommended. - -## Trust-on-first-use (host opt-in) - -Trust-on-first-use (TOFU) is **off by default** and is an explicit *host* opt-in for fully trusted -networks. A host enables it by running open — `serve --open` — which makes it advertise -`pair=optional` over mDNS and accept unpaired clients. Only then does a client offer the -TOFU path: connecting to such a host for the first time shows the host's fingerprint and asks you to -confirm it (compare it with the one the host logged at startup), then pins it. The client presents -this clearly as the reduced-security option, alongside **Pair with PIN**. - -> **Warning:** TOFU cannot detect an impostor on the first connection — if someone is impersonating -> the host the very first time you connect, you'll pin the attacker's fingerprint. PIN pairing closes -> that gap (the SPAKE2 ceremony binds both identities), which is why it's the default. Use TOFU only -> on a network you fully trust — see [Security & Safe Use](/docs/security). - -For every other case — a host advertising `pair=required` (the default), a host you typed in by hand, -or a discovered host whose pair policy is unknown — TOFU is not offered and the client routes straight -to the PIN ceremony. - -Once a host is pinned, a fingerprint change is treated as the impostor signal: the client forces -re-pairing through the PIN ceremony rather than offering to re-trust the new identity. +Defaults are *Full control · Forever* — right for your own new laptop. For a friend's device there +is a one-click **Approve as guest**: Controller only, for 4 hours, then it expires on its own. The +same two controls sit on the **Pair a device** card, and apply to whichever device completes the PIN. ## Managing paired devices -The [web console](/docs/web-console) lists every paired device and lets you remove one (revoking its -access). Each row also shows the device's **Access** — the preset and, for temporary grants, a live -countdown ("Controller · 2 h left") — and an edit sheet to change the level, extend or cut the -expiry, or expire it right now; edits reach a live session immediately. See -[Access levels](/docs/access-levels). Re-pairing is just the PIN ceremony again — and it keeps the -device's existing access rather than widening it. +The console lists every paired device with its access (and a live countdown for temporary grants). +From there you can change the level, extend or cut the expiry, or **remove** the device — removing +revokes it immediately, even mid-session. Re-pairing a removed device is just the PIN ceremony again. -If a client can't pair at all, see [Troubleshooting → Pairing is -rejected](/docs/troubleshooting#pairing-is-rejected--the-client-cant-connect). +Can't pair at all? [Troubleshooting → Pairing is rejected](/docs/troubleshooting#pairing-is-rejected--the-client-cant-connect). -(There is also a developer/measurement host, `punktfunk-host punktfunk1-host` — a subcommand of the -same binary, not the host you install. It has its own `--allow-tofu` / `--pairing-pin` flags for test -harnesses; nothing on this page applies to it.) +## How it works, briefly + +Each host has a stable identity (a certificate); clients pin its fingerprint, the host stores the +client's. The PIN ceremony is SPAKE2, so someone who doesn't know the PIN gets one online guess and +no offline attack. A host whose fingerprint changes is treated as an impostor — the client forces a +fresh PIN ceremony rather than re-trusting it. Pairing is **required by default**; the reduced-security +alternatives (`serve --open`, trust-on-first-use) exist for fully trusted single-user networks and +are covered in [Security & Safe Use](/docs/security#pairing-policy-open-hosts-and-trust-on-first-use). +Scripts can pair from a terminal — [the `punktfunk` CLI](/docs/clients#scripting-the-punktfunk-cli). diff --git a/docs-site/content/docs/plugins.mdx b/docs-site/content/docs/plugins.mdx index 84dc5bce1..aad5d2d14 100644 --- a/docs-site/content/docs/plugins.mdx +++ b/docs-site/content/docs/plugins.mdx @@ -7,8 +7,8 @@ Plugins extend the host through the **scripting runner** (see [Events & hooks](/ plugin runs alongside the host, reconciles titles into your [game library](/docs/game-library) as a provider — so they appear in the grid on every client — and can add its own page to the [web console](/docs/web-console). -**Your game library is built from plugins.** From v0.28.0 the host scans no launcher on its own, so -install the one for each launcher you use — that is what puts games in the grid. +**Your game library is built from plugins.** From v0.28.0 the host scans no launcher on its own; +install the one for each launcher you use. | Plugin | Host | What it does | |---|---|---| @@ -23,37 +23,33 @@ install the one for each launcher you use — that is what puts games in the gri ## Installing from the console -Every plugin runs inside the **plugin runner**, a separate service that is off until you switch it -on. Installing a plugin while it's off succeeds and starts nothing, so do this first: +Every plugin runs inside the **plugin runner**, a separate service. Installing a plugin while it's +off succeeds and starts nothing, so check it first: 1. Open the [web console](/docs/web-console) → **Plugins** → **Installed** and look at the **Plugin - runner** card. If it says *Not installed*, install the runner package first (see - [Troubleshooting](#troubleshooting) below). If it says *Disabled*, press **Enable runner** — - once per host. *Running* means you're set; *Stopped* means the runner is enabled but not up right - now, and its log says why (see [Troubleshooting](#troubleshooting)). + runner** card. *Not installed*: install the runner package first (see + [Troubleshooting](#troubleshooting)). *Disabled*: press **Enable runner** — once per host. + *Running* means you're set; *Stopped* means the runner is enabled but not up right now, and its + log says why (see [Troubleshooting](#troubleshooting)). 2. Go to **Browse**, pick a plugin from the catalog and confirm. The host installs it and restarts the runner, and the plugin's own page appears in the console's nav. **Sources** is the third tab: where catalogs come from. -The rest of this page covers the CLI, which does the same thing, and the trust model behind the -badges — worth reading once, because a plugin runs with the same privileges as the host. - ### What "Verified" means -Every catalogued plugin pins **one exact version** and that version's package hash. A verified entry -means somebody at unom reviewed *that exact package* — not the project in general, and not whatever -it publishes next. When a plugin releases a new version, the store keeps offering the reviewed one -until the new release is reviewed too. Before anything is downloaded, the host re-checks the pinned -hash against the registry, so a package that was quietly republished under the same version number -is refused rather than installed. +Every catalogued plugin pins **one exact version** and its package hash. **Verified** means somebody +at unom reviewed *that exact package* — not the project in general, not whatever it publishes next; +when a plugin releases a new version, the store keeps offering the reviewed one until the new release +is reviewed too. Before anything is downloaded, the host re-checks the pinned hash against the +registry, so a package quietly republished under the same version number is refused. A catalog can also **revoke** a version. When an advisory covers an entry, the console shows the -reason against it and won't install that version — on **Browse** as a red-ringed panel with the -install button disabled, on **Installed** as a warning against the plugin you already have. It -never removes running code for you; that stays your decision. +reason and won't install that version — on **Browse** as a red-ringed panel with the install button +disabled, on **Installed** as a warning against the plugin you already have. It never removes +running code for you. -Three things a plugin can be: +The three badges: | Badge | Where it came from | |---|---| @@ -67,8 +63,8 @@ Three things a plugin can be: `ed25519:…` public key — with a key set, the host refuses any index from that source that isn't correctly signed, rather than falling back to an unsigned one. -Adding a source is a trust decision you make once: its plugins become installable on this host. -They are always attributed to it and never carry the Verified badge, which belongs to the built-in +Adding a source is a trust decision you make once: its plugins become installable on this host, +always attributed to it and never carrying the Verified badge, which belongs to the built-in catalog alone. To publish a plugin to the built-in catalog, open a pull request against @@ -111,15 +107,13 @@ the full path: `& "$env:ProgramFiles\punktfunk\punktfunk-host.exe" plugins add p -Open the [web console](/docs/web-console) and the plugin's page appears in the nav automatically — -that's the whole install. +The plugin's page then appears in the [web console](/docs/web-console)'s nav automatically. The runner is **on by default** on a new install — your game sources are plugins, so a host without -it would show an empty library. (On a host that predates this, it stays however you left it; turn it -on with `punktfunk-host plugins enable`, which you only need once.) The runner discovers plugins -when it starts, so one installed later needs a restart to come up -(`systemctl --user restart punktfunk-scripting`, or `Restart` the `PunktfunkScripting` task) — the -console does that restart for you as part of installing. +it would show an empty library. (A host that predates this stays however you left it; turn it on +with `punktfunk-host plugins enable`, once.) The runner discovers plugins when it starts, so one +installed later needs a restart (`systemctl --user restart punktfunk-scripting`, or `Restart` the +`PunktfunkScripting` task) — the console does that restart for you as part of installing. Don't want it? It is a normal service you can switch off: `systemctl --user mask punktfunk-scripting` on Linux, or disable the `PunktfunkScripting` scheduled task on Windows. Your host keeps streaming; @@ -143,8 +137,7 @@ and it carries its catalog badge instead. A bare name resolves to the first-party package — `playnite` installs `@punktfunk/plugin-playnite`, always from Punktfunk's own package registry. Any other name (`punktfunk-plugin-*`, a foreign `@scope/pkg`) would install from the **public npm registry** and is refused unless you add -`--allow-public-registry` — a guard against typos and look-alike packages pulling untrusted code -onto your host. +`--allow-public-registry` — a guard against typos and look-alike packages. > Plugins are operator-installed code with operator privileges — they can launch games and run > commands. Install only plugins you trust, from a registry you control. @@ -155,17 +148,16 @@ onto your host. catalog pins a newer version than the one you have; it installs through the same confirmation as a fresh install, and the console restarts the runner for you. There is no `plugins update` command — from a terminal, re-run `punktfunk-host plugins add `. That installs the newest version the -package registry has rather than the version the catalog pins, and it does **not** restart the -runner: restart it yourself so the new code is picked up -(`systemctl --user restart punktfunk-scripting`, or `Restart` the `PunktfunkScripting` task). +package registry has rather than the version the catalog pins, and does **not** restart the runner: +restart it yourself (`systemctl --user restart punktfunk-scripting`, or `Restart` the +`PunktfunkScripting` task) so the new code is picked up. **Remove.** The **Uninstall** (bin) button on the **Installed** tab removes the package *and* restarts the runner, so the plugin stops straight away. `punktfunk-host plugins remove ` -removes the package only — restart the runner yourself, as above, to stop a plugin that is still -running. +removes the package only — restart the runner yourself, as above, to stop a plugin still running. -Uninstalling removes the package and nothing else. A plugin's own config and cache stay where it -wrote them — `~/.config/punktfunk/plugin-state//` on Linux, +Uninstalling removes the package only; a plugin's own config and cache stay where it wrote them — +`~/.config/punktfunk/plugin-state//` on Linux, `%ProgramData%\punktfunk\plugin-state\\` on Windows — so re-installing later picks your settings back up. Delete that directory yourself if you want it gone. @@ -233,8 +225,8 @@ GOG, Epic, Xbox, itch, emulators, manually-added games — everything Playnite m library. Launching a title hands it back to Playnite, which performs the real launch, so there are no per-store launch commands to maintain. Covers are served by the host, so it scales to large libraries. -Playnite is Windows-only, so both halves of this one live on the **Windows host**. Because Playnite -keeps its library locked while running, there are **two parts**: +Playnite is Windows-only, so both halves live on the **Windows host**. Because Playnite keeps its +library locked while running, there are **two parts**: 1. **The plugin** — from an elevated PowerShell: @@ -255,9 +247,9 @@ seconds of any library change. Filters (installed-only, per-store, hidden) live ## VirtualHere (USB passthrough) `@punktfunk/plugin-virtualhere` — hands a **physical USB device** on your couch machine to the -host while you play, and gives it back afterwards. The game sees the real device, so this is the -answer for a racing wheel, a HOTAS, pedals, an arcade stick, or any controller whose value is that -it is not emulated. +host while you play, and gives it back afterwards. The game sees the real device — the answer for a +racing wheel, a HOTAS, pedals, an arcade stick, or any controller whose value is that it is not +emulated. This plugin drives [VirtualHere](https://www.virtualhere.com/), a commercial USB-over-IP product @@ -273,8 +265,7 @@ You need both halves of VirtualHere running before the plugin is any use: - **The USB Client on the host**, ideally installed as a service so it survives logging out. Servers exist for Windows, Linux, macOS and Android couches. **There is no VirtualHere server for -iOS or tvOS**, so iPhones, iPads and Apple TVs cannot pass devices through — nothing on the -Punktfunk side can change that. +iOS or tvOS**, so iPhones, iPads and Apple TVs cannot pass devices through. ```sh punktfunk-host plugins add virtualhere @@ -288,8 +279,8 @@ starts and returned when it stops, so the couch keeps its own controller the res you can widen that to the whole session, or to the entire time a client is connected. If nothing happens, the **Diagnostics** tab walks the whole two-sided setup and tells you which -part to fix. The same checks are available as `punktfunk-plugin-virtualhere doctor`, which is the -useful thing to paste into a support thread. +part to fix. The same checks run as `punktfunk-plugin-virtualhere doctor` — the useful thing to +paste into a support thread. Full configuration is in [the plugin's repo](https://git.unom.io/unom/punktfunk-plugin-virtualhere). @@ -311,10 +302,10 @@ default whenever the host is). On Windows, re-run the installer and keep the scr If the runner *is* installed and the host still says it isn't, the host could not find the `punktfunk-scripting` executable. It looks beside its own binary, then on `PATH`, then in the packaged `/usr` and `~/.local` layouts — so a runner installed somewhere else needs -`PUNKTFUNK_SCRIPTING` pointed at it (see [Configuration](/docs/configuration)). Note that the -console installs plugins from inside the host *service*, whose `PATH` is usually much shorter than -your shell's: a runner that `punktfunk-host plugins add` finds and the console does not is that -difference, and the env var is the fix. +`PUNKTFUNK_SCRIPTING` pointed at it (see [Configuration](/docs/configuration)). The console installs +plugins from inside the host *service*, whose `PATH` is usually much shorter than your shell's: a +runner that `punktfunk-host plugins add` finds and the console does not is that difference, and the +env var is the fix. **Where a plugin's log output goes** — the console's **Logs** page, under the **Plugins** filter. The runner ships everything your plugins print to the host, so a plugin's own lines sit next to the @@ -327,12 +318,11 @@ with `punktfunk-host plugins status`. Nothing is lost if the host is down: the runner keeps buffering and sends the backlog when the host -comes back. It says so in the log if the buffer overflowed, rather than presenting a gap as -continuity. +comes back, and says so in the log if the buffer overflowed. -**Reading the runner's log directly** — rarely needed now, but it is the ground truth if the runner -can't reach the host at all: +**Reading the runner's log directly** — rarely needed, but the ground truth if the runner can't +reach the host at all: @@ -345,9 +335,9 @@ journalctl --user -u punktfunk-scripting -f The runner task writes its output to `%ProgramData%\punktfunk\plugin-state\runner.log` (the -previous run is kept as `runner.log.1`). This is the file to read — or send — when the console's -Plugins view stays empty although the runner is running: everything the runner and its plugins -printed lands here even when they can't reach the host. +previous run is kept as `runner.log.1`). Read — or send — this file when the console's Plugins view +stays empty although the runner is running: everything the runner and its plugins printed lands +here even when they can't reach the host. ```powershell Get-Content "$env:ProgramData\punktfunk\plugin-state\runner.log" -Tail 100 @@ -367,10 +357,9 @@ foreground (stop it with Ctrl+C): **A plugin can't reach a service running on the same box (Linux)** — plugins that drive a local daemon usually talk to it over a socket or FIFO in `/tmp`. The runner's unit shipped with -`PrivateTmp=yes` in earlier releases, which gave it a private `/tmp` and hid all of it: the plugin would -launch the vendor's binary happily and then time out reaching the daemon behind it, while the same -command worked perfectly in your own shell. If you are on an older host, or you have a drop-in that -reinstates it, put the real `/tmp` back: +`PrivateTmp=yes` in earlier releases, hiding the real `/tmp`: the plugin would launch the vendor's +binary and then time out reaching the daemon, while the same command worked in your own shell. On an +older host, or with a drop-in that reinstates it, put the real `/tmp` back: ```sh systemctl --user edit punktfunk-scripting diff --git a/docs-site/content/docs/ports.mdx b/docs-site/content/docs/ports.mdx new file mode 100644 index 000000000..085615242 --- /dev/null +++ b/docs-site/content/docs/ports.mdx @@ -0,0 +1,55 @@ +--- +title: Ports & firewall +description: Every port a Punktfunk host and its console use, which firewall profile opens each, and what to do with a host behind ufw or firewalld. +--- + +A Linux package **never edits your firewall** — it installs service definitions for **firewalld** and +application profiles for **ufw**, and you enable the ones you need once. The table is the single +list; the install guides quote the one or two lines that apply to each distro. + + + +- **`punktfunk-native`** is all a default host needs: the packaged unit runs the secure native-only + host, so paired Punktfunk clients, discovery and the management API are covered by this one + profile. +- **`punktfunk-web`** only matters if you open the console from *another* device (both console + ports are in it; plugin interfaces are served on the second one — [Two ports, not + one](/docs/web-console#two-ports-not-one)). +- **`punktfunk-gamestream`** is needed only once you turn on Moonlight compat + (`PUNKTFUNK_GAMESTREAM=1` in `host.env` — [Moonlight](/docs/moonlight)). +- **Video needs nothing opened.** The data plane uses an ephemeral UDP port the *client* opens with a + hole-punch; the host streams back through the path the client opened, so only outbound UDP has to + be allowed (the default in both ufw and firewalld). + +## Enabling the profiles + +```sh +# firewalld (Fedora, Bazzite, EndeavourOS, …) +sudo firewall-cmd --reload # load the definitions the package installed +sudo firewall-cmd --permanent --add-service=punktfunk-native --add-service=punktfunk-web +sudo firewall-cmd --permanent --add-service=punktfunk-gamestream # only with Moonlight compat on +sudo firewall-cmd --reload + +# ufw (CachyOS; Ubuntu and Debian if you enabled it) +sudo ufw allow punktfunk-native +sudo ufw allow punktfunk-web +sudo ufw allow punktfunk-gamestream # only with Moonlight compat on +``` + +Stock Arch and Debian ship no firewall; Ubuntu installs ufw but leaves it inactive; Fedora, Bazzite +and most Fedora-family spins run firewalld; CachyOS enables ufw. On **NixOS** the module's +`openFirewall = true` does all of this; on **Windows** the installer registers the rules. + +## Moving a port + +Two are configurable, and both are how you share a machine with another streaming host — see +[Switching from Sunshine](/docs/switching-from-sunshine): + +- The **management API** via `PUNKTFUNK_MGMT_BIND` (`IP:PORT`; `127.0.0.1:…` also keeps it off the + LAN, at the cost of paired clients browsing your library). Clients relearn the port from + discovery; the console, the plugin runner and the tray read it from + `~/.config/punktfunk/mgmt-endpoint`, which the host rewrites on every start. +- The **native control port** via `PUNKTFUNK_NATIVE_PORT`. Clients discover it over mDNS; a host + added by hand keeps the port it was added with. + +Both live in `host.env` — [Configuration](/docs/configuration). diff --git a/docs-site/content/docs/profiles-and-links.md b/docs-site/content/docs/profiles-and-links.md index b6203d463..73790fe2c 100644 --- a/docs-site/content/docs/profiles-and-links.md +++ b/docs-site/content/docs/profiles-and-links.md @@ -3,87 +3,80 @@ title: Profiles and links description: How settings profiles override your client defaults per host or per connect, and how punktfunk:// links start a stream from a shortcut, a script or a browser. --- -Two features that landed together in 0.22.0 and work with each other: **settings profiles**, named -bundles of stream settings you can attach to a host, and **`punktfunk://` links**, URLs that start a -stream you have already set up. +Two features that landed together in 0.22.0: **settings profiles** — named bundles of stream +settings you attach to a host — and **`punktfunk://` links** — URLs that start a stream you have +already set up. -Both live in the client apps — the Apple app, the Linux GTK client, the Windows client and the -Android app. Neither exists in the host's [web console](/docs/web-console). +Both live in the client apps (Apple, Linux GTK, Windows, Android), not in the host's +[web console](/docs/web-console). -The controller-driven surfaces are a half-exception: Apple TV, the Android app's console mode and -the Steam Deck console the Decky plugin launches all *use* the profile a host is bound to and can -pin one as its own card, but none of them can create or edit one. Do that on a desktop or a phone -first. The Decky panel itself only *shows* those pins, nested under their host as one-tap cards. +The controller-driven surfaces — Apple TV, the Android app's console mode and the Steam Deck console +the Decky plugin launches — *use* the profile a host is bound to and can pin one as its own card, +but cannot create or edit one; do that on a desktop or phone first. The Decky panel only *shows* +those pins, nested under their host as one-tap cards. ## What a profile is -A profile is a *sparse* set of overrides on top of your normal client settings. Only the rows you -actually touch are stored. Everything else keeps following your defaults **live**, so changing a -default later also moves every profile that never overrode it. +A profile is a *sparse* set of overrides on your normal client settings. Only the rows you touch are +stored; everything else follows your defaults **live**, so changing a default later also moves every +profile that never overrode it. -Touching a row records the override even when you pick the value the default already has. That is a -deliberate *pin*: the profile keeps that value when the default later moves. The only way back to -inheriting is the row's explicit **Reset**. +Touching a row records the override even when you pick the value the default already has — a +deliberate *pin* that holds when the default later moves. Only the row's **Reset** returns it to +inheriting. -Where the catalog is kept: - -| Client | Stored in | +| Client | Catalog stored in | |---|---| | Linux | `~/.config/punktfunk/client-profiles.json` | | Windows | `%APPDATA%\punktfunk\client-profiles.json` | | Apple | the app-group store, beside your saved hosts | | Android | app-private storage | -The catalog is per device, and nothing syncs it, so a profile you make on your laptop doesn't appear -on your phone. +The catalog is per device and nothing syncs it: a profile made on your laptop isn't on your phone. ## Creating and editing one -Profiles are created and edited in the client's own **Settings** screen — there is no second editor, -so a profile can never drift from the surface it overrides. +Profiles are created and edited in the client's own **Settings** screen — no second editor, so a +profile can't drift from the surface it overrides. -1. Open Settings. At the top is a scope switcher listing **Default settings**, your profiles, and a - **New profile** entry. Linux, Windows and iOS label the control **Editing**; macOS heads the - preferences window with the layer's name; Android shows the choices as a row of chips. -2. Create a new profile. Linux, Apple and Android ask for a name (and a colour) first; Windows - creates one called *Profile 1* and opens its edit sheet, where you rename it. Names must be - unique, ignoring case. -3. Change the rows you want. Every row shows the *effective* value — the inherited default until you - touch it. -4. A row you have overridden grows a marker and a **Reset** control. Reset drops that one override - and puts the row back to following your defaults. +1. Open Settings. A scope switcher at the top lists **Default settings**, your profiles, and **New + profile**. Linux, Windows and iOS label it **Editing**; macOS heads the preferences window with + the layer's name; Android shows the choices as a row of chips. +2. Create a profile. Linux, Apple and Android ask for a name (and colour) first; Windows creates + *Profile 1* and opens its edit sheet, where you rename it. Names must be unique, ignoring case. +3. Change the rows you want. Every row shows the *effective* value — the inherited default until + you touch it. +4. An overridden row grows a marker and a **Reset** control, which drops that one override. -Each profile can carry a colour from a small preset palette (the exact swatches differ slightly -between apps). It tints the profile's chip on host cards, so a grid of hosts is readable at a -glance. +Each profile can carry a colour from a small preset palette (swatches differ slightly between +apps); it tints the profile's chip on host cards, so a grid of hosts reads at a glance. -Renaming, duplicating (overrides and colour included) and deleting sit next to the switcher on -Linux and in the same menu on Apple. Windows puts them in the sheet the switcher's **Edit** entry -opens; Android puts them on the selected profile's own chip — tap it a second time. +Rename, duplicate (overrides and colour included) and delete sit next to the switcher on Linux and +in the same menu on Apple; Windows puts them in the sheet the switcher's **Edit** entry opens; +Android on the selected profile's chip — tap it a second time. -While a stream runs with a profile, the profile's name closes the first line of the +While a stream runs with a profile, its name closes the first line of the [stats overlay](/docs/stats) — on the Apple client, from the Normal tier up. ## What a profile can't change -In profile scope, rows that aren't profileable simply don't render. They are facts about *this -device* — the video decoder and GPU it uses, its audio endpoints, which physical controller you -hold, whether it wakes hosts on connect and whether it shows a game library — not about how you -want a stream to look. **Share clipboard** is out for the neighbouring reason: it's a per-host trust -decision stored on the host record rather than a client setting at all — see -[Clipboard](/docs/clipboard). +In profile scope, rows that aren't profileable don't render. They are facts about *this device* — +its video decoder and GPU, its audio endpoints, which physical controller you hold, whether it +wakes hosts on connect, whether it shows a game library — not about how a stream should look. +**Share clipboard** is out for a neighbouring reason: it's a per-host trust decision stored on the +host record, not a client setting — see [Clipboard](/docs/clipboard). -The row-by-row list, and why each row stays global, is on +The row-by-row list, and why each stays global, is on [Client settings](/docs/client-settings#settings-that-are-facts-about-your-device). ## Three ways to use a profile -**Bind it to a host.** Open a saved host's edit sheet and set **Profile**. Every plain click on that -host's card now uses it. This is the only sticky choice. (The Apple app also offers **Connect with ▸ -Set Default Profile** on the card itself.) +**Bind it to a host.** In a saved host's edit sheet, set **Profile**. Every plain click on that +host's card now uses it — the only sticky choice. (The Apple app also offers **Connect with ▸ Set +Default Profile** on the card.) -**Use it once.** A card's menu has **Connect with** — pick a profile for this connect only. It never -rebinds the host. **Default settings** in that menu is a real choice: on a bound host it forces your +**Use it once.** A card's menu has **Connect with** — a profile for this connect only; it never +rebinds the host. **Default settings** there is a real choice: on a bound host it forces your globals for one session. (Android lists the same choices flat, as *Connect with: …*.) **Pin it as its own card.** A pinned profile gets its own card beside the host — one click, no menu. @@ -93,23 +86,23 @@ second host: unpinning changes neither the profile nor the host's binding. ## Deleting a profile -The confirmation tells you what breaks: how many hosts will fall back to **Default settings**, and -how many pinned cards will disappear. Bindings and pins are deliberately left pointing at the gone -profile rather than rewritten; everywhere they are read, a dangling reference resolves as "no -profile", which is exactly your defaults. Nothing errors and no connect is blocked. +The confirmation says what breaks: how many hosts fall back to **Default settings** and how many +pinned cards disappear. Bindings and pins are left pointing at the gone profile rather than +rewritten; wherever they are read, a dangling reference resolves as "no profile" — your defaults. +Nothing errors and no connect is blocked. ## `punktfunk://` links A link starts a stream on a host this device already trusts. All four apps register the scheme with -the operating system, so a link works from a browser (behind the browser's own "open this app?" -prompt), a desktop shortcut, a home-automation rule or a script: +the operating system, so a link works from a browser (behind its "open this app?" prompt), a +desktop shortcut, a home-automation rule or a script: ```text punktfunk://connect/[?fp=<64-hex>][&host=][&launch=][&profile=][&name=