Remote Desktop with Hyprland

I am doing it a little differently (but still using sunshine and a mesh VPN service).

First of all, I am pretty sure that the information in the github issue (or discussion, I believe), is incorrect. Hyprland does start without a physical monitor, as long as it has a working backend with access to rendering devices, etc. This access is what is missing in things like ssh connections.
I believe that the reasoning behind Hyprland not running execs until there is a monitor is that most applications would require a monitor anyways. But I could be wrong.

As of the latest release, there is a bug that makes Hyprland crash if it is launched without monitors and graphical applications are autostarted (e.g. by systemd) while there is still no monitor. Refer to this topic for more information.

Since I only use my PC headless if I am away from it for longer periods of time, I integrate the headless setup with my regular setup. As follows:

Login

I use greetd to start ReGreet within a Hyprland instance as a compositor (see the section on Hyprland on the github page of ReGreet). This makes it easy for me to initiate a remote connection, since I can just exec-once sunshine within this Hyprland instance (which means that I have two different configurations of sunshine, one for the greeter user and one for my regular user account). From here, I simply launch uwsm-managed (i.e. systemd-based) Hyprland.

Regular account

Here, I just run sunshine as a systemd user unit. The unit file looks like this (I believe there is something wrong with the After and WantedBy directives, which is why I also left the 5 + additional 10 second sleep in there to guarantee a monitor existing):

unit file (adapted from default flathub unit file)
[Unit]
Description=Self-hosted game stream host for Moonlight
StartLimitIntervalSec=500
StartLimitBurst=5
After=hyprmonitor.service

[Service]
# Avoid starting Sunshine before the desktop is fully initialized.
ExecStartPre=/bin/sleep 15
ExecStart=/bin/sunshine
Restart=on-failure
RestartSec=5s
# Environment=WAYLAND_DEBUG=1

[Install]
WantedBy=xdg-desktop-autostart.target

You might have noticed the reference to hyprmonitor.service. This is the final component of my setup:

Creation of a headless output

Since I also use the PC regularly, I only want a headless output to be created if there is no real output available. I have two scripts for this:

Within the login Hyprland

This Hyprland instance is launched directly from greetd, without sytemd. Therefore, I adjusted the command executed by greetd to the following:

 command = "Hyprland --config /etc/greetd/hyprland.conf& (until (hyprctl instances) | grep 'instance' &> /dev/null; do sleep 0.5; done; sleep 5; if (hyprctl -i 0 monitors) | grep 'Monitor'&> /dev/null; then true; else hyprctl -i 0 output create headless virt-1; fi)& wait"

This command executes Hyprland, but also launches a parallel process which does the following:

  1. Wait until a Hyprland session has registered
  2. Wait for 5 extra seconds (in case of startup taking longer or something)
  3. Fetch the monitor list from the Hyprland session
  4. If it is empty, create a headless output with name virt-1

That works since greetd simply executes its command in /bin/sh. So a similar shell script could be used in a setup without greetd.

Within the regular Hyprland

Here, I have another systemd user unit file (of the name hyprmonitor.service) which basically does the same thing. Additionally, it is intended to be ordered before the sunshine.service unit, but I don’t think that works at the moment (which, again, is why I added the long sleep to sunshine.service).

hyprmonitor.service

[Unit]
Description=Make sure that monitor exists
[email protected]

[Service]
Type=oneshot
ExecStart=/usr/bin/sh -c “/bin/sleep 5;if (hyprctl -i 0 monitors | grep ‘Monitor’); then true; else hyprctl -i 0 output create headless virt-1;fi”
Slice=background-graphical.slice

[Install]
[email protected]

I use this setup because I basically use my PC the same way whether I am accessing it remotely or directly, so I don’t need separate config files. But it should be easy to convert to a two-config setup by creating a dedicated remote-hyprland-uwsm.desktop in /usr/share/wayland-sessions/ and pointing it to the separate config.

Downside

The only issue I have with this setup is that it requires me to connect to the sunshine of my login manager, log in, disconnect from the login manager and then connect to the sunshine of the real Hyprland instance every time I restart my PC or log out of the Hyprland session (only while being connected remotely, of course). But I don’t really see a way around this (minor, in my personal opinion) issue unless I want to write a custom login manager that runs sunshine and wraps any started user sessions within its own graphical session (and I really do not want to do that, it would be too much work and likely insecure).