Files
punktfunk/docs-site/content/docs/sway.md
T
enricobuehler 23d0452157 feat(host): GameStream opt-in on every route; the native plane is deny(unsafe_code)-enforced
The user direction after WP0: ENet exists only for Moonlight, so the native
plane must be provably safe and the compat planes a deliberate choice.

Opt-in, everywhere. Windows already was (unchecked installer task). The three
opt-out surfaces are flipped: the shipped systemd user unit (deb/RPM/Arch/
sysext) no longer bakes --gamestream into ExecStart — a new
PUNKTFUNK_GAMESTREAM=1 host.env knob (pf-host-config, OR-ed with the CLI
flag) is the packaged opt-in; the NixOS module default goes true→false, with
a module-check assertion that unset = native-only; the Deck installer takes
--gamestream to opt in (--no-gamestream kept as explicit-off). Docs
(quickstart, running-as-a-service, moonlight, ubuntu/fedora/arch firewall
sections, gnome/sway, how-it-works) rewritten to the opt-in shape; the
CHANGELOG carries the upgrade note.

Enforced-safe. punktfunk-core is #![deny(unsafe_code)] crate-wide — every
module that parses network bytes is safe Rust as a compile error, not a
census result. Carve-outs are exactly two documented classes, neither of
which interprets attacker bytes: the client surface (abi, client) and the
transport syscall-batching shims (udp/{apple,linux,windows}, qos_windows).
In punktfunk-host, the modules a secure-default host exposes — native
(cfg-not-test: its tests exercise the client C ABI on purpose),
native_pairing, mgmt, mgmt_token, discovery, wol — are #[forbid(unsafe_code)].

Gates: Linux amd64 container clippy --all-targets -D warnings clean over
core+host-config+host; core 204 tests green under the deny; mgmt 46/46,
control 6/6. .133 Windows clippy (shipped features, clean-first,
sentinel-checked) clean — covers the qos_windows/udp-windows carve-outs.
macOS + iOS cargo check green (the apple.rs carve-out compiles for real).
2026-08-11 20:21:16 +02:00

102 lines
4.7 KiB
Markdown

---
title: Sway / wlroots
description: Configure a Punktfunk host on sway.
---
Sway can host: the host adds a per-client headless output at the client's exact mode with
`swaymsg create_output` and captures it through the xdg-desktop-portal-wlr (xdpw) ScreenCast portal,
injecting input via the wlroots virtual pointer/keyboard protocols.
Despite the backend's name, this path is **sway** specifically. Everything it does for video —
creating the headless output, setting its mode, listing your monitors — goes through sway's IPC
(`swaymsg`), so a wlroots compositor without that IPC (River, dwl, …) cannot host: the session fails
straight away with `swaymsg get_outputs (is the host inside the sway session env — SWAYSOCK?)`.
Input would be fine there — it uses the wlroots virtual pointer/keyboard protocols, which those
compositors do have — but with no video there is no stream.
> On **Hyprland**? It's a separate first-class backend (its own `hyprctl` IPC and xdph portal) —
> see [Hyprland](/docs/hyprland). This page is for sway.
This is **not a primary target.** It works and is validated live on **sway 1.11** (zero-copy), but it
sees far less testing than the KDE and GNOME paths — expect rougher edges. If you have a choice,
[KDE](/docs/kde) or [GNOME](/docs/gnome) are the better-exercised desktops.
This page assumes the package is already installed — see [Arch](/docs/arch), [Ubuntu](/docs/ubuntu),
or [Fedora](/docs/fedora).
> 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.
## host.env
The host auto-detects a wlroots session, so the starter `~/.config/punktfunk/host.env` is one line:
```ini
PUNKTFUNK_VIDEO_SOURCE=virtual
# GPU zero-copy capture→encode is ON by default; auto-falls back to CPU. Set PUNKTFUNK_ZEROCOPY=0 to force CPU.
```
To force the backend (CI/testing — note that pinning turns live-session auto-detection **off**, so
the host stops following session switches):
```ini
PUNKTFUNK_COMPOSITOR=wlroots # aliases: sway, wlr
PUNKTFUNK_INPUT_BACKEND=wlr
```
See [Configuration](/docs/configuration) for the full reference.
## How it works
- **Video** — the host adds a headless output at the client's exact mode with `swaymsg create_output`.
This uses sway's IPC specifically, and so does everything else on the video side (mode setting,
monitor listing). (Hyprland is driven by its own [backend](/docs/hyprland), not this one.)
- **Capture** — it captures that output through the **xdg-desktop-portal-wlr (xdpw)** ScreenCast
portal. The host writes a managed chooser config so the output pick is automatic — no interactive
picker dialog to answer.
- **Input** — mouse and keyboard are injected via the wlroots **virtual pointer** and **virtual
keyboard** protocols.
For how long the virtual output lives, and extend-vs-exclusive topology, see
[Virtual displays](/docs/virtual-displays).
## Requirements
- A running **sway** session — its IPC socket (`SWAYSOCK`) is what the whole video path runs on. You
don't have to export it: the host finds the live sway instance itself on every connect, so a
`systemd --user` host works even though it never inherited your login shell's environment. On
Hyprland, use the [Hyprland backend](/docs/hyprland) instead.
- **xdg-desktop-portal-wlr (xdpw)** installed and running — the host captures through its ScreenCast
portal. Without it there is no video.
- **ScreenCast routed to xdpw** — only if another portal backend (gtk, gnome) is installed alongside
it. `xdg-desktop-portal` picks one implementation per interface, and if it hands ScreenCast to the
wrong backend the host steers an xdpw chooser nobody is reading. Pin it for your session by
creating `~/.config/xdg-desktop-portal/sway-portals.conf`:
```ini
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=wlr
```
Then `systemctl --user restart xdg-desktop-portal`. On a box with only xdpw installed there is
nothing to choose between, so you can skip this.
## Start the host
With the backend selected, start the host from **inside your Sway session**:
```sh
systemctl --user enable --now punktfunk-host
journalctl --user -u punktfunk-host -f
```
This unit runs the secure native-only host. To also serve stock [Moonlight](/docs/moonlight)
clients, GameStream compat is opt-in (`PUNKTFUNK_GAMESTREAM=1` in `host.env`, trusted LANs only) —
see [What the unit starts](/docs/running-as-a-service#what-the-unit-starts).
## Bring up the console and pair
Enable the web console, read its login password, and arm PIN pairing — see
[The Web Console](/docs/web-console). Then [connect a client](/docs/clients).