Skip to content

Instantly share code, notes, and snippets.

@Dregu
Last active January 28, 2025 15:46
Show Gist options
  • Save Dregu/6a6021bda7108b3e063d05ad7407f914 to your computer and use it in GitHub Desktop.
Save Dregu/6a6021bda7108b3e063d05ad7407f914 to your computer and use it in GitHub Desktop.
Experiments in running Sunshine on Sway WM headless (virtual display) on demand (WOL request) on nvidia 565 drivers on Arch Linux.

Headless Sway Sunshine stuff

This setup allows you to start a single Sway remote desktop session on a virtual display directly from Moonlight by sending the Wake PC request, waiting a few seconds and connecting to a fresh new desktop. Or you can just have a desktop running constantly in the background. This requires no dummy plugs or input devices attached to the host, but will also allow you to sometimes log in locally and and have that session available on sunshine too. Some of this stuff probably also works with other wlroots-based compositors with small changes.

I'm assuming you've first read and memorized the Sunshine documentation, it probably includes some very important stuff I didn't put here. The stuff about udev rules is probably important, cap_sys_admin not so much.

This is not supposed to be a complete guide to set up headless sunshine streaming and I assume you already know how to set up a sway desktop with hardware acceleration, but some (Arch) packages you probably also want are:

sway swaybg foot wmenu nvidia libvdpau libva-nvidia-driver cuda egl-wayland libva-utils nvtop pavucontrol pipewire-pulse steam gamescope

Obviously you'll need sunshine from the official repository, or maybe sunshine-git from AUR. The sunshine settings I have left completely as defaults, which probably makes it use wlroots for capture and nvenc for encoding.

Other stuff you probably need to do:

# no idea if all of these are needed
sudo usermod -a -G users,wheel,audio,video,input dregu
# start service on demand with WoL ping
sudo systemctl enable --now sway-sunshine.socket
# or if you don't want the WoL stuff
sudo systemctl enable --now sway-sunshine.service

Because the systemd service runs a login session in tty1 as a workaround to get input working without actually logging in, this is only meant for remote use. That said you can also log in to tty1 locally, run sway and connect to that session with moonlight, but only when the headless one is not running.

These silly input hacks are needed because there's currently no easier way to just assign the virtual input devices to a headless sway process. (Please correct me if I'm wrong, I'd really like to just use libseat or something properly.) Other hack that worked was setting up console autologin for the user running the sway service, or just running sway from tty1, but that's not very on demand and servery any more.

To top it off add a sunshine prep command /bin/bash /home/dregu/.config/sway/scripts/sway-sunshine.sh for Desktop to match the client resolution automatically.

Homework

# /etc/sway/config.d/sway-sunshine.conf
# Of course you can also just add these to ~/.config/sway/config
# Minimizes latency when game has vsync off
output * allow_tearing yes max_render_time off
# Starts sunshine inside the sway session
exec sunshine
# /etc/systemd/system/sway-sunshine.service
# Runs sway in headless mode, unless sunshine is already running (maybe on a real display then)
[Unit]
Description=Sway Sunshine Headless
[Service]
User=dregu
Group=users
PAMName=login
TTYPath=/dev/tty1
WorkingDirectory=/home/dregu
Environment=WLR_RENDERER=vulkan
Environment=WLR_BACKENDS=libinput,headless
Environment=WLR_LIBINPUT_NO_DEVICES=1
Environment=XDG_CURRENT_DESKTOP=sway
Environment=XDG_SESSION_DESKTOP=sway
Environment=XDG_SESSION_CLASS=user
Environment=XDG_SESSION_TYPE=wayland
Environment=XDG_RUNTIME_DIR=/run/user/1000
ExecStartPre=/bin/sh -c '! "$@"' -- pidof -q sunshine
ExecStart=dbus-run-session sway --unsupported-gpu
#Restart=always
[Install]
WantedBy=multi-user.target
#!/bin/bash
# /home/dregu/.config/sway/scripts/sway-sunshine.sh
# Sets output mode to match client settings
swaymsg "output HEADLESS-1 mode ${SUNSHINE_CLIENT_WIDTH}x${SUNSHINE_CLIENT_HEIGHT}@${SUNSHINE_CLIENT_FPS}Hz"
# /etc/systemd/system/sway-sunshine.socket
# Listens for WakeOnLAN requests from Moonlight and starts the service if not running
[Socket]
ListenDatagram=9
Accept=no
FlushPending=yes
[Install]
WantedBy=sockets.target
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment