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
This commit is contained in:
Deploy Bot
2026-05-13 23:49:15 +01:00
commit 4d4b04d7bb
5 changed files with 628 additions and 0 deletions

340
README.md Normal file
View File

@@ -0,0 +1,340 @@
# RDtop — RustDesk Headless Display Setup
**Автоматический виртуальный дисплей для RustDesk на Intel iGPU без подключённого HDMI монитора.**
---
## Содержание
1. [Кратко о проблеме](#кратко-о-проблеме)
2. [Архитектура решения](#архитектура-решения)
3. [Что где лежит и куда ставить](#что-где-лежит-и-куда-ставить)
4. [Установка](#установка)
5. [Как это работает на самом деле](#как-это-работает-на-самом-деле)
6. [Проверка](#проверка)
7. [Troubleshooting](#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
bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
```
После установки **перезагрузить компьютер** или перезапустить GDM:
```bash
sudo systemctl restart gdm
```
### Способ 2: Вручную
```bash
# 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. Проверка:
```bash
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
```bash
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`
```bash
# Цикл каждые 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`:
```bash
# Установка:
sudo apt-get install xserver-xorg-video-dummy
# Конфигурация:
# Driver "dummy", Monitor, Screen → DUMMY-1
```
Однако на Intel i915 Alder Lake-N modesetting + VIRTUAL1 работает стабильнее.
---
## Проверка
```bash
# 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
```bash
# Добавляем 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:
```bash
xrandr --verbose | grep primary
# Должно быть: VIRTUAL1 connected primary 1920x1080+0+0
```
Если primary = HDMI1 — переключаем:
```bash
xrandr --output VIRTUAL1 --mode "1920x1080_60.00" --primary
xrandr --output HDMI1 --auto --same-as VIRTUAL1
```
### Сервис не запускается
```bash
# Проверь лог скрипта
cat ~/.local/logs/drm-hotplug.log
# Проверь конфиг юнита
systemctl --user cat hdmi-fallback.service
```
---
## Откат
```bash
# 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)"`