On-demand headless virtual display for Sunshine on CachyOS (KDE Plasma 6 / Wayland / NVIDIA).
Stream your PC with Moonlight / Artemis at your client's native resolution β phone, tablet, laptop, TV β with the host's physical monitors turned off for the duration of the stream, then restored automatically when you disconnect.
No dummy HDMI plug. No always-on fake display. No Windows. Pure software, built for and tested on CachyOS + Plasma 6 Wayland + NVIDIA proprietary drivers.
The clip shows the real hook output: a phone connects at 1080Γ2340@90, the virtual display is created at that exact resolution, the physical monitors are disabled, and on disconnect everything is restored β primary monitor and all.
When a Moonlight client connects, Sunveil:
- Creates a virtual monitor at the client's exact resolution and refresh rate (e.g. your phone gets 1080Γ2340@90, your laptop gets 2560Γ1440@144).
- Turns off your physical monitors so the stream is an exclusive, private headless display (optional β see Coexist mode).
- Points Sunshine's KWin capture at the virtual display and streams it.
- On disconnect, restores everything β physical monitors back on, virtual display destroyed, primary monitor and layout exactly as they were.
It's driven entirely by Sunshine's global_prep_cmd hooks, so there's nothing
running when you're not streaming.
- Dummy HDMI plugs work but are a fixed resolution, always present, and cost money/ports. Sunveil is dynamic and leaves no trace when idle.
- Apollo's virtual display is Windows-only (it relies on SudoVDA). On Linux it can't create a virtual display. Sunveil fills that gap for CachyOS.
- Hermes (an Apollo-for-Linux fork) is a great heavier alternative if you want a purpose-built host; Sunveil instead enhances the Sunshine you already run, in ~15 minutes, with stock packages.
| OS | CachyOS / Arch (uses pacman) |
| Desktop | KDE Plasma 6 on Wayland |
| GPU | Any Sunshine-supported GPU. Developed on NVIDIA (proprietary/open). |
| Sunshine | Already installed and working (systemctl --user status app-dev.lizardbyte.app.Sunshine.service) |
| Packages | krfb, kscreen β the installer adds these for you |
X11 is not supported. The virtual display is created at the KWin/Wayland compositor level. If
echo $XDG_SESSION_TYPEprintsx11, log into a "Plasma (Wayland)" session first.
git clone https://github.com/ImStillBlue/sunshine-virtual-display.git
cd sunshine-virtual-display
./install.shThat's it. The installer is idempotent and safe on an existing Sunshine
setup β it backs up your sunshine.conf, merges only the keys it needs
(preserving any prep commands you already have), installs the hooks, and adds a
systemd fix for the boot-time click bug (see Troubleshooting).
Then just connect from Moonlight and start streaming.
./install.sh --coexist # keep physical monitors ON (extend, not replace)
./install.sh --no-restart # don't restart Sunshine at the end
./install.sh --yes # non-interactive./uninstall.shRestores your Sunshine config from the backup and removes everything Sunveil
added. (Leaves the krfb package and any autologin you set up β those are
yours to keep or remove.)
Set in ~/.config/sunshine/hooks/sunveil.conf (or via --coexist at install):
DISABLE_PHYSICAL |
Behaviour |
|---|---|
true (default) |
Headless β physical monitors off while streaming, restored on disconnect. |
false |
Coexist β virtual display is an extra screen; your real monitors keep showing your desktop. Great for streaming to a second device while someone uses the PC. |
Changes take effect on the next stream β no restart needed.
Sunshine runs as a user service, so it only starts after you log into a Plasma session. To stream a freshly-booted machine (e.g. a headless box in a closet), enable autologin so Plasma comes up on boot:
# Plasma 6 uses SDDM ("plasmalogin"). Create an autologin drop-in:
printf '[Autologin]\nUser=%s\nSession=plasma.desktop\nRelogin=false\n' "$USER" \
| sudo tee /etc/sddm.conf.d/autologin.confRelogin=falseβ autologin only at boot; you can still log out normally.Relogin=trueβ re-login even after logout/crash (pure appliance mode).
Note:
loginctl enable-lingerdoes not help here β linger starts user daemons at boot but never starts a graphical Plasma session, which Sunshine needs to capture. Autologin is the right tool.
See docs/AUTOLOGIN.md for the security tradeoffs and a
KWallet note.
The single most common issue on autologin/fast-boot systems. Sunshine created its virtual input device before KWin finished starting, so pointer motion bound but button capabilities didn't.
Sunveil installs a systemd override that fixes this by making Sunshine wait for KWin before it starts. If you still hit it (e.g. after a manual change):
systemctl --user restart app-dev.lizardbyte.app.Sunshine.serviceβ¦once the desktop is fully up. See docs/TROUBLESHOOTING.md
for the full explanation and how we diagnosed it.
Sunshine's default capture (kms) cannot see compositor-level virtual
monitors. Sunveil sets capture = kwin for you. Confirm it's set:
grep -E 'capture|output_name' ~/.config/sunshine/hooks/../sunshine.conf
# capture = kwin
# output_name = Virtual-sunshine-vmWatch the hook log live while you connect:
tail -f ~/.config/sunshine/hooks/hook.logYou should see client requested WxH@fps, virtual output: Virtual-sunshine-vm,
and (in headless mode) disabled physical: β¦. If it says
virtual output never appeared, krfb-virtualmonitor failed β check that
krfb is installed and you're on Wayland.
Harmless. It's a cosmetic app-id warning from krfb; the virtual monitor still works. Ignore it.
More in docs/TROUBLESHOOTING.md.
Moonlight client connects
β
βΌ
Sunshine runs global_prep_cmd "do" βββΊ hooks/stream-start.sh
β β’ reads SUNSHINE_CLIENT_WIDTH/HEIGHT/FPS
β β’ krfb-virtualmonitor --resolution WxH
β β’ kscreen-doctor: add custom mode, set it
β β’ kscreen-doctor: disable physical outputs
βΌ
Sunshine captures output_name = Virtual-sunshine-vm (capture = kwin)
β
βΌ
β¦ you stream β¦
β
Moonlight disconnects
βΌ
Sunshine runs global_prep_cmd "undo" βββΊ hooks/stream-end.sh
β’ re-enable physical outputs
β’ restore original primary
β’ kill krfb-virtualmonitor
Files installed:
~/.config/sunshine/hooks/stream-start.sh # the "do" hook
~/.config/sunshine/hooks/stream-end.sh # the "undo" hook
~/.config/sunshine/hooks/sunveil.conf # your tunables
~/.config/sunshine/hooks/hook.log # per-stream debug log
~/.config/systemd/user/app-dev.lizardbyte.app.Sunshine.service.d/sunveil.conf
~/.config/sunshine/sunshine.conf # merged: capture, output_name, global_prep_cmd
# (original saved as sunshine.conf.sunveil-bak.*)
The game-streaming ecosystem Sunveil plugs into:
| Project | What it is |
|---|---|
| Sunshine | The self-hosted stream host Sunveil enhances. |
| Moonlight (GitHub) | The original open-source streaming client, for basically every platform. |
| Apollo | A Sunshine fork with a built-in virtual display β but Windows-only (SudoVDA). The inspiration for doing this on Linux. |
| Artemis | Apollo's companion Android client (a moonlight-android fork). Works great as the client for a Sunveil host. |
| Hermes | An Apollo-for-Linux host fork with its own virtual-display driver β a heavier alternative if you'd rather replace Sunshine than enhance it. |
| krfb | KDE app that ships krfb-virtualmonitor, the tool that actually creates the virtual display. |
Built on the shoulders of Sunshine,
KDE's krfb-virtualmonitor, and
kscreen-doctor. The virtual-monitor approach was inspired by community
write-ups on running Sunshine headless on Plasma 6 Wayland, and by
Apollo's native virtual-display
feature on Windows.
MIT β see LICENSE.
Contributions welcome, especially test reports from other CachyOS/Arch + Plasma 6 configurations. See CONTRIBUTING.md.
