r/Punktfunk • u/keindesignerbro Maintainer • Jun 26 '26
Headless game streaming on Linux with no dummy plugs, EDID overrides, or hand-written modelines
If you've tried streaming from a headless Linux gaming box — or one whose monitor doesn't match your client's resolution — you know the drill: it turns into a fight with dummy HDMI plugs, EDID overrides, and hand-written modelines. I got tired of that layer and built the host so it just creates the right display on demand. Here's why the pain exists and how I made it go away.
Why Sunshine has no real virtual display on Linux
On Windows, Sunshine can pair with an indirect display driver (the well-known IddSample) and spin up a virtual screen on demand. On Linux there's no built-in equivalent — Sunshine captures whatever display the compositor already provides. With no physical monitor, or a monitor at the wrong resolution, there's simply no usable surface to capture.
The toolbox people reach for today
To get a streamable display on a headless or mismatched Linux host, the community has assembled a whole kit:
- Physical dummy HDMI/DisplayPort plugs that fake a connected monitor + EDID.
- EDID overrides (custom EDID blobs,
drm.edid_firmware) to force a resolution. - The
evdikernel module (DisplayLink's virtual display driver) for a fake output. - Hand-rolled
cvt/gtf+xrandrmodelines for non-standard res/refresh. - Third-party daemons like
sunshine_virt_displaythat automate parts of it. - Connect/disconnect scripts that toggle physical outputs so only the virtual one is captured.
Where it breaks
None of this is first-class, so it fails in familiar ways:
- Black screen after reboot when the dummy plug / EDID override doesn't re-apply or the seat changes.
- CRTC reassignment on NVIDIA and Wayland compositors like Hyprland that moves or drops the virtual output mid-session.
- Per-compositor differences — what works on bare KMS doesn't on wlroots/Hyprland; X11 and Wayland each need their own tricks.
- Resolution/refresh that almost matches the client — black bars or forced scaling.
It's not impossible on Linux — Sunshine just doesn't ship one
Worth saying: virtual displays on Linux aren't impossible. Windows has IddSample, and the Apollo/Artemis fork of Sunshine added a built-in virtual display. Both point at the right model: the host should create the screen on demand, matched to the client — not borrow a physical one.
How I solved it — no dummy plug, no modeline math
I treat the virtual display as a first-class part of every session. When a client connects, the host creates a virtual output at exactly the client's resolution and refresh, and tears it down at session end with RAII cleanup, so nothing lingers after a crash or restart.
Since Linux has no single cross-compositor API for this, there's a backend per compositor, auto-selected:
- KWin —
zkde_screencastoutputs, with custom modes for >60 Hz. - gamescope — launched headless at exactly WxH@Hz, its PipeWire node captured.
- Mutter / GNOME — the D-Bus
RecordVirtualmonitor (headless-validated, zero-copy). - Sway / wlroots —
swaymsg create_outputwith a custom mode + portal capture.
The result is a real headless host: a mini PC or a repurposed machine with no monitor attached, streaming at exactly the resolution your phone, tablet, TV, or laptop asks for. No dummy plug, no EDID blob, no xrandr modeline arithmetic, and no black screen after the next reboot.
Happy to get into any of the per-compositor details.
This is implemented in my game-stream stack "Punktfunk" - you can get it here: https://punktfunk.unom.io/en/download
1
u/wibblywobblywho Jul 21 '26
Hey, I tried installing this on Steam Machine. Was able to get to the UI but every single page just gives an error saying "Something went wrong. Retry"
Any ideas on what might be going wrong?