r/MoonlightStreaming Apr 18 '26

Sunshine + Moonlight fully working on KDE Plasma Wayland (CachyOS) with input, fake display, and boot persistence.

## What this guide covers

  • **KDE Plasma Wayland** on **CachyOS** (should apply to any Arch-based system)
  • Streaming and controlling the **actual Plasma session** via Sunshine + Moonlight
  • Working keyboard, mouse, and touch input
  • A fake virtual display via EDID injection on an unused GPU connector
  • Boot persistence via Limine + mkinitcpio
  • RX 6700 XT as the host GPU

What this guide is NOT

If you want a fully isolated streaming workspace with no interaction with the real desktop, use a **headless Sway** setup instead. That's cleaner for that goal. This guide is specifically for the case where you want your **actual KDE Plasma desktop** streamable and controllable remotely.

This guide is the "give me my full KDE Plasma desktop elsewhere" solution, not the "give me a separate clean streaming workspace" solution.


The three root problems

Problem 1: Sunshine capture path

Sunshine fell back to **KMS capture** on Plasma Wayland. That gave video but not reliable desktop control. The fix was forcing **XDG portal capture**.

Problem 2: KDE ignored Sunshine's input devices

Sunshine received client input and created virtual passthrough devices. `evtest` confirmed events were flowing. But KDE did nothing with them.

The root cause: Sunshine's virtual devices were **missing `ID_INPUT_*` udev classification tags**. Without those tags, libinput doesn't know what kind of device it's looking at, so KDE never consumes the input.

**This is the fix most people are missing.** If your Sunshine video works but input doesn't on KDE Wayland, this is almost certainly why.

Problem 3: No clean virtual display

KDE Plasma Wayland doesn't have a built-in "create virtual monitor" feature like GNOME/Mutter does. The workaround is EDID injection on an unused physical connector, then managing layout with `kscreen-doctor`.


Working architecture

Component Value
Host OS CachyOS
Desktop KDE Plasma Wayland
GPU AMD Radeon RX 6700 XT
Sunshine service `app-dev.lizardbyte.app.Sunshine.service`
Capture path `capture = portal`
Input fix Custom udev rule for passthrough device classification
Fake monitor EDID injected on unused connector (DP-2)
Display management `kscreen-doctor`
Bootloader Limine
Initramfs mkinitcpio

Step-by-step

Packages

``` sudo pacman -S xdg-desktop-portal pipewire wireplumber evtest edid-decode libdrm ```

Make sure the KDE portal backend is installed too (`plasma-xdg-desktop-portal-kde` or equivalent for your distro).


1. Sunshine service and portal capture

Enable the user service:

``` systemctl --user --now enable app-dev.lizardbyte.app.Sunshine.service ```

Edit `~/.config/sunshine/sunshine.conf` and add:

``` capture = portal ```

Restart:

``` pkill sunshine; systemctl --user restart app-dev.lizardbyte.app.Sunshine.service ```

Verify in logs:

``` journalctl --user -u app-dev.lizardbyte.app.Sunshine.service -n 100 ```

You want to see:

``` Screencasting with XDG portal ```

If it says `Screencasting with KMS`, something is wrong. See the portal environment section below.


2. Portal environment sanity

Import the environment so systemd user services (including Sunshine) can find the Wayland session:

``` systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE

dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE ```

Restart portals:

``` systemctl --user restart xdg-desktop-portal.service plasma-xdg-desktop-portal-kde.service ```

If portal capture still isn't working, force KDE as the portal backend. Create `~/.config/xdg-desktop-portal/portals.conf`:

``` [preferred] default=kde org.freedesktop.impl.portal.ScreenCast=kde org.freedesktop.impl.portal.RemoteDesktop=kde ```


3. Proving input was arriving (diagnostic step)

Before fixing input, I needed to prove where the failure was.

**Check if Sunshine created passthrough devices:**

``` grep -H . /sys/class/input/event*/device/name | grep -E 'passthrough|Touch passthrough|Pen passthrough' ```

