Skip to content

Add experimental virtual display support for Linux#1477

Open
AdivonSlav wants to merge 7 commits into
ClassicOldSong:masterfrom
AdivonSlav:vdisplay-linux
Open

Add experimental virtual display support for Linux#1477
AdivonSlav wants to merge 7 commits into
ClassicOldSong:masterfrom
AdivonSlav:vdisplay-linux

Conversation

@AdivonSlav

@AdivonSlav AdivonSlav commented Apr 25, 2026

Copy link
Copy Markdown

Summary

Adds support for creating a virtual display on the fly for Linux using EDID overrides via debugfs. Works similar to the Windows impl in behavior. This method is being floated around via scripts already online, but I thought packaging it up into actual functionality would at least provide for experimental support.

How it works

  1. We build a valid EDID firmware binary on the fly based on what the client requested in terms of the res and refresh rate. A hash of the client's ID is also encoded in the EDID so compositors (hopefully) can remember display settings and such.
  2. The built EDID then needs to be overriden onto an unused GPU connector. We can either specify the connector to use via the app settings or we automatically find the first disconnected connector by enumerating them when the stream is booting.
  3. The EDID is then written to debugfs and the connector is forced online, after which the compositor picks up the connector automatically. As for whether this works in X11 I am unsure as I do not have access to a system with it currently. An xrandr invocation is added that might do the trick.
  4. We get the KMS index for the new vdisplay and set the output to it.
  5. When the stream session ends we clear the EDID override, reset the connector and the compositor restores the initial display configuraiton.

Note on privilege model

The nature of this approach requires access to debugfs which technically is not supposed to be accessed by userspace like this. To actually get this and not have apollo run as root we'd need the app process to have CAP_DAC_OVERRIDE as a capability. However giving apollo the ability to elevate to this cap is not ideal, so debugfs ops are moved to a separate minimal helper binary apollo-vdisplay-helper.

I've added several things to make sure we mitigate security risks as much as possible around this:

  • Strict path allowlist for debugfs
  • Only specific string values can be written to these paths
  • Binary access is restricted to root and the new apollo group which is automatically created as part of postinst with the user installing the pkg being added to it.

This should make this pretty hardened, but I'm open to any suggestions/alternatives to improve this if possible.

Testing

I've performed tests on an AMD and Intel system with both KDE and GNOME. In all cases compositors respect the connector changes, react to them and restore display configurations as expected. However, I've not been able to test Nvidia/X11 as I do not have access atm. From what I've seen online Nvidia may be shaky going with this approach.

@ClassicOldSong

ClassicOldSong commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Thank you! This looks like a plausible solution to Linux without touching kernel source.

One question, is it fully behavior compatible with the Windows version? Like on demand virtual display requested from client?

Also please don't touch unrelated parts like code formatting. This is adding tons of noise to the diff, and things like sticking * to the param name is not making sense to me.

And if you're ok with that, can you name the feature with Apollo instead of Sunshine, if you're not planning to commiting it to Sunshine?

@AdivonSlav

Copy link
Copy Markdown
Author

Thank you! This looks like a plausible solution to Linux without touching kernel source.

One question, is it fully behavior compatible with the Windows version? Like on demand virtual display requested from client?

Also please don't touch unrelated parts like code formatting. This is adding tons of noise to the diff, and things like sticking * to the param name is not making sense to me.

And if you're ok with that, can you name the feature with Apollo instead of Sunshine, if you're not planning to commiting it to Sunshine?

Yes, I've tested starting streams from Artemis by specifying that it should start in a vdisplay and it correctly picks up the request and outputs to it. The client hash is also the vdisplay's serial number so compositors do seem to respect and remember changed window layouts, the primary display and so on. Though Artemis does complain that the "server doesn't support virtual displays". Could this be hardcoded somewhere?

I'll revert unrelated formatting, the repo root has .clang-format and my formatter just picked it up and auto applied.

As for renaming to Apollo, sure. I'll change the new group name to apollo and rename the helper bin along with any other occurrences.

@ClassicOldSong

ClassicOldSong commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Could this be hardcoded somewhere?

Yes, it's hardcoded in nvhttp.cpp iirc with a #ifdef _WIN32 block to include a flag

@onijuiku-rgb

Copy link
Copy Markdown

@AdivonSlav how does this work on apollo client for linux since this is a .exe? or is running this on lutris or bottles completely fine?

@Toremous

Toremous commented May 2, 2026

Copy link
Copy Markdown

@AdivonSlav how does this work on apollo client for linux since this is a .exe? or is running this on lutris or bottles completely fine?

I built it via cmake on cachyos. its not a exe. but im struggling to get it to use the 'virtual' display, maybe due to using a nvidia card.

Ended up reverting to the aur package.

@spyral98

spyral98 commented May 2, 2026

Copy link
Copy Markdown

I built it also from source on cachyOS but it required changing the permissions of apollo-vdisplay-helper to 0755, otherwise it would give me permission denied errors when it was trying to create the virtual display.

@AdivonSlav

Copy link
Copy Markdown
Author

@AdivonSlav how does this work on apollo client for linux since this is a .exe? or is running this on lutris or bottles completely fine?

I built it via cmake on cachyos. its not a exe. but im struggling to get it to use the 'virtual' display, maybe due to using a nvidia card.

Ended up reverting to the aur package.

Yeah I was not able to actually test anything regarding Nvidia. Do you have any logs?

I built it also from source on cachyOS but it required changing the permissions of apollo-vdisplay-helper to 0755, otherwise it would give me permission denied errors when it was trying to create the virtual display.

Yeah this is by design since the binary should only be exec'd by the apollo group, hence the 750 default. The user installing apollo should be added to the apollo group, this is part of the DEB/RPM postinst steps, I think I also added it to arch?

@Toremous

Toremous commented May 3, 2026

Copy link
Copy Markdown

@AdivonSlav how does this work on apollo client for linux since this is a .exe? or is running this on lutris or bottles completely fine?

I built it via cmake on cachyos. its not a exe. but im struggling to get it to use the 'virtual' display, maybe due to using a nvidia card.
Ended up reverting to the aur package.

Yeah I was not able to actually test anything regarding Nvidia. Do you have any logs?

I built it also from source on cachyOS but it required changing the permissions of apollo-vdisplay-helper to 0755, otherwise it would give me permission denied errors when it was trying to create the virtual display.

Yeah this is by design since the binary should only be exec'd by the apollo group, hence the 750 default. The user installing apollo should be added to the apollo group, this is part of the DEB/RPM postinst steps, I think I also added it to arch?

actually i got it working completely fine on nvidia now, literally no issues as of yet, had to massage the build and permissions a bit.

Build environment: CachyOS, GCC 16, CUDA 13.2, KDE Plasma 6 Wayland
Issues encountered during build:

Boost version mismatch — repo requires 1.89.0, system has 1.91.0. Fixed by patching cmake/dependencies/Boost_Sunshine.cmake to use 1.91.0.
boost_system component no longer ships a CMake config file in 1.91.0 (header-only). Fixed by patching third-party/Simple-Web-Server/CMakeLists.txt to remove the system component from find_package and Boost::system from target_link_libraries.
CUDA compiler not found if nvcc is not in cmake's default search path. Fixed by passing -DCMAKE_CUDA_COMPILER=/path/to/nvcc.
GCC 16 incompatible with CUDA 13.2. Fixed by passing -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-14.

Virtual display:

I set the permissions as noted by @spyral98, on apollo-vdisplay-helper to 0755 I didnt verify if it was necessary

sudo chmod 0755 $(which apollo-vdisplay-helper)

trigger_hotplug fails, but KWin picked up the connector within ~400ms via normal DRM polling, so it was non-fatal in this case.
Virtual display creation succeeds and KMS capture works correctly.
To disable physical monitors without freezing the vdisplay, use kscreen-doctor output.X.disable rather than DPMS — DPMS affected all outputs including the vdisplay even with --dpms-excluded.

Im sure there is much more elegant ways of doing all of this, but im learning :)

@spyral98

spyral98 commented May 3, 2026

Copy link
Copy Markdown

@AdivonSlav Yeah I see you added it in apollo.install but that wouldn't work for me at the time. However, changing it manually to 0750 now does work fine. Perhaps it needed a reboot and not just a session reset since I remember relogging multiple times when I was troubleshooting it.

@Toremous Same for me, I used kscreen-doctor in the DO and UNDO commands to disable/re-enable the physical monitors since I mostly use apollo headlessly. Maybe a headless option in the web UI would be more elegant to auto disable the physical monitors when streaming.

@AdivonSlav

Copy link
Copy Markdown
Author

I mostly compiled for Fedora inside Docker. I'm guessing compilation should be a breeze for Arch over the AUR.

As for display management, if you disable the physical displays once manually when the vdisplay is up and set it as the primary then KDE should remember this. And it should also revert to enabling physical monitors when the vdisplay is killed. In my testing I found it wasn't necessary to have kscreen-doctor hooks.

@onijuiku-rgb

onijuiku-rgb commented May 3, 2026

Copy link
Copy Markdown

@AdivonSlav
i just used EVDI, dumped my main monitor EDID file, moved it my firmware/edid/ folder, then in the grub config(/boot/grub/grub.cfg) added to this line "GRUB_CMDLINE_LINUX_DEFAULT" = video=HDMI-A-1:e drm.edid_firmware=HDMI-A-1:edid/virtual_clean.bin' i did HDMI-A-1 as that was one of my ports disconnected

/etc/mkinitcpio.conf then edited this file to point it the FILES=(/usr/lib/firmware/edid/virtual_clean.bin),
then i regened both the files using "sudo grub-mkconfig -o /boot/grub/grub.cfg" and "sudo mkinitcpio -P"
rebooted and works fine for me, im on cachyOS arch based, KDE Plasma, and works on my Nvidia card just fine, im also using Kscreendoctor to switch the second VDisplay to on and off with alias so it doesnt eat up resources when im not using it, and im using apollo-linux fork of apollo, pointed the outputnumber to the VDisplay
sorry if some of this doesnt make sense, this is just how my brain pieced this together, also to add, i am getting 1920x1080x144 for my VDisplay

@AdivonSlav

Copy link
Copy Markdown
Author

@AdivonSlav i just used EVDI, dumped my main monitor EDID file, moved it my firmware/edid/ folder, then in the grub config(/boot/grub/grub.cfg) added to this line "GRUB_CMDLINE_LINUX_DEFAULT" = video=HDMI-A-1:e drm.edid_firmware=HDMI-A-1:edid/virtual_clean.bin' i did HDMI-A-1 as that was one of my ports disconnected

/etc/mkinitcpio.conf then edited this file to point it the FILES=(/usr/lib/firmware/edid/virtual_clean.bin), then i regened both the files using "sudo grub-mkconfig -o /boot/grub/grub.cfg" and "sudo mkinitcpio -P" rebooted and works fine for me, im on cachyOS arch based, KDE Plasma, and works on my Nvidia card just fine, im also using Kscreendoctor to switch the second VDisplay to on and off with alias so it doesnt eat up resources when im not using it, and im using apollo-linux fork of apollo, pointed the outputnumber to the VDisplay sorry if some of this doesnt make sense, this is just how my brain pieced this together, also to add, i am getting 1920x1080x144 for my VDisplay

you should not need to dump EDIDs or regen initramfs with this PR, the idea is that it generates an EDID on the fly based on the client and dynamically loads it onto an unused connector.

@onijuiku-rgb

Copy link
Copy Markdown

@AdivonSlav I tried without it but it kept failing, and forcing the VDisplay to 768p at 60fps, 4:3 screen, so I did the edid myself and got 1080,144fps, 16:9

@AdivonSlav

Copy link
Copy Markdown
Author

@AdivonSlav I tried without it but it kept failing, and forcing the VDisplay to 768p at 60fps, 4:3 screen, so I did the edid myself and got 1080,144fps, 16:9

weird, did you try setting the vdisplay resolution manually through KDE settings and seeing if it sticks? I've tested on multiple different devices and it worked. What's your system and client?

@IrakliChkhetia

IrakliChkhetia commented May 12, 2026

Copy link
Copy Markdown

Any progress on this pull request?

@DizzyThermal

Copy link
Copy Markdown

Just installed CachyOS (replacing my Windows 10 instance) and virtual display is a killer feature - thank you ClassicOldSong for forking and maintaining Apollo/Artemis and thank you AdivonSlav for this PR to bring VD support to Linux -- very excited to see this!

All that being said, I have an NVIDIA GTX 1080 Ti card in my setup and could help testing if it's helpful

If I'm following correctly it seems like just building using cmake and possibly correcting permissions on the apollo-vdisplay-helper binary? I'll try and set this all up tonight.

@DizzyThermal

DizzyThermal commented May 13, 2026

Copy link
Copy Markdown

No luck yet, but I did get it compiled and the frontend running at least..
Here's the rough steps (CachyOS):

# Checkout PR and Submodules
git clone https://github.com/AdivonSlav/ApolloStream.git
cd ApolloStream
git checkout vdisplay-linux
git submodule update --init --recursive

# Install Build Deps (CachyOS):
sudo pacman -S --needed git cmake ninja make pkgconf gcc clang nodejs npm boost avahi curl libayatana-appindicator libevdev libmfx libnotify libpulse libva libvdpau libx11 libxcb libxfixes libxrandr libxtst numactl openssl opus wayland wayland-protocols libdrm libcap

# Build Frontend
npm install

# Run CMake
cmake -B build -G Ninja -S . \
  -DCMAKE_BUILD_TYPE=Release \
  -DSUNSHINE_ENABLE_WAYLAND=ON \
  -DSUNSHINE_ENABLE_X11=ON \
  -DSUNSHINE_ENABLE_DRM=ON \
  -DSUNSHINE_ENABLE_CUDA=OFF

# Build / Install Apollo
ninja -C build
sudo ninja -C build install

# Set Binary Capabilities and Run Sunshine
sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine))
sunshine

I can hit the front end, login, but I don't see a Virtual Display option.. Here are the logs from TroubleShooting:

[2026-05-12 20:01:44.731]: Info: Apollo version: 0.0.0.b805ef0f commit: 
[2026-05-12 20:01:44.731]: Info: Package Publisher: SudoMaker
[2026-05-12 20:01:44.731]: Info: Publisher Website: https://www.sudomaker.com
[2026-05-12 20:01:44.732]: Info: Get support: https://github.com/ClassicOldSong/Apollo/issues
[2026-05-12 20:01:44.732]: Info: config: 'encoder' = nvenc
[2026-05-12 20:01:44.732]: Info: config: 'sunshine_name' = CachyOS
[2026-05-12 20:01:44.733]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-12 20:01:44.733]: Error: Environment variable WAYLAND_DISPLAY has not been defined
[2026-05-12 20:01:44.733]: Error: Unable to initialize capture method
[2026-05-12 20:01:44.733]: Error: Platform failed to initialize
[2026-05-12 20:01:44.812]: Info: Trying encoder [nvenc]
[2026-05-12 20:01:45.212]: Info: Encoder [nvenc] failed
[2026-05-12 20:01:45.212]: Error: Couldn't find any working encoder matching [nvenc]
[2026-05-12 20:01:45.212]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-05-12 20:01:45.212]: Info: Trying encoder [vaapi]
[2026-05-12 20:01:45.612]: Info: Encoder [vaapi] failed
[2026-05-12 20:01:45.612]: Info: Trying encoder [software]
[2026-05-12 20:01:46.012]: Info: Encoder [software] failed
[2026-05-12 20:01:46.012]: Fatal: Unable to find display or encoder during startup.
[2026-05-12 20:01:46.012]: Fatal: Please check that a display is connected and powered on.
[2026-05-12 20:01:46.012]: Error: Video failed to find working encoder: probing failed.
[2026-05-12 20:01:46.013]: Info: Starting system tray
[2026-05-12 20:01:46.015]: Info: Adding avahi service cachy
[2026-05-12 20:01:46.016]: Info: Configuration UI available at [https://localhost:47990]
[2026-05-12 20:01:46.068]: Info: System tray created
[2026-05-12 20:01:46.068]: Info: Starting main loop
[2026-05-12 20:01:46.980]: Info: Avahi service cachy successfully established.
[2026-05-12 20:01:57.665]: Info: Web UI: [192.168.1.xxx] -- redirecting
[2026-05-12 20:02:49.863]: Info: Display mode for client [DEVICE_NAME] requested to [1920x1080x30]
[2026-05-12 20:02:49.863]: Info: Trying encoder [nvenc]
[2026-05-12 20:02:50.263]: Info: Encoder [nvenc] failed
[2026-05-12 20:02:50.263]: Error: Couldn't find any working encoder matching [nvenc]
[2026-05-12 20:02:50.263]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-05-12 20:02:50.263]: Info: Trying encoder [vaapi]
[2026-05-12 20:02:50.664]: Info: Encoder [vaapi] failed
[2026-05-12 20:02:50.664]: Info: Trying encoder [software]
[2026-05-12 20:02:51.065]: Info: Encoder [software] failed
[2026-05-12 20:02:51.065]: Fatal: Unable to find display or encoder during startup.
[2026-05-12 20:02:51.065]: Fatal: Please check that a display is connected and powered on.

Some notes here:

  • I completely removed the sunshine package from the repos (pacman -Rns sunshine), so I didn't have any sunshine/apollo groups to add my user to for the apollo-vdisplay-helper binary.
  • I did install cuda and attempt to build with it ON and use CMAKE_CUDA_ARCHITECTURES=61 (for my older GTX 1080 Ti), but the version of CUDA currently in the Arch repos (12.8+) dropped support for Pascal (GTX 10-series / sm_61). So my card is probably too old for the current CUDA toolkit, but I don't think I need CUDA?
  • I tried creating a new Launcher called "Virtual Display" and enabled: Always create Virtual Display.. From Artemis I tried to launch Virtual Display, but I get Failed to start Virtual Display (error 503)

@AdivonSlav

Copy link
Copy Markdown
Author

No luck yet, but I did get it compiled and the frontend running at least.. Here's the rough steps (CachyOS):

# Checkout PR and Submodules
git clone https://github.com/AdivonSlav/ApolloStream.git
cd ApolloStream
git checkout vdisplay-linux
git submodule update --init --recursive

# Install Build Deps (CachyOS):
sudo pacman -S --needed git cmake ninja make pkgconf gcc clang nodejs npm boost avahi curl libayatana-appindicator libevdev libmfx libnotify libpulse libva libvdpau libx11 libxcb libxfixes libxrandr libxtst numactl openssl opus wayland wayland-protocols libdrm libcap

# Build Frontend
npm install

# Run CMake
cmake -B build -G Ninja -S . \
  -DCMAKE_BUILD_TYPE=Release \
  -DSUNSHINE_ENABLE_WAYLAND=ON \
  -DSUNSHINE_ENABLE_X11=ON \
  -DSUNSHINE_ENABLE_DRM=ON \
  -DSUNSHINE_ENABLE_CUDA=OFF

# Build / Install Apollo
ninja -C build
sudo ninja -C build install

# Set Binary Capabilities and Run Sunshine
sudo setcap cap_sys_admin+p $(readlink -f $(which sunshine))
sunshine

I can hit the front end, login, but I don't see a Virtual Display option.. Here are the logs from TroubleShooting:

[2026-05-12 20:01:44.731]: Info: Apollo version: 0.0.0.b805ef0f commit: 
[2026-05-12 20:01:44.731]: Info: Package Publisher: SudoMaker
[2026-05-12 20:01:44.731]: Info: Publisher Website: https://www.sudomaker.com
[2026-05-12 20:01:44.732]: Info: Get support: https://github.com/ClassicOldSong/Apollo/issues
[2026-05-12 20:01:44.732]: Info: config: 'encoder' = nvenc
[2026-05-12 20:01:44.732]: Info: config: 'sunshine_name' = CachyOS
[2026-05-12 20:01:44.733]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-12 20:01:44.733]: Error: Environment variable WAYLAND_DISPLAY has not been defined
[2026-05-12 20:01:44.733]: Error: Unable to initialize capture method
[2026-05-12 20:01:44.733]: Error: Platform failed to initialize
[2026-05-12 20:01:44.812]: Info: Trying encoder [nvenc]
[2026-05-12 20:01:45.212]: Info: Encoder [nvenc] failed
[2026-05-12 20:01:45.212]: Error: Couldn't find any working encoder matching [nvenc]
[2026-05-12 20:01:45.212]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-05-12 20:01:45.212]: Info: Trying encoder [vaapi]
[2026-05-12 20:01:45.612]: Info: Encoder [vaapi] failed
[2026-05-12 20:01:45.612]: Info: Trying encoder [software]
[2026-05-12 20:01:46.012]: Info: Encoder [software] failed
[2026-05-12 20:01:46.012]: Fatal: Unable to find display or encoder during startup.
[2026-05-12 20:01:46.012]: Fatal: Please check that a display is connected and powered on.
[2026-05-12 20:01:46.012]: Error: Video failed to find working encoder: probing failed.
[2026-05-12 20:01:46.013]: Info: Starting system tray
[2026-05-12 20:01:46.015]: Info: Adding avahi service cachy
[2026-05-12 20:01:46.016]: Info: Configuration UI available at [https://localhost:47990]
[2026-05-12 20:01:46.068]: Info: System tray created
[2026-05-12 20:01:46.068]: Info: Starting main loop
[2026-05-12 20:01:46.980]: Info: Avahi service cachy successfully established.
[2026-05-12 20:01:57.665]: Info: Web UI: [192.168.1.xxx] -- redirecting
[2026-05-12 20:02:49.863]: Info: Display mode for client [DEVICE_NAME] requested to [1920x1080x30]
[2026-05-12 20:02:49.863]: Info: Trying encoder [nvenc]
[2026-05-12 20:02:50.263]: Info: Encoder [nvenc] failed
[2026-05-12 20:02:50.263]: Error: Couldn't find any working encoder matching [nvenc]
[2026-05-12 20:02:50.263]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-05-12 20:02:50.263]: Info: Trying encoder [vaapi]
[2026-05-12 20:02:50.664]: Info: Encoder [vaapi] failed
[2026-05-12 20:02:50.664]: Info: Trying encoder [software]
[2026-05-12 20:02:51.065]: Info: Encoder [software] failed
[2026-05-12 20:02:51.065]: Fatal: Unable to find display or encoder during startup.
[2026-05-12 20:02:51.065]: Fatal: Please check that a display is connected and powered on.

Some notes here:

  • I completely removed the sunshine package from the repos (pacman -Rns sunshine), so I didn't have any sunshine/apollo groups to add my user to for the apollo-vdisplay-helper binary.
  • I did install cuda and attempt to build with it ON and use CMAKE_CUDA_ARCHITECTURES=61 (for my older GTX 1080 Ti), but the version of CUDA currently in the Arch repos (12.8+) dropped support for Pascal (GTX 10-series / sm_61). So my card is probably too old for the current CUDA toolkit, but I don't think I need CUDA?
  • I tried creating a new Launcher called "Virtual Display" and enabled: Always create Virtual Display.. From Artemis I tried to launch Virtual Display, but I get Failed to start Virtual Display (error 503)

There should be a checkbox for the vdisplay in the Audio/Video tab.

Looking at the logs this is pretty suspicious:

