Files
RDtop/README.md
Deploy Bot 4d4b04d7bb feat: RDtop RustDesk headless fallback for Intel iGPU
- Adds VIRTUAL1 as permanent primary output via modesetting driver
- HDMI1 set as clone (--same-as) of VIRTUAL1
- hdmi-fallback.sh script monitors drm connector status
- systemd user unit auto-creates VIRTUAL1 and switches on hotplug loss
- Xorg fallback guarantees 1920x1080 framebuffer without HDMI
- Includes xserver-xorg-video-dummy as backup driver
2026-05-13 23:49:15 +01:00

14 KiB
Raw Blame History

RDtop — RustDesk Headless Display Setup

Автоматический виртуальный дисплей для RustDesk на Intel iGPU без подключённого HDMI монитора.


Содержание

  1. Кратко о проблеме
  2. Архитектура решения
  3. Что где лежит и куда ставить
  4. Установка
  5. Как это работает на самом деле
  6. Проверка
  7. Troubleshooting
  8. Откат
  9. Технические детали

Кратко о проблеме

Оборудование: Intel Alder Lake-N (i915), Ubuntu 24.04 (Noble), X11 (GDM/GNOME).

Что происходит

  1. При подключённом HDMI — Xorg инициализирует HDMI-1 на отдельный CRTC.
  2. Когда кабель отключают — CRTC уничтожается.
  3. У Xorg не остаётся активного выхода. Может остаться чёрный экран или fallback resolution 8×8.
  4. RustDesk всё ещё подключён, но захватывает Screen 0 — и видит либо чёрный экран, либо 8×8 пикселей.

Почему стандартные способы не помогли

Способ Результат
Option "VirtualHeads" "1" (intel driver) Не работает на Alder Lake-N: VIRTUAL1 создаётся, но intel driver не инициализирует на Gen11+.
modesetting driver Даёт VIRTUAL1 через randr, но через uAPI DRI он остаётся disconnected.
NoOutputInitialSize Требует пересборки xorg или патчей.
Dummy plug (аппаратный) Работает, но нужно покупать. Для программного режима — нет простого способа.

Архитектура решения

+-----------------------------------------------------------------------------+
|                    X11 Screen 0: 1920x1080                                   |
|                                                                             |
|   +------------------------+     +------------------------------------+     |
|   |   VIRTUAL1             |     |   HDMI1                            |     |
|   |   PRIMARY              |     |   CLONE (--same-as VIRTUAL1)     |     |
|   |   1920×1080 @ 60Hz     |     |   1920×1080 @ 60Hz               |     |
|   |   Full HD              |     |   Full HD (если кабель подключён) |     |
|   |   ВСЕГДА АКТИВЕН       |     |   При отключении → off            |     |
|   +------------------------+     +------------------------------------+     |
|                                                                             |
|   RustDesk: всегда захватывает PRIMARY (VIRTUAL1), вне зависимости          |
|   от того, подключён HDMI или нет.                                          |
+-----------------------------------------------------------------------------+

Ключевой принцип: VIRTUAL1 = primary. HDMI1 — это просто клон. Когда HDMI1 отключается, его CRTC удаляется, но primary остаётся — RustDesk всегда видит рабочий стол.


Что где лежит и куда ставить

Файл репозитория Назначение Системный путь
bin/hdmi-fallback.sh Мониторинг HDMI-A-1 в sysfs и автоматическое переключение на fallback ~/.local/bin/hdmi-fallback.sh
config/x11/90-fallback.conf Xorg конфиг: гарантирует framebuffer 1920×1080 даже при отключённом HDMI /etc/X11/xorg.conf.d/90-fallback.conf
config/systemd/hdmi-fallback.service systemd user service: запускает скрипт после входа в графическую сессию ~/.config/systemd/user/hdmi-fallback.service
install.sh Установщик: делает всё автоматически (запускается один раз)

Установка

Способ 1: Одной командой

bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"

После установки перезагрузить компьютер или перезапустить GDM:

sudo systemctl restart gdm

Способ 2: Вручную