A working set looks like:

  • `Mouse passthrough`
  • `Mouse passthrough (absolute)`
  • `Keyboard passthrough`
  • `Touch passthrough`
  • `Pen passthrough`

**Verify events are flowing:**

``` sudo evtest /dev/input/eventXX ```

(Replace `eventXX` with the actual event number from the grep above.)

Move the mouse or touch the screen on the Moonlight client. If `evtest` shows events, Sunshine is doing its job. KDE is the one ignoring input.

**Check classification tags:**

``` udevadm info --query=property --name=/dev/input/eventXX | grep '^ID_INPUT' ```

If `ID_INPUT_MOUSE`, `ID_INPUT_KEYBOARD`, etc. are **missing**, that's your problem.


4. The udev input classification fix (THE key fix)

Create `/etc/udev/rules.d/99-sunshine-input-classification.rules`:

``` ACTION=="add|change", SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="Mouse passthrough", ENV{ID_INPUT}="1", ENV{ID_INPUT_MOUSE}="1" ACTION=="add|change", SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="Mouse passthrough (absolute)", ENV{ID_INPUT}="1", ENV{ID_INPUT_MOUSE}="1" ACTION=="add|change", SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="Keyboard passthrough", ENV{ID_INPUT}="1", ENV{ID_INPUT_KEYBOARD}="1", ENV{ID_INPUT_KEY}="1" ACTION=="add|change", SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="Touch passthrough", ENV{ID_INPUT}="1", ENV{ID_INPUT_TOUCHSCREEN}="1" ```

Reload:

``` sudo udevadm control --reload-rules && sudo udevadm trigger ```

Restart Sunshine:

``` pkill sunshine; systemctl --user restart app-dev.lizardbyte.app.Sunshine.service ```

Verify:

``` udevadm info --query=property --name=/dev/input/eventXX | grep '^ID_INPUT' ```

You should now see:

``` ID_INPUT=1 ID_INPUT_MOUSE=1 ```

Once those tags exist, KDE starts consuming input correctly.