[2026-05-12 20:01:44.733]: Error: Environment variable WAYLAND_DISPLAY has not been defined
[2026-05-12 20:01:44.733]: Error: Unable to initialize capture method
[2026-05-12 20:01:44.733]: Error: Platform failed to initialize
[2026-05-12 20:01:44.812]: Info: Trying encoder [nvenc]
[2026-05-12 20:01:45.212]: Info: Encoder [nvenc] failed
[2026-05-12 20:01:45.212]: Error: Couldn't find any working encoder matching [nvenc]
[2026-05-12 20:01:45.212]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-05-12 20:01:45.212]: Info: Trying encoder [vaapi]
[2026-05-12 20:01:45.612]: Info: Encoder [vaapi] failed
[2026-05-12 20:01:45.612]: Info: Trying encoder [software]
[2026-05-12 20:01:46.012]: Info: Encoder [software] failed
[2026-05-12 20:01:46.012]: Fatal: Unable to find display or encoder during startup.
[2026-05-12 20:01:46.012]: Fatal: Please check that a display is connected and powered on.

Can't find any encoder or display session. Are your drivers okay, does nvidia-smi work? Are you running it through an actual display session from your DE?

As for your compilation there's a linux_build.sh script but i'm not sure if it would match CachyOS since it specifically looks at Arch. Your build flags may be off and that's why some assets could be missing if you're not seeing the checkbox, try adding these cmake flags and rebuild:

  -DCMAKE_INSTALL_PREFIX=/usr
  -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine
  -DSUNSHINE_ASSETS_DIR="share/sunshine"

and then do sudo ./src_assets/linux/misc/postinst so the perms and group are good.

@DizzyThermal

DizzyThermal commented May 13, 2026