# 1. Клонируем репозиторий
git clone https://git.softuniq.eu/NW/RDtop.git ~/RDtop
cd ~/RDtop

# 2. Устанавливаем dummy драйвер (опционально, запасной)
sudo apt-get update
sudo apt-get install -y xserver-xorg-video-dummy

# 3. Копируем X11 конфиг
sudo mkdir -p /etc/X11/xorg.conf.d
sudo cp config/x11/90-fallback.conf /etc/X11/xorg.conf.d/

# 4. Устанавливаем скрипт
mkdir -p ~/.local/bin
chmod +x bin/hdmi-fallback.sh
cp bin/hdmi-fallback.sh ~/.local/bin/

# 5. Устанавливаем systemd unit
mkdir -p ~/.config/systemd/user
cp config/systemd/hdmi-fallback.service ~/.config/systemd/user/

# 6. Активируем сервис
systemctl --user daemon-reload
systemctl --user enable --now hdmi-fallback.service

# 7. Перезагрузка (обязательно для X11)
sudo systemctl restart gdm

Как это работает на самом деле

Шаг 1. Xorg fallback конфиг (90-fallback.conf)

Section "Screen"
    Identifier "AutoScreen"
    ...
    SubSection "Display"
        Depth 24
        Virtual 1920 1080
    EndSubSection
EndSection

Параметр Virtual 1920 1080 задаёт размер виртуального framebuffer на уровне экрана X11. Даже если физических выхода нет, Xorg создаёт Screen фиксированного размера. Без этого при отключённом HDMI Xorg мог задать fallback resolution 8×8 или не инициализировать экран вовсе.

Шаг 2. Modesetting драйвер создаёт VIRTUAL1

Драйвер modesetting (встроен в xorg-server) на современных Intel GPU создаёт VIRTUAL1 как software output. Проверка:

xrandr | grep VIRTUAL
# VIRTUAL1 disconnected (normal left inverted right x axis y axis)
# VIRTUAL2 disconnected (normal left inverted right x axis y axis)

Это отдаётся через uAPI randr, но не через DRI.

Шаг 3. Мы активируем VIRTUAL1

xrandr --addmode VIRTUAL1 "1920x1080_60.00"
xrandr --output VIRTUAL1 --mode "1920x1080_60.00" --primary
xrandr --output HDMI1 --auto --same-as VIRTUAL1

--same-as означает клонирование. Обе точки вывода ссылаются на один и тот же framebuffer. Когда HDMI1 теряет кабель, его CRTC удаляется, но данные остаются на VIRTUAL1.

Шаг 4. Скрипт hdmi-fallback.sh

# Цикл каждые 2 секунды проверяет sysfs:
# /sys/class/drm/card1-HDMI-A-1/status
# connected   → HDMI1 = clone(VIRTUAL1)
# disconnected → HDMI1 = off, VIRTUAL1 = primary

Почему не через udev? drm_connector в ядре i915 не генерирует reliable hotplug events через sysfs. Поэтому используется polling каждые 2 сек. Это безопасно — sysfs read очень лёгкий.

Шаг 5. Dummy driver — запасной вариант

Если VIRTUAL1 по какой-то причине не создаётся (например, если modesetting не загружается), dummy_drv.so позволяет создать виртуальный выход через xf86-video-dummy:

# Установка:
sudo apt-get install xserver-xorg-video-dummy

# Конфигурация:
# Driver "dummy", Monitor, Screen → DUMMY-1

Однако на Intel i915 Alder Lake-N modesetting + VIRTUAL1 работает стабильнее.


Проверка

# 1. После перезагрузки — проверяем primary output
xrandr --listmonitors
# Должно быть:
#   0: +*VIRTUAL1 1920/508x1080/286+0+0  VIRTUAL1   [PRIMARY]
#   1: +HDMI1    1920/700x1080/390+0+0  HDMI1     [CLONE]

# 2. Проверяем сервис
systemctl --user status hdmi-fallback.service
# ● hdmi-fallback.service - HDMI to VIRTUAL1 Fallback Monitor
#    Active: active (running)

