Desktop game streaming
Context
I stumbled upon the Valve Steam Deck once again and was wondering whether it was worth buying. I have the original Nintendo Switch, and while I did use it for a while, it is gathering dust next to my old PlayStation 4. Since my desktop is a pretty capable machine, using another device to game on feels like a downgrade.
After some sparring with Gemini and looking into Steam Deck alternatives like the ASUS ROG Ally X and the Lenovo Legion Go S (which is pretty cool, because it runs SteamOS) and Go 2, Gemini raised another idea: desktop game streaming. I knew this existed; I just assumed it would never be any good.
Gemini suggested buying an inexpensive Android handheld gaming device and streaming games to it. While this seemed like a good approach, I was looking into solutions that would work with hardware I already owned, instead of buying even more toys to have lying around.
Set-up
I settled for the following set-up:
- My desktop running a Sunshine server
- My iPhone as a Moonlight client
- An old PS4 controller
- Xreal One Pro glasses for a big-ass portable screen
Moonlight and Sunshine
These names sound like they are the new Pokémon games, but they are the client and the server you use to stream your desktop.
Sunshine is an open-source project that hosts a server on your desktop which allows you to stream games to other devices as if you were sitting in front of your desktop.
Moonlight is a bundle of open-source clients for NVIDIA GameStream (discontinued) and Sunshine. There are also iOS and Android apps.
iPhone set-up
The iPhone set-up was a breeze: install the Moonlight app, open it, and it detects my computer. Ready to stream. Done!
Connecting the PS4 controller to the iPhone was easy as well: open Bluetooth settings, start the controller, and hold the PS and Share buttons for a few seconds to enter pairing mode. Select controller and good to go.
Desktop set-up
I went the easy way: with my desktop booted into Windows, I installed the Sunshine server using a simple MSI installer. It was a few clicks, and the server was up and running. It all worked out of the box.
The booting problem
This all works fine if I boot my desktop into Windows. But that is pretty pointless when I am not at home and want to play some games. As explained in connection to home I enabled Wake-on-LAN, but that has been set up to boot into Linux, not Windows.
The pro solution for this is buying an IP-KVM like the PiKVM, but again: I was looking for inexpensive solutions.
The workaround is booting into Linux and instructing it to boot into Windows once and restarting.
- Make sure GRUB allows rebooting into a saved value:
sudo vim /etc/default/grub.- Change
GRUB_DEFAULT(likely set to0) tosaved:GRUB_DEFAULT=saved. - Save and exit.
- Update GRUB:
sudo update-grub.
- Figure out what the name of Windows is in your GRUB configuration:
grep -i "^menuentry 'Windows" /boot/grub/grub.cfg. This will return something likeWindows Boot Manager (on /dev/nvme0n1p1). - Trigger the reboot:
sudo grub-reboot "Windows Boot Manager (on /dev/nvme0n1p1)". - Optional extra step: add an alias to your
.zshrcor.bashrcto make it easier to reboot:alias bootw='sudo grub-reboot "Windows Boot Manager (on /dev/nvme0n1p1)"'.
Since this command requires sudo privileges, it might make sense to configure your user to run it without a password. Especially if you plan on automating this via something like HomeKit.
sudo visudo -f /etc/sudoers.d/grub-reboot.- Add the following line:
<your_username> ALL=(ALL) NOPASSWD: /usr/sbin/grub-reboot, /sbin/reboot, /usr/bin/systemctl reboot.
Use visudo instead of editing the file directly with an editor: it validates the syntax before saving, so a typo
cannot lock you out of sudo. A drop-in file under /etc/sudoers.d/ also survives package upgrades of /etc/sudoers.
The first attempt at booting into Windows from Linux and logging in using the Moonlight client on my iPhone failed
miserably. explorer.exe was not starting, Task Manager was closing itself, and I couldn't open CMD or PowerShell.
This might have to do with the Desktop Window Manager having failed because of a boot without an active monitor, which
is sort of odd, since I have two displays connected. The fix for that would be to use an HDMI dummy plug on the GPU or
to install a Virtual Display Driver. I was going to need these later on to prevent the monitor from showing the game
anyway. More on that below.
The second attempt was even worse. I don't like Windows and Windows doesn't like me. I got stuck on "Scanning and repairing drive (C:): 100% complete". It's the Windows type of complete. A while back I had the same error, let it sit overnight to wait for that repair, and it just never got through. A reboot fixed it. This might be caused by Windows not shutting down cleanly. This seems to be a common issue with dual boot systems. So, a nice little extra for this post. Here is how to disable Fast Startup in Windows and prevent such dirty shutdowns:
- Open the Control Panel.
- Go to Power Options.
- Click "Choose what the power buttons do".
- Click the blue link at the top: "Change settings that are currently unavailable" (requires Admin privileges).
- Uncheck "Turn on fast startup (recommended)".
- Then open CMD with admin privileges and run
powercfg /hibernate offto disable hibernation.
The third attempt worked just fine. I could log in using my phone and stream games.
The display "problem"
When I first tried to stream games to my iPhone, I noticed my displays were just showing the game as well. Which obviously makes sense, because you're just streaming what's on there to another device. But it is a bit of a waste.
There are two potential solutions for this:
- Use a Virtual Display Driver that mimics an actual display.
- Use an HDMI dummy plug to trick the GPU into thinking a monitor is connected to it.
Both solutions still require a way to tell Windows to not use the physical displays though.
I wanted the option that needed the least configuration, so I bought an inexpensive HDMI dummy plug. But getting it to work properly was more complicated than I expected.
I downloaded MonitorSwapAutomation, but had to fiddle around to get it working correctly. It has a README file that explains how to use it, but I thought it was a bit confusing. It basically boils down to the following:
- Download the latest version of the tool, unzip it and move the unzipped folder to a location of your choice.
- Moving it before using it is an important step. One of the issues I ran into was caused by moving the folder after running the installer.
- Run
Install.bat: this automatically configures your Sunshine server to use the MonitorSwapAutomation tool for every Sunshine application. - With your HDMI dummy plug connected, configure your displays in the Windows display settings the way you want to use your desktop by default: disable the dummy display!
- Save this as your primary config:
- Open CMD in the folder where you installed MonitorSwapAutomation.
- Run
.\MonitorSwitcher.exe -save:Primary.xml(this will create a file called Primary.xml in the same folder).
- Now connect a Moonlight client to your desktop and configure the displays in the Windows display settings so that all your monitors are disabled, and only the dummy display enabled. (this is not an easy step if you are doing this on your phone!)
- Then save this config as your dummy:
.\MonitorSwitcher.exe -save:Dummy.xml.
This should be it. Next time you connect a Moonlight client, it should automatically disable your physical displays and use only the dummy display. As soon as you disconnect the client, your displays should be turned on again.
MonitorSwapAutomation uses MonitorSwitcher under the hood and you can configure it yourself as well if you want to. MonitorSwapAutomation just simplifies the process and it supposedly has better debugging support. I did end up using the MonitorSwapAutomation logs to figure out an issue!
Side note: dummy display on Linux
The dummy display on Linux caused some issues: at the first boot, it was considered an actual display, causing my resolutions to be all wrong. At first I tried to manually disable that display, but later I learned that this does not stop the NVIDIA drivers from spending resources on it. So in the end I opted to completely "remove" that port from the config.
- Figure out what the identifier of the port is:
xrandr | grep " connected"(note: the space is required to not grep on "disconnected"). - Create (or edit) a file:
sudo vim /etc/X11/xorg.conf.d/10-ignore-dummy.conf. - Add the following, replacing the Identifier with the one from the first step:
Section "Monitor" Identifier "HDMI-0" Option "Ignore" "true" EndSection - Save and restart the display manager:
sudo systemctl restart lightdm(replace "lightdm" with whatever display manager you use).