Copy link
Copy Markdown
[2026-05-13 10:49:12.115]: Info: Apollo version: 0.0.0.b805ef0f commit: 
[2026-05-13 10:49:12.115]: Info: Package Publisher: SudoMaker
[2026-05-13 10:49:12.115]: Info: Publisher Website: https://www.sudomaker.com
[2026-05-13 10:49:12.115]: Info: Get support: https://github.com/ClassicOldSong/Apollo/issues
[2026-05-13 10:49:12.115]: Info: config: 'encoder' = nvenc
[2026-05-13 10:49:12.115]: Info: config: 'linux_virtual_display_experimental' = true
[2026-05-13 10:49:12.115]: Info: config: 'sunshine_name' = CachyOS
[2026-05-13 10:49:12.115]: Info: config: 'min_log_level' = 2
[2026-05-13 10:49:12.116]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:12.116]: Error: Environment variable WAYLAND_DISPLAY has not been defined
[2026-05-13 10:49:12.148]: Warning: Unable to create virtual mouse: Permission denied
[2026-05-13 10:49:12.148]: Warning: Unable to create virtual keyboard: Permission denied
[2026-05-13 10:49:12.148]: Warning: Gamepad xone is disabled due to Permission denied
[2026-05-13 10:49:12.148]: Warning: Gamepad ds5 is disabled due to Permission denied
[2026-05-13 10:49:12.148]: Warning: Gamepad switch is disabled due to Permission denied
[2026-05-13 10:49:12.148]: Warning: No gamepad input is available
[2026-05-13 10:49:12.148]: Info: Trying encoder [nvenc]
[2026-05-13 10:49:12.148]: Info: Screencasting with KMS
[2026-05-13 10:49:12.148]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:12.148]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:12.148]: Info: Found connector ID [93]
[2026-05-13 10:49:12.148]: Info: Found cursor plane [55]
[2026-05-13 10:49:12.148]: Warning: Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU
[2026-05-13 10:49:12.148]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:12.148]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:12.148]: Info: Found connector ID [93]
[2026-05-13 10:49:12.148]: Info: Found cursor plane [55]
[2026-05-13 10:49:12.168]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:12.168]: Info: Creating encoder [h264_nvenc]
[2026-05-13 10:49:12.168]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:12.169]: Info: Color depth: 8-bit
[2026-05-13 10:49:12.169]: Info: Color range: JPEG
[2026-05-13 10:49:12.237]: Warning: [h264_nvenc @ 0x55746b8c4c00] Multiple reference frames are not supported by the device
[2026-05-13 10:49:12.237]: Error: [h264_nvenc @ 0x55746b8c4c00] Provided device doesn't support required NVENC features
[2026-05-13 10:49:12.508]: Error: Could not open codec [h264_nvenc]: Function not implemented
[2026-05-13 10:49:12.508]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:12.508]: Info: Creating encoder [h264_nvenc]
[2026-05-13 10:49:12.508]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:12.508]: Info: Color depth: 8-bit
[2026-05-13 10:49:12.508]: Info: Color range: JPEG
[2026-05-13 10:49:12.842]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:12.842]: Info: Creating encoder [hevc_nvenc]
[2026-05-13 10:49:12.842]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:12.842]: Info: Color depth: 8-bit
[2026-05-13 10:49:12.842]: Info: Color range: JPEG
[2026-05-13 10:49:12.892]: Warning: [hevc_nvenc @ 0x55746b8c4c00] Multiple reference frames are not supported by the device
[2026-05-13 10:49:12.892]: Error: [hevc_nvenc @ 0x55746b8c4c00] Provided device doesn't support required NVENC features
[2026-05-13 10:49:13.164]: Error: Could not open codec [hevc_nvenc]: Function not implemented
[2026-05-13 10:49:13.164]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:13.164]: Info: Creating encoder [hevc_nvenc]
[2026-05-13 10:49:13.164]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:13.164]: Info: Color depth: 8-bit
[2026-05-13 10:49:13.164]: Info: Color range: JPEG
[2026-05-13 10:49:13.493]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:13.494]: Info: Creating encoder [av1_nvenc]
[2026-05-13 10:49:13.494]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:13.494]: Info: Color depth: 8-bit
[2026-05-13 10:49:13.494]: Info: Color range: JPEG
[2026-05-13 10:49:13.539]: Warning: [av1_nvenc @ 0x55746b8c4c00] Codec not supported
[2026-05-13 10:49:13.539]: Error: [av1_nvenc @ 0x55746b8c4c00] Provided device doesn't support required NVENC features
[2026-05-13 10:49:13.815]: Error: Could not open codec [av1_nvenc]: Function not implemented
[2026-05-13 10:49:13.815]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:13.815]: Info: Creating encoder [av1_nvenc]
[2026-05-13 10:49:13.815]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:13.815]: Info: Color depth: 8-bit
[2026-05-13 10:49:13.815]: Info: Color range: JPEG
[2026-05-13 10:49:13.879]: Warning: [av1_nvenc @ 0x55746b8c4c00] Codec not supported
[2026-05-13 10:49:13.879]: Error: [av1_nvenc @ 0x55746b8c4c00] Provided device doesn't support required NVENC features
[2026-05-13 10:49:14.151]: Error: Could not open codec [av1_nvenc]: Function not implemented
[2026-05-13 10:49:14.151]: Info: Screencasting with KMS
[2026-05-13 10:49:14.151]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:14.151]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:14.151]: Info: Found connector ID [93]
[2026-05-13 10:49:14.152]: Info: Found cursor plane [55]
[2026-05-13 10:49:14.152]: Warning: Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU
[2026-05-13 10:49:14.152]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:14.152]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:14.152]: Info: Found connector ID [93]
[2026-05-13 10:49:14.152]: Info: Found cursor plane [55]
[2026-05-13 10:49:14.153]: Info: Client dynamicRange: 1, Display is HDR: false
[2026-05-13 10:49:14.153]: Info: Creating encoder [hevc_nvenc]
[2026-05-13 10:49:14.153]: Info: Color coding: SDR (Rec. 709)
[2026-05-13 10:49:14.153]: Info: Color depth: 10-bit
[2026-05-13 10:49:14.153]: Info: Color range: JPEG
[2026-05-13 10:49:14.475]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-05-13 10:49:14.475]: Info: 
[2026-05-13 10:49:14.475]: Info: // Ignore any errors mentioned above, they are not relevant. //
[2026-05-13 10:49:14.475]: Info: 
[2026-05-13 10:49:14.475]: Info: Found H.264 encoder: h264_nvenc [nvenc]
[2026-05-13 10:49:14.475]: Info: Found HEVC encoder: hevc_nvenc [nvenc]
[2026-05-13 10:49:14.476]: Info: Starting system tray
[2026-05-13 10:49:14.476]: Warning: Failed to create system tray
[2026-05-13 10:49:14.477]: Info: Starting main loop
[2026-05-13 10:49:14.477]: Info: Adding avahi service cachy
[2026-05-13 10:49:14.478]: Info: Configuration UI available at [https://localhost:47990]
[2026-05-13 10:49:15.371]: Info: Web UI: [192.168.1.xxx] -- redirecting
[2026-05-13 10:49:15.402]: Info: Avahi service cachy successfully established.
[2026-05-13 10:49:45.402]: Info: Display mode for client [HOSTNAME] requested to [1280x720x60]
[2026-05-13 10:49:45.410]: Warning: [Experimental] NVIDIA card detected (card1) — EDID override may cause issues
[2026-05-13 10:49:45.410]: Warning: [Experimental] No disconnected GPU connectors found
[2026-05-13 10:49:45.410]: Warning: [Experimental] Linux virtual display failed (status -2)
[2026-05-13 10:49:45.410]: Info: Trying encoder [nvenc]
[2026-05-13 10:49:45.410]: Info: Screencasting with KMS
[2026-05-13 10:49:45.410]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:45.411]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:45.411]: Info: Found connector ID [93]
[2026-05-13 10:49:45.411]: Info: Found cursor plane [55]
[2026-05-13 10:49:45.411]: Warning: Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU
[2026-05-13 10:49:45.411]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:45.411]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:45.411]: Info: Found connector ID [93]
[2026-05-13 10:49:45.411]: Info: Found cursor plane [55]
[2026-05-13 10:49:45.433]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:45.433]: Info: Creating encoder [h264_nvenc]
[2026-05-13 10:49:45.433]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:45.433]: Info: Color depth: 8-bit
[2026-05-13 10:49:45.433]: Info: Color range: JPEG
[2026-05-13 10:49:45.494]: Warning: [h264_nvenc @ 0x7f3b343f8d80] Multiple reference frames are not supported by the device
[2026-05-13 10:49:45.494]: Error: [h264_nvenc @ 0x7f3b343f8d80] Provided device doesn't support required NVENC features
[2026-05-13 10:49:45.775]: Error: Could not open codec [h264_nvenc]: Function not implemented
[2026-05-13 10:49:45.775]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:45.775]: Info: Creating encoder [h264_nvenc]
[2026-05-13 10:49:45.775]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:45.776]: Info: Color depth: 8-bit
[2026-05-13 10:49:45.776]: Info: Color range: JPEG
[2026-05-13 10:49:46.104]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:46.104]: Info: Creating encoder [hevc_nvenc]
[2026-05-13 10:49:46.104]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:46.104]: Info: Color depth: 8-bit
[2026-05-13 10:49:46.104]: Info: Color range: JPEG
[2026-05-13 10:49:46.158]: Warning: [hevc_nvenc @ 0x7f3b343f8d80] Multiple reference frames are not supported by the device
[2026-05-13 10:49:46.158]: Error: [hevc_nvenc @ 0x7f3b343f8d80] Provided device doesn't support required NVENC features
[2026-05-13 10:49:46.435]: Error: Could not open codec [hevc_nvenc]: Function not implemented
[2026-05-13 10:49:46.435]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:46.435]: Info: Creating encoder [hevc_nvenc]
[2026-05-13 10:49:46.435]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:46.435]: Info: Color depth: 8-bit
[2026-05-13 10:49:46.435]: Info: Color range: JPEG
[2026-05-13 10:49:46.765]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:46.765]: Info: Creating encoder [av1_nvenc]
[2026-05-13 10:49:46.765]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:46.765]: Info: Color depth: 8-bit
[2026-05-13 10:49:46.765]: Info: Color range: JPEG
[2026-05-13 10:49:46.814]: Warning: [av1_nvenc @ 0x7f3b343f8d80] Codec not supported
[2026-05-13 10:49:46.814]: Error: [av1_nvenc @ 0x7f3b343f8d80] Provided device doesn't support required NVENC features
[2026-05-13 10:49:47.091]: Error: Could not open codec [av1_nvenc]: Function not implemented
[2026-05-13 10:49:47.091]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:47.091]: Info: Creating encoder [av1_nvenc]
[2026-05-13 10:49:47.091]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:47.091]: Info: Color depth: 8-bit
[2026-05-13 10:49:47.091]: Info: Color range: JPEG
[2026-05-13 10:49:47.143]: Warning: [av1_nvenc @ 0x7f3b343f8d80] Codec not supported
[2026-05-13 10:49:47.143]: Error: [av1_nvenc @ 0x7f3b343f8d80] Provided device doesn't support required NVENC features
[2026-05-13 10:49:47.420]: Error: Could not open codec [av1_nvenc]: Function not implemented
[2026-05-13 10:49:47.421]: Info: Screencasting with KMS
[2026-05-13 10:49:47.421]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:47.421]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:47.421]: Info: Found connector ID [93]
[2026-05-13 10:49:47.421]: Info: Found cursor plane [55]
[2026-05-13 10:49:47.421]: Warning: Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU
[2026-05-13 10:49:47.421]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:47.421]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:47.421]: Info: Found connector ID [93]
[2026-05-13 10:49:47.421]: Info: Found cursor plane [55]
[2026-05-13 10:49:47.423]: Info: Client dynamicRange: 1, Display is HDR: false
[2026-05-13 10:49:47.423]: Info: Creating encoder [hevc_nvenc]
[2026-05-13 10:49:47.423]: Info: Color coding: SDR (Rec. 709)
[2026-05-13 10:49:47.423]: Info: Color depth: 10-bit
[2026-05-13 10:49:47.423]: Info: Color range: JPEG
[2026-05-13 10:49:47.750]: Info: // Testing for available encoders, this may generate errors. You can safely ignore those errors. //
[2026-05-13 10:49:47.750]: Info: 
[2026-05-13 10:49:47.750]: Info: // Ignore any errors mentioned above, they are not relevant. //
[2026-05-13 10:49:47.750]: Info: 
[2026-05-13 10:49:47.750]: Info: Found H.264 encoder: h264_nvenc [nvenc]
[2026-05-13 10:49:47.750]: Info: Found HEVC encoder: hevc_nvenc [nvenc]
[2026-05-13 10:49:47.750]: Info: No commands configured, showing desktop...
[2026-05-13 10:49:48.332]: Info: Client Requested bitrate is [10000kbps]
[2026-05-13 10:49:48.332]: Info: Host Streaming bitrate is [10000kbps]
[2026-05-13 10:49:48.332]: Info: New streaming session started [active sessions: 1]
[2026-05-13 10:49:48.332]: Warning: Unable to create virtual touch screen: Permission denied
[2026-05-13 10:49:48.332]: Warning: Unable to create virtual pen tablet: Permission denied
[2026-05-13 10:49:48.333]: Info: Session resuming for app [Virtual Display].
[2026-05-13 10:49:48.431]: Info: CLIENT CONNECTED
[2026-05-13 10:49:48.836]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:48.836]: Info: Screencasting with KMS
[2026-05-13 10:49:48.836]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:48.837]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:48.837]: Info: Found connector ID [93]
[2026-05-13 10:49:48.837]: Info: Found cursor plane [55]
[2026-05-13 10:49:48.837]: Warning: Attempting to use NVENC without CUDA support. Reverting back to GPU -> RAM -> GPU
[2026-05-13 10:49:48.837]: Info: /dev/dri/card1 -> nvidia-drm
[2026-05-13 10:49:48.837]: Info: Found monitor for DRM screencasting
[2026-05-13 10:49:48.838]: Info: Found connector ID [93]
[2026-05-13 10:49:48.838]: Info: Found cursor plane [55]
[2026-05-13 10:49:48.858]: Info: Client dynamicRange: 0, Display is HDR: false
[2026-05-13 10:49:48.858]: Info: Creating encoder [h264_nvenc]
[2026-05-13 10:49:48.858]: Info: Color coding: SDR (Rec. 601)
[2026-05-13 10:49:48.858]: Info: Color depth: 8-bit
[2026-05-13 10:49:48.858]: Info: Color range: MPEG
[2026-05-13 10:49:48.858]: Warning: Client requested reference frame limit, but encoder doesn't support it!
[2026-05-13 10:49:48.903]: Info: Selected audio sink: sink-sunshine-stereo
[2026-05-13 10:49:48.903]: Info: Setting default sink to: [sink-sunshine-stereo]
[2026-05-13 10:49:48.903]: Error: Couldn't set default-sink [sink-sunshine-stereo]: Not supported
[2026-05-13 10:49:48.903]: Error: Unable to initialize audio capture. The stream will not have audio.
[2026-05-13 10:49:48.941]: Info: Minimum FPS target set to ~6fps (1.66667e+08ns)
[2026-05-13 10:49:48.941]: Info: Encoding Frame threshold: 16666666ns
[2026-05-13 10:50:16.557]: Info: Setting default sink to: [@DEFAULT_SINK@]
[2026-05-13 10:50:16.713]: Info: Session pausing for app [Virtual Display].