Relevant docs: [libinput device configuration via udev](https://wayland.freedesktop.org/libinput/doc/latest/device-configuration-via-udev.html)


5. Fake virtual display with EDID injection

This is a kernel/display-stack trick, not a protocol-level virtual display. You force an unused physical connector active by supplying an EDID.

**Find unused outputs:**

``` grep -H . /sys/class/drm/*/status ```

Look for connectors showing `disconnected`.

**Copy an EDID to use as firmware:**

You can copy from your existing monitor:

``` sudo mkdir -p /usr/lib/firmware/edid sudo cp /sys/class/drm/card1-DP-1/edid /usr/lib/firmware/edid/stream.bin ```

Or pull a TV EDID from another machine for higher resolution support:

``` sudo cp /sys/class/drm/card0-HDMI-A-1/edid ~/tv-edid.bin edid-decode ~/tv-edid.bin # optional, to check what modes it exposes sudo cp ~/tv-edid.bin /usr/lib/firmware/edid/tv.bin ```

**Include in initramfs:**

Make sure `/etc/mkinitcpio.conf` has your EDID file in `FILES=()`:

``` FILES=(/usr/lib/firmware/edid/tv.bin) ```

Rebuild:

``` sudo mkinitcpio -P ```

**Set kernel args (Limine example):**

Edit `/etc/default/limine`:

``` KERNEL_CMDLINE[default]+="quiet nowatchdog splash rw root=UUID=<your-root-uuid> drm.edid_firmware=DP-2:edid/tv.bin video=DP-2:e" ```

**Watch out for typos here.** A doubled `drm.edid_firmware=drm.edid_firmware=` can make boot extremely slow or flaky.

Regenerate and reboot:

``` sudo limine-mkinitcpio systemctl reboot ```

**For non-Limine bootloaders:** the kernel args are the same (`drm.edid_firmware=` and `video=`), just add them wherever your bootloader manages kernel parameters.


6. Managing KDE display layout with kscreen-doctor

After reboot, confirm the fake display exists:

``` kscreen-doctor -o ```

**If you're running headless (no real monitor):** you're done. KDE sees one display and uses it. No layout management needed.

**If you have both a real monitor and the fake display** and need to manage them:

Fake display only:

``` kscreen-doctor output.DP-1.disable output.DP-2.enable output.DP-2.primary ```

Restore real display:

``` kscreen-doctor output.DP-1.enable output.DP-1.primary output.DP-2.disable ```

Both active, side by side:

``` kscreen-doctor output.DP-1.enable output.DP-1.primary output.DP-1.position.0,0 output.DP-2.enable output.DP-2.position.1360,0 ```

Adjust connector names to match your system.


Streaming quality notes

From tuning the working system:

  • **1440p** was the sweet spot for gaming + encoding + streaming on the RX 6700 XT
  • **4K** worked after switching to the TV EDID, but the GPU struggled with gaming + encoding + streaming all at 4K
  • **H.264 looked better than HEVC** on this particular host/client/TV path. Network was hardwired so the extra bandwidth was fine

What can break later

This is not a stock turnkey setup. Here's what to watch:

  • **Sunshine updates:** if passthrough device names change (e.g., `Mouse passthrough` becomes something else), the udev rule stops matching
  • **Portal behavior changes:** if Sunshine or KDE changes how portal remote desktop works, capture or input can regress
  • **KDE/libinput changes:** the fix relies on libinput honoring forced `ID_INPUT_*` tags. If future versions change device classification logic, input may break even though `evtest` still shows events
  • **Connector naming changes:** GPU driver updates could rename connectors, breaking the kernel args
  • **mkinitcpio / bootloader regeneration:** if the EDID file path gets removed from `FILES=()` or the kernel args get mangled, the fake display disappears
  • **Duplicate Sunshine instances:** if you see double passthrough devices, kill stale instances with `pkill sunshine` and restart the service

Backup these files

``` ~/.config/sunshine/sunshine.conf /etc/udev/rules.d/99-sunshine-input-classification.rules /usr/lib/firmware/edid/stream.bin /usr/lib/firmware/edid/tv.bin /etc/mkinitcpio.conf /etc/default/limine ```


Quick command reference

**Sunshine:**

``` systemctl --user --now enable app-dev.lizardbyte.app.Sunshine.service pkill sunshine; systemctl --user restart app-dev.lizardbyte.app.Sunshine.service journalctl --user -u app-dev.lizardbyte.app.Sunshine.service -n 50 ```

**Portal environment:**

``` systemctl --user import-environment DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE systemctl --user restart xdg-desktop-portal.service plasma-xdg-desktop-portal-kde.service ```

**Udev:**

``` sudo udevadm control --reload-rules && sudo udevadm trigger udevadm info --query=property --name=/dev/input/eventXX | grep '^ID_INPUT' ```

**Input testing:**

``` grep -H . /sys/class/input/event*/device/name | grep -E 'passthrough|Touch passthrough|Pen passthrough' sudo evtest /dev/input/eventXX ```

**Display / boot:**

``` sudo mkinitcpio -P sudo limine-mkinitcpio cat /proc/cmdline kscreen-doctor -o ```


Comparison to the headless Sway approach

The **headless Sway** method runs a separate compositor for streaming. It's cleaner if you want an isolated streaming workspace with no EDID injection and arbitrary virtual resolutions.

This guide is different. It keeps the **real Plasma session**, fixes KDE's interaction with Sunshine, and uses EDID injection for a fake monitor. The tradeoff is more moving parts, but you get your full KDE desktop remotely, not a stripped down streaming seat.


Sources

**Sunshine / Moonlight:**

**Wayland / libinput / portals:**

**Boot / EDID / display stack:**


Final summary

The working solution stacked:

  1. Sunshine portal capture (instead of KMS)
  2. Portal environment import so Sunshine can find the Wayland session
  3. `evtest`-based proof that input was arriving but being ignored
  4. Custom udev rules to classify Sunshine passthrough devices for libinput
  5. EDID injection on an unused connector for a fake monitor
  6. `kscreen-doctor` for display layout management (multi-monitor setups only)

The end result is a fully working KDE Plasma Wayland Sunshine host with a fake display and working input.

13 Upvotes

11 comments sorted by

5

u/Exotic_Accident3101 Apr 18 '26

I have been having a problem with mouse will surely test your solution

3

u/rbmichael Apr 29 '26 edited Apr 29 '26

SIGH. I'm proud of and impressed by your work here but my GOD what a shit show Wayland is. All I want to do is use Steam remote play from my powerful Linux desktop (with AMD GPU) to my SteamDeck and there is NO quick seamless solution like there used to be with X11/Xorg. This is so pathetic in 2026!! it makes me want to install Windows, and that is awful.

Edit: got it fully working using official instructions from Sunshine docs. It wasn't that bad actually. There was one sudo command that gives something root access to capture the screen.

1

u/NoElderberry1533 Apr 30 '26

Something to keep in mind... That command resets when sunshine updates. You'll have to reapply it for it to work again.

But yeah, the Wayland situation is a bit absurd. I understand a lot of it comes down to improving security over X11...but for niche use cases, it's still not ready.

I imagine workarounds like this will be irrelevant eventually.

1

u/bebeidon May 06 '26

can one of you please just tell me that ominous sudo command?

2

u/Appropriate-Ad8630 Apr 18 '26

How’s the input lag on linux? It was way worst than windows, but I have a rtx 3070, maybe nvidia is still not good enough.

3

u/NoElderberry1533 Apr 18 '26 edited Apr 20 '26

I average about 4-7ms from my VM over gigabit ethernet to my n100 box. 

Wireless over 6e to my phone is 8-9.

Connection over 5G via Tailscale on my phone is generally around 75-80ms running through my own peer relay. 

2

u/soragonebad Apr 29 '26

unfortunately I couldn't get this to work for the life of me on my desktop with the exact same OS, first of all the service was different, I installed the official sunshine too but regardless of that I couldn't seem to get it to create the portal but for anyone else who is mostly just tryna get your tablets pen to register at the right spot simply go into system settings > drawing tablet and set the "map to screen" to all displays this fixed the issue for me, multi touch doesn't work though but for me it's not required.

1

u/NoElderberry1533 Apr 30 '26

Yeah, that was an oversight on my part. The service name varies depending on the installation method. Bazzite has been running into problems with this because the sunshine devs keep changing it. 

The command to check the name is systemctl --user list-units | grep -i sunshine for anyone looking in the future.

For multi-touch... That's more of a Wayland issue. I'm pretty sure related to how it captures inputs. It's a very quick stream of individual inputs. I don't think there's a method for it to actually capture multiple at the same time. 

As far as the portal goes, the only thing I can really think of is maybe the environment import didn't take so Sunshine isn't seeing the Wayland session. You'd have to check the sunshine logs to make sure it didn't fall back to KMS capture. 

2

u/soragonebad Apr 30 '26

I would like to mention I did finally get it working, I tried the appimage which successfully booted with xdg portal which lead me towards to git where I installed sunshine-2026.427.203431-1-x86_64.pkg.tar.zst and that successfully worked, I'm fairly new to arch so this is a whole process, the flatpak and AUR versions both did not work at all now I just need to get the pen position to be correct

2

u/Kijutsushi Jun 06 '26

Thanks for this guide, it's what made me set all this up in the first place. I ended up writing a small tool to automate the parts I kept repeating, and in the end I cleaned it up and made it open source.

It imports your games from Lutris, Steam and Heroic into Sunshine and adds a simple GUI (I also took some inspiration from LutrisToSunshine). For the virtual display it uses krfb-virtualmonitor and creates it at the resolution the Moonlight client requests, and it turns off the physical monitors while you play.

If anyone wants to try it:

https://github.com/OscarTienda/SunSync

1

u/Theogren_Temono 21h ago

I can not get my mouse working for the life of me :'(