Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 69fcb6e0b1 | |||
| 8ebb61400c |
@@ -58,25 +58,30 @@ tuning, and example configs. Updates later are just `sudo pacman -Syu`.
|
|||||||
|
|
||||||
## 4. Configure and run
|
## 4. Configure and run
|
||||||
|
|
||||||
The host runs as a systemd **`--user`** service — it needs your session's PipeWire and D-Bus.
|
The host runs as a systemd **`--user`** service — it needs your session's PipeWire and D-Bus. Copy a
|
||||||
Copy a starting config, enable the service, and enable linger so it starts at boot without a login:
|
starting config:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
mkdir -p ~/.config/punktfunk
|
mkdir -p ~/.config/punktfunk
|
||||||
cp /usr/share/punktfunk/host.env.example ~/.config/punktfunk/host.env # then edit
|
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)
|
||||||
|
- [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 daemon-reload
|
||||||
systemctl --user enable --now punktfunk-host
|
systemctl --user enable --now punktfunk-host
|
||||||
sudo loginctl enable-linger "$USER"
|
sudo loginctl enable-linger "$USER"
|
||||||
```
|
```
|
||||||
|
|
||||||
Which compositor the host captures depends on your desktop — it drives a per-client virtual output
|
|
||||||
via KWin (Plasma), Mutter (GNOME), or wlroots (Sway), or spawns a headless **gamescope** session
|
|
||||||
per connect. For a headless appliance, the package also ships `punktfunk-kde-session.service`
|
|
||||||
(a dedicated `kwin --virtual` session, same as the [Fedora KDE](/docs/fedora-kde#3-kwin-streaming-session)
|
|
||||||
guide — `cp /usr/share/punktfunk/host.env.kde ~/.config/punktfunk/host.env` and enable it alongside
|
|
||||||
the host). See [Configuration](/docs/configuration) for every knob and
|
|
||||||
[Running as a Service](/docs/running-as-a-service) for the service model.
|
|
||||||
|
|
||||||
Check it came up:
|
Check it came up:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -84,27 +89,10 @@ systemctl --user status punktfunk-host # active
|
|||||||
journalctl --user -u punktfunk-host -f # watch a client connect
|
journalctl --user -u punktfunk-host -f # watch a client connect
|
||||||
```
|
```
|
||||||
|
|
||||||
### Web console
|
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
|
||||||
The console (status, paired devices, arm pairing) ships as `punktfunk-web` — enable it, then open
|
graphical login, see [KDE → Headless session](/docs/kde#headless-session). Full reference:
|
||||||
`http://<host-ip>:47992`:
|
[Configuration](/docs/configuration) · [Running as a Service](/docs/running-as-a-service).
|
||||||
|
|
||||||
```sh
|
|
||||||
systemctl --user enable --now punktfunk-web
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Console login password
|
|
||||||
|
|
||||||
On first start `punktfunk-web-init` generates a random login password and saves it to
|
|
||||||
`~/.config/punktfunk/web-password` (as `PUNKTFUNK_UI_PASSWORD=…`). Read it back at any time:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web-password
|
|
||||||
```
|
|
||||||
|
|
||||||
To set your own, edit that file and `systemctl --user restart punktfunk-web`. Forgot it? See
|
|
||||||
[Forgot your Password?](/docs/forgot-password).
|
|
||||||
|
|
||||||
## 5. Open the firewall (if you have one)
|
## 5. Open the firewall (if you have one)
|
||||||
|
|
||||||
@@ -147,9 +135,9 @@ opened. Full port lists (`nftables`, explicit ports) are in
|
|||||||
## 6. Connect a client
|
## 6. Connect a client
|
||||||
|
|
||||||
From any [client](/docs/clients), `--discover` finds the host on the LAN. On first connect, complete
|
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, which displays a 4-digit PIN to type into
|
the **PIN pairing**: arm it from [The Web Console](/docs/web-console#arm-pairing), which displays a
|
||||||
the client. (Pairing is required by default; pass `serve --open` only if you deliberately want to
|
4-digit PIN to type into the client. (Pairing is required by default; pass `serve --open` only if
|
||||||
disable it.) See [Clients](/docs/clients) and [Pairing](/docs/pairing).
|
you deliberately want to disable it.) See [Clients](/docs/clients) for per-platform setup.
|
||||||
|
|
||||||
## Appendix — build from source (PKGBUILD)
|
## Appendix — build from source (PKGBUILD)
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ mid-stream. You flip between Gaming Mode and Desktop with Bazzite's normal Steam
|
|||||||
`host.env` forces a mode.
|
`host.env` forces a mode.
|
||||||
|
|
||||||
> Ideal for a dedicated game-streaming box that you also occasionally want as a remote desktop. For a
|
> Ideal for a dedicated game-streaming box that you also occasionally want as a remote desktop. For a
|
||||||
> pure desktop machine, [Ubuntu/Fedora KDE](/docs/ubuntu-kde) or [GNOME](/docs/ubuntu-gnome) are
|
> pure desktop machine, install on [Ubuntu](/docs/ubuntu) or [Fedora](/docs/fedora) and configure the
|
||||||
> simpler.
|
> [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
|
> 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.
|
> the machine, so keep it on a trusted LAN or VPN and require pairing.
|
||||||
@@ -60,7 +60,7 @@ For a fully baked appliance image there's also a **bootc** Containerfile that in
|
|||||||
from the registry at image-build time — see `packaging/bootc/` in the repo. Plain `rpm-ostree`
|
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 (see
|
layering from the [RPM registry](https://git.unom.io/unom/-/packages) keeps working too (see
|
||||||
`packaging/bazzite/README.md`), but the sysext is the supported default. Building from source
|
`packaging/bazzite/README.md`), but the sysext is the supported default. Building from source
|
||||||
also works (Bazzite is Fedora Atomic underneath — same steps as [Fedora KDE](/docs/fedora-kde)).
|
also works (Bazzite is Fedora Atomic underneath — same steps as [Fedora](/docs/fedora)).
|
||||||
|
|
||||||
## Allow controller input
|
## Allow controller input
|
||||||
|
|
||||||
@@ -99,15 +99,14 @@ PUNKTFUNK_GAMESCOPE_ATTACH=1 # Gaming Mode = attach to the box's own session
|
|||||||
|
|
||||||
For Gaming Mode there are two models (pick one; the shipped default is **attach**):
|
For Gaming Mode there are two models (pick one; the shipped default is **attach**):
|
||||||
|
|
||||||
- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`, the default) — the **box** owns its gamescope session
|
- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`, the default) — the **box** owns its gamescope session,
|
||||||
and decides Gaming vs Desktop via the normal Steam UI. The host just attaches to whatever's live
|
the host attaches to whatever's live and never tears it down, and the streamed game-mode resolution
|
||||||
and never tears it down, so switching Desktop ↔ Game is rock-solid and disconnecting leaves the box
|
is the box's own gamescope mode. Switching Desktop ↔ Game is rock-solid.
|
||||||
where it was. The streamed game-mode resolution is the box's gamescope mode
|
- **Managed** (`PUNKTFUNK_GAMESCOPE_MANAGED=1`, and remove the attach line) — the host launches its
|
||||||
(`SCREEN_WIDTH/HEIGHT` in `/etc/gamescope-session-plus/sessions.d/steam`), not the client's.
|
**own** gamescope at the *client's* exact resolution and refresh. Client-mode-following, but there
|
||||||
- **Managed** (`PUNKTFUNK_GAMESCOPE_MANAGED=1`, and remove the attach line) — the host tears the
|
must be no physical gaming session already running.
|
||||||
box's gamescope down on connect and launches its **own** at the *client's* exact resolution and
|
|
||||||
refresh, restoring on idle. Client-mode-following, but it can't coexist with a box-owned game-mode
|
Full treatment: [Steam / gamescope → Attach vs managed](/docs/gamescope#attach-vs-managed).
|
||||||
session, and there must be **no physical gaming session already running**.
|
|
||||||
|
|
||||||
Mid-stream Gaming ↔ Desktop following (`PUNKTFUNK_SESSION_WATCH`) is **on by default** on
|
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.
|
Bazzite/SteamOS. See [Configuration](/docs/configuration) for the full list of knobs.
|
||||||
@@ -116,8 +115,8 @@ Bazzite/SteamOS. See [Configuration](/docs/configuration) for the full list of k
|
|||||||
|
|
||||||
The **virtual output** (video) for the Desktop session needs no config — the host package ships an
|
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
|
`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 (least-privilege, the same
|
restricted screencast protocol on a normal interactive Plasma session (background:
|
||||||
mechanism krfb/krdp use). After a **fresh host install, log out and back into the Desktop session
|
[KDE Plasma](/docs/kde)). After a **fresh host install, log out and back into the Desktop session
|
||||||
once** so KWin re-reads that grant.
|
once** so KWin re-reads that grant.
|
||||||
|
|
||||||
The one thing a normal KDE login lacks is the RemoteDesktop grant for headless **input** injection.
|
The one thing a normal KDE login lacks is the RemoteDesktop grant for headless **input** injection.
|
||||||
@@ -138,26 +137,11 @@ Desktop; it follows whichever the box is in.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
systemctl --user enable --now punktfunk-host
|
systemctl --user enable --now punktfunk-host
|
||||||
# Web console (pairing + status) — enable it and read the auto-generated login password,
|
systemctl --user enable --now punktfunk-web # web console: pairing + status
|
||||||
# then open http://<host-ip>:47992:
|
|
||||||
systemctl --user enable --now punktfunk-web
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Console login password
|
Then open [The Web Console](/docs/web-console) for the login password and to
|
||||||
|
[arm pairing](/docs/web-console#arm-pairing).
|
||||||
The console is password-protected. On first start `punktfunk-web-init` generates a random login
|
|
||||||
password and saves it to `~/.config/punktfunk/web-password` (as `PUNKTFUNK_UI_PASSWORD=…`). Read it
|
|
||||||
back at any time — from the init service's journal, or straight from the file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web-password
|
|
||||||
```
|
|
||||||
|
|
||||||
To set your own password, edit that file (`PUNKTFUNK_UI_PASSWORD=<your-password>`) and restart the
|
|
||||||
console: `systemctl --user restart punktfunk-web`. Forgot it? This is the recovery path linked from
|
|
||||||
the console login screen — see [Forgot your Password?](/docs/forgot-password).
|
|
||||||
|
|
||||||
## Good to know
|
## Good to know
|
||||||
|
|
||||||
@@ -170,5 +154,7 @@ These apply to the **Gaming Mode (gamescope)** path; the KDE Desktop path is una
|
|||||||
- **HDR isn't supported yet** on the gamescope path — gamescope's capture output is 8-bit. SDR streams
|
- **HDR isn't supported yet** on the gamescope path — gamescope's capture output is 8-bit. SDR streams
|
||||||
normally.
|
normally.
|
||||||
|
|
||||||
|
Canonical list: [gamescope → Known limits](/docs/gamescope#known-limits).
|
||||||
|
|
||||||
Then [connect a client](/docs/clients) — Moonlight works great for couch gaming, and the Apple app for
|
Then [connect a client](/docs/clients) — Moonlight works great for couch gaming, and the Apple app for
|
||||||
Apple TV / iPad.
|
Apple TV / iPad.
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ let you pick a mode or default to the device's display.)
|
|||||||
|
|
||||||
## gamescope / session following (Linux, Bazzite/SteamOS)
|
## gamescope / session following (Linux, Bazzite/SteamOS)
|
||||||
|
|
||||||
Two mutually-exclusive models for a Steam/gamescope box. See [Bazzite](/docs/bazzite) for the full
|
Two mutually-exclusive models for a Steam/gamescope box. See [Steam / gamescope](/docs/gamescope) for
|
||||||
picture.
|
the full picture (and [Bazzite](/docs/bazzite) for that distro's specifics).
|
||||||
|
|
||||||
| Setting | Values | Meaning |
|
| Setting | Values | Meaning |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -62,6 +62,8 @@ picture.
|
|||||||
|
|
||||||
## Compositor-specific (Linux)
|
## Compositor-specific (Linux)
|
||||||
|
|
||||||
|
See your desktop page ([KDE](/docs/kde), [GNOME](/docs/gnome)) for when to set these.
|
||||||
|
|
||||||
> **Managing virtual displays** — keep-alive after disconnect, exclusive vs. extend, and (on
|
> **Managing virtual displays** — keep-alive after disconnect, exclusive vs. extend, and (on
|
||||||
> Windows/KDE) persistent per-client scaling — now has its own settings surface in the web console
|
> Windows/KDE) persistent per-client scaling — now has its own settings surface in the web console
|
||||||
> and `display-settings.json`. See [Virtual displays](/docs/virtual-displays). The two
|
> and `display-settings.json`. See [Virtual displays](/docs/virtual-displays). The two
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
---
|
---
|
||||||
title: Fedora — KDE Plasma
|
title: Fedora
|
||||||
description: Reproducible punktfunk host setup on Fedora KDE (KWin) via the RPM.
|
description: Install the punktfunk host on Fedora from the RPM registry.
|
||||||
---
|
---
|
||||||
|
|
||||||
Set up a punktfunk host on **Fedora KDE** (the KDE Plasma spin). The host runs as an RPM-managed
|
Install a punktfunk host on **Fedora** from the self-hosted RPM registry. The host installs as an
|
||||||
systemd service and uses KWin to create per-client virtual displays, captured zero-copy
|
RPM-managed systemd **`--user`** service and updates with `dnf upgrade` like the rest of your
|
||||||
(dmabuf → CUDA → NVENC) on NVIDIA.
|
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
|
||||||
> Validated live on **Fedora 44 KDE Plasma** with an RTX 4090: KWin virtual output + full
|
[desktop configure pages](#3-configure-your-desktop). Host encode is **NVENC on NVIDIA** and **VAAPI on
|
||||||
> zero-copy capture. Everything below is the reproducible flow — paste it on a fresh box.
|
AMD/Intel** (`PUNKTFUNK_ENCODER=auto` picks per GPU).
|
||||||
|
|
||||||
> New here? Read [Security & Safe Use](/docs/security) first — a streaming host is remote control of
|
> 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.
|
> the machine, so keep it on a trusted LAN or VPN and require pairing.
|
||||||
|
|
||||||
The setup has three parts: **NVIDIA driver** → **host RPM** → **KWin streaming session**.
|
Install is two parts: **GPU driver** → **host RPM**. Then point the host at your desktop from the
|
||||||
|
[desktop configure pages](#3-configure-your-desktop).
|
||||||
|
|
||||||
## 1. NVIDIA driver (RPM Fusion akmod)
|
## 1. NVIDIA driver (RPM Fusion akmod)
|
||||||
|
|
||||||
Enable RPM Fusion (free + nonfree), then install the akmod driver + CUDA. RPM Fusion's nonfree
|
Enable RPM Fusion (free + nonfree), then install the akmod driver + CUDA. RPM Fusion's nonfree
|
||||||
NVIDIA repo is sometimes pre-enabled on the KDE spin; the full free/nonfree repos below are still
|
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).
|
needed (they carry the NVENC ffmpeg in the next step).
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -55,6 +56,11 @@ ffmpeg -hide_banner -encoders | grep nvenc
|
|||||||
|
|
||||||
(Or disable Secure Boot in firmware to skip the MOK step — fine for a dedicated test box.)
|
(Or disable Secure Boot in firmware to skip the MOK step — fine for a dedicated test box.)
|
||||||
|
|
||||||
|
**AMD / Intel (VAAPI).** No akmod needed — the Mesa stack provides the VAAPI encoder. 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. The host auto-picks
|
||||||
|
VAAPI on these GPUs.
|
||||||
|
|
||||||
## 2. Install the host (RPM)
|
## 2. Install the host (RPM)
|
||||||
|
|
||||||
The host is published to the self-hosted Gitea RPM registry, in a per-Fedora-release group (an RPM
|
The host is published to the self-hosted Gitea RPM registry, in a per-Fedora-release group (an RPM
|
||||||
@@ -85,71 +91,37 @@ udev rule, the UDP socket-buffer sysctl tuning, and example configs.
|
|||||||
> `docker build --build-arg FEDORA_VERSION=NN -f ci/fedora-rpm.Dockerfile -t pf-rpm ci` then run
|
> `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).
|
> `packaging/rpm/build-rpm.sh` inside it — or build from source (appendix below).
|
||||||
|
|
||||||
## 3. KWin streaming session
|
## 3. Configure your desktop
|
||||||
|
|
||||||
KWin's virtual-output capture uses its **privileged** `zkde_screencast` protocol, which an
|
How the host creates its virtual display and injects input depends on your desktop, not your distro.
|
||||||
*interactive* Plasma session will not hand to an external client. So the host streams from a
|
Continue on the page for the desktop you run — it covers your `host.env`, any compositor quirks, and
|
||||||
**dedicated headless KWin session** (`kwin --virtual` launched with
|
starting the host:
|
||||||
`KWIN_WAYLAND_NO_PERMISSION_CHECKS=1`) — shipped as `punktfunk-kde-session.service`. This also
|
|
||||||
makes the box a self-contained appliance: it streams at boot with no graphical login.
|
|
||||||
|
|
||||||
```sh
|
- [KDE Plasma (KWin)](/docs/kde)
|
||||||
# KWin appliance config (ships with the package):
|
- [GNOME (Mutter)](/docs/gnome)
|
||||||
mkdir -p ~/.config/punktfunk
|
- [Steam / gamescope](/docs/gamescope)
|
||||||
cp /usr/share/punktfunk/host.env.kde ~/.config/punktfunk/host.env
|
- [Sway / wlroots](/docs/sway)
|
||||||
|
|
||||||
# Start the headless KWin session + the host, and start user units at boot without a login:
|
Enable the browser management console (status, paired devices, arm pairing) — see
|
||||||
systemctl --user daemon-reload
|
[Web Console](/docs/web-console).
|
||||||
systemctl --user enable --now punktfunk-kde-session punktfunk-host
|
|
||||||
sudo loginctl enable-linger "$USER"
|
|
||||||
```
|
|
||||||
|
|
||||||
Check it came up:
|
For a headless KWin appliance that streams at boot with no graphical login, see
|
||||||
|
[KDE → Headless session](/docs/kde#headless-session).
|
||||||
|
|
||||||
```sh
|
Full config reference: [Configuration](/docs/configuration). Service model:
|
||||||
systemctl --user status punktfunk-host # active
|
[Running as a Service](/docs/running-as-a-service).
|
||||||
journalctl --user -u punktfunk-host -f # watch a client connect
|
|
||||||
```
|
|
||||||
|
|
||||||
The host now listens on `9777` (native punktfunk/1) + the GameStream ports, and advertises over
|
|
||||||
mDNS. It requires **PIN pairing** by default (secure on a LAN); pair once from your client.
|
|
||||||
|
|
||||||
### Web console
|
|
||||||
|
|
||||||
The console (status, paired devices, arm pairing) ships as `punktfunk-web` — enable it, then open
|
|
||||||
`http://<host-ip>:47992`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
systemctl --user enable --now punktfunk-web
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Console login password
|
|
||||||
|
|
||||||
The console is password-protected. On first start `punktfunk-web-init` generates a random login
|
|
||||||
password and saves it to `~/.config/punktfunk/web-password` (as `PUNKTFUNK_UI_PASSWORD=…`). Read it
|
|
||||||
back at any time — from the init service's journal, or straight from the file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web-password
|
|
||||||
```
|
|
||||||
|
|
||||||
To set your own password, edit that file (`PUNKTFUNK_UI_PASSWORD=<your-password>`) and restart the
|
|
||||||
console: `systemctl --user restart punktfunk-web`. Forgot it? This is the recovery path linked from
|
|
||||||
the console login screen — see [Forgot your Password?](/docs/forgot-password).
|
|
||||||
|
|
||||||
## 4. Connect a client
|
## 4. Connect a client
|
||||||
|
|
||||||
From any [client](/docs/clients) — `punktfunk-client --discover` finds the host on the LAN. On
|
From any [client](/docs/clients), `--discover` finds the host on the LAN. On first connect, complete
|
||||||
first connect, complete the PIN pairing — **arm it from the host's web console / mgmt API**, which
|
the **PIN pairing** — arm it from the host's [web console](/docs/web-console#arm-pairing), which
|
||||||
makes the host display a 4-digit PIN to type into the client. (Pairing is required by default; pass
|
displays a 4-digit PIN to type into the client. See [Clients](/docs/clients) and
|
||||||
`serve --open` only if you deliberately want to disable the requirement.) See
|
[Pairing](/docs/pairing).
|
||||||
[Clients](/docs/clients) and [Running as a Service](/docs/running-as-a-service).
|
|
||||||
|
|
||||||
## Appendix — build from source
|
## Appendix — build from source
|
||||||
|
|
||||||
If there's no RPM for your Fedora release and you don't want to build one, compile the host
|
If there's no RPM for your Fedora release and you don't want to build one, compile the host directly
|
||||||
directly (no clean updates / no packaged units — you wire those up by hand):
|
(no clean updates / no packaged units — you wire those up by hand):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo dnf install gcc gcc-c++ make cmake clang clang-devel nasm git \
|
sudo dnf install gcc gcc-c++ make cmake clang clang-devel nasm git \
|
||||||
@@ -162,4 +134,5 @@ cargo build --release -p punktfunk-host
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then write `~/.config/punktfunk/host.env` (as in `/usr/share/punktfunk/host.env.kde`, but the host
|
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 the KWin session from step 3.
|
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).
|
||||||
@@ -8,21 +8,20 @@ password. That password is generated — or, on Windows, chosen — when the con
|
|||||||
it lives on the **host**. So if you can't get past the login screen, you recover or change it on the
|
it lives on the **host**. So if you can't get past the login screen, you recover or change it on the
|
||||||
host machine itself, not from the browser.
|
host machine itself, not from the browser.
|
||||||
|
|
||||||
|
New to the console? See [The Web Console](/docs/web-console) to enable it and arm pairing.
|
||||||
|
|
||||||
> This is **only** the web console login. It is **not** your client/device pairing — if a client
|
> This is **only** the web console login. It is **not** your client/device pairing — if a client
|
||||||
> won't connect, that's [Pairing](/docs/pairing), not this password.
|
> won't connect, that's [Pairing](/docs/pairing), not this password.
|
||||||
|
|
||||||
## Find your host
|
## Find your host
|
||||||
|
|
||||||
Jump to your host platform for exactly where the password lives and how to read or reset it:
|
Find your host platform for exactly where the password lives, then read or reset it below:
|
||||||
|
|
||||||
| Host | Where the password lives | Section |
|
| Host | Where the password lives | Section |
|
||||||
|------|--------------------------|---------|
|
|------|--------------------------|---------|
|
||||||
| **Ubuntu — GNOME** | `~/.config/punktfunk/web-password` | [Console login password](/docs/ubuntu-gnome#console-login-password) |
|
| **Linux packages (apt / RPM / Bazzite)** | `~/.config/punktfunk/web-password` | [Login password](/docs/web-console#login-password) |
|
||||||
| **Ubuntu — KDE Plasma** | `~/.config/punktfunk/web-password` | [Console login password](/docs/ubuntu-kde#console-login-password) |
|
| **SteamOS (host)** | `~/.config/punktfunk/web.env` | [Login password](/docs/web-console#login-password) |
|
||||||
| **Fedora — KDE Plasma** | `~/.config/punktfunk/web-password` | [Console login password](/docs/fedora-kde#console-login-password) |
|
| **Windows host** | `%ProgramData%\punktfunk\web-password` | [Login password](/docs/web-console#login-password) · [Windows Host](/docs/windows-host) |
|
||||||
| **Bazzite — gamescope** | `~/.config/punktfunk/web-password` | [Console login password](/docs/bazzite#console-login-password) |
|
|
||||||
| **SteamOS (host)** | `~/.config/punktfunk/web.env` | [Console login password](/docs/steamos-host#console-login-password) |
|
|
||||||
| **Windows host** | `%ProgramData%\punktfunk\web-password` | [Console login password](/docs/windows-host#console-login-password) |
|
|
||||||
|
|
||||||
## The short version
|
## The short version
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
title: Steam / gamescope
|
||||||
|
description: Configure a gamescope/Steam host — attach vs managed, session following, and limits.
|
||||||
|
---
|
||||||
|
|
||||||
|
gamescope is the compositor behind Steam **Gaming Mode** — the couch/handheld game UI on Bazzite,
|
||||||
|
SteamOS, or any distro running a gamescope session. The host **auto-detects** gamescope from your
|
||||||
|
live session, so you rarely need to set anything here. It also **follows a Gaming ↔ Desktop switch
|
||||||
|
mid-stream** — flip between Gaming Mode and the desktop with Steam's normal UI and the host
|
||||||
|
re-targets whatever's running without a reconnect.
|
||||||
|
|
||||||
|
This page covers the gamescope-specific choices. To get a host running on an appliance box, start
|
||||||
|
from the install guide for your OS: [Bazzite](/docs/bazzite) or [SteamOS (Host)](/docs/steamos-host).
|
||||||
|
|
||||||
|
> 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.
|
||||||
|
|
||||||
|
## Attach vs managed
|
||||||
|
|
||||||
|
There are two mutually-exclusive models for a gamescope box; pick one. The shipped default is
|
||||||
|
**attach**.
|
||||||
|
|
||||||
|
- **Attach** (`PUNKTFUNK_GAMESCOPE_ATTACH=1`, the default) — the **box** owns its gamescope session
|
||||||
|
and decides Gaming vs Desktop via the normal Steam UI. The host just attaches to whatever's live
|
||||||
|
and never tears it down, so switching Desktop ↔ Game is rock-solid and disconnecting leaves the box
|
||||||
|
where it was. The streamed game-mode resolution is the box's gamescope mode
|
||||||
|
(`SCREEN_WIDTH/HEIGHT` in `/etc/gamescope-session-plus/sessions.d/steam`), not the client's.
|
||||||
|
- **Managed** (`PUNKTFUNK_GAMESCOPE_MANAGED=1`, and remove the attach line) — the host tears the
|
||||||
|
box's gamescope down on connect and launches its **own** at the *client's* exact resolution and
|
||||||
|
refresh, restoring on idle. Client-mode-following, but it can't coexist with a box-owned game-mode
|
||||||
|
session, and there must be **no physical gaming session already running**.
|
||||||
|
|
||||||
|
## Session following
|
||||||
|
|
||||||
|
`PUNKTFUNK_SESSION_WATCH` follows a Gaming ↔ Desktop switch **mid-stream** — the host rebuilds the
|
||||||
|
backend in place, with no reconnect. It is **on by default** on Bazzite/SteamOS; set `0` to disable.
|
||||||
|
One host service covers both faces of the box: it streams Gaming Mode over gamescope and the desktop
|
||||||
|
over its own compositor, and re-targets whichever is live on each switch.
|
||||||
|
|
||||||
|
## Start the host
|
||||||
|
|
||||||
|
On an appliance box (Bazzite, SteamOS) the install guide already enables the host service for you. On
|
||||||
|
any other distro running a gamescope session, start it from your session — the default attach model
|
||||||
|
just latches onto whatever gamescope session is live:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl --user enable --now punktfunk-host
|
||||||
|
```
|
||||||
|
|
||||||
|
Then bring up [The Web Console](/docs/web-console) to arm pairing.
|
||||||
|
|
||||||
|
## gamescope knobs
|
||||||
|
|
||||||
|
The gamescope-specific settings in `host.env`. Leave them unset to auto-detect; set one only to force
|
||||||
|
a model. See the full [Configuration reference](/docs/configuration) for every other knob.
|
||||||
|
|
||||||
|
| Setting | Values | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| `PUNKTFUNK_GAMESCOPE_ATTACH` | `1` | **Attach** model: the box owns its gamescope session; the host captures whatever's live and never tears it down. Streamed resolution is the box's gamescope mode. The default. |
|
||||||
|
| `PUNKTFUNK_GAMESCOPE_MANAGED` | `1` | **Managed** model: the host tears the box's gamescope down on connect and launches its own at the client's exact mode, restoring on idle. Doesn't coexist with a box-owned game-mode session. |
|
||||||
|
| `PUNKTFUNK_GAMESCOPE_SESSION` | `steam` | The host owns a `gamescope-session-plus` (Steam) session at the client's mode — a headless appliance with no physical session running. |
|
||||||
|
| `PUNKTFUNK_GAMESCOPE_NODE` | `auto` · node id | Discover and capture a **running** gamescope's PipeWire node at a fixed mode. Do **not** combine with `SESSION`. |
|
||||||
|
| `PUNKTFUNK_GAMESCOPE_APP` | command | For an ad-hoc bare-gamescope session, the nested command to run (e.g. `vkcube`). |
|
||||||
|
| `PUNKTFUNK_SESSION_WATCH` | `1` · `0` | Follow a Gaming ↔ Desktop switch mid-stream (rebuild in place, no reconnect). On by default on Bazzite/SteamOS; set `0` to disable. |
|
||||||
|
|
||||||
|
## Known limits
|
||||||
|
|
||||||
|
These apply to the **Gaming Mode (gamescope)** path only; the desktop path is unaffected.
|
||||||
|
|
||||||
|
- **gamescope 3.16.22 or newer is required.** Older versions can deadlock during capture. Bazzite's
|
||||||
|
and SteamOS's current gamescope is fine; this only bites if you've pinned an old one.
|
||||||
|
- **The mouse cursor isn't included in the captured image** — a gamescope limitation for now.
|
||||||
|
- **HDR isn't supported on the gamescope path** — gamescope's capture output is 8-bit. SDR streams
|
||||||
|
normally.
|
||||||
|
|
||||||
|
To stream the KDE Plasma desktop of a Steam box instead, see [KDE Plasma](/docs/kde). To bring up the
|
||||||
|
web console and pair a client, see [The Web Console](/docs/web-console).
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
title: GNOME (Mutter)
|
||||||
|
description: Configure a punktfunk host for GNOME — host.env, the EGL/lock traps, and a headless session.
|
||||||
|
---
|
||||||
|
|
||||||
|
Configure a host running **GNOME**. The host drives GNOME's Mutter compositor to create a per-client
|
||||||
|
virtual display over D-Bus (`RecordVirtual`), zero-copy. This page assumes the host is already
|
||||||
|
installed — see [Ubuntu](/docs/ubuntu), [Fedora](/docs/fedora), or [Arch](/docs/arch).
|
||||||
|
|
||||||
|
> 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
|
||||||
|
|
||||||
|
Write `~/.config/punktfunk/host.env` with the GNOME settings. The host auto-detects the compositor
|
||||||
|
from your session, so the explicit `PUNKTFUNK_COMPOSITOR` is belt-and-braces:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# ~/.config/punktfunk/host.env
|
||||||
|
WAYLAND_DISPLAY=wayland-0
|
||||||
|
XDG_CURRENT_DESKTOP=GNOME
|
||||||
|
PUNKTFUNK_COMPOSITOR=mutter
|
||||||
|
PUNKTFUNK_VIDEO_SOURCE=virtual
|
||||||
|
PUNKTFUNK_ZEROCOPY=1
|
||||||
|
PUNKTFUNK_INPUT_BACKEND=libei
|
||||||
|
```
|
||||||
|
|
||||||
|
You must be on a **Wayland** session (not X11), and Mutter must be **≥ 48**. See the
|
||||||
|
[Configuration reference](/docs/configuration) for every option.
|
||||||
|
|
||||||
|
## The GL/EGL userspace
|
||||||
|
|
||||||
|
On NVIDIA, gnome-shell fails to start — or the host logs **"GPU … not supported by EGL"** — when the
|
||||||
|
NVIDIA GL/EGL userspace is missing. The base driver package doesn't always pull it in. Install your
|
||||||
|
distro's NVIDIA GL/EGL userspace package — on **Ubuntu/Debian** it's `libnvidia-gl-<version>` matching
|
||||||
|
your driver; on **Fedora/Arch** it ships with the RPM Fusion / repo driver — then confirm the glvnd
|
||||||
|
vendor file exists:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ls /usr/share/glvnd/egl_vendor.d/10_nvidia.json # must exist
|
||||||
|
```
|
||||||
|
|
||||||
|
Installing the driver itself is covered on your distro's install page
|
||||||
|
([Ubuntu](/docs/ubuntu), [Fedora](/docs/fedora), [Arch](/docs/arch)).
|
||||||
|
|
||||||
|
## Do not lock the session
|
||||||
|
|
||||||
|
A **locked** GNOME session blocks screen capture — the host fails with
|
||||||
|
**"Session creation inhibited"**. On an always-on or headless host there's no one to unlock it, so
|
||||||
|
disable the lock:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gsettings set org.gnome.desktop.screensaver lock-enabled false
|
||||||
|
gsettings set org.gnome.desktop.session idle-delay 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start the host
|
||||||
|
|
||||||
|
With `host.env` in place, start the host from **inside your GNOME session**:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl --user enable --now punktfunk-host
|
||||||
|
journalctl --user -u punktfunk-host -f # watch it come up and print its identity fingerprint
|
||||||
|
```
|
||||||
|
|
||||||
|
Then bring up [The Web Console](/docs/web-console) to arm pairing and connect a
|
||||||
|
[client](/docs/clients). For an always-on box, see the [headless session](#headless-session) below.
|
||||||
|
|
||||||
|
## Headless session
|
||||||
|
|
||||||
|
To run with no monitor and no login, keep a GNOME Wayland session up at all times and start the host
|
||||||
|
without a login. Have GDM auto-login your user:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/gdm3/custom.conf (Ubuntu) · /etc/gdm/custom.conf (Fedora)
|
||||||
|
[daemon]
|
||||||
|
AutomaticLoginEnable = true
|
||||||
|
AutomaticLogin = your-user
|
||||||
|
```
|
||||||
|
|
||||||
|
Disable the lock (see [above](#do-not-lock-the-session)), then enable the host user service and let it
|
||||||
|
linger past logout:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl --user enable --now punktfunk-host
|
||||||
|
sudo loginctl enable-linger "$USER"
|
||||||
|
```
|
||||||
|
|
||||||
|
Reboot and the host comes up on the auto-login session. Full walkthrough:
|
||||||
|
[Running as a Service](/docs/running-as-a-service).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
More fixes — black screen, discovery, pairing — in [Troubleshooting](/docs/troubleshooting).
|
||||||
|
|
||||||
|
Once the host is up, bring the console up and pair — see [The Web Console](/docs/web-console).
|
||||||
@@ -68,4 +68,4 @@ LAN.
|
|||||||
## Multiple devices at once
|
## Multiple devices at once
|
||||||
|
|
||||||
A host can stream to several clients simultaneously — your laptop and your TV both viewing (and
|
A host can stream to several clients simultaneously — your laptop and your TV both viewing (and
|
||||||
controlling) the desktop, each at its own resolution. See [Multiple devices](/docs/configuration#multiple-devices).
|
controlling) the desktop, each at its own resolution. See [Multiple devices](/docs/configuration#multiple-devices-at-once).
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ It's built for the things that make streaming feel native:
|
|||||||
<Cards>
|
<Cards>
|
||||||
<Card title="How It Works" href="/docs/how-it-works" description="The ideas behind punktfunk in a few minutes — virtual displays, the two protocols, pairing." />
|
<Card title="How It Works" href="/docs/how-it-works" description="The ideas behind punktfunk in a few minutes — virtual displays, the two protocols, pairing." />
|
||||||
<Card title="Quick Start" href="/docs/quickstart" description="From nothing to streaming: set up a host and connect your first client." />
|
<Card title="Quick Start" href="/docs/quickstart" description="From nothing to streaming: set up a host and connect your first client." />
|
||||||
<Card title="Host Setup" href="/docs/requirements" description="Install the host on Ubuntu (GNOME or KDE), Fedora (KDE), or Bazzite." />
|
<Card title="Host Setup" href="/docs/requirements" description="Install on Ubuntu, Fedora, Arch, Bazzite, SteamOS, or Windows." />
|
||||||
<Card title="Connect a Client" href="/docs/clients" description="Stream with the native app for your device — macOS, Linux, Windows, Android — or any Moonlight client." />
|
<Card title="Connect a Client" href="/docs/clients" description="Stream with the native app for your device — macOS, Linux, Windows, Android — or any Moonlight client." />
|
||||||
<Card title="API Reference" href="/api" description="Interactive OpenAPI reference for the host's management REST API — status, devices, pairing, library." />
|
<Card title="API Reference" href="/api" description="Interactive OpenAPI reference for the host's management REST API — status, devices, pairing, library." />
|
||||||
</Cards>
|
</Cards>
|
||||||
@@ -37,7 +37,7 @@ It's built for the things that make streaming feel native:
|
|||||||
## What you need
|
## What you need
|
||||||
|
|
||||||
- A **host** with a supported GPU — either a **Linux** machine running one of the
|
- A **host** with a supported GPU — either a **Linux** machine running one of the
|
||||||
[supported setups](/docs/requirements) (**Ubuntu** GNOME or KDE, **Fedora** KDE, or **Bazzite**), or
|
[supported setups](/docs/requirements) (**Ubuntu**, **Fedora**, **Arch**, or **Bazzite**), or
|
||||||
a **[Windows](/docs/windows-host) PC**.
|
a **[Windows](/docs/windows-host) PC**.
|
||||||
- A **client device** to stream to — there are native apps for **macOS, iOS/iPadOS, tvOS, Linux,
|
- A **client device** to stream to — there are native apps for **macOS, iOS/iPadOS, tvOS, Linux,
|
||||||
Windows, and Android**, plus any device that runs **Moonlight**.
|
Windows, and Android**, plus any device that runs **Moonlight**.
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ On **Windows**, the host ships as a signed installer instead — see [Windows](#
|
|||||||
|
|
||||||
| Distro | Package manager | One-command happy path | Guide |
|
| Distro | Package manager | One-command happy path | Guide |
|
||||||
|--------|-----------------|------------------------|-------|
|
|--------|-----------------|------------------------|-------|
|
||||||
| **Ubuntu / Debian** | apt | `sudo apt install punktfunk-host` | [Ubuntu — GNOME](/docs/ubuntu-gnome) · [Ubuntu — KDE](/docs/ubuntu-kde) · [packaging/debian](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md) |
|
| **Ubuntu / Debian** | apt | `sudo apt install punktfunk-host` | [Ubuntu / Debian](/docs/ubuntu) · [packaging/debian](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md) |
|
||||||
| **Bazzite / Fedora Atomic** | systemd-sysext | `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) |
|
| **Bazzite / Fedora Atomic** | systemd-sysext | `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 | `dnf install punktfunk punktfunk-web` | [Fedora — KDE](/docs/fedora-kde) · [packaging/rpm](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/rpm/README.md) |
|
| **Fedora (dnf)** | dnf / rpm-ostree | `dnf install punktfunk punktfunk-web` | [Fedora](/docs/fedora) · [packaging/rpm](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/rpm/README.md) |
|
||||||
| **Arch** | pacman | `pacman -Sy punktfunk-host` (binary repo) | [Arch Linux](/docs/arch) · [packaging/arch](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md) |
|
| **Arch** | pacman | `pacman -Sy punktfunk-host` (binary repo) | [Arch Linux](/docs/arch) · [packaging/arch](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md) |
|
||||||
| **SteamOS (host)** | on-device script | `bash scripts/steamdeck/install.sh` | [SteamOS (Host)](/docs/steamos-host) |
|
| **SteamOS (host)** | on-device script | `bash scripts/steamdeck/install.sh` | [SteamOS (Host)](/docs/steamos-host) |
|
||||||
|
|
||||||
@@ -79,13 +79,22 @@ fallback without one. More detail — including the CLI `punktfunk-host service
|
|||||||
Bare `serve` is the secure native-only default (native `punktfunk/1` + the web console). On a
|
Bare `serve` is the secure native-only default (native `punktfunk/1` + the web console). On a
|
||||||
trusted LAN, add `--gamestream` to also serve stock [Moonlight](/docs/moonlight) clients.
|
trusted LAN, add `--gamestream` to also serve stock [Moonlight](/docs/moonlight) clients.
|
||||||
|
|
||||||
3. Enable the web console and read its login password, then open `http://<host-ip>:47992`:
|
3. Enable the web console:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
systemctl --user enable --now punktfunk-web
|
systemctl --user enable --now punktfunk-web
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Then open `http://<host-ip>: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), 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
|
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).
|
automatically at boot, see [Running as a Service](/docs/running-as-a-service).
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
---
|
||||||
|
title: KDE Plasma (KWin)
|
||||||
|
description: Configure a punktfunk host for KDE — host.env, quirks, and a headless KWin session.
|
||||||
|
---
|
||||||
|
|
||||||
|
Configure a punktfunk host on **KDE Plasma**. The host uses KDE's KWin compositor to create a
|
||||||
|
per-client virtual display, captured zero-copy on NVIDIA. This page assumes the package is already
|
||||||
|
installed — see [Ubuntu](/docs/ubuntu), [Fedora](/docs/fedora), [Arch](/docs/arch), or the
|
||||||
|
[Bazzite](/docs/bazzite) appliance.
|
||||||
|
|
||||||
|
> 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
|
||||||
|
|
||||||
|
A KDE starter `~/.config/punktfunk/host.env`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
WAYLAND_DISPLAY=wayland-0
|
||||||
|
XDG_CURRENT_DESKTOP=KDE
|
||||||
|
PUNKTFUNK_COMPOSITOR=kwin
|
||||||
|
PUNKTFUNK_VIDEO_SOURCE=virtual
|
||||||
|
PUNKTFUNK_ZEROCOPY=1
|
||||||
|
PUNKTFUNK_INPUT_BACKEND=libei
|
||||||
|
```
|
||||||
|
|
||||||
|
The host auto-detects the running compositor on every connect, so most of this is optional — the
|
||||||
|
values above are just what it resolves to on a KWin session. See the
|
||||||
|
[Configuration reference](/docs/configuration) for every option.
|
||||||
|
|
||||||
|
## Use a Wayland session
|
||||||
|
|
||||||
|
KDE must run on **Wayland**, not X11 — pick the Wayland session from the picker on the login screen.
|
||||||
|
The virtual-display path is Wayland-only and will not come up under X11.
|
||||||
|
|
||||||
|
KWin must be **6.5.6 or newer** (virtual outputs land there). Check with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kwin_wayland --version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Streaming the interactive desktop
|
||||||
|
|
||||||
|
To stream a logged-in Plasma desktop (rather than a headless session, below), KWin has to hand the
|
||||||
|
host its restricted screencast protocol. The host package ships an `io.unom.Punktfunk.Host.desktop`
|
||||||
|
file whose `X-KDE-Wayland-Interfaces` grants exactly that on a normal interactive session
|
||||||
|
(least-privilege, the same mechanism krfb/krdp use). After a **fresh install, log out and back into
|
||||||
|
the Desktop session once** so KWin re-reads the grant.
|
||||||
|
|
||||||
|
A normal KDE login still lacks the RemoteDesktop grant that **input** injection needs — without it the
|
||||||
|
host pops an "Allow remote control?" dialog no headless box can answer. **Fedora and Bazzite** ship a
|
||||||
|
one-shot helper that seeds it (run once as the streaming user, no root):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bash /usr/share/punktfunk/bazzite/kde-desktop-setup.sh # Fedora / Bazzite
|
||||||
|
```
|
||||||
|
|
||||||
|
The `.deb` and Arch packages don't include that wrapper. Seed the grant by hand instead — copy the
|
||||||
|
shipped `kde-authorized` file into the portal store (the share dir is `/usr/share/punktfunk-host` on
|
||||||
|
Debian/Ubuntu, `/usr/share/punktfunk` on Arch), then log out and back in:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p ~/.local/share/flatpak/db
|
||||||
|
cp /usr/share/punktfunk*/headless/kde-authorized ~/.local/share/flatpak/db/kde-authorized
|
||||||
|
```
|
||||||
|
|
||||||
|
A login-less appliance skips all of this — its headless session (below) needs none of these grants.
|
||||||
|
|
||||||
|
## Start the host
|
||||||
|
|
||||||
|
With `host.env` in place, start the host from **inside your Plasma session**:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl --user enable --now punktfunk-host
|
||||||
|
journalctl --user -u punktfunk-host -f # watch it come up and print its identity fingerprint
|
||||||
|
```
|
||||||
|
|
||||||
|
Then bring up [The Web Console](/docs/web-console) to arm pairing and connect a
|
||||||
|
[client](/docs/clients). To start at boot — including fully headless — see the
|
||||||
|
[headless session](#headless-session) below or [Running as a Service](/docs/running-as-a-service).
|
||||||
|
|
||||||
|
## Persistent per-client scaling
|
||||||
|
|
||||||
|
KWin round-trips per-client display scale: it names each session's virtual output per client, so a
|
||||||
|
scale you set for one client (150 %, 125 %, …) is reapplied on that client's next connect. See
|
||||||
|
[Virtual displays](/docs/virtual-displays).
|
||||||
|
|
||||||
|
## Headless session
|
||||||
|
|
||||||
|
For a login-less appliance — a box that streams at boot with no graphical login — the host brings up a
|
||||||
|
**dedicated headless KWin session** rather than relying on an interactive one. It runs its own
|
||||||
|
`kwin --virtual` session (shipped as the `punktfunk-kde-session.service` unit) with permission checks
|
||||||
|
relaxed, so it needs none of the interactive grants above.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
mkdir -p ~/.config/punktfunk
|
||||||
|
cp /usr/share/punktfunk/host.env.kde ~/.config/punktfunk/host.env # Debian/Ubuntu: /usr/share/punktfunk-host/host.env.kde
|
||||||
|
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user enable --now punktfunk-kde-session punktfunk-host
|
||||||
|
sudo loginctl enable-linger "$USER"
|
||||||
|
```
|
||||||
|
|
||||||
|
The session unit brings up headless KWin; the host unit follows it and starts listening. See
|
||||||
|
[Running as a Service](/docs/running-as-a-service) for the full headless setup.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **KWin too old:** virtual outputs need KWin **≥ 6.5.6**. Check with `kwin_wayland --version`.
|
||||||
|
- **Black screen / no picture:** confirm you're on a Wayland session (not X11) and the NVIDIA GL
|
||||||
|
userspace is installed. More in [Troubleshooting](/docs/troubleshooting).
|
||||||
|
|
||||||
|
To bring the console up and pair, see [The Web Console](/docs/web-console).
|
||||||
@@ -5,27 +5,31 @@
|
|||||||
"how-it-works",
|
"how-it-works",
|
||||||
"security",
|
"security",
|
||||||
"quickstart",
|
"quickstart",
|
||||||
"install",
|
"---Install the host---",
|
||||||
"---Host Setup---",
|
|
||||||
"requirements",
|
"requirements",
|
||||||
"ubuntu-gnome",
|
"install",
|
||||||
"ubuntu-kde",
|
"ubuntu",
|
||||||
"fedora-kde",
|
"fedora",
|
||||||
"arch",
|
"arch",
|
||||||
"bazzite",
|
"bazzite",
|
||||||
"steamos-host",
|
"steamos-host",
|
||||||
"windows-host",
|
"windows-host",
|
||||||
|
"web-console",
|
||||||
|
"---Configure your desktop---",
|
||||||
|
"configuration",
|
||||||
|
"kde",
|
||||||
|
"gnome",
|
||||||
|
"gamescope",
|
||||||
|
"sway",
|
||||||
"running-as-a-service",
|
"running-as-a-service",
|
||||||
|
"virtual-displays",
|
||||||
|
"host-cli",
|
||||||
"---Connecting---",
|
"---Connecting---",
|
||||||
"clients",
|
"clients",
|
||||||
"install-client",
|
"install-client",
|
||||||
"steam-deck",
|
"steam-deck",
|
||||||
"moonlight",
|
"moonlight",
|
||||||
"pairing",
|
"pairing",
|
||||||
"---Configuration---",
|
|
||||||
"configuration",
|
|
||||||
"virtual-displays",
|
|
||||||
"host-cli",
|
|
||||||
"---Troubleshooting---",
|
"---Troubleshooting---",
|
||||||
"troubleshooting",
|
"troubleshooting",
|
||||||
"stats",
|
"stats",
|
||||||
|
|||||||
@@ -11,15 +11,19 @@ This is the shortest path to a working stream. Each step links to the details.
|
|||||||
|
|
||||||
## 1. Set up the host
|
## 1. Set up the host
|
||||||
|
|
||||||
On your Linux gaming machine (NVIDIA, AMD, or Intel GPU), follow the guide for your system:
|
On your gaming machine (NVIDIA, AMD, or Intel GPU), follow the install guide for your system:
|
||||||
|
|
||||||
- [Ubuntu — GNOME](/docs/ubuntu-gnome)
|
- [Ubuntu / Debian](/docs/ubuntu)
|
||||||
- [Ubuntu — KDE Plasma](/docs/ubuntu-kde)
|
- [Fedora](/docs/fedora)
|
||||||
- [Fedora — KDE Plasma](/docs/fedora-kde)
|
- [Arch](/docs/arch)
|
||||||
- [Bazzite — gamescope / Steam](/docs/bazzite)
|
- [Bazzite](/docs/bazzite)
|
||||||
|
- [SteamOS](/docs/steamos-host)
|
||||||
|
- [Windows host](/docs/windows-host)
|
||||||
|
|
||||||
Each one covers the GPU driver, the dependencies, and how to build and run the host. Check the
|
Each one covers the GPU driver, the dependencies, and how to install and run the host. After
|
||||||
[Requirements](/docs/requirements) first if you're not sure your machine is a fit.
|
installing, configure for your desktop ([KDE](/docs/kde) / [GNOME](/docs/gnome) /
|
||||||
|
[gamescope](/docs/gamescope) / [Sway](/docs/sway)). Check the [Requirements](/docs/requirements)
|
||||||
|
first if you're not sure your machine is a fit.
|
||||||
|
|
||||||
## 2. Start the host
|
## 2. Start the host
|
||||||
|
|
||||||
@@ -44,9 +48,9 @@ latency, or any Moonlight client:
|
|||||||
the list of hosts found on your network. Select it, and when prompted, **pair**.
|
the list of hosts found on your network. Select it, and when prompted, **pair**.
|
||||||
- **Anything with Moonlight:** add the host (it should be discovered automatically), then pair.
|
- **Anything with Moonlight:** add the host (it should be discovered automatically), then pair.
|
||||||
|
|
||||||
To pair, the host needs to show a PIN. Arm pairing from the host's web console — the host displays a
|
To pair, the host needs to show a PIN. [Arm pairing](/docs/web-console#arm-pairing) from the host's
|
||||||
4-digit PIN, you type it into the client, and they trust each other from then on. Pairing is required
|
web console — the host displays a 4-digit PIN, you type it into the client, and they trust each other
|
||||||
by default. Full details: [Pairing & Trust](/docs/pairing).
|
from then on. Pairing is required by default. Full details: [Pairing & Trust](/docs/pairing).
|
||||||
|
|
||||||
## 4. Stream
|
## 4. Stream
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,31 @@ description: What you need to run a punktfunk host — GPU, driver, desktop, and
|
|||||||
## Supported setups
|
## Supported setups
|
||||||
|
|
||||||
A punktfunk host runs primarily on a Linux machine with a dedicated GPU — NVIDIA (NVENC) is the
|
A punktfunk host runs primarily on a Linux machine with a dedicated GPU — NVIDIA (NVENC) is the
|
||||||
most-exercised path, and AMD/Intel GPUs work via VAAPI (a native
|
most-exercised path, and AMD/Intel GPUs work via VAAPI. A native [Windows host](/docs/windows-host)
|
||||||
[Windows host](/docs/windows-host) is also available — see below). These are the Linux desktop
|
is also available. Setup splits along two axes: you **install** the package per distro, then
|
||||||
environments it supports today, each with its own guide:
|
**configure** the host — and learn its quirks — per desktop/compositor.
|
||||||
|
|
||||||
| Setup | Desktop / compositor | Guide |
|
> 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.
|
||||||
| **Ubuntu** (Desktop or Server) | GNOME (Mutter) | [Ubuntu — GNOME](/docs/ubuntu-gnome) |
|
|
||||||
| **Ubuntu** (Desktop or Server) | KDE Plasma (KWin) | [Ubuntu — KDE](/docs/ubuntu-kde) |
|
|
||||||
| **Fedora** | KDE Plasma (KWin) | [Fedora — KDE](/docs/fedora-kde) |
|
|
||||||
| **Bazzite** | gamescope (Steam) | [Bazzite](/docs/bazzite) |
|
|
||||||
|
|
||||||
Other wlroots compositors (Sway/Hyprland) also work but aren't a primary target. If your desktop isn't
|
**Distros — install the package:**
|
||||||
listed, the host still needs one of these compositor backends to create a virtual display.
|
|
||||||
|
- [Ubuntu / Debian](/docs/ubuntu)
|
||||||
|
- [Fedora](/docs/fedora)
|
||||||
|
- [Arch](/docs/arch)
|
||||||
|
- [Bazzite](/docs/bazzite)
|
||||||
|
- [SteamOS](/docs/steamos-host)
|
||||||
|
|
||||||
|
**Desktops — configure and quirks:**
|
||||||
|
|
||||||
|
- [KDE Plasma (KWin)](/docs/kde)
|
||||||
|
- [GNOME (Mutter)](/docs/gnome)
|
||||||
|
- [Steam / gamescope](/docs/gamescope)
|
||||||
|
- [Sway / wlroots](/docs/sway)
|
||||||
|
|
||||||
|
Pick your distro to install, then your desktop to configure — the two are independent. Other
|
||||||
|
wlroots compositors (Hyprland) work but aren't a primary target; the host still needs one of these
|
||||||
|
compositor backends to create a virtual display.
|
||||||
|
|
||||||
> **Windows host:** punktfunk also runs as a native host on **Windows 11 22H2 or newer (x64)** — a
|
> **Windows host:** punktfunk also runs as a native host on **Windows 11 22H2 or newer (x64)** — a
|
||||||
> signed installer that registers a service and bundles a virtual-display driver (whose driver-
|
> signed installer that registers a service and bundles a virtual-display driver (whose driver-
|
||||||
@@ -32,8 +44,8 @@ listed, the host still needs one of these compositor backends to create a virtua
|
|||||||
encodes the video in hardware.
|
encodes the video in hardware.
|
||||||
- **NVIDIA driver 535 or newer** (550+ recommended). The driver must include the **GL/EGL userspace**,
|
- **NVIDIA driver 535 or newer** (550+ recommended). The driver must include the **GL/EGL userspace**,
|
||||||
not just `nvidia-utils` — without it the compositor can't initialise the GPU and capture fails. Each
|
not just `nvidia-utils` — without it the compositor can't initialise the GPU and capture fails. Each
|
||||||
setup guide installs the right package (e.g. `libnvidia-gl-<version>` on Ubuntu).
|
install guide installs the right package (e.g. `libnvidia-gl-<version>` on Ubuntu).
|
||||||
- **`nvidia-drm modeset=1`** must be enabled (Wayland on NVIDIA needs it). The setup guides cover this.
|
- **`nvidia-drm modeset=1`** must be enabled (Wayland on NVIDIA needs it). The install guides cover this.
|
||||||
- **AMD / Intel GPUs** encode via **VAAPI** instead (install `mesa-va-drivers` or
|
- **AMD / Intel GPUs** encode via **VAAPI** instead (install `mesa-va-drivers` or
|
||||||
`intel-media-driver`; validated live on AMD RDNA3). The NVIDIA-specific notes above don't apply
|
`intel-media-driver`; validated live on AMD RDNA3). The NVIDIA-specific notes above don't apply
|
||||||
there. On modern Intel (Gen12/Tiger Lake and newer, including Arc) the driver only offers the
|
there. On modern Intel (Gen12/Tiger Lake and newer, including Arc) the driver only offers the
|
||||||
@@ -57,9 +69,9 @@ needs to be running for the user the host runs as. This can be:
|
|||||||
|
|
||||||
Minimum compositor versions (newer is fine):
|
Minimum compositor versions (newer is fine):
|
||||||
|
|
||||||
- **KWin ≥ 6.5.6** (KDE Plasma) — headless virtual outputs.
|
- **KWin ≥ 6.5.6** ([KDE Plasma](/docs/kde)) — headless virtual outputs.
|
||||||
- **GNOME ≥ 48** (Mutter) — virtual-monitor screen-cast.
|
- **GNOME ≥ 48** ([Mutter](/docs/gnome)) — virtual-monitor screen-cast.
|
||||||
- **gamescope ≥ 3.16.22** (Bazzite/Steam) — older versions deadlock during capture.
|
- **gamescope ≥ 3.16.22** ([Bazzite/Steam](/docs/gamescope)) — older versions deadlock during capture.
|
||||||
|
|
||||||
## Network
|
## Network
|
||||||
|
|
||||||
@@ -70,10 +82,6 @@ Minimum compositor versions (newer is fine):
|
|||||||
- For best results, a wired or fast Wi-Fi link. The host can run a built-in **speed test** to pick a
|
- For best results, a wired or fast Wi-Fi link. The host can run a built-in **speed test** to pick a
|
||||||
bitrate for your link (see [Configuration](/docs/configuration)).
|
bitrate for your link (see [Configuration](/docs/configuration)).
|
||||||
|
|
||||||
> **Before you set up a host, read [Security & Safe Use](/docs/security).** A streaming host is
|
|
||||||
> remote control of the machine — it's important to understand what that exposes, why to keep it on a
|
|
||||||
> trusted network, and how pairing protects you.
|
|
||||||
|
|
||||||
## A client
|
## A client
|
||||||
|
|
||||||
You also need something to stream *to* — see [Connect a Client](/docs/clients). There are native
|
You also need something to stream *to* — see [Connect a Client](/docs/clients). There are native
|
||||||
|
|||||||
@@ -37,50 +37,21 @@ Start by making the host service start at boot even when nobody logs in:
|
|||||||
sudo loginctl enable-linger "$USER"
|
sudo loginctl enable-linger "$USER"
|
||||||
```
|
```
|
||||||
|
|
||||||
Then bring up a session automatically, depending on your desktop:
|
Then bring up a session automatically. How you do that is desktop-specific — auto-login, lock
|
||||||
|
disable, and the session unit differ per compositor, so each is documented on its own page:
|
||||||
|
|
||||||
### Headless GNOME
|
- GNOME: [GNOME → Headless session](/docs/gnome#headless-session).
|
||||||
|
- KDE Plasma: [KDE → Headless session](/docs/kde#headless-session).
|
||||||
|
- Steam / gamescope: [gamescope](/docs/gamescope) — the host launches its own session per client, so
|
||||||
|
there's no separate session unit.
|
||||||
|
|
||||||
Have GDM auto-login your user, so a GNOME Wayland session is always running:
|
Once a session comes up at boot, enable the host user service (section A) and reboot. The host comes up
|
||||||
|
on that session.
|
||||||
```ini
|
|
||||||
# /etc/gdm3/custom.conf (Ubuntu) · /etc/gdm/custom.conf (Fedora)
|
|
||||||
[daemon]
|
|
||||||
AutomaticLoginEnable = true
|
|
||||||
AutomaticLogin = your-user
|
|
||||||
```
|
|
||||||
|
|
||||||
Then **disable the screen lock** — a locked GNOME session blocks screen capture, and there's no one to
|
|
||||||
unlock a headless box:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
gsettings set org.gnome.desktop.screensaver lock-enabled false
|
|
||||||
gsettings set org.gnome.desktop.session idle-delay 0
|
|
||||||
```
|
|
||||||
|
|
||||||
Enable the host user service (section A) and reboot. The host comes up on the auto-login session.
|
|
||||||
|
|
||||||
### Headless KDE
|
|
||||||
|
|
||||||
punktfunk ships a unit that brings up a headless KWin/Plasma session with no display manager, so the
|
|
||||||
host has a desktop to stream even with no monitor attached:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cp scripts/punktfunk-kde-session.service scripts/punktfunk-host.service ~/.config/systemd/user/
|
|
||||||
# host.env: PUNKTFUNK_COMPOSITOR=kwin, WAYLAND_DISPLAY=wayland-kde
|
|
||||||
systemctl --user daemon-reload
|
|
||||||
systemctl --user enable punktfunk-kde-session punktfunk-host
|
|
||||||
sudo loginctl enable-linger "$USER"
|
|
||||||
reboot
|
|
||||||
```
|
|
||||||
|
|
||||||
The session unit starts headless KWin; the host unit follows it and starts listening. (KWin only needs
|
|
||||||
to be up by the time a client connects, so the ordering is soft.)
|
|
||||||
|
|
||||||
### Headless Bazzite
|
### Headless Bazzite
|
||||||
|
|
||||||
On Bazzite, the host launches its own gamescope/Steam session per client, so you don't need a separate
|
On Bazzite, the host launches its own gamescope/Steam session per client, so you don't need a separate
|
||||||
session unit — see [Bazzite](/docs/bazzite).
|
session unit — see [Bazzite](/docs/bazzite) and [gamescope](/docs/gamescope).
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
|
|||||||
@@ -86,9 +86,8 @@ When it finishes it prints the web-console URL and how to pair.
|
|||||||
|
|
||||||
By default the host **requires PIN pairing** (secure). Two ways to pair:
|
By default the host **requires PIN pairing** (secure). Two ways to pair:
|
||||||
|
|
||||||
- **Web console** (printed at the end of step 2): open `http://<device-ip>:47992`, log in with the
|
- **Web console** (printed at the end of step 2): open `http://<device-ip>:47992`,
|
||||||
generated password (in `~/.config/punktfunk/web.env`), go to **Devices → arm pairing**, and enter
|
[arm pairing](/docs/web-console#arm-pairing), and enter the PIN on your client.
|
||||||
the PIN on your client.
|
|
||||||
- **From the client directly**: pick this host (it advertises over mDNS as `_punktfunk._udp`) and
|
- **From the client directly**: pick this host (it advertises over mDNS as `_punktfunk._udp`) and
|
||||||
enter the PIN the host shows.
|
enter the PIN the host shows.
|
||||||
|
|
||||||
@@ -96,17 +95,9 @@ On a trusted home LAN you can instead install with `--open` and skip pairing ent
|
|||||||
|
|
||||||
### Console login password
|
### Console login password
|
||||||
|
|
||||||
The installer generates a random console login password and writes it to
|
The installer generates a random console login password (printed at the end of step 2) and writes it
|
||||||
`~/.config/punktfunk/web.env` (as `PUNKTFUNK_UI_PASSWORD=…`); it's also printed at the end of the
|
to `~/.config/punktfunk/web.env`. To read it back or set your own, see
|
||||||
install run (step 2). Read it back with:
|
[The Web Console](/docs/web-console#login-password).
|
||||||
|
|
||||||
```sh
|
|
||||||
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web.env
|
|
||||||
```
|
|
||||||
|
|
||||||
To set your own password, edit that file and restart the console:
|
|
||||||
`systemctl --user restart punktfunk-web`. Forgot it? This is the recovery path linked from the
|
|
||||||
console login screen — see [Forgot your Password?](/docs/forgot-password).
|
|
||||||
|
|
||||||
## 4. Verify
|
## 4. Verify
|
||||||
|
|
||||||
@@ -118,7 +109,8 @@ journalctl --user -u punktfunk-host -f # watch a client connect
|
|||||||
Connect from a [native client](/docs/clients), or from [Moonlight](/docs/moonlight) (unless you
|
Connect from a [native client](/docs/clients), or from [Moonlight](/docs/moonlight) (unless you
|
||||||
installed with `--no-gamestream`). In Game Mode the host attaches to the running gamescope session and
|
installed with `--no-gamestream`). In Game Mode the host attaches to the running gamescope session and
|
||||||
streams it at your client's resolution; in Desktop Mode it streams the KDE desktop. The host
|
streams it at your client's resolution; in Desktop Mode it streams the KDE desktop. The host
|
||||||
auto-detects which session is live per connection.
|
auto-detects which session is live per connection. See [Steam / gamescope](/docs/gamescope) for the
|
||||||
|
attach-vs-managed detail and known limits.
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
---
|
||||||
|
title: Sway / wlroots
|
||||||
|
description: Configure a punktfunk host on a wlroots compositor (Sway, Hyprland).
|
||||||
|
---
|
||||||
|
|
||||||
|
The wlroots family can host — but **Sway is the only validated path.** The host adds a per-client
|
||||||
|
headless output at the client's exact mode and captures it through the xdg-desktop-portal-wlr (xdpw)
|
||||||
|
ScreenCast portal, injecting input via the wlroots virtual pointer/keyboard protocols. Hyprland and
|
||||||
|
other wlroots compositors are best-effort (see [How it works](#how-it-works) for the caveat).
|
||||||
|
|
||||||
|
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 you usually need nothing here. To force the backend, set
|
||||||
|
these in `~/.config/punktfunk/host.env`:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
PUNKTFUNK_COMPOSITOR=wlroots # aliases: sway, hyprland
|
||||||
|
PUNKTFUNK_INPUT_BACKEND=wlr
|
||||||
|
PUNKTFUNK_VIDEO_SOURCE=virtual
|
||||||
|
PUNKTFUNK_ZEROCOPY=1 # GPU zero-copy capture→encode; auto-falls back to CPU
|
||||||
|
```
|
||||||
|
|
||||||
|
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; other wlroots compositors (Hyprland, …) don't expose an
|
||||||
|
equivalent, so virtual-output creation isn't wired up for them yet — Sway is the supported wlroots
|
||||||
|
path today.
|
||||||
|
- **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 wlroots session (Sway, Hyprland, …).
|
||||||
|
- **xdg-desktop-portal-wlr (xdpw)** installed and running — the host captures through its ScreenCast
|
||||||
|
portal. Without it there is no video.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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).
|
||||||
@@ -71,10 +71,14 @@ The NVIDIA **GL/EGL userspace** is missing — the base driver package doesn't a
|
|||||||
- **Ubuntu:** `sudo apt install libnvidia-gl-<version>` (matching your driver).
|
- **Ubuntu:** `sudo apt install libnvidia-gl-<version>` (matching your driver).
|
||||||
- Confirm `/usr/share/glvnd/egl_vendor.d/10_nvidia.json` exists and `nvidia-drm modeset` is `Y`.
|
- Confirm `/usr/share/glvnd/egl_vendor.d/10_nvidia.json` exists and `nvidia-drm modeset` is `Y`.
|
||||||
|
|
||||||
|
See [GNOME](/docs/gnome) for the GL/EGL userspace details.
|
||||||
|
|
||||||
## Black screen / no picture, but the client connects
|
## Black screen / no picture, but the client connects
|
||||||
|
|
||||||
- You must be on a **Wayland** session, not X11 (check the login-screen session picker).
|
- You must be on a **Wayland** session, not X11 (check the login-screen session picker).
|
||||||
- KWin must be **≥ 6.5.6** (`kwin_wayland --version`); GNOME **≥ 48**; gamescope **≥ 3.16.22**.
|
- KWin must be **≥ 6.5.6** (`kwin_wayland --version`); GNOME **≥ 48**; gamescope **≥ 3.16.22**. See
|
||||||
|
[KDE](/docs/kde) for the KWin/Wayland requirement and [gamescope](/docs/gamescope) for the
|
||||||
|
gamescope one.
|
||||||
- Confirm `PUNKTFUNK_COMPOSITOR` in [`host.env`](/docs/configuration) matches your desktop.
|
- Confirm `PUNKTFUNK_COMPOSITOR` in [`host.env`](/docs/configuration) matches your desktop.
|
||||||
|
|
||||||
## Capture fails: "Session creation inhibited" (GNOME)
|
## Capture fails: "Session creation inhibited" (GNOME)
|
||||||
@@ -86,7 +90,8 @@ gsettings set org.gnome.desktop.screensaver lock-enabled false
|
|||||||
gsettings set org.gnome.desktop.session idle-delay 0
|
gsettings set org.gnome.desktop.session idle-delay 0
|
||||||
```
|
```
|
||||||
|
|
||||||
See [Running as a Service](/docs/running-as-a-service).
|
See [GNOME → Headless session](/docs/gnome#headless-session) and
|
||||||
|
[Running as a Service](/docs/running-as-a-service).
|
||||||
|
|
||||||
## A controller is detected but does nothing (Bazzite)
|
## A controller is detected but does nothing (Bazzite)
|
||||||
|
|
||||||
@@ -96,7 +101,8 @@ The host user needs to be in the `input` group. On Bazzite:
|
|||||||
ujust add-user-to-input-group
|
ujust add-user-to-input-group
|
||||||
```
|
```
|
||||||
|
|
||||||
Then log out and back in. On other distros this is `sudo usermod -aG input $USER` + re-login.
|
Then log out and back in. On other distros this is `sudo usermod -aG input $USER` + re-login. See
|
||||||
|
[Bazzite](/docs/bazzite).
|
||||||
|
|
||||||
## Pairing is rejected / the client can't connect
|
## Pairing is rejected / the client can't connect
|
||||||
|
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
---
|
|
||||||
title: Ubuntu — GNOME
|
|
||||||
description: Set up a punktfunk host on Ubuntu with the GNOME desktop (Mutter).
|
|
||||||
---
|
|
||||||
|
|
||||||
Set up a punktfunk host on **Ubuntu** (Desktop or Server) running **GNOME**. The host uses GNOME's
|
|
||||||
Mutter compositor to create a per-client virtual display. Tested on Ubuntu 24.04+ and GNOME 48+.
|
|
||||||
|
|
||||||
> New to this? Skim [Requirements](/docs/requirements) first, and read
|
|
||||||
> [Security & Safe Use](/docs/security) — a streaming host is remote control of the machine, so keep it
|
|
||||||
> on a trusted LAN or VPN and require pairing.
|
|
||||||
|
|
||||||
## 1. NVIDIA driver
|
|
||||||
|
|
||||||
Install the recommended NVIDIA driver:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo ubuntu-drivers install # or: sudo apt install nvidia-driver-<version>
|
|
||||||
```
|
|
||||||
|
|
||||||
Then make sure the **GL/EGL userspace** is present — GNOME on NVIDIA needs it, and the base driver
|
|
||||||
package doesn't always pull it in. Install the `libnvidia-gl` package matching your driver version:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo apt install libnvidia-gl-<version> # e.g. libnvidia-gl-550
|
|
||||||
```
|
|
||||||
|
|
||||||
Reboot, then confirm the driver and KMS modeset:
|
|
||||||
|
|
||||||
```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:** on a machine with Secure Boot **enabled**, the NVIDIA kernel module won't load
|
|
||||||
> until you enrol its signing key. If `nvidia-smi` reports it can't talk to the driver, run
|
|
||||||
> `sudo mokutil --import /var/lib/shim-signed/mok/MOK.der` (set a one-time password), reboot, and
|
|
||||||
> choose **Enrol MOK** at the blue screen. Or disable Secure Boot in firmware.
|
|
||||||
|
|
||||||
## 2. Install the host (apt)
|
|
||||||
|
|
||||||
`punktfunk-host` is published as a `.deb` to the public Gitea apt registry, so the box installs and
|
|
||||||
updates with plain `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 desktop *client* (`punktfunk-client`) is a separate package for the machine you stream *to* — not
|
|
||||||
installed on a host. The NVIDIA driver is **not** a dependency — you installed it out of band in
|
|
||||||
step 1. Later updates are just `sudo apt update && sudo apt upgrade`.
|
|
||||||
|
|
||||||
## 3. Configure
|
|
||||||
|
|
||||||
The package ships the systemd **user** unit, the `/dev/uinput` udev rule, the socket-buffer sysctl
|
|
||||||
tuning, and an example config. As the desktop user, grant gamepad access and write the GNOME config:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo usermod -aG input "$USER" # /dev/uinput for virtual gamepads (re-login to apply)
|
|
||||||
mkdir -p ~/.config/punktfunk
|
|
||||||
cat > ~/.config/punktfunk/host.env <<'ENV'
|
|
||||||
WAYLAND_DISPLAY=wayland-0
|
|
||||||
XDG_CURRENT_DESKTOP=GNOME
|
|
||||||
PUNKTFUNK_COMPOSITOR=mutter
|
|
||||||
PUNKTFUNK_VIDEO_SOURCE=virtual
|
|
||||||
PUNKTFUNK_ZEROCOPY=1
|
|
||||||
PUNKTFUNK_INPUT_BACKEND=libei
|
|
||||||
ENV
|
|
||||||
```
|
|
||||||
|
|
||||||
See the [Configuration reference](/docs/configuration) for every option.
|
|
||||||
|
|
||||||
## 4. Run
|
|
||||||
|
|
||||||
Start the host as a user service from **inside your GNOME session** (so it can reach Mutter):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
systemctl --user enable --now punktfunk-host
|
|
||||||
journalctl --user -u punktfunk-host -f # watch it come up + print its fingerprint
|
|
||||||
```
|
|
||||||
|
|
||||||
The host listens on UDP `9777` (native punktfunk/1) plus the GameStream ports, and advertises itself
|
|
||||||
over mDNS. It requires **PIN pairing** by default (secure on a LAN) — arm pairing from the web
|
|
||||||
console (next step) and pair once from your [client](/docs/clients).
|
|
||||||
|
|
||||||
### Web console
|
|
||||||
|
|
||||||
The console (status, paired devices, arm pairing) ships as `punktfunk-web`:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
systemctl --user enable --now punktfunk-web
|
|
||||||
# read the auto-generated login password, then open http://<host-ip>:47992
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Console login password
|
|
||||||
|
|
||||||
The console is password-protected. On first start `punktfunk-web-init` generates a random login
|
|
||||||
password and saves it to `~/.config/punktfunk/web-password` (as `PUNKTFUNK_UI_PASSWORD=…`). Read it
|
|
||||||
back at any time — from the init service's journal, or straight from the file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web-password
|
|
||||||
```
|
|
||||||
|
|
||||||
To set your own password, edit that file (`PUNKTFUNK_UI_PASSWORD=<your-password>`) and restart the
|
|
||||||
console: `systemctl --user restart punktfunk-web`. Forgot it? This is the recovery path linked from
|
|
||||||
the console login screen — see [Forgot your Password?](/docs/forgot-password).
|
|
||||||
|
|
||||||
To run the host automatically at boot — including on a **headless** machine with no monitor — see
|
|
||||||
[Running as a Service](/docs/running-as-a-service).
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- **gnome-shell fails to start / "GPU … not supported by EGL":** the NVIDIA GL/EGL userspace is
|
|
||||||
missing. Install `libnvidia-gl-<version>` (step 1) and confirm
|
|
||||||
`/usr/share/glvnd/egl_vendor.d/10_nvidia.json` exists.
|
|
||||||
- **Capture fails with "Session creation inhibited":** a **locked** GNOME session blocks screen
|
|
||||||
capture. On a headless/always-on host, disable the lock — see
|
|
||||||
[Running as a Service](/docs/running-as-a-service).
|
|
||||||
- More in [Troubleshooting](/docs/troubleshooting).
|
|
||||||
|
|
||||||
## Appendix — build from source
|
|
||||||
|
|
||||||
If the apt registry doesn't have a build for your release, or you want to track `main` directly,
|
|
||||||
compile the host yourself (no clean updates / no packaged units — you wire those up by hand).
|
|
||||||
|
|
||||||
Install the build toolchain and runtime libraries:
|
|
||||||
|
|
||||||
```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 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
|
|
||||||
```
|
|
||||||
|
|
||||||
Install Rust if you don't have it, then build:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
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 -p punktfunk-host
|
|
||||||
```
|
|
||||||
|
|
||||||
The host binary lands at `target/release/punktfunk-host`. Write `~/.config/punktfunk/host.env` as in
|
|
||||||
step 3, then run it inside your GNOME session:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo run --release -p punktfunk-host -- serve --gamestream
|
|
||||||
```
|
|
||||||
|
|
||||||
(The native plane is always on; `--gamestream` adds the Moonlight-compat surface this guide's
|
|
||||||
GameStream ports refer to — trusted LAN only. Drop it for a secure native-only host.)
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
---
|
|
||||||
title: Ubuntu — KDE Plasma
|
|
||||||
description: Set up a punktfunk host on Ubuntu with KDE Plasma (KWin).
|
|
||||||
---
|
|
||||||
|
|
||||||
Set up a punktfunk host on **Ubuntu** running **KDE Plasma**. The host uses KDE's KWin compositor to
|
|
||||||
create a per-client virtual display. Needs **KWin 6.5.6 or newer**.
|
|
||||||
|
|
||||||
> New to this? Skim [Requirements](/docs/requirements) first, and read
|
|
||||||
> [Security & Safe Use](/docs/security) — a streaming host is remote control of the machine, so keep it
|
|
||||||
> on a trusted LAN or VPN and require pairing.
|
|
||||||
|
|
||||||
## 1. NVIDIA driver
|
|
||||||
|
|
||||||
Identical to the GNOME guide — follow **step 1** of
|
|
||||||
[Ubuntu — GNOME](/docs/ubuntu-gnome#1-nvidia-driver): install the NVIDIA driver **and** the
|
|
||||||
`libnvidia-gl-<version>` userspace, enable `nvidia-drm modeset=1`, reboot, and verify with
|
|
||||||
`nvidia-smi`.
|
|
||||||
|
|
||||||
## 2. Install the host (apt)
|
|
||||||
|
|
||||||
The host is published as a `.deb` to the public Gitea apt registry — install and update with plain
|
|
||||||
`apt`. Trust the repo's signing key, add the repo, and install:
|
|
||||||
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
This also pulls the web console (`punktfunk-web`) via `Recommends` (the pairing/status UI). The
|
|
||||||
desktop *client* — `punktfunk-client`, for the machine you stream *to* — is a separate package, not
|
|
||||||
needed on a host. The NVIDIA driver stays out of band (step 1). Updates later are just
|
|
||||||
`sudo apt update && sudo apt upgrade`.
|
|
||||||
|
|
||||||
## 3. Configure
|
|
||||||
|
|
||||||
The package ships the systemd **user** unit, the udev rule, and the sysctl tuning. As the desktop
|
|
||||||
user, grant gamepad access and write the KDE config:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo usermod -aG input "$USER" # /dev/uinput for virtual gamepads (re-login to apply)
|
|
||||||
mkdir -p ~/.config/punktfunk
|
|
||||||
cat > ~/.config/punktfunk/host.env <<'ENV'
|
|
||||||
WAYLAND_DISPLAY=wayland-0
|
|
||||||
XDG_CURRENT_DESKTOP=KDE
|
|
||||||
PUNKTFUNK_COMPOSITOR=kwin
|
|
||||||
PUNKTFUNK_VIDEO_SOURCE=virtual
|
|
||||||
PUNKTFUNK_ZEROCOPY=1
|
|
||||||
PUNKTFUNK_INPUT_BACKEND=libei
|
|
||||||
ENV
|
|
||||||
```
|
|
||||||
|
|
||||||
> Make sure you're on a **KDE Wayland** session (not X11) — the picker on the login screen. The
|
|
||||||
> virtual-display path is Wayland-only. See the [Configuration reference](/docs/configuration) for
|
|
||||||
> every option.
|
|
||||||
|
|
||||||
## 4. Run
|
|
||||||
|
|
||||||
Start the host as a user service from **inside your Plasma session**:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
systemctl --user enable --now punktfunk-host
|
|
||||||
journalctl --user -u punktfunk-host -f # watch it come up + print its fingerprint
|
|
||||||
```
|
|
||||||
|
|
||||||
The host listens on UDP `9777` (native punktfunk/1) plus the GameStream ports and advertises over
|
|
||||||
mDNS. It requires **PIN pairing** by default — arm pairing from the web console and pair once from
|
|
||||||
your [client](/docs/clients).
|
|
||||||
|
|
||||||
### Web console
|
|
||||||
|
|
||||||
```sh
|
|
||||||
systemctl --user enable --now punktfunk-web
|
|
||||||
# read the auto-generated login password, then open http://<host-ip>:47992
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Console login password
|
|
||||||
|
|
||||||
The console is password-protected. On first start `punktfunk-web-init` generates a random login
|
|
||||||
password and saves it to `~/.config/punktfunk/web-password` (as `PUNKTFUNK_UI_PASSWORD=…`). Read it
|
|
||||||
back at any time — from the init service's journal, or straight from the file:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
|
||||||
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web-password
|
|
||||||
```
|
|
||||||
|
|
||||||
To set your own password, edit that file (`PUNKTFUNK_UI_PASSWORD=<your-password>`) and restart the
|
|
||||||
console: `systemctl --user restart punktfunk-web`. Forgot it? This is the recovery path linked from
|
|
||||||
the console login screen — see [Forgot your Password?](/docs/forgot-password).
|
|
||||||
|
|
||||||
To run it at boot — including fully **headless**, with KWin brought up automatically and no login —
|
|
||||||
see [Running as a Service](/docs/running-as-a-service); the headless appliance is built around KDE.
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- **KWin too old:** virtual outputs need KWin **≥ 6.5.6**. Check with `kwin_wayland --version`.
|
|
||||||
- **No picture / capture fails:** confirm you're on a Wayland session and the NVIDIA GL userspace is
|
|
||||||
installed (`libnvidia-gl-<version>`). More in [Troubleshooting](/docs/troubleshooting).
|
|
||||||
|
|
||||||
## Appendix — build from source
|
|
||||||
|
|
||||||
If the apt registry has no build for your release, compile the host yourself (no clean updates / no
|
|
||||||
packaged units). Install the build toolchain and runtime libraries — the same `apt` line as the
|
|
||||||
[GNOME build-from-source appendix](/docs/ubuntu-gnome#appendix--build-from-source) — then:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
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 -p punktfunk-host
|
|
||||||
```
|
|
||||||
|
|
||||||
Write `~/.config/punktfunk/host.env` as in step 3, then run it inside your Plasma session:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cargo run --release -p punktfunk-host -- serve --gamestream
|
|
||||||
```
|
|
||||||
|
|
||||||
(The native plane is always on; `--gamestream` adds the Moonlight-compat surface this guide's
|
|
||||||
GameStream ports refer to — trusted LAN only. Drop it for a secure native-only host.)
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
---
|
||||||
|
title: Ubuntu / Debian
|
||||||
|
description: Install the punktfunk host on Ubuntu or Debian with apt.
|
||||||
|
---
|
||||||
|
|
||||||
|
Install a punktfunk host on **Ubuntu** (Desktop or Server) or **Debian** from the apt registry. This
|
||||||
|
page covers the distro-level setup — GPU driver, package, gamepad access. It works with either GNOME
|
||||||
|
or KDE; 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.
|
||||||
|
|
||||||
|
## 1. GPU driver
|
||||||
|
|
||||||
|
On **NVIDIA**, install the recommended driver:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo ubuntu-drivers install # or: sudo apt install nvidia-driver-<version>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then make sure the **GL/EGL userspace** is present — Wayland compositors on NVIDIA need it, and the
|
||||||
|
base driver package doesn't always pull it in. Install the `libnvidia-gl` package matching your
|
||||||
|
driver version:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt install libnvidia-gl-<version> # e.g. libnvidia-gl-550
|
||||||
|
```
|
||||||
|
|
||||||
|
Reboot, then confirm the driver and KMS modeset:
|
||||||
|
|
||||||
|
```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:** on a machine with Secure Boot **enabled**, the NVIDIA kernel module won't load
|
||||||
|
> until you enrol its signing key. If `nvidia-smi` reports it can't talk to the driver, run
|
||||||
|
> `sudo mokutil --import /var/lib/shim-signed/mok/MOK.der` (set a one-time password), reboot, and
|
||||||
|
> choose **Enrol MOK** at the blue screen. Or disable Secure Boot in firmware.
|
||||||
|
|
||||||
|
On **AMD/Intel** none of the NVIDIA steps apply. Encode runs through VAAPI on the Mesa stack —
|
||||||
|
`mesa-va-drivers` on AMD, `intel-media-driver` on Intel — which your desktop install already provides.
|
||||||
|
|
||||||
|
## 2. Install the host (apt)
|
||||||
|
|
||||||
|
`punktfunk-host` is published as a `.deb` to the public Gitea apt registry, so the box installs and
|
||||||
|
updates with plain `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 desktop *client* (`punktfunk-client`) is a separate package for the machine you stream *to* — not
|
||||||
|
installed on a host. The NVIDIA driver is **not** a dependency — you installed it out of band in
|
||||||
|
step 1. Later updates are just `sudo apt update && sudo apt upgrade`.
|
||||||
|
|
||||||
|
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`, which is gated by the `input` group. Add yourself and
|
||||||
|
re-login so the new group membership takes effect:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo usermod -aG input "$USER" # re-login to apply
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
- [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).
|
||||||
|
|
||||||
|
## Appendix — build from source
|
||||||
|
|
||||||
|
If the apt registry doesn't have a build for your release, or you want to track `main` directly,
|
||||||
|
compile the host yourself (no clean updates / no packaged units — you wire those up by hand).
|
||||||
|
|
||||||
|
Install the build toolchain and runtime libraries:
|
||||||
|
|
||||||
|
```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 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Install Rust if you don't have it, then build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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 -p punktfunk-host
|
||||||
|
```
|
||||||
|
|
||||||
|
The host binary lands at `target/release/punktfunk-host`. Configure your desktop as above, then run
|
||||||
|
it from inside your session:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --release -p punktfunk-host -- serve --gamestream
|
||||||
|
```
|
||||||
|
|
||||||
|
(The native plane is always on; `--gamestream` adds the Moonlight-compat surface — trusted LAN only.
|
||||||
|
Drop it for a secure native-only host.)
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
title: The Web Console
|
||||||
|
description: Enable the punktfunk browser console, read or change its login password, and arm PIN pairing.
|
||||||
|
---
|
||||||
|
|
||||||
|
The web console is the browser UI for a punktfunk host — live status, paired devices, and the PIN
|
||||||
|
pairing flow. It ships as the **`punktfunk-web`** systemd user unit on Linux and the **`PunktfunkWeb`**
|
||||||
|
task on Windows, and serves on **`http://<host-ip>:47992`**. The host's own management API stays
|
||||||
|
loopback-only behind it, so the console is the one surface you expose on the LAN.
|
||||||
|
|
||||||
|
> 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.
|
||||||
|
|
||||||
|
## Enable the console
|
||||||
|
|
||||||
|
- **Linux packages (apt / RPM / Bazzite):** `punktfunk-host` recommends `punktfunk-web`, so your
|
||||||
|
package manager pulls it in. Enable and start it as your desktop user, then open the URL:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
systemctl --user enable --now punktfunk-web
|
||||||
|
# then browse to http://<host-ip>:47992
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Windows host:** the installer sets up the console, its runtime, and the `PunktfunkWeb` task and
|
||||||
|
starts it at boot. There is nothing to enable — open `http://<this-PC>:47992`.
|
||||||
|
|
||||||
|
- **SteamOS host:** the install script builds and starts the console as a user service for you. It
|
||||||
|
prints the URL when it finishes.
|
||||||
|
|
||||||
|
## Login password
|
||||||
|
|
||||||
|
The console is password-protected. Where that password lives and how you change it depends on the
|
||||||
|
host platform.
|
||||||
|
|
||||||
|
**Linux packages (apt / RPM / Bazzite).** On first start `punktfunk-web-init` generates a random
|
||||||
|
password and saves it to `~/.config/punktfunk/web-password` (as `PUNKTFUNK_UI_PASSWORD=…`). Read it
|
||||||
|
back from the init service's journal or straight from the file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
||||||
|
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web-password
|
||||||
|
```
|
||||||
|
|
||||||
|
To set your own, edit that file (`PUNKTFUNK_UI_PASSWORD=<your-password>`) and restart the console:
|
||||||
|
`systemctl --user restart punktfunk-web`.
|
||||||
|
|
||||||
|
**SteamOS host.** Same idea, but the install script writes the generated password to
|
||||||
|
`~/.config/punktfunk/web.env` and prints it at the end of the install run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sed -n 's/^PUNKTFUNK_UI_PASSWORD=//p' ~/.config/punktfunk/web.env
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit that file and `systemctl --user restart punktfunk-web` to change it.
|
||||||
|
|
||||||
|
**Windows host.** You choose the password during install — a secure random default is pre-filled and
|
||||||
|
shown again on the installer's final page. It's stored in `%ProgramData%\punktfunk\web-password` (as
|
||||||
|
`PUNKTFUNK_UI_PASSWORD=…`), readable only by Administrators and SYSTEM. To change it, edit the file
|
||||||
|
and restart the task in an **elevated** PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
notepad "$env:ProgramData\punktfunk\web-password" # set PUNKTFUNK_UI_PASSWORD=<your-password>
|
||||||
|
schtasks /End /TN PunktfunkWeb; schtasks /Run /TN PunktfunkWeb
|
||||||
|
```
|
||||||
|
|
||||||
|
Forgot it? See [Forgot your Password?](/docs/forgot-password).
|
||||||
|
|
||||||
|
## Arm pairing
|
||||||
|
|
||||||
|
The host **requires PIN pairing** by default (secure on a LAN). To connect the first time, open the
|
||||||
|
console, log in, and go to **Devices → arm pairing**. The host displays a 4-digit PIN — enter it on
|
||||||
|
your [client](/docs/clients) to pair. See [Pairing & Trust](/docs/pairing) for the full trust model
|
||||||
|
and how to approve or remove devices later.
|
||||||
@@ -59,29 +59,24 @@ Packaging internals live in
|
|||||||
|
|
||||||
### Web console & pairing
|
### Web console & pairing
|
||||||
|
|
||||||
|
See [The Web Console](/docs/web-console) for the console + pairing model shared with the Linux host;
|
||||||
|
the Windows specifics follow.
|
||||||
|
|
||||||
The installer also sets up the **web management console** (status, paired devices, the PIN pairing
|
The installer also sets up the **web management console** (status, paired devices, the PIN pairing
|
||||||
flow): it bundles the console plus its own runtime and runs it as the **`PunktfunkWeb`** task on
|
flow): it bundles the console plus its own runtime and runs it as the **`PunktfunkWeb`** task on
|
||||||
**`http://<this-PC>:47992`**, starting at boot.
|
**`http://<this-PC>:47992`**, starting at boot.
|
||||||
|
|
||||||
#### Console login password
|
#### Console login password
|
||||||
|
|
||||||
During setup you choose the console **login password** — it's pre-filled with a secure random default
|
You choose the console **login password** during setup — a secure random default is pre-filled and
|
||||||
and shown again on the installer's final page. It's stored in `%ProgramData%\punktfunk\web-password`
|
shown on the installer's final page. It's stored in `%ProgramData%\punktfunk\web-password`, readable
|
||||||
(as `PUNKTFUNK_UI_PASSWORD=…`), readable only by Administrators and SYSTEM.
|
only by Administrators and SYSTEM. To read or change it (with the `schtasks` restart), see
|
||||||
|
[The Web Console → Login password](/docs/web-console#login-password); forgot it entirely?
|
||||||
To change it, edit that file and restart the console task. In an **elevated** PowerShell:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
notepad "$env:ProgramData\punktfunk\web-password" # set PUNKTFUNK_UI_PASSWORD=<your-password>
|
|
||||||
schtasks /End /TN PunktfunkWeb; schtasks /Run /TN PunktfunkWeb
|
|
||||||
```
|
|
||||||
|
|
||||||
Forgot it? This is the recovery path linked from the console login screen — see
|
|
||||||
[Forgot your Password?](/docs/forgot-password).
|
[Forgot your Password?](/docs/forgot-password).
|
||||||
|
|
||||||
The host **requires PIN pairing** by default (secure on a LAN). To connect the first time, open the
|
The host **requires PIN pairing** by default (secure on a LAN). To connect the first time, open the
|
||||||
console from any browser on the LAN, log in, go to **Devices → arm pairing**, and enter the PIN on
|
console from any browser on the LAN, log in, go to **Devices → [arm pairing](/docs/web-console#arm-pairing)**,
|
||||||
your [client](/docs/clients). The host's own management API stays loopback-only behind the console.
|
and enter the PIN on your [client](/docs/clients). The host's own management API stays loopback-only behind the console.
|
||||||
|
|
||||||
### Configure
|
### Configure
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"nav_pairing": "Kopplung",
|
"nav_pairing": "Kopplung",
|
||||||
"nav_library": "Bibliothek",
|
"nav_library": "Bibliothek",
|
||||||
"nav_settings": "Einstellungen",
|
"nav_settings": "Einstellungen",
|
||||||
|
"nav_more": "Mehr",
|
||||||
"status_title": "Live-Status",
|
"status_title": "Live-Status",
|
||||||
"status_video": "Video",
|
"status_video": "Video",
|
||||||
"status_audio": "Audio",
|
"status_audio": "Audio",
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
"display_preset_current": "Aktiv",
|
"display_preset_current": "Aktiv",
|
||||||
"display_preset_soon": "in Kürze",
|
"display_preset_soon": "in Kürze",
|
||||||
"display_keep_alive_help": "„Aus“ baut die Anzeige sofort beim Trennen ab. Halte sie (und bei gamescope ihr Spiel) am Leben, damit ein schnelles Wiederverbinden sofort fortsetzt, statt neu aufzubauen.",
|
"display_keep_alive_help": "„Aus“ baut die Anzeige sofort beim Trennen ab. Halte sie (und bei gamescope ihr Spiel) am Leben, damit ein schnelles Wiederverbinden sofort fortsetzt, statt neu aufzubauen.",
|
||||||
"display_topology_help": "Was mit den physischen Monitoren des Hosts während des Streamings geschieht.",
|
"display_topology_help": "Wie sich die gestreamte Anzeige in den Desktop des Hosts einfügt. Erweitern: ein zusätzlicher Bildschirm neben deinen Monitoren. Primär: die gestreamte Anzeige wird der primäre Ausgang, deine Monitore bleiben. Exklusiv: die gestreamte Anzeige ist der einzige Ausgang — physische Monitore werden beim Streamen deaktiviert und danach wiederhergestellt. Auf einem Host ohne Monitor legt es nur fest, ob die gestreamte Anzeige primär ist.",
|
||||||
"display_conflict": "Wenn ein weiterer Client verbindet",
|
"display_conflict": "Wenn ein weiterer Client verbindet",
|
||||||
"display_conflict_help": "Was passiert, wenn ein zweiter Client verbindet, während bereits gestreamt wird, und eine andere Auflösung anfragt.",
|
"display_conflict_help": "Was passiert, wenn ein zweiter Client verbindet, während bereits gestreamt wird, und eine andere Auflösung anfragt.",
|
||||||
"display_conflict_separate": "Eigene Anzeige",
|
"display_conflict_separate": "Eigene Anzeige",
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
"display_conflict_join": "Ansicht teilen",
|
"display_conflict_join": "Ansicht teilen",
|
||||||
"display_conflict_reject": "Besetzt — ablehnen",
|
"display_conflict_reject": "Besetzt — ablehnen",
|
||||||
"display_identity": "Client-Identität",
|
"display_identity": "Client-Identität",
|
||||||
"display_identity_help": "Jedem Client eine stabile Anzeige geben, damit der Desktop seine Monitor-Einstellungen merkt (z. B. Skalierung).",
|
"display_identity_help": "Ob die gestreamte Anzeige eine stabile Client-Identität trägt, sodass der Desktop des Hosts die Monitor-Einstellungen dieses Clients (Skalierung, Auflösung) merkt und beim erneuten Verbinden wieder anwendet. Geteilt: eine Identität für alle. Pro Client: jedes Gerät eigene. Pro Client + Auflösung: separate Einstellungen je Gerät und Auflösung.",
|
||||||
"display_identity_shared": "Geteilt",
|
"display_identity_shared": "Geteilt",
|
||||||
"display_identity_per_client": "Pro Client",
|
"display_identity_per_client": "Pro Client",
|
||||||
"display_identity_per_client_mode": "Pro Client + Auflösung",
|
"display_identity_per_client_mode": "Pro Client + Auflösung",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"nav_pairing": "Pairing",
|
"nav_pairing": "Pairing",
|
||||||
"nav_library": "Library",
|
"nav_library": "Library",
|
||||||
"nav_settings": "Settings",
|
"nav_settings": "Settings",
|
||||||
|
"nav_more": "More",
|
||||||
"status_title": "Live status",
|
"status_title": "Live status",
|
||||||
"status_video": "Video",
|
"status_video": "Video",
|
||||||
"status_audio": "Audio",
|
"status_audio": "Audio",
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
"display_preset_current": "Active",
|
"display_preset_current": "Active",
|
||||||
"display_preset_soon": "coming soon",
|
"display_preset_soon": "coming soon",
|
||||||
"display_keep_alive_help": "Off tears the display down as soon as the client disconnects. Keep it alive (and, on gamescope, its game) so a quick reconnect resumes instantly instead of rebuilding.",
|
"display_keep_alive_help": "Off tears the display down as soon as the client disconnects. Keep it alive (and, on gamescope, its game) so a quick reconnect resumes instantly instead of rebuilding.",
|
||||||
"display_topology_help": "What happens to the host's physical monitors while streaming.",
|
"display_topology_help": "How the streamed display fits into the host's desktop. Extend: an extra screen alongside your monitors. Primary: the streamed display becomes the primary output, your monitors kept. Exclusive: the streamed display is the sole output — physical monitors are disabled while streaming and restored after. On a headless host it just sets whether the streamed display is the primary.",
|
||||||
"display_conflict": "When another client connects",
|
"display_conflict": "When another client connects",
|
||||||
"display_conflict_help": "What happens if a second client connects while one is already streaming and asks for a different resolution.",
|
"display_conflict_help": "What happens if a second client connects while one is already streaming and asks for a different resolution.",
|
||||||
"display_conflict_separate": "Own display",
|
"display_conflict_separate": "Own display",
|
||||||
@@ -95,7 +96,7 @@
|
|||||||
"display_conflict_join": "Share view",
|
"display_conflict_join": "Share view",
|
||||||
"display_conflict_reject": "Busy — reject",
|
"display_conflict_reject": "Busy — reject",
|
||||||
"display_identity": "Per-client identity",
|
"display_identity": "Per-client identity",
|
||||||
"display_identity_help": "Give each client a stable display so the desktop remembers its per-monitor settings (e.g. scaling).",
|
"display_identity_help": "Whether the streamed display carries a stable per-client identity, so the host's desktop remembers that client's per-monitor settings (scaling, resolution) and reapplies them when it reconnects. Shared: one identity for everyone. Per client: each device keeps its own. Per client + resolution: a device keeps separate settings per resolution it connects at.",
|
||||||
"display_identity_shared": "Shared",
|
"display_identity_shared": "Shared",
|
||||||
"display_identity_per_client": "Per client",
|
"display_identity_per_client": "Per client",
|
||||||
"display_identity_per_client_mode": "Per client + resolution",
|
"display_identity_per_client_mode": "Per client + resolution",
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
import { Link, useRouterState } from "@tanstack/react-router";
|
||||||
import {
|
import {
|
||||||
Activity,
|
Activity,
|
||||||
GaugeCircle,
|
GaugeCircle,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
LibraryBig,
|
LibraryBig,
|
||||||
MonitorPlay,
|
MonitorPlay,
|
||||||
|
MoreHorizontal,
|
||||||
ScrollText,
|
ScrollText,
|
||||||
Server,
|
Server,
|
||||||
Settings,
|
Settings,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { motion, stagger } from "motion/react";
|
import { motion, stagger } from "motion/react";
|
||||||
import type { ReactNode } from "react";
|
import { type ReactNode, useState } from "react";
|
||||||
import { BrandMark } from "@/components/brand-mark";
|
import { BrandMark } from "@/components/brand-mark";
|
||||||
import { Wordmark } from "@/components/wordmark";
|
import { Wordmark } from "@/components/wordmark";
|
||||||
import { changeLocale, type Locale, locales, useLocale } from "@/lib/i18n";
|
import { changeLocale, type Locale, locales, useLocale } from "@/lib/i18n";
|
||||||
@@ -30,6 +31,11 @@ const NAV = [
|
|||||||
{ to: "/settings", icon: Settings, label: () => m.nav_settings() },
|
{ to: "/settings", icon: Settings, label: () => m.nav_settings() },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
// On phones a flat 8-tab bar is too cramped, so the first four are pinned and the rest live behind a
|
||||||
|
// "More" tab that opens a sheet above the bar. Keep it ≤ 5 slots including "More".
|
||||||
|
const MOBILE_PRIMARY = NAV.slice(0, 4);
|
||||||
|
const MOBILE_OVERFLOW = NAV.slice(4);
|
||||||
|
|
||||||
export function AppShell({ children }: { children: ReactNode }) {
|
export function AppShell({ children }: { children: ReactNode }) {
|
||||||
// Read the locale so the whole shell re-renders on a language switch.
|
// Read the locale so the whole shell re-renders on a language switch.
|
||||||
useLocale();
|
useLocale();
|
||||||
@@ -108,29 +114,88 @@ export function AppShell({ children }: { children: ReactNode }) {
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile bottom tab bar (< sm): the primary navigation on phones. */}
|
<MobileNav />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Mobile bottom navigation (< sm): four pinned tabs + a "More" tab whose sheet holds the rest. */
|
||||||
|
function MobileNav() {
|
||||||
|
const [moreOpen, setMoreOpen] = useState(false);
|
||||||
|
const pathname = useRouterState({ select: (s) => s.location.pathname });
|
||||||
|
// Highlight "More" when the current route lives in the overflow.
|
||||||
|
const overflowActive = MOBILE_OVERFLOW.some(
|
||||||
|
(n) => pathname === n.to || pathname.startsWith(`${n.to}/`),
|
||||||
|
);
|
||||||
|
// Fixed two-line-tall label box so a 1- or 2-line label (labels vary by locale) keeps every icon
|
||||||
|
// at the same height.
|
||||||
|
const tab =
|
||||||
|
"flex flex-1 flex-col items-center justify-center gap-1 px-0.5 py-2 text-muted-foreground transition-colors";
|
||||||
|
const lbl =
|
||||||
|
"flex h-7 w-full items-center justify-center text-center text-[10px] leading-tight";
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Tap-outside backdrop, under the bar (z-50) but over the page. */}
|
||||||
|
{moreOpen && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Close menu"
|
||||||
|
className="fixed inset-0 z-40 bg-black/40 sm:hidden"
|
||||||
|
onClick={() => setMoreOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<nav
|
<nav
|
||||||
className="fixed inset-x-0 bottom-0 z-40 flex border-t bg-card/95 backdrop-blur sm:hidden"
|
className="fixed inset-x-0 bottom-0 z-50 sm:hidden"
|
||||||
style={{ paddingBottom: "env(safe-area-inset-bottom)" }}
|
style={{ paddingBottom: "env(safe-area-inset-bottom)" }}
|
||||||
>
|
>
|
||||||
{NAV.map(({ to, icon: Icon, label }) => (
|
{/* The "More" sheet sits directly above the bar (bottom-full of the fixed nav). */}
|
||||||
<Link
|
{moreOpen && (
|
||||||
key={to}
|
<div className="absolute inset-x-0 bottom-full border-t bg-card/95 backdrop-blur">
|
||||||
to={to}
|
<div className="grid grid-cols-4 gap-1 p-2">
|
||||||
activeOptions={{ exact: to === "/" }}
|
{MOBILE_OVERFLOW.map(({ to, icon: Icon, label }) => (
|
||||||
className="flex flex-1 flex-col items-center justify-center gap-1 px-0.5 py-2 text-muted-foreground transition-colors"
|
<Link
|
||||||
activeProps={{ className: "text-[var(--brand-light)]" }}
|
key={to}
|
||||||
|
to={to}
|
||||||
|
onClick={() => setMoreOpen(false)}
|
||||||
|
className={cn(tab, "rounded-md")}
|
||||||
|
activeProps={{ className: "text-[var(--brand-light)]" }}
|
||||||
|
>
|
||||||
|
<Icon className="size-5 shrink-0" />
|
||||||
|
<span className={lbl}>{label()}</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex border-t bg-card/95 backdrop-blur">
|
||||||
|
{MOBILE_PRIMARY.map(({ to, icon: Icon, label }) => (
|
||||||
|
<Link
|
||||||
|
key={to}
|
||||||
|
to={to}
|
||||||
|
onClick={() => setMoreOpen(false)}
|
||||||
|
activeOptions={{ exact: to === "/" }}
|
||||||
|
className={tab}
|
||||||
|
activeProps={{ className: "text-[var(--brand-light)]" }}
|
||||||
|
>
|
||||||
|
<Icon className="size-5 shrink-0" />
|
||||||
|
<span className={lbl}>{label()}</span>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setMoreOpen((o) => !o)}
|
||||||
|
aria-expanded={moreOpen}
|
||||||
|
className={cn(
|
||||||
|
tab,
|
||||||
|
(moreOpen || overflowActive) && "text-[var(--brand-light)]",
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Icon className="size-5 shrink-0" />
|
<MoreHorizontal className="size-5 shrink-0" />
|
||||||
{/* Fixed two-line-tall box so a 1- or 2-line label keeps every icon
|
<span className={lbl}>{m.nav_more()}</span>
|
||||||
at the same height (the labels vary by locale). */}
|
</button>
|
||||||
<span className="flex h-7 w-full items-center justify-center text-center text-[10px] leading-tight">
|
</div>
|
||||||
{label()}
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const DisplaySection: FC = () => {
|
|||||||
<CardTitle>{m.display_config_title()}</CardTitle>
|
<CardTitle>{m.display_config_title()}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<p className="text-sm text-muted-foreground">{m.host_displays_help()}</p>
|
<p className="max-w-prose text-sm text-muted-foreground">{m.host_displays_help()}</p>
|
||||||
<QueryState isLoading={q.isLoading} error={q.error} refetch={q.refetch}>
|
<QueryState isLoading={q.isLoading} error={q.error} refetch={q.refetch}>
|
||||||
{q.data && draft && (
|
{q.data && draft && (
|
||||||
<DisplayForm
|
<DisplayForm
|
||||||
@@ -154,8 +154,8 @@ const DisplayForm: FC<{
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* One-click presets — a 2-up grid so each has room to breathe */}
|
{/* One-click presets — a 2-up grid so each has room to breathe */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-4">
|
||||||
<Label className="text-base font-semibold">{m.display_preset()}</Label>
|
<Label className="mb-1 block text-base font-semibold">{m.display_preset()}</Label>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
{PRESET_ORDER.map((id) => {
|
{PRESET_ORDER.map((id) => {
|
||||||
const p = presets.find((x) => x.id === id);
|
const p = presets.find((x) => x.id === id);
|
||||||
@@ -341,7 +341,7 @@ const DisplayForm: FC<{
|
|||||||
<Badge variant="outline">{`${effective.max_displays}×`}</Badge>
|
<Badge variant="outline">{`${effective.max_displays}×`}</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground">{m.display_pending_note()}</p>
|
<p className="max-w-prose text-xs text-muted-foreground">{m.display_pending_note()}</p>
|
||||||
{error && <p className="text-sm text-amber-600 dark:text-amber-500">{error}</p>}
|
{error && <p className="text-sm text-amber-600 dark:text-amber-500">{error}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -357,7 +357,7 @@ const Field: FC<{ label: string; help?: string; children: ReactNode }> = ({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<Label className="block">{label}</Label>
|
<Label className="block">{label}</Label>
|
||||||
{children}
|
{children}
|
||||||
{help && <p className="text-xs text-muted-foreground">{help}</p>}
|
{help && <p className="max-w-prose text-xs text-muted-foreground">{help}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user