Seeing the CUDA failures in the log isn't very inspiring, but it's more functional than it was yesterday :)

I followed the steps that @Toremous took:

  • Boost 1.89.0 -> 1.91.0
  • Installed gcc14

Removed build/ directory and rebuild with:

cmake -B build -G Ninja -S . \
        -DCMAKE_INSTALL_PREFIX=/usr \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_CUDA_COMPILER=/opt/cuda/bin/nvcc \
        -DCMAKE_CUDA_HOST_COMPILER=/usr/bin/g++-14 \
        -DSUNSHINE_EXECUTABLE_PATH=/usr/bin/sunshine \
        -DSUNSHINE_ASSETS_DIR="share/sunshine" \
        -DSUNSHINE_ENABLE_WAYLAND=ON \
        -DSUNSHINE_ENABLE_X11=ON \
        -DSUNSHINE_ENABLE_DRM=ON \
        -DSUNSHINE_ENABLE_CUDA=ON

Also ran the postinst script like you mentioned, verified my user is in the apollo group and restarted my machine.

I have 3 physical monitors connected to my PC (2 DP, 1 HDMI) and 1 empty HDMI slot (that I want the virtual display to use).

Audio/Video I enable "Virtual Display (experimental)" and then restart Apollo

[2026-05-13 15:15:50.684]: Warning: [Experimental] NVIDIA card detected (card1) — EDID override may cause issues
[2026-05-13 15:15:50.684]: Warning: [Experimental] No disconnected GPU connectors found
[2026-05-13 15:15:50.684]: Warning: [Experimental] Linux virtual display failed (status -2)

Going to dig deeper on this bit later. Might be a permission issue because I do have 1 empty/under HDMI port on the card

@DizzyThermal

Copy link
Copy Markdown

I'm realizing my problem here is, if I don't initially login after a reboot, no Wayland session is created.
Apollo is trying to create a Virtual Display and attach it to an already established Wayland/X11 session, but because after reboot (and before login) it doesn't exist yet it fails to create the Virtual Display (503 kicks back).

So for my setup (KDE on CachyOS) I could theoretically create Do/Undo commands that:

  • DO: Run a script to check if a wayland session is established, if not create a headless session with KWin
  • UNDO: Run a script to check if a wayland session exists and if it is a headless session, if so, clean it up so the machine and monitors can regain control

Obviously this is specific to Wayland (vs. X11) and KWin (vs. mutter, cage), but is this overkill?

I could setup KDEConnect, jiggle the mouse and login that way if I reboot, but I was hoping for a more automated solution like how it works on Windows.

@Toremous

Copy link
Copy Markdown

After continued use id say that this solution seems very robust, its handled every weird display / resolution ive thrown at it, and it releases the display exceptionally quickly on closing the session. I hope to see it merged (and subsequently pushed to aur).

for testing I used Artemis as the client on shield tv, android phone (on various resolutions), and linux via this project. All functioned well.

@AdivonSlav

Copy link
Copy Markdown
Author

I'm realizing my problem here is, if I don't initially login after a reboot, no Wayland session is created. Apollo is trying to create a Virtual Display and attach it to an already established Wayland/X11 session, but because after reboot (and before login) it doesn't exist yet it fails to create the Virtual Display (503 kicks back).

So for my setup (KDE on CachyOS) I could theoretically create Do/Undo commands that:

  • DO: Run a script to check if a wayland session is established, if not create a headless session with KWin
  • UNDO: Run a script to check if a wayland session exists and if it is a headless session, if so, clean it up so the machine and monitors can regain control

Obviously this is specific to Wayland (vs. X11) and KWin (vs. mutter, cage), but is this overkill?

I could setup KDEConnect, jiggle the mouse and login that way if I reboot, but I was hoping for a more automated solution like how it works on Windows.

Did not really think of that situation where this would be used prior to a login but I can see the usecase, I'll have to investigate this further. Theoretically you could just enable autologin on your system if you don't mind the security implications?

@DizzyThermal

Copy link
Copy Markdown

I'm realizing my problem here is, if I don't initially login after a reboot, no Wayland session is created. Apollo is trying to create a Virtual Display and attach it to an already established Wayland/X11 session, but because after reboot (and before login) it doesn't exist yet it fails to create the Virtual Display (503 kicks back).
So for my setup (KDE on CachyOS) I could theoretically create Do/Undo commands that:

  • DO: Run a script to check if a wayland session is established, if not create a headless session with KWin
  • UNDO: Run a script to check if a wayland session exists and if it is a headless session, if so, clean it up so the machine and monitors can regain control

Obviously this is specific to Wayland (vs. X11) and KWin (vs. mutter, cage), but is this overkill?
I could setup KDEConnect, jiggle the mouse and login that way if I reboot, but I was hoping for a more automated solution like how it works on Windows.

Did not really think of that situation where this would be used prior to a login but I can see the usecase, I'll have to investigate this further. Theoretically you could just enable autologin on your system if you don't mind the security implications?

Right, this is what I'm going to do here to get logged in / Wayland initialized.

