Field triage (Nobara, Discord): the kde.md starter host.env told desktop users to set PUNKTFUNK_COMPOSITOR=kwin, which PINS the backend — detect() short-circuits and the capture-loss rebuild never re-detects — so a mid-stream switch to Game Mode killed the stream instead of following it. A follow-up hardcoded XDG_RUNTIME_DIR=/run/user/1000 anchor broke PipeWire for any non-1000 uid (pw audio connect: Creation failed). Revamp across every starter/example/reference: - Desktop starters (kde/gnome/hyprland/sway) shrink to PUNKTFUNK_VIDEO_SOURCE=virtual + an explicit warning that pinning disables session-following; forcing a backend is CI/appliance-only. - host.env.example: rewritten around auto-detection; anchors demoted to a commented ssh/cron-only block with the uid trap spelled out; the gamescope ATTACH/MANAGED knobs documented (previously missing); case-sensitivity called out. - packaging/bazzite/host.env + README: drop the uid-1000 anchors (a systemctl --user service inherits/derives them); README's stale PUNKTFUNK_COMPOSITOR=gamescope-era template synced to the real one. - packaging/kde/host.env: loud APPLIANCE-ONLY header (it pins on purpose). - configuration.md: session-anchors section inverted to "leave unset", compositor row states the pin consequence, case-sensitivity note. - troubleshooting.md: new "session fails right after editing host.env" section (case, wrong-uid anchors, stale pin, restart-to-apply). - gamescope.md/bazzite.md: attach/managed descriptions match current behavior (managed is the infra-detected default; attach re-modes a box-owned session to the client's resolution). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.0 KiB
title, description
| title | description |
|---|---|
| Hyprland | Configure a punktfunk host on a Hyprland session — headless output via hyprctl, capture via xdg-desktop-portal-hyprland. |
Hyprland is a first-class backend. The host adds a per-client headless output at the client's
exact mode with hyprctl, captures it through the xdg-desktop-portal-hyprland (xdph) ScreenCast
portal (zero-copy dmabuf), and injects input via the wlroots virtual pointer/keyboard protocols —
which Hyprland still implements even after dropping wlroots in v0.42.
This is a distinct backend from Sway / wlroots: Hyprland has its own IPC (hyprctl)
and its own portal (xdph), so it is auto-detected and driven separately.
This page assumes the package is already installed — see Arch, Ubuntu, or Fedora.
New here? Read Security & Safe Use 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 Hyprland session, so the starter ~/.config/punktfunk/host.env is one line:
PUNKTFUNK_VIDEO_SOURCE=virtual
# GPU zero-copy capture→encode is ON by default; auto-falls back to CPU. Set PUNKTFUNK_ZEROCOPY=0 to force CPU.
To force the backend (CI/testing — note that pinning turns live-session auto-detection off, so the host stops following session switches):
PUNKTFUNK_COMPOSITOR=hyprland
PUNKTFUNK_INPUT_BACKEND=wlr
See Configuration for the full reference.
How it works
- Video — the host runs
hyprctl output create headless PF-1and applies a monitor rule for the client's exact mode. Outputs are named, so there's no before/after diffing. The rule useshyprctl keyword monitor …(the hyprlang config manager — the default on every release, 0.55 included) and falls back to the Luahyprctl eval 'hl.monitor{…}'only if you've opted into the Lua config manager. The host confirms the output actually adopted the mode before streaming. - Capture — it captures that output through the xdg-desktop-portal-hyprland (xdph) ScreenCast
portal. To pick the output without a GUI on a headless host, the host writes a managed
~/.config/hypr/xdph.confpointing xdph'scustom_picker_binaryat a small shim that selects the new output automatically — no interactive picker dialog to answer. - Input — mouse and keyboard are injected via the wlroots virtual pointer and virtual keyboard protocols (Hyprland kept them). Gamepads and audio are compositor-independent.
For how long the virtual output lives, and extend-vs-exclusive topology, see Virtual displays.
Requirements
- A running Hyprland session (the
hyprctl/xdph contracts are verified on 0.55.4; older releases share the samehyprctlsurface). - xdg-desktop-portal-hyprland (xdph) installed and running — the host captures through its ScreenCast portal, and steers its custom picker. Without it there is no video.
- The ScreenCast interface routed to xdph — see
scripts/headless/portals.conf(a[Hyprland]section pinsorg.freedesktop.impl.portal.ScreenCast=hyprland).
Troubleshooting: black / no video (headless output at 0×0)
A headless output only gets a framebuffer once the compositor can allocate one. On some GPU/driver
combinations (notably NVIDIA, and in nested test setups) that GBM/dmabuf allocation fails and the
output stays 0×0 — you'll see GBM: Failed to allocate a GBM buffer: bo null in the Hyprland log
(cf. Sunshine #4197). The host detects this
and fails the session with a clear error rather than streaming a blank surface. If you hit it,
capture the Hyprland log (hyprctl instance dir → hyprland.log) and check your GPU's GBM support;
running Hyprland as a real session (not nested) is the supported configuration.
Permission system
Hyprland's permission system (ecosystem.enforce_permissions, 0.49+, off by default) can deny
direct screencopy and virtual-input clients — and denial is silent: capture goes to black
frames and input is dropped, with no error. If you've enabled it, grant the host explicitly in
your Hyprland config:
ecosystem {
enforce_permissions = true
}
permission = /usr/bin/punktfunk-host, screencopy, allow
permission = /usr/bin/punktfunk-host, virtual-pointer, allow
permission = /usr/bin/punktfunk-host, virtual-keyboard, allow
The host logs a warning at startup when it detects enforcement is on. (Adjust the binary path to
where your package installed punktfunk-host.)
Start the host
With the backend selected, start the host from inside your Hyprland session:
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. Then connect a client.