feat: switch host from intel VirtualHeads to dummy driver for headless boot
- Add 20-dummy-headless.conf (Driver dummy, 1920x1080) - Remove 20-intel-virtual.conf (intel driver required HDMI at boot) - Update install.sh: installs dummy driver, saves EDID, updates GRUB - Update README: document dummy driver + kernel EDID fallback approach - hdmi-fallback.sh now detects DUMMY0 or VIRTUAL1 Rationale: intel VirtualHeads only worked when HDMI was connected at boot. Dummy driver creates DUMMY0 unconditionally, working with or without cable. Kernel EDID fallback (video=... drm.edid_firmware=...) is insurance if dummy fails.
This commit is contained in:
148
README.md
148
README.md
@@ -1,6 +1,6 @@
|
||||
# RDtop — RustDesk Headless Display Setup
|
||||
|
||||
**Автоматический виртуальный дисплей для RustDesk без подключенного HDMI монитора. Работает на Intel iGPU и CPU-only VPS.**
|
||||
**Автоматический виртуальный дисплей для RustDesk без подключенного HDMI монитора. Работает на любой Linux машине (Intel iGPU, AMD, NVIDIA, CPU-only VPS).**
|
||||
|
||||
---
|
||||
|
||||
@@ -10,8 +10,6 @@
|
||||
2. [Решение](#решение)
|
||||
3. [Файлы проекта](#файлы-проекта)
|
||||
4. [Установка](#установка)
|
||||
- [Вариант А: Десктоп с Intel iGPU](#вариант-а-десктоп-с-intel-igpu)
|
||||
- [Вариант Б: CPU-only VPS](#вариант-б-cpu-only-vps)
|
||||
5. [Как это работает](#как-это-работает)
|
||||
6. [Проверка](#проверка)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
@@ -34,8 +32,8 @@
|
||||
|
||||
| Способ | Результат |
|
||||
|--------|-----------|
|
||||
| `Option "VirtualHeads" "1"` (intel driver) | **Работает!** Но требует `Driver "intel"` + `VirtualHeads` в `xorg.conf`. |
|
||||
| modesetting driver | Создает VIRTUAL1 через randr, но после reboot пропадает без `intel` config. |
|
||||
| `Option "VirtualHeads" "1"` (intel driver) | **Работает только с кабелем на момент загрузки.** Без HDMI — VIRTUAL1 не создается. |
|
||||
| modesetting driver | Создает VIRTUAL1 через randr, но после reboot пропадает. |
|
||||
| `NoOutputInitialSize` | Требует пересборки xorg или патчей. |
|
||||
| Dummy plug (аппаратный) | Работает, но нужно покупать. |
|
||||
|
||||
@@ -43,24 +41,37 @@
|
||||
|
||||
## Решение
|
||||
|
||||
### Хост (Intel iGPU): VIRTUAL1 = primary, HDMI1 = clone
|
||||
### Хост: Dummy driver + kernel EDID fallback
|
||||
|
||||
Работает **без кабеля HDMI на момент загрузки** — dummy driver не зависит от физических выходов.
|
||||
|
||||
```
|
||||
+------------------------------------------------------------------------+
|
||||
| X11 Screen 0: 1920x1080 |
|
||||
| Xorg Dummy Driver (1920×1080) |
|
||||
| +-------------------------+ |
|
||||
| | DUMMY0 = primary | ← RustDesk --server захватывает |
|
||||
| | 1920×1080 @ 60Hz | |
|
||||
| +-------------------------+ |
|
||||
| |
|
||||
| +-------------------------+ +------------------------------------+ |
|
||||
| | VIRTUAL1 | | HDMI1 | |
|
||||
| | [PRIMARY] 1920×1080 | | [CLONE --same-as VIRTUAL1] | |
|
||||
| | Всегда активен | | При отключении → off | |
|
||||
| +-------------------------+ +------------------------------------+ |
|
||||
| |
|
||||
| RustDesk: всегда захватывает PRIMARY (VIRTUAL1) |
|
||||
| При подключении HDMI кабеля: |
|
||||
| скрипт hdmi-fallback.sh клонирует HDMI1 на DUMMY0 |
|
||||
| При отключении: HDMI1 off, DUMMY0 остается primary |
|
||||
+------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
### Kernel EDID fallback (дополнительно)
|
||||
|
||||
Параметры ядра:
|
||||
```
|
||||
video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin
|
||||
```
|
||||
|
||||
Это заставляет ядро `i915` инициализировать HDMI-A-1 с сохраненным EDID даже без кабеля. Если dummy driver не сработает (редкий случай), ядро все равно создаст виртуальный framebuffer.
|
||||
|
||||
### VPS (CPU-only): Dummy driver + Xorg + RustDesk
|
||||
|
||||
Тот же dummy driver, но без GPU. Работает на любом VPS.
|
||||
|
||||
```
|
||||
+------------------------------------------------------------------------+
|
||||
| Xorg Dummy Driver (1920×1080) |
|
||||
@@ -78,10 +89,10 @@
|
||||
|
||||
| Файл репозитория | Назначение | Системный путь |
|
||||
|------------------|------------|----------------|
|
||||
| `bin/hdmi-fallback.sh` | Мониторинг HDMI-A-1 и авто-fallback на VIRTUAL1 | `~/.local/bin/hdmi-fallback.sh` |
|
||||
| `bin/hdmi-fallback.sh` | Мониторинг HDMI-A-1 и авто-fallback на DUMMY0/VIRTUAL1 | `~/.local/bin/hdmi-fallback.sh` |
|
||||
| `bin/vps-start-xorg.sh` | Стартер Xorg с dummy driver на VPS | `/usr/local/bin/start-xorg-dummy.sh` |
|
||||
| `config/x11/90-fallback.conf` | Xorg fallback: Virtual 1920 1080 для modesetting | `/etc/X11/xorg.conf.d/90-fallback.conf` |
|
||||
| `config/x11-host/20-intel-virtual.conf` | Intel driver + VirtualHeads=1 | `/etc/X11/xorg.conf.d/20-intel-virtual.conf` |
|
||||
| `config/x11-host/20-dummy-headless.conf` | Dummy driver headless config | `/etc/X11/xorg.conf.d/20-dummy-headless.conf` |
|
||||
| `config/x11-host/20-intel-virtual.conf.bak` | Backup старого Intel config (не используется) | — |
|
||||
| `config/systemd/hdmi-fallback.service` | systemd user unit (host) | `~/.config/systemd/user/hdmi-fallback.service` |
|
||||
| `config/vps/xorg-dummy.service` | systemd system unit (VPS Xorg) | `/etc/systemd/system/xorg-dummy.service` |
|
||||
| `config/vps/rustdesk-dummy.service` | systemd system unit (VPS RustDesk) | `/etc/systemd/system/rustdesk-dummy.service` |
|
||||
@@ -91,15 +102,15 @@
|
||||
|
||||
## Установка
|
||||
|
||||
### Вариант А: Десктоп с Intel iGPU
|
||||
### Хост (Desktop с GPU)
|
||||
|
||||
```bash
|
||||
bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
|
||||
```
|
||||
|
||||
После установки **перезагрузить компьютер** или перезапустить GDM:
|
||||
После установки **перезагрузить компьютер**:
|
||||
```bash
|
||||
sudo systemctl restart gdm
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
**Вручную:**
|
||||
@@ -107,24 +118,33 @@ sudo systemctl restart gdm
|
||||
# 1. Клонируем
|
||||
git clone https://git.softuniq.eu/NW/RDtop.git ~/RDtop && cd ~/RDtop
|
||||
|
||||
# 2. Intel driver + VirtualHeads
|
||||
sudo cp config/x11-host/20-intel-virtual.conf /etc/X11/xorg.conf.d/
|
||||
# 2. Dummy driver config (главный способ)
|
||||
sudo cp config/x11-host/20-dummy-headless.conf /etc/X11/xorg.conf.d/
|
||||
|
||||
# 3. Xorg fallback (резерв)
|
||||
sudo cp config/x11/90-fallback.conf /etc/X11/xorg.conf.d/
|
||||
# 3. Удалить старые конфиги если были
|
||||
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
|
||||
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
|
||||
|
||||
# 4. Скрипт мониторинга
|
||||
# 4. Сохранить EDID для kernel fallback (опционально)
|
||||
sudo mkdir -p /lib/firmware/edid
|
||||
sudo cp /sys/class/drm/card0-HDMI-A-1/edid /lib/firmware/edid/samsung.bin
|
||||
|
||||
# 5. Обновить GRUB для kernel EDID fallback
|
||||
sudo sed -i 's|GRUB_CMDLINE_LINUX_DEFAULT=".*"|GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin"|' /etc/default/grub
|
||||
sudo update-grub
|
||||
|
||||
# 6. Скрипт мониторинга
|
||||
chmod +x bin/hdmi-fallback.sh
|
||||
cp bin/hdmi-fallback.sh ~/.local/bin/
|
||||
|
||||
# 5. systemd unit
|
||||
# 7. systemd unit
|
||||
mkdir -p ~/.config/systemd/user
|
||||
cp config/systemd/hdmi-fallback.service ~/.config/systemd/user/
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now hdmi-fallback.service
|
||||
|
||||
# 6. Перезагрузка
|
||||
sudo systemctl restart gdm
|
||||
# 8. Перезагрузка
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
### Вариант Б: CPU-only VPS
|
||||
@@ -159,13 +179,13 @@ xrandr --listmonitors
|
||||
|
||||
### Хост (Intel iGPU)
|
||||
|
||||
1. **Xorg конфиг** `20-intel-virtual.conf` указывает `Driver "intel"` + `Option "VirtualHeads" "1"`.
|
||||
2. **Intel driver** создает VIRTUAL1 как software output (проверено на Alder Lake-N).
|
||||
1. **Dummy driver** (`Driver "dummy"`) создает DUMMY0 сразу при старте Xorg, **независимо от HDMI**.
|
||||
2. **Kernel EDID fallback** (`drm.edid_firmware`) — если dummy каким-то образом не сработает, ядро `i915` инициализирует HDMI-A-1 с сохраненным EDID.
|
||||
3. **Скрипт** `hdmi-fallback.sh` при старте:
|
||||
- Всегда активирует VIRTUAL1 как primary
|
||||
- Всегда активирует DUMMY0 как primary
|
||||
- Если HDMI connected — делает HDMI1 clone (`--same-as`)
|
||||
- Если HDMI disconnected — выключает HDMI1, VIRTUAL1 остается primary
|
||||
4. **RustDesk** захватывает VIRTUAL1 (primary), а не HDMI1.
|
||||
- Если HDMI disconnected — выключает HDMI1, DUMMY0 остается primary
|
||||
4. **RustDesk** захватывает DUMMY0 (primary).
|
||||
|
||||
### VPS (CPU-only)
|
||||
|
||||
@@ -178,18 +198,27 @@ xrandr --listmonitors
|
||||
|
||||
## Проверка
|
||||
|
||||
### Хост
|
||||
### Хост (после reboot без HDMI)
|
||||
```bash
|
||||
# После перезагрузки
|
||||
# Должно показать DUMMY0 как primary
|
||||
xrandr --listmonitors
|
||||
# Должно быть:
|
||||
# 0: +*VIRTUAL1 1920x... VIRTUAL1 [PRIMARY]
|
||||
# 1: +HDMI1 1920x... HDMI1 [CLONE]
|
||||
# Ожидаемый вывод:
|
||||
# 0: +*DUMMY0 1920/508x1080/286+0+0 DUMMY0 [PRIMARY]
|
||||
|
||||
# Сервис
|
||||
# Если HDMI подключен:
|
||||
# 1: +HDMI-1 1920/508x1080/286+0+0 HDMI-1 [CLONE]
|
||||
```
|
||||
|
||||
### Сервис
|
||||
```bash
|
||||
systemctl --user status hdmi-fallback.service
|
||||
```
|
||||
|
||||
### Логи
|
||||
```bash
|
||||
cat ~/.local/logs/drm-hotplug.log
|
||||
```
|
||||
|
||||
### VPS
|
||||
```bash
|
||||
export DISPLAY=:0
|
||||
@@ -199,23 +228,25 @@ xrandr --listmonitors
|
||||
|
||||
# RustDesk
|
||||
ps aux | grep rustdesk
|
||||
# /usr/lib/rustdesk/rustdesk --server должен быть запущен
|
||||
# /usr/lib/rustdesk/rustdesk --server должен быть запущен
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### VIRTUAL1 не появляется после reboot
|
||||
### DUMMY0 не появляется после reboot
|
||||
|
||||
```bash
|
||||
# Проверить intel driver:
|
||||
grep "Loading.*intel" /var/log/Xorg.0.log
|
||||
# Должно быть: (II) Loading /usr/lib/xorg/modules/drivers/intel_drv.so
|
||||
# Проверить dummy driver:
|
||||
grep "Loading.*dummy" /var/log/Xorg.0.log
|
||||
# Должно быть: (II) Loading /usr/lib/xorg/modules/drivers/dummy_drv.so
|
||||
|
||||
# Если modesetting загружается вместо intel:
|
||||
# Убедиться что 20-intel-virtual.conf существует:
|
||||
ls /etc/X11/xorg.conf.d/20-intel-virtual.conf
|
||||
# Проверить конфиг:
|
||||
ls /etc/X11/xorg.conf.d/20-dummy-headless.conf
|
||||
# Должен существовать
|
||||
|
||||
# Если intel driver загружается вместо dummy:
|
||||
# Убедиться что нет других конфликтующих xorg.conf:
|
||||
ls /etc/X11/xorg.conf 2>/dev/null || echo "OK: no xorg.conf"
|
||||
```
|
||||
@@ -225,11 +256,11 @@ ls /etc/X11/xorg.conf 2>/dev/null || echo "OK: no xorg.conf"
|
||||
```bash
|
||||
# Проверить primary:
|
||||
xrandr --verbose | grep primary
|
||||
# Должно быть: VIRTUAL1 connected primary (или DUMMY0)
|
||||
# Должно быть: DUMMY0 connected primary
|
||||
|
||||
# Если primary = HDMI1 — переключить:
|
||||
xrandr --output VIRTUAL1 --mode "1920x1080_60.00" --primary
|
||||
xrandr --output HDMI1 --auto --same-as VIRTUAL1
|
||||
# Если primary не DUMMY0:
|
||||
xrandr --output DUMMY0 --mode "1920x1080" --primary
|
||||
xrandr --output HDMI-1 --auto --same-as DUMMY0
|
||||
```
|
||||
|
||||
### VPS: RustDesk не видит DUMMY0
|
||||
@@ -249,11 +280,16 @@ ls /tmp/.X11-unix/X0
|
||||
|
||||
```bash
|
||||
# Хост
|
||||
sudo rm -f /etc/X11/xorg.conf.d/20-dummy-headless.conf
|
||||
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
|
||||
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
|
||||
systemctl --user disable --now hdmi-fallback.service
|
||||
rm -f ~/.local/bin/hdmi-fallback.sh
|
||||
|
||||
# Вернуть стандартный GRUB:
|
||||
sudo sed -i 's|GRUB_CMDLINE_LINUX_DEFAULT=".*"|GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"|' /etc/default/grub
|
||||
sudo update-grub
|
||||
|
||||
# VPS
|
||||
systemctl disable --now xorg-dummy.service
|
||||
systemctl disable --now rustdesk-dummy.service
|
||||
@@ -265,13 +301,17 @@ sudo rm -f /etc/systemd/system/rustdesk-dummy.service
|
||||
|
||||
## Технические детали
|
||||
|
||||
### Почему intel driver, а не modesetting?
|
||||
### Почему dummy driver, а не intel с VirtualHeads?
|
||||
|
||||
На Alder Lake-N (46d2) modesetting driver создает VIRTUAL1 только при определенных условиях и не гарантирует его после reboot. Intel driver с `VirtualHeads=1` создает VIRTUAL1 стабильно.
|
||||
`intel` driver + `VirtualHeads=1` работает **только если HDMI подключен при загрузке**. Без кабеля — ядро не создает connector/CRTC для HDMI-A-1, и `VirtualHeads` не помогает. Dummy driver не зависит от физических выходов.
|
||||
|
||||
### Почему kernel EDID fallback?
|
||||
|
||||
Параметры `video=HDMI-A-1:1920x1080@60` и `drm.edid_firmware=HDMI-A-1:edid/samsung.bin` заставляют ядро `i915` инициализировать connector с фиктивным EDID. Это "страховка": если dummy driver по какой-то причине не загрузится (например, обновление Xorg удалит его), ядро все равно создаст выход.
|
||||
|
||||
### Почему клон, а не extended desktop?
|
||||
|
||||
`--same-as` клонирует framebuffer. При disconnect HDMI clone просто перестает рендериться, но primary (VIRTUAL1) не затрагивается — Xorg не теряет framebuffer.
|
||||
`--same-as` клонирует framebuffer. При disconnect HDMI clone просто перестает рендериться, но primary (DUMMY0) не затрагивается — Xorg не теряет framebuffer.
|
||||
|
||||
### Dummy driver на VPS
|
||||
|
||||
|
||||
23
config/x11-host/20-dummy-headless.conf
Normal file
23
config/x11-host/20-dummy-headless.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
Section "Device"
|
||||
Identifier "DummyHeadless"
|
||||
Driver "dummy"
|
||||
Option "ConstantDPI" "true"
|
||||
VideoRam 256000
|
||||
EndSection
|
||||
|
||||
Section "Monitor"
|
||||
Identifier "DummyMonitor"
|
||||
HorizSync 28-80
|
||||
VertRefresh 48-75
|
||||
EndSection
|
||||
|
||||
Section "Screen"
|
||||
Identifier "DummyScreen"
|
||||
Device "DummyHeadless"
|
||||
Monitor "DummyMonitor"
|
||||
DefaultDepth 24
|
||||
SubSection "Display"
|
||||
Depth 24
|
||||
Modes "1920x1080" "1280x720"
|
||||
EndSubSection
|
||||
EndSection
|
||||
@@ -1,5 +0,0 @@
|
||||
Section "Device"
|
||||
Identifier "Intel Graphics"
|
||||
Driver "intel"
|
||||
Option "VirtualHeads" "1"
|
||||
EndSection
|
||||
9
config/x11-host/20-intel-virtual.conf.bak
Normal file
9
config/x11-host/20-intel-virtual.conf.bak
Normal file
@@ -0,0 +1,9 @@
|
||||
# BACKUP of original Intel VirtualHeads config
|
||||
# Disabled in favour of dummy headless config (20-dummy-headless.conf)
|
||||
# which works without HDMI connected at boot.
|
||||
#
|
||||
# Section "Device"
|
||||
# Identifier "Intel Graphics"
|
||||
# Driver "intel"
|
||||
# Option "VirtualHeads" "1"
|
||||
# EndSection
|
||||
219
install.sh
219
install.sh
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# RDtop Installer — RustDesk headless display setup
|
||||
# RDtop Installer — RustDesk headless display setup (dummy driver)
|
||||
# Usage: bash -c "$(curl -fsSL https://git.softuniq.eu/NW/RDtop/raw/branch/main/install.sh)"
|
||||
set -euo pipefail
|
||||
|
||||
@@ -8,9 +8,10 @@ echo "Installing RustDesk headless display fallback..."
|
||||
|
||||
# Config paths
|
||||
REPO_URL="https://git.softuniq.eu/NW/RDtop"
|
||||
X11_CONF="/etc/X11/xorg.conf.d/90-fallback.conf"
|
||||
X11_CONF="/etc/X11/xorg.conf.d/20-dummy-headless.conf"
|
||||
BIN_DST="$HOME/.local/bin/hdmi-fallback.sh"
|
||||
SYSTEMD_DST="$HOME/.config/systemd/user/hdmi-fallback.service"
|
||||
EDID_DST="/lib/firmware/edid/samsung.bin"
|
||||
LOG="$HOME/.local/logs/rdtop-install.log"
|
||||
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
@@ -24,130 +25,203 @@ if [ "$EUID" -eq 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! xset q >/dev/null 2>&1; then
|
||||
log "WARNING: No X11 display found. You need to run this from a graphical session."
|
||||
# --- Check sudo works ---
|
||||
if ! sudo -n true 2>/dev/null; then
|
||||
echo "This script requires sudo privileges. Please enter your password when prompted."
|
||||
fi
|
||||
|
||||
# --- Check if dummy driver exists or can be installed ---
|
||||
# --- Install dummy driver ---
|
||||
if [ -f /usr/lib/xorg/modules/drivers/dummy_drv.so ]; then
|
||||
log "Dummy driver already present"
|
||||
else
|
||||
log "Dummy driver not found. Will try to install xserver-xorg-video-dummy..."
|
||||
log "Installing xserver-xorg-video-dummy..."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq xserver-xorg-video-dummy || log "WARNING: apt failed, dummy not installed (VIRTUAL1 should still work)"
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq xserver-xorg-video-dummy || true
|
||||
else
|
||||
log "WARNING: apt-get not found, skipping dummy driver install"
|
||||
log "WARNING: apt-get not found, cannot install dummy driver"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# --- Create X11 fallback config ---
|
||||
log "Creating X11 fallback config at $X11_CONF"
|
||||
echo "retrowest" | sudo -S bash -c 'cat > /etc/X11/xorg.conf.d/90-fallback.conf' <<'EOF'
|
||||
# Fallback Xorg config: guarantee a framebuffer even without HDMI
|
||||
# --- Remove old conflicting configs ---
|
||||
log "Removing old Intel X11 configs"
|
||||
sudo rm -f /etc/X11/xorg.conf.d/20-intel-virtual.conf
|
||||
sudo rm -f /etc/X11/xorg.conf.d/90-fallback.conf
|
||||
|
||||
# --- Copy EDID firmware ---
|
||||
log "Saving EDID firmware"
|
||||
if [ -f /sys/class/drm/card0-HDMI-A-1/edid ]; then
|
||||
sudo mkdir -p /lib/firmware/edid
|
||||
sudo cp /sys/class/drm/card0-HDMI-A-1/edid "$EDID_DST"
|
||||
sudo chmod 644 "$EDID_DST"
|
||||
log "EDID saved to $EDID_DST"
|
||||
else
|
||||
log "WARNING: No EDID found at /sys/class/drm/card0-HDMI-A-1/edid"
|
||||
fi
|
||||
|
||||
# --- Create dummy headless X11 config ---
|
||||
log "Creating dummy headless X11 config at $X11_CONF"
|
||||
cat <<'XORG' | sudo tee "$X11_CONF" >/dev/null
|
||||
Section "Device"
|
||||
Identifier "AutoDevice"
|
||||
Driver "modesetting"
|
||||
Identifier "DummyHeadless"
|
||||
Driver "dummy"
|
||||
Option "ConstantDPI" "true"
|
||||
VideoRam 256000
|
||||
EndSection
|
||||
|
||||
Section "Monitor"
|
||||
Identifier "AutoMonitor"
|
||||
Option "DPMS" "false"
|
||||
Identifier "DummyMonitor"
|
||||
HorizSync 28-80
|
||||
VertRefresh 48-75
|
||||
EndSection
|
||||
|
||||
Section "Screen"
|
||||
Identifier "AutoScreen"
|
||||
Device "AutoDevice"
|
||||
Monitor "AutoMonitor"
|
||||
Identifier "DummyScreen"
|
||||
Device "DummyHeadless"
|
||||
Monitor "DummyMonitor"
|
||||
DefaultDepth 24
|
||||
SubSection "Display"
|
||||
Depth 24
|
||||
Virtual 1920 1080
|
||||
Modes "1920x1080"
|
||||
Modes "1920x1080" "1280x720"
|
||||
EndSubSection
|
||||
EndSection
|
||||
EOF
|
||||
|
||||
sudo chmod 644 /etc/X11/xorg.conf.d/90-fallback.conf
|
||||
XORG
|
||||
sudo chmod 644 "$X11_CONF"
|
||||
log "X11 config installed"
|
||||
|
||||
# --- Update GRUB for kernel EDID fallback ---
|
||||
log "Updating GRUB cmdline for EDID fallback"
|
||||
if [ -f /etc/default/grub ]; then
|
||||
sudo sed -i 's|GRUB_CMDLINE_LINUX_DEFAULT=".*"|GRUB_CMDLINE_LINUX_DEFAULT="quiet splash video=HDMI-A-1:1920x1080@60 drm.edid_firmware=HDMI-A-1:edid/samsung.bin"|' /etc/default/grub
|
||||
sudo update-grub >/dev/null 2>&1 || log "WARNING: update-grub failed"
|
||||
log "GRUB updated"
|
||||
else
|
||||
log "WARNING: /etc/default/grub not found"
|
||||
fi
|
||||
|
||||
# --- Install fallback script ---
|
||||
log "Installing hdmi-fallback.sh to ~/.local/bin/"
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
cat > "$BIN_DST" <<'SCRIPT'
|
||||
#!/bin/bash
|
||||
# Universal HDMI Fallback: DUMMY0 (dummy driver) or VIRTUAL1 (intel)
|
||||
# Works both with HDMI connected and disconnected at boot
|
||||
|
||||
LOG="$HOME/.local/logs/drm-hotplug.log"
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
mkdir -p "$HOME/.local/logs"
|
||||
export DISPLAY=:0
|
||||
|
||||
log() {
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG" >&1
|
||||
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" | tee -a "$LOG" >/dev/null
|
||||
}
|
||||
|
||||
log "=== Starting VIRTUAL1-primary fallback monitor ==="
|
||||
|
||||
MODE="1920x1080_60.00"
|
||||
if ! xrandr 2>/dev/null | grep -q "$MODE"; then
|
||||
xrandr --newmode "$MODE" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 2>/dev/null || true
|
||||
fi
|
||||
xrandr --addmode VIRTUAL1 "$MODE" 2>/dev/null || true
|
||||
|
||||
setup_virtual_primary() {
|
||||
log "Setting up VIRTUAL1 as primary..."
|
||||
xrandr --output VIRTUAL1 --mode "$MODE" --primary 2>&1 && log "VIRTUAL1 primary OK" || log "VIRTUAL1 primary failed"
|
||||
# Find available primary output (DUMMY0 or VIRTUAL1)
|
||||
find_primary_output() {
|
||||
if xrandr 2>/dev/null | grep -q "^DUMMY0"; then
|
||||
echo "DUMMY0"
|
||||
elif xrandr 2>/dev/null | grep -q "^VIRTUAL1"; then
|
||||
echo "VIRTUAL1"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
enable_hdmi_clone() {
|
||||
log "Enabling HDMI1 as clone of VIRTUAL1"
|
||||
xrandr --output HDMI1 --auto --same-as VIRTUAL1 2>&1 && log "HDMI1 cloned OK" || log "HDMI1 clone failed"
|
||||
# Find HDMI output name (HDMI-1, HDMI1, etc)
|
||||
find_hdmi_output() {
|
||||
xrandr 2>/dev/null | grep -E "^HDMI-[0-9] connected" | awk '{print $1}' | head -1
|
||||
}
|
||||
|
||||
disable_hdmi() {
|
||||
log "HDMI1 disconnected, keeping VIRTUAL1 primary"
|
||||
xrandr --output HDMI1 --off 2>&1 && log "HDMI1 off OK" || true
|
||||
# Set up mode for virtual output
|
||||
setup_mode() {
|
||||
local OUTPUT="$1"
|
||||
local MODE_NAME="1920x1080"
|
||||
|
||||
if [ "$OUTPUT" = "VIRTUAL1" ]; then
|
||||
local MODELINE="1920x1080_60.00"
|
||||
if ! xrandr 2>/dev/null | grep -q "$MODELINE"; then
|
||||
xrandr --newmode "$MODELINE" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 2>/dev/null || true
|
||||
fi
|
||||
xrandr --addmode VIRTUAL1 "$MODELINE" 2>/dev/null || true
|
||||
MODE_NAME="$MODELINE"
|
||||
fi
|
||||
echo "$MODE_NAME"
|
||||
}
|
||||
|
||||
log "Start monitor (user=$USER, display=$DISPLAY)"
|
||||
|
||||
CONNECTOR=""
|
||||
for p in /sys/class/drm/card1-HDMI-A-1 /sys/class/drm/card0-HDMI-A-1; do
|
||||
[ -e "$p/status" ] && { CONNECTOR="$p"; break; }
|
||||
done
|
||||
[ -z "$CONNECTOR" ] && CONNECTOR=$(find /sys/class/drm -maxdepth 1 -name "*HDMI-A-1" -type d | head -1)
|
||||
|
||||
if [ -z "$CONNECTOR" ] || [ ! -e "$CONNECTOR/status" ]; then
|
||||
log "ERROR: No HDMI-A-1 found"
|
||||
setup_virtual_primary
|
||||
log "WARNING: No HDMI-A-1 connector found, running headless only"
|
||||
fi
|
||||
|
||||
# Initial state
|
||||
LAST_STATE=$(cat "$CONNECTOR/status" 2>/dev/null || echo unknown)
|
||||
log "Initial HDMI: $LAST_STATE"
|
||||
|
||||
# Detect outputs
|
||||
PRIMARY=$(find_primary_output)
|
||||
HDMI=$(find_hdmi_output)
|
||||
|
||||
if [ -z "$PRIMARY" ]; then
|
||||
log "ERROR: No virtual output found (DUMMY0 or VIRTUAL1)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Monitoring: $CONNECTOR"
|
||||
log "Primary: $PRIMARY, HDMI: ${HDMI:-none}"
|
||||
|
||||
setup_virtual_primary
|
||||
HDMI_STATE=$(cat "$CONNECTOR/status" 2>/dev/null || echo "unknown")
|
||||
log "Initial HDMI state: $HDMI_STATE"
|
||||
MODE=$(setup_mode "$PRIMARY")
|
||||
|
||||
if [ "$HDMI_STATE" = "connected" ]; then
|
||||
enable_hdmi_clone
|
||||
# Initial setup
|
||||
if [ "$LAST_STATE" = "connected" ] && [ -n "$HDMI" ]; then
|
||||
log "HDMI connected, setting clone: $PRIMARY primary + $HDMI same-as"
|
||||
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
|
||||
xrandr --output "$HDMI" --auto --same-as "$PRIMARY" 2>/dev/null || true
|
||||
else
|
||||
disable_hdmi
|
||||
log "HDMI disconnected or not found, keeping $PRIMARY primary"
|
||||
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
|
||||
[ -n "$HDMI" ] && xrandr --output "$HDMI" --off 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log "Initial setup complete. Monitors:"
|
||||
xrandr --listmonitors 2>&1 | while read line; do log " $line"; done
|
||||
|
||||
LAST_STATE="$HDMI_STATE"
|
||||
log "Setup complete. Monitors:"
|
||||
xrandr --listmonitors 2>/dev/null | while read -r line; do log " $line"; done
|
||||
|
||||
# Main loop
|
||||
while true; do
|
||||
CURRENT=$(cat "$CONNECTOR/status" 2>/dev/null || echo "unknown")
|
||||
CURRENT=$(cat "$CONNECTOR/status" 2>/dev/null || echo unknown)
|
||||
|
||||
if [ "$CURRENT" != "$LAST_STATE" ]; then
|
||||
log "HDMI changed: $LAST_STATE -> $CURRENT"
|
||||
setup_virtual_primary
|
||||
if [ "$CURRENT" = "connected" ]; then
|
||||
enable_hdmi_clone
|
||||
else
|
||||
disable_hdmi
|
||||
|
||||
# Re-detect
|
||||
PRIMARY=$(find_primary_output)
|
||||
HDMI=$(find_hdmi_output)
|
||||
MODE=$(setup_mode "$PRIMARY")
|
||||
|
||||
if [ -z "$PRIMARY" ]; then
|
||||
log "ERROR: No virtual output detected after HDMI change"
|
||||
sleep 5
|
||||
continue
|
||||
fi
|
||||
log "Monitor layout after change:"
|
||||
xrandr --listmonitors 2>&1 | while read line; do log " $line"; done
|
||||
|
||||
# Always make PRIMARY primary
|
||||
xrandr --output "$PRIMARY" --mode "$MODE" --primary 2>/dev/null || true
|
||||
|
||||
if [ "$CURRENT" = "connected" ] && [ -n "$HDMI" ]; then
|
||||
log "HDMI connected, cloning $HDMI to $PRIMARY"
|
||||
xrandr --output "$HDMI" --auto --same-as "$PRIMARY" 2>/dev/null || true
|
||||
else
|
||||
log "HDMI disconnected, keeping $PRIMARY primary"
|
||||
[ -n "$HDMI" ] && xrandr --output "$HDMI" --off 2>/dev/null || true
|
||||
fi
|
||||
|
||||
LAST_STATE="$CURRENT"
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
done
|
||||
SCRIPT
|
||||
@@ -160,7 +234,7 @@ log "Installing systemd user unit"
|
||||
mkdir -p "$HOME/.config/systemd/user"
|
||||
cat > "$SYSTEMD_DST" <<'UNIT'
|
||||
[Unit]
|
||||
Description=HDMI to VIRTUAL1 Fallback Monitor
|
||||
Description=HDMI to Virtual Fallback Monitor
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
@@ -175,8 +249,8 @@ WantedBy=graphical-session.target
|
||||
UNIT
|
||||
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now hdmi-fallback.service
|
||||
log "Systemd unit enabled and started"
|
||||
systemctl --user enable --now hdmi-fallback.service 2>/dev/null || true
|
||||
log "Systemd unit enabled"
|
||||
|
||||
# --- Summary ---
|
||||
echo ""
|
||||
@@ -185,15 +259,16 @@ echo "RDtop installation complete!"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Reboot or re-login to apply X11 config"
|
||||
echo " sudo systemctl restart gdm"
|
||||
echo "1. REBOOT now for changes to take effect:"
|
||||
echo " sudo reboot"
|
||||
echo ""
|
||||
echo "2. After reboot, verify VIRTUAL1 is primary:"
|
||||
echo "2. After reboot without HDMI, check:"
|
||||
echo " xrandr --listmonitors"
|
||||
echo ""
|
||||
echo "3. Check service status:"
|
||||
echo "3. Service status:"
|
||||
echo " systemctl --user status hdmi-fallback.service"
|
||||
echo ""
|
||||
echo "4. To verify: unplug HDMI cable, VIRTUAL1 will stay active"
|
||||
echo "4. View logs:"
|
||||
echo " cat ~/.local/logs/drm-hotplug.log"
|
||||
echo ""
|
||||
log "Install complete"
|
||||
|
||||
Reference in New Issue
Block a user