# 3. Смотрим лог
journalctl --user -u hdmi-fallback.service -f

# 4. Тест: вытаскиваем HDMI
# Через 2-3 секунды xrandr должен показывать только VIRTUAL1,
# при этом RustDesk продолжает работать.

Troubleshooting

VIRTUAL1 не появляется в xrandr

# Добавляем modeline вручную
xrandr --addmode VIRTUAL1 "1920x1080_60.00"
# Если ошибка — создаём моделайн:
xrandr --newmode "1920x1080_60.00" 173.00 1920 2048 2248 2576 1080 1083 1088 1120
xrandr --addmode VIRTUAL1 "1920x1080_60.00"

RustDesk всё ещё чёрный экран

Проверка primary:

xrandr --verbose | grep primary
# Должно быть: VIRTUAL1 connected primary 1920x1080+0+0

Если primary = HDMI1 — переключаем:

xrandr --output VIRTUAL1 --mode "1920x1080_60.00" --primary
xrandr --output HDMI1 --auto --same-as VIRTUAL1

Сервис не запускается

# Проверь лог скрипта
cat ~/.local/logs/drm-hotplug.log

# Проверь конфиг юнита
systemctl --user cat hdmi-fallback.service

Откат

# 1. Удалить конфиги X11
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf

# 2. Остановить и удалить сервис
systemctl --user disable --now hdmi-fallback.service
rm -f ~/.config/systemd/user/hdmi-fallback.service

# 3. Удалить скрипт
rm -f ~/.local/bin/hdmi-fallback.sh

# 4. Перезагрузка
sudo reboot

Технические детали

Железо

lspci | grep VGA
# 00:02.0 VGA compatible controller: Intel Corporation Alder Lake-N [UHD Graphics]

# Ядро: i915
cat /sys/class/drm/card1-HDMI-A-1/status
# connected (или disconnected)

# Только 1 CRTC:
cat /sys/kernel/debug/dri/*/i915_display_info | grep "CRTC"
# [CRTC:80:pipe A] — один pipe

Почему не intel драйвер?

Драйвер xf86-video-intel — legacy. Официально не рекомендуется для Gen 9 и новее (Broadwell+). На Alder Lake-N он вообще не инициализируется корректно. Ubuntu по умолчанию использует modesetting — и это правильно.

Почему именно клон, а не side-by-side?

HDMI1 right-of VIRTUAL1  → CRTC для HDMI1 используется отдельно
При отключении HDMI1      → CRTC освобождается
                            → если VIRTUAL1 не primary, Screen может схлопнуться

HDMI1 same-as VIRTUAL1    → Один framebuffer, два выхода
При отключении HDMI1      → Просто один output становится неактивным
                            → Primary (VIRTUAL1) никогда не пострадает

Dummy драйвер как fallback

На некоторых системах modesetting может не создать VIRTUAL1 (например, если DRI отключен). dummy_drv.so решает это:

Driver "dummy"
→ Создаёт DUMMY-1 с фиксированным разрешением
→ Всегда работает, независимо от GPU
→ Пакет: xserver-xorg-video-dummy

В нашем случае i915 Alder Lake-N — modesetting даёт VIRTUAL1, dummy не нужен, но мы его установили как запасной вариант.

Права доступа

/etc/X11/xorg.conf.d/90-fallback.conf — root:root 644
~/.local/bin/hdmi-fallback.sh       — swp:swp 755
~/.config/systemd/user/...          — swp:swp 644

Авторы и цели

  • Задача: Запуск RustDesk на headless машине (нет физического монитора) с Intel iGPU.
  • Ключевой инсайт: VIRTUAL1 создаётся modesetting driver'ом на Intel Alder Lake-N; нужно только сделать его primary и отслеживать HDMI.
  • Результат: При отключённом HDMI кабеле RustDesk продолжает работать через VIRTUAL1 (1920×1080, fullscreen).
  • Создано: через AI agent pipeline (Kilo Code Orchestrator) для SWP.

Ссылки

  • Репозиторий: https://git.softuniq.eu/NW/RDtop
  • Установщик: bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"