Still playing with some settings to get all other monitors disabled (and reenabled) when using Virtual Display launcher, Kwin won't let me disable all monitors at once, so I can't achieve this with a Do/Undo command set (at least I don't think I can)

@primez-x

primez-x commented May 17, 2026

Copy link
Copy Markdown

I'm doing this in my fork but using EVDI, not EDID. Requires MOK enrollment for secure boot enabled machines. Not sure EDID is much better than a quick/easy MOK enrollment. While EDID may avoid MOK enrollment, would require secure boot lockdown to be disabled in order to get debugfs access.

EVDI is a one step MOK signing and might be preferred/required to keep secure boot, which makes EDID not possible on Secure Boot enabled devices.

In my fork, EVDI is used to create the virtual display on demand but Mutter/PipeWire used for capture.

Sharing here to collaborate. Happy to see if there's another path forward, but EVDI path on my fork is proving to be incredibly stable and responsive.

https://github.com/primez-x/Apollo-Ubuntu

EDIT: Also worth noting that I did have to enable password-less login for the same Wayland/Gnome session creation limitation. Exploring this further as well. Don't have a great option otherwise at the moment.

@ClassicOldSong

Copy link
Copy Markdown
Owner

@primez-x do you really know what EDID and EVDI are? Can you explain why "EDID" needs disabling secure boot?

Purely vibe coded PRs won't stand a chance here.

@AdivonSlav

Copy link
Copy Markdown
Author

He's probably right regarding secure boot as the kernel lockdown man page does specify that kernel lockdown (due to secure boot being on) does limit access to hardware and configuring it, which should include debugfs. I don't run secure boot so I failed to notice this. I would not call this a big problem due to the fact that secure boot is not used by many users and it's not even supported out of the box for many distros, Ubuntu and Fedora being the only exceptions I know of. This issue regarding system76-scheduler does make it likely: pop-os/system76-scheduler#50

Due to this being marked "experimental" anyway, a simple error message indicating that secure boot being on does not allow for virtual displays is fine imo.

As for EVDI, I've taken a look and the implementation seems much more complex and dependency-heavy. Given that the fork is called Apollo-Ubuntu and you do mention Mutter, is it safe to assume this only works on Ubuntu/Gnome? What's the deal with that.

@primez-x

primez-x commented May 17, 2026

Copy link
Copy Markdown

My understanding on EDID is that it's using a debug route that basically forces the GPU to recognize a physical monitor that doesn't actually exist via a physical GPU port, so you're essentially telling the GPU that "a monitor exists here, so output to that GPU monitor port", which Apollo then captures and streams.

My fork actually started from https://github.com/MrOz59/Apollo-Linux.

I wanted Apollo support for Ubuntu 26.04 (current Ubuntu dev build). At the time, there were no other Linux forks that supported virtual displays. The fork above, however, did not work at all with Ubuntu. I was able to vibe code it to a barely functioning EVDI capture but that barely functioned in practice, far too slow, laggy, choppy, unusable. I think the MrOz59 fork was using an X11 capture, don't recall exactly. My understanding is that Ubuntu is planning to drop X11 support entirely so Wayland is simply the path forward, and given its also the default for Ubuntu, I wanted to support this natively. I am also using Secure Boot, as it sounds like that's the general direction Ubuntu is heading in. EVDI is used to emulate the monitor, but all the capture is actually happening on GPU via Mutter.

EVDI capture directly is awful. It can be done. But barely functions even at 30fps 720p.

I'm tested the EVDI/Mutter capture route up to 4k resolution with no issues, and have thrown a few random combinations of screen resolution without any issues as well.

Song noted in my other PR that I created just to bump the visibility that it's full of noise and thousands of changes - which is likely because of the Wayland/Mutter capture dependencies and also, this fork started life as a fork from a ~2 year not super maintained fork.

I would be willing to bet that I could get a fresh fork from the last ClassicOldSong commit that would be far less bloated and less dependency heavy. I really don't think EVDI"s dependencies are too dramatic, just the big one which is MOK signing.

@DizzyThermal

Copy link
Copy Markdown

Just wanted to follow-up on my experience so far. Other than needing a method to wake up my displays, I've had a flawless experience gaming and using remote desktop with the Virtual Display.

For my environment I use this script, which is KDE-specific (kscreen-doctor commands):

/usr/local/bin/init-vdisplay:

#!/bin/bash

# Parameters
export WAYLAND_DISPLAY=wayland-0
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
CARD_SLOT="card1"
VIRTUAL_DISPLAY_CARD="HDMI-A-2"

# Gather displays
DISPLAYS=()
for DISPLAY in $(ls /sys/class/drm); do
        if [[ ${DISPLAY} == *"${CARD_SLOT}-"* ]]; then
                DISPLAYS+=("${DISPLAY}")
        fi
done

# Determine if a display is on
DISPLAY_ON=0
for DISPLAY in ${DISPLAYS[@]}; do
        if grep -q "enabled" /sys/class/drm/${DISPLAY}/enabled; then
                DISPLAY_ON=1
                break
        fi
done

# If no displays are on, toggle VIRTUAL_DISPLAY_CARD.
if [[ ${DISPLAY_ON} -eq 0 ]]; then
        /usr/bin/kscreen-doctor output.${VIRTUAL_DISPLAY_CARD}.enable
fi

I would love to have this be Command Preparation Do/Undo commands, but the Virtual Display attempts to create before the Command Preparation steps run, so doing something like:

Do: /usr/local/bin/init-vdisplay
Undo: /usr/bin/kscreen-doctor output.DP-1.enable output.DP-2.enable output.HDMI-A-1.enable

won't work, so I ultimately ssh in and run /usr/local/bin/init-vdisplay.

I can connect after a few seconds (I previously had a poll loop that would wait for a valid display resolution, but since I have to ssh in anyways, by the time I'm using Artemis, it's already ready).

Is there a way to make the Virtual Display creation happen after the Command Preparations, or is there an earlier point I can inject this to run?

Thanks again for this PR, I run it daily 👍

itsDNNS commented May 26, 2026

Copy link
Copy Markdown

Tested this on CachyOS / KDE Plasma Wayland / NVIDIA RTX 4090.

It works for me now with the PR branch. Apollo creates the virtual connector, KWin picks it up, and KMS capture switches to it correctly.

Relevant setup:

  • GPU: NVIDIA RTX 4090
  • Driver: 595.71.05
  • Desktop: KDE Plasma Wayland
  • Apollo commit: b805ef0
  • Client: Artemis on Android

Config I needed:

capture = kms
encoder = nvenc
adapter_name = /dev/dri/renderD128
vdisplay_connector = card1-DP-2
linux_virtual_display_experimental = enabled

App config:

"virtual-display": true

Successful log path:

[Experimental] Using configured connector: card1-DP-2
[Experimental] EDID written to /sys/kernel/debug/dri/1/DP-2/edid_override
[Experimental] Connector status set to 'on' on card1-DP-2
[Experimental] Connector active with CRTC after 600ms
[Experimental] Linux virtual display created: card1-DP-2
[Experimental] Virtual display is at KMS monitor index 2
[Experimental] Capturing virtual display at KMS index 2

Two notes from testing:

  1. trigger_hotplug is missing on my NVIDIA setup.

Apollo logs:

[Experimental] Helper failed to trigger hotplug on card1-DP-2

On this system that is harmless. KWin still notices the connector and assigns a CRTC after about 600ms. Might be worth downgrading this warning when the file does not exist, or only warning if the connector does not become active afterwards.

  1. Disconnect cleanup only happened when I set:
"terminate-on-pause": true

Without that, Apollo paused the Desktop session and left card1-DP-2 connected with vdisplay.state still present. With terminate-on-pause enabled, cleanup works as expected:

CLIENT DISCONNECTED
Terminating app [Desktop] when all clients are disconnected
[Experimental] EDID override cleared on card1-DP-2
[Experimental] Connector status set to 'detect' on card1-DP-2
[Experimental] Virtual display removed
[Experimental] Linux virtual display removed

So the feature works on NVIDIA/KDE/Wayland here, but the pause behavior can leave the virtual display alive unless the app is configured to terminate on pause.

@IrakliChkhetia

Copy link
Copy Markdown

Honestly, this project will determine whether I'll ever switch to Linux. Apollo on Windows is a godsend.

@ClassicOldSong

Copy link
Copy Markdown
Owner

For whatever reason, I don't recommend switching to Linux, if you need your computer to dual role for some daily tasks.

It's a common problem in open source, if something is satisfactory, it'll stay there forever, until someone gets annoyed by the current state. Gaming on Linux improves because someone is not satisfied and valve needs it to make money, other than these, they're still far from being able to daily drive.

@BLACK4585

Copy link
Copy Markdown

I have to strongly contradict you.
Driving it daily since one and a half years only. No Windows required and needed. Everything I want runs here. Gaming is smooth and works perfectly. The only games not working are the ones with crazy kernel level software which I do not play anyway.
Although this is kinda offtopic I had to say it. We as a community cannot afford the constant Linux blaming anymore while installing bloatware and tracking software on our hardware because we are caught in the net of big tech.
IMHO most people saying "Linux is bad" have not even informed themselves and are referring to opinions from ten years ago.

@ClassicOldSong

ClassicOldSong commented May 27, 2026

Copy link
Copy Markdown
Owner

In case you don't know, I've been using Linux for nearly 20 years, but at the same time, I'm a creator, and Linux never satisfied me. My time is precious, I don't have that much time to improve something that's broken at the beginning and is refusing contributions.

I wanted Linux to get better, so I and my partener tried to submit patches to it, but the maintainer just rejected them without any good reasons. Years later, the maintainer suddenly replied saying I was right, but still nothing happened to the kernel source, nothing is fixed there. I don't think you have strong enough evidence to convince me that Linux is getting better. Also checkout how Asahi Linux starts and ends.

I know how much you want to escape the Windows bloat, but believe me, if you have any professional needs, despite doing website hosting/networking/server management, no matter which you need, CAD/image editing/movie editing/music production, none of them are great on Linux. You'll spend more time on figuring out why things don't work, finding alternatives that seems work for this exact issue you're facing but later you discovered that it can't complete your other basic requirements. With the time you spent, you lose more than paying for those commercial softwares.

Since you have no idea how the reality of Linux development is, it's understandable that you think it's a paradise and can't bear hearing any bad words about it. But I don't want to see more downvotes on the previous reply, because now you know.

@ClassicOldSong

Copy link
Copy Markdown
Owner

And most importantly, it's still an open source issue. Open source project maintainers are massively underpaid for their contributions. That's why it's only getting better recently, since Valve use it for SteamDeck and SteamMachine, which they sell to make money for them.

@ClassicOldSong

Copy link
Copy Markdown
Owner

I welcome opinions here, but not altitude and emotions. If anyone of you can solve the fragmentation of Linux, unify developing resources, gather funds for developers, it can get even better, but it won't if you blame someone that can and had tried to make it better.

@AdivonSlav

Copy link
Copy Markdown
Author

Id rather we not clutter a code PR with unrelated discussions

@AdivonSlav

Copy link
Copy Markdown
Author

Just wanted to follow-up on my experience so far. Other than needing a method to wake up my displays, I've had a flawless experience gaming and using remote desktop with the Virtual Display.

For my environment I use this script, which is KDE-specific (kscreen-doctor commands):

/usr/local/bin/init-vdisplay:

#!/bin/bash

# Parameters
export WAYLAND_DISPLAY=wayland-0
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
CARD_SLOT="card1"
VIRTUAL_DISPLAY_CARD="HDMI-A-2"

# Gather displays
DISPLAYS=()
for DISPLAY in $(ls /sys/class/drm); do
        if [[ ${DISPLAY} == *"${CARD_SLOT}-"* ]]; then
                DISPLAYS+=("${DISPLAY}")
        fi
done

# Determine if a display is on
DISPLAY_ON=0
for DISPLAY in ${DISPLAYS[@]}; do
        if grep -q "enabled" /sys/class/drm/${DISPLAY}/enabled; then
                DISPLAY_ON=1
                break
        fi
done

# If no displays are on, toggle VIRTUAL_DISPLAY_CARD.
if [[ ${DISPLAY_ON} -eq 0 ]]; then
        /usr/bin/kscreen-doctor output.${VIRTUAL_DISPLAY_CARD}.enable
fi

I would love to have this be Command Preparation Do/Undo commands, but the Virtual Display attempts to create before the Command Preparation steps run, so doing something like:

Do: /usr/local/bin/init-vdisplay Undo: /usr/bin/kscreen-doctor output.DP-1.enable output.DP-2.enable output.HDMI-A-1.enable

won't work, so I ultimately ssh in and run /usr/local/bin/init-vdisplay.

I can connect after a few seconds (I previously had a poll loop that would wait for a valid display resolution, but since I have to ssh in anyways, by the time I'm using Artemis, it's already ready).

Is there a way to make the Virtual Display creation happen after the Command Preparations, or is there an earlier point I can inject this to run?

Thanks again for this PR, I run it daily 👍

I'll check this out tomorrow probably. If vdisplay creation for Win already happens before command prep then id rather not introduce inconsistencies.

Thanks a lot for testing this in detail

@AdivonSlav

Copy link
Copy Markdown
Author

Tested this on CachyOS / KDE Plasma Wayland / NVIDIA RTX 4090.

It works for me now with the PR branch. Apollo creates the virtual connector, KWin picks it up, and KMS capture switches to it correctly.

Relevant setup:

* GPU: NVIDIA RTX 4090

* Driver: 595.71.05

* Desktop: KDE Plasma Wayland

* Apollo commit: [b805ef0](https://github.com/ClassicOldSong/Apollo/commit/b805ef0f7d6d7fb07614bd7a118cdbefea3501b6)

* Client: Artemis on Android

Config I needed:

capture = kms
encoder = nvenc
adapter_name = /dev/dri/renderD128
vdisplay_connector = card1-DP-2
linux_virtual_display_experimental = enabled

App config:

"virtual-display": true

Successful log path:

[Experimental] Using configured connector: card1-DP-2
[Experimental] EDID written to /sys/kernel/debug/dri/1/DP-2/edid_override
[Experimental] Connector status set to 'on' on card1-DP-2
[Experimental] Connector active with CRTC after 600ms
[Experimental] Linux virtual display created: card1-DP-2
[Experimental] Virtual display is at KMS monitor index 2
[Experimental] Capturing virtual display at KMS index 2

Two notes from testing:

1. `trigger_hotplug` is missing on my NVIDIA setup.

Apollo logs:

[Experimental] Helper failed to trigger hotplug on card1-DP-2

On this system that is harmless. KWin still notices the connector and assigns a CRTC after about 600ms. Might be worth downgrading this warning when the file does not exist, or only warning if the connector does not become active afterwards.

2. Disconnect cleanup only happened when I set:
"terminate-on-pause": true

Without that, Apollo paused the Desktop session and left card1-DP-2 connected with vdisplay.state still present. With terminate-on-pause enabled, cleanup works as expected:

CLIENT DISCONNECTED
Terminating app [Desktop] when all clients are disconnected
[Experimental] EDID override cleared on card1-DP-2
[Experimental] Connector status set to 'detect' on card1-DP-2
[Experimental] Virtual display removed
[Experimental] Linux virtual display removed

So the feature works on NVIDIA/KDE/Wayland here, but the pause behavior can leave the virtual display alive unless the app is configured to terminate on pause.

The hot plug trigger is mostly just a "fuck it why not" type of thing to try and coerce the compositor to recognize it. Can definitely just be a warning

As for pausing, do we know what the behavior is on Win?

@ClassicOldSong

Copy link
Copy Markdown
Owner

Terminate on pause will terminate the session once all clients are disconnected.

Probably it's a timing issue

@AdivonSlav

Copy link
Copy Markdown
Author

So this behavior should be consistent with Windows, we're not killing the vdisplay if paused? As far as I can see removeVirtualDisplay() for win is only called in terminate() unless terminate-on-pause is ticked.

@ClassicOldSong

Copy link
Copy Markdown
Owner

Remove should be called whenever a session terminates, it doesn't matter that check is ticked or not.

Also, by design, virtual display should be created prior to prep commands and tear down after the undo commands.

@AdivonSlav

Copy link
Copy Markdown
Author

@DizzyThermal win has the same ordering, so we can't change it here.

@AdivonSlav

Copy link
Copy Markdown
Author

@ClassicOldSong anything else that this needs in your opinion or are we good for now?

@ClassicOldSong

Copy link
Copy Markdown
Owner

I'll take a look once I get some free time, thanks again for the PR!

@IrakliChkhetia

Copy link
Copy Markdown

How is the progress going?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants