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:
Deploy Bot
2026-05-15 18:12:05 +01:00
parent a0b2b5ebf7
commit 00c36b8d7e
5 changed files with 273 additions and 131 deletions

148
README.md
View File

@@ -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

View 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

View File

@@ -1,5 +0,0 @@
Section "Device"
Identifier "Intel Graphics"
Driver "intel"
Option "VirtualHeads" "1"
EndSection

View 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

View File

@@ -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"