Remote Desktop with Hyprland

Hello,

I have been messing around with trying to get remote desktop to work with Hyprland.
This is just a post for me to talk about my experience, how I have done it. And to discuss better ways to go about it.

Problems I Have Faced

Wayland

Wayland is still missing alot of features compared to x11, and remote desktop protocol support is one of them. There are not many tools out there.

Hyprland

Hyprland is a bit qwerky, with a remote desktop session often you can also run things in headless modes. Where the output is not actually done at the physical device. This is for doing things remotely or only with a CLI.
This is really usefull if you are only ever gonna access the machine with a remote desktop.
However, Hyprland a while back doesnt really care about the headless mode, so it no longer works from what I have read on github.

You can still have headless outputs, but Hyprland will not start if theres no valid output. eg on an ssh session.

Remote startup

Now, how should I be able to start a user session remotely with Hyprland, there is no good way I belive.

How I am doing things

Software

  • Sunshine: A project for remote desktop access. It has wayland support, and works pretty well. Since its meant for the moonlight, a game streaming solution, the performance is quite high. And theres also the benefits of full device capture and audio working out of the box. Since it creates virtual devices on the host and passes client events though. This works really well especially for games which struggle with absolute mouse positioning.
  • Moonlight client: This is for the clients to connect to the host machine.
  • OpenVPN/WireGuard: A VPN tunnel to have access to the host machine. (I find this the easiest method to have safe access to the host machine.

Startup

I am still working on this, but the method I have found that works well, is to make an autologin service for tty6 for example, so that from ssh you can switch the session to tty6 remotely, and have it auto start the hyprland session. From there it will set an env variable, and withing the .bash_profile, it will execute Hyprland with a specific Hyprland config for remote desktop purposes. Eg: disabeling physical screens/devices. executing different things at start up etc. Creating Headless monitors.

There is a bug which I still have to make a report for. Hyprland will not exec anything/create any clients for some reason when a monitor isnt really there. Eg: If you disable all physical monitors, and then create a headless one with exec-once=hyprctl output create headless it will successfully create the headless monitor, however it will not fully start Hyprland. The only way I have found a way around it is in ssh to create and delete a new headless output with hyprctl --instance 0

Inside of the remote hyprland config, also execute sunshine, preferebly with a delay.

configuring sunshine

Sunshine has a web based configuration and authenticator. Which is on localhost by default. If you are using a vpn, you can access this remotely aswell. You can probably also change this to be exposed aswell if you need to.

Tip: If you really need to see whats going on on your desktop, wayvnc for the host is really usefull to see what is happening. and connect to it with the tigervnc client. You can start wayvnc with hyprctl --instance 0 dispatch exec 'wayvnc 0.0.0.0 -o HEADLESS-1 --gpu -f 60 -R'
Warning: Make sure to give permissions to all the right applications for hyprland. or disable the permission system if you dont care.

AutoLogin stuff:

/etc/systemd/system/[email protected]/autologin.conf

[Service]
ExecStart=
ExecStart=-/sbin/agetty -o '-p -f -- \\u' --noclear --autologin <USERNAME HERE> %I $TERM
Environment=AUTOSTART_HYPR=1

~/.bash_profile (will be ran when you sudo chvt 6 from ssh)

if [[ "$AUTOSTART_HYPR" == "1" ]]; then
    [[ -f ~/.local/bin/wrappedhl_remote ]] && ~/.local/bin/wrappedhl_remote
else
    [[ -f ~/.bashrc ]] && . ~/.bashrc
fi

~/.local/bin/wrappedhl_remote (The exports is for me, do what you want)

#!/bin/sh
cd ~
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx
export SDL_IM_MODULE=fcitx
export GLFW_IM_MODULE=fcitx

exec Hyprland --config ~/.config/hypr/hyprlandRemote.conf

Downsides

The current issues I’m facing is for one that any new session spawned off of hyprland currently also launch Hyprland :P
For example launching tmux creates a new imbedded Hyprland Session.

This still requires manual steps for initial startup. So if you restart you pc, you will have to ssh into the machine. chvt to 6, and then create & destroy a headless monitor.

Conclusion

It works pretty well when you get things going. The gaming performance is pretty nice, Audio works, and you can probably do the same thing with a gamescope only session.

You can experiment with something like this in the bash_profile: STEAM_MULTIPLE_XWAYLANDS=1 gamescope -W 1280 -H 720 -r 60 -e --xwayland-count 2 -- steam -gamepadui -steamdeck I dont remember how I streamed it ngl.

Again, I made this post also to discuss any better ways to do this than what I have done. So feel free to comment on anything or ask questions.

4 Likes

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).

1 Like

Just want to say thanks to both of you for these thorough explanations. I’m new to using Linux as my main gaming PC, and wanted to set up an easy way to remote-in if I’m using my laptop somewhere else, and was struggling to figure it out on my own. Both of these solutions look great.

There’s rustdesk that now has experimental support for Wayland.

I’ve been meaning to try it, but it’s been a while since I’ve needed remote desktop functionality.

Afaik, Rustdesk is still not supported because it requires some protocols that xdg-desktop-portal-hyprland doesn’t implement.
There’s a PR for it, but it seem mostly dead so idk if we’ll see this anytime soon tbh.
Looking forward to it tho.