Create a virtual monitor on Linux (Wayland-friendly, no dummy plug) so you can stream your desktop to a tablet or phone with Sunshine + Moonlight while your physical monitors stay off.
It forces an unused graphics connector to appear "connected" with a fake EDID that you define (resolution, refresh rate, name), then switches your desktop to use only that monitor while you stream and restores your normal layout afterwards.
Status: developed and tested on one machine (Ubuntu, GNOME 50 on Wayland, NVIDIA RTX 3060 + AMD Vega iGPU, Sunshine → Samsung Galaxy Tab S6 Lite). Other setups are untested; see Compatibility. Reports and PRs welcome (CONTRIBUTING).
- Phantom connector. Kernel parameters
video=<CONNECTOR>:e drm.edid_firmware=<CONNECTOR>:edid/virtual-monitor.binmake the GPU driver treat a free connector as plugged in, with an EDID generated bylib/gen-edid.py(CVT reduced-blanking timings, no dependencies). The desktop sees an ordinary monitor. - Solo mode.
ondisables the physical monitors and keeps only the virtual one;offrestores the layout saved byon. Implemented for GNOME/mutter throughgdctl. - Sunshine. An app entry runs
onwhen a stream starts andoffwhen it ends. Sunshine captures via KMS.
Why the connector must be on a non-NVIDIA GPU: the proprietary NVIDIA driver ignores drm.edid_firmware.
Use a free connector on an AMD/Intel GPU (an iGPU is fine); Sunshine then encodes with VAAPI on that GPU.
If your only GPU is NVIDIA, use an HDMI/DP dummy plug instead (see Alternatives).
- A free (unplugged) connector on an
amdgpu/i915/xe/nouveauGPU. Check with./virtual-monitor.sh connectors. python3(3.8+),bash,sudo, a GRUB or manual bootloader entry, and an initrd generator (initramfs-tools, dracut or mkinitcpio).- For
on/off/login: GNOME withgdctl(shipped with recent mutter; GNOME 48+ to my knowledge). - Sunshine with KMS capture working (its package normally grants the needed capability) and VAAPI encoding
(
mesa-va-drivers/libvafor your GPU).
git clone <this repo> && cd virtual-monitor
./virtual-monitor.sh connectors # which connector will be used?
./virtual-monitor.sh profile tab-s6-lite # or: fullhd-60, ipad-pro-11, phone-landscape, or edit the config
sudo ./virtual-monitor.sh install # EDID + kernel params + initrd config
sudo reboot
./virtual-monitor.sh status # should list the virtual monitor and its mode
./virtual-monitor.sh sunshine # adds the "Virtual Monitor" app + KMS/VAAPI settings (backs up originals)
# restart Sunshine (the command prints the exact line), then launch "Virtual Monitor" from Moonlight
./virtual-monitor.sh login # GNOME/GDM only; see "Login screen"install prints and runs everything it changes; sudo ./virtual-monitor.sh uninstall removes it all.
config/virtual-monitor.conf is your active config (git-ignored). It is created from the documented template
config/virtual-monitor.conf.example on first run, or replaced by a preset with
./virtual-monitor.sh profile <name>. Copy a file from profiles/ to add a device.
| Variable | Meaning |
|---|---|
CONNECTOR |
Connector to fake, e.g. DP-4, or auto (first unplugged DP, then HDMI, then others, never NVIDIA/eDP) |
WIDTH HEIGHT REFRESH |
Native resolution and refresh rate of the client device |
EXTRA_MODE |
Optional second mode, 1920x1080@60; empty to disable |
DIAGONAL_INCH |
Physical size hint for desktop scaling |
NAME |
EDID product name (≤13 chars, A-Za-z0-9_-); used to find the monitor again |
VENDOR |
3-letter EDID maker id. GNOME shows "maker + inches" (e.g. VMN 11"): a registered code in /usr/share/hwdata/pnp.ids shows its company name (LNX = The Linux Foundation), an unregistered one shows the raw letters. No free-text label exists |
SCALE |
Desktop scale applied to the virtual monitor in solo mode (1, 1.25, 1.5, 2) |
Change a value, then sudo ./virtual-monitor.sh install and reboot — or sudo ./virtual-monitor.sh reload to
re-apply the EDID live through debugfs (best effort, untested on most drivers). In Moonlight, request the same
resolution so nothing is rescaled. If you change VENDOR, re-run login: GNOME keys saved layouts by maker.
| Command | Root | Does |
|---|---|---|
connectors |
no | List DRM connectors, state, driver, and the resolved CONNECTOR |
profile NAME |
no | Activate a profile from profiles/ |
gen |
no | Build the EDID into build/ and show how GNOME will label it |
kargs |
no | Print the kernel parameters install would add (for manual bootloaders) |
install / uninstall |
yes | Add / remove EDID, kernel parameters and initrd config |
reload |
yes | Apply a changed EDID without rebooting (best effort) |
on / off |
no | Solo mode / restore the saved layout (needs a graphical GNOME session, not a tty) |
login |
sudo | Persist a layout without the virtual monitor and copy it to GDM |
status |
no | Show configuration, kernel parameters and what the desktop sees |
sunshine / sunshine-restore |
no | Add the app + KMS/VAAPI settings / restore the originals |
Only new files where possible (all removed by uninstall):
/usr/lib/firmware/edid/virtual-monitor.bin- Debian/Ubuntu:
/etc/default/grub.d/99-virtual-monitor.cfg. Other GRUB distros: a marked block appended to/etc/default/grub(backup at/etc/default/grub.vm-backup). No GRUB: it prints the parameters for you to add. - Initrd, because GPU drivers load before the root filesystem:
/etc/initramfs-tools/hooks/virtual-monitor-edid,/etc/dracut.conf.d/99-virtual-monitor.confand/or/etc/mkinitcpio.conf.d/virtual-monitor.conf, depending on what is installed, then the matching regeneration command. On Ubuntu with dracut,update-initramfsmay call dracut: check withsudo lsinitrd /boot/initrd.img-$(uname -r) | grep edid.
Boot entries for recovery don't carry the parameters, so they are a clean fallback. If a boot goes wrong, edit the
GRUB entry (e), delete video=...:e drm.edid_firmware=..., boot with F10, then run uninstall.
With three monitors connected, GDM has no saved layout and may put the login prompt on the virtual one (physical
monitors look grey), and GNOME may extend your desktop onto it. Run ./virtual-monitor.sh login once, with your
physical monitors active (not while streaming). It persists a layout with the virtual monitor disabled in
~/.config/monitors.xml and copies it to GDM's monitors.xml. Undo: delete ~gdm/.config/monitors.xml
(/var/lib/gdm3/.config/monitors.xml on Debian/Ubuntu). Other display managers are not handled; if you get a blank login,
type your credentials blind (Enter, password, Enter).
sunshinesetscapture = kms,encoder = vaapiandadapter_nameto the render node of the GPU that owns the connector, and adds an app whoseprep-cmdrunsvirtual-monitor.sh on/off. Originals are kept once as*.orignext to the config;sunshine-restoreputs them back. Flatpak Sunshine config is detected too.- If Sunshine captures the wrong display, set
output_nameinsunshine.conf(check the log for detected displays). - VAAPI AV1 is not available on many iGPUs; the
av1_vaapierror in the log is harmless, H.264/HEVC are used. - With no physical monitor active, the desktop may lock or suspend when idle: disable automatic suspend while streaming.
| Component | Tested | Expected / notes |
|---|---|---|
| Distro | Ubuntu (dracut + GRUB, kernel 7.0) | Fedora/Arch paths are implemented but untested |
| Desktop | GNOME 50, Wayland | on/off/login are GNOME-only (see below) |
| GPU for the connector | AMD (amdgpu) |
Intel (i915/xe) should work; NVIDIA proprietary cannot |
| Streaming | Sunshine + Moonlight (Android tablet) | |
| Bootloader | GRUB | systemd-boot/others: use kargs and add manually |
The phantom connector works with any compositor that reads DRM connectors. Only the solo-mode switching is
GNOME-specific (lib/gnome-layout.py). For KDE (kscreen-doctor), Sway/Hyprland (wlr-randr/hyprctl) or X11
(xrandr) you can still run ./virtual-monitor.sh install, then disable your physical outputs with your desktop's
tool, or contribute a backend (see CONTRIBUTING).
- Dummy plug (HDMI/DP, a few euros) in a free port: no kernel changes, works on NVIDIA (so you can use NVENC), but fixed resolutions.
- NVIDIA on X11:
CustomEDIDinxorg.confwith a connected-monitor override, not covered here. - Software virtual displays (e.g. evdi, or Sunshine's Windows virtual display driver) are separate projects.
python3 -m unittest discover -s tests -vMIT, see LICENSE.