Dependencies
The tools I use are the following:
- wayvnc for the VNC server
- wlvncc for the VNC client
- PulseAudio for forwarding audio
- fzf for setting up headless Wayland session
That’s it!
Starting Headless Wayland session
I wrote a start-sway script that launches on login, it can also be run via SSH. What it does is show a FZF menu that let me select whether I want to launch swaywm in headless mode or in normal mode.
The headless mode lets you run sway without having a real monitor, instead it creates a dummy output that you can still interact with.
To launch a wayland session in headless mode, we need to set the following environment variables.
export WLR_BACKENDS=headless
export WLR_LIBINPUT_NO_DEVICES=1
export WAYLAND_DISPLAY=wayland-1
The script set up more environment variables, but those are the essential ones.
This is the complete script which creates XDG_RUNTIME_DIR, and setup other essential XDG variables before starting sway:
setup_runtime() {
ID=$(id -u)
if test -z "${XDG_RUNTIME_DIR}"; then
export XDG_RUNTIME_DIR="/run/user/${ID}"
fi
if test -d "${XDG_RUNTIME_DIR}"; then
perms="$(stat -c '%a %u' "${XDG_RUNTIME_DIR}")"
if [ "${perms}" != "700 ${ID}" ]; then
unset XDG_RUNTIME_DIR
echo "WARNING! XDG_RUNTIME_DIR has incorrect permissions, unsetting it"
fi
else
mkdir -p "${XDG_RUNTIME_DIR}"
chmod 0700 "${XDG_RUNTIME_DIR}"
fi
if test -z "${DBUS_SESSION_BUS_ADDRESS}"; then
#https://github.com/OpenRC/openrc/issues/924
export DBUS_SESSION_BUS_ADDRESS="unix:path=${XDG_RUNTIME_DIR}/bus"
fi
}
select_session() {
session_type=$(printf 'Headless\nNormal\n' | fzf --reverse --height=10 --prompt='Choose Session type > ')
if [ "${session_type}" = "Headless" ]; then
export WLR_BACKENDS=headless
export WLR_LIBINPUT_NO_DEVICES=1
export WAYLAND_DISPLAY=wayland-1
fi
export XDG_SESSION_TYPE=wayland
export XDG_SESSION_DESKTOP=sway
export XDG_CURRENT_DESKTOP=sway
}
setup_runtime
select_session
dbus-run-session sway
You can replace sway at the end with any Wayland compositor.
Connecting to VNC
Once sway has started in headless mode (you can start it through SSH too!), the next step is to run the VNC server.
I usually run only one VNC server at a time, so I kill any existing server before starting a new one. So that my programs won’t overwrite files from different sessions.
ssh [ip address] WAYLAND_DISPLAY=wayland-1 killall wayvnc
The next step is to run a VNC server and forward the server port to the local.
ssh -L 5900:localhost:5900 [ip address] WAYLAND_DISPLAY=wayland-1 wayvnc localhost
You may also use a nix socket instead, like this, before running the server:
ssh -L 35900:/home/[username]/.config/wayvnc/sock
The WAYLAND_DISPLAY and XDG_RUNTIME_DIR variables may not be needed depending on your setup. I set my system in a way that most user-specific variables are unset when connecting through ssh, so this is more of a failsafe.
Connecting to PulseAudio
In the client, we need to load the module-native-protocol-tcp module though PulseAudio so that the server can connect to it.
pactl load-module module-native-protocol-tcp
You can unload it at the end of the session
pactl unload-module module-native-protocol-tcp
You could combine everything together on the client in a single script:
printf 'Killing existing VNC server\n'
ssh "${USERNAME}@${HOST}" killall wayvnc
printf 'Setting up audio tunnel\n'
pactl load-module module-native-protocol-tcp
printf 'Starting VNC\n'
ssh -L 5900:localhost:5900 "${USERNAME}@${HOST}" WAYLAND_DISPLAY=wayland-1 XDG_RUNTIME_DIR=/var/run/user/1000 wayvnc localhost
printf 'Removing audio tunnel\n'
pactl unload-module module-native-protocol-tcp
Now on the server, you need to connect to the client’s PulseAudio tunnel/server:
pactl load-module module-tunnel-sink server="${CLIENT_IP}"
That’s it! Now you can select the tunnel as the audio output to get audio.
Launching VNC
You could use any VNC client for this. All I do is wlvncc localhost 5800.
Conclusion
Running VNC through Wayland is now easier than before. But we still lack an all-in-one solution, unlike the X11 ecosystem has.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.