From 00c36b8d7e87b38bf39eba482f4ab0d1c935c2f8 Mon Sep 17 00:00:00 2001 From: Deploy Bot Date: Fri, 15 May 2026 18:12:05 +0100 Subject: [PATCH] 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. --- README.md | 148 +++++++++------ config/x11-host/20-dummy-headless.conf | 23 +++ config/x11-host/20-intel-virtual.conf | 5 - config/x11-host/20-intel-virtual.conf.bak | 9 + install.sh | 219 +++++++++++++++------- 5 files changed, 273 insertions(+), 131 deletions(-) create mode 100644 config/x11-host/20-dummy-headless.conf delete mode 100644 config/x11-host/20-intel-virtual.conf create mode 100644 config/x11-host/20-intel-virtual.conf.bak diff --git a/README.md b/README.md index 9272eaa..b93e7bf 100644 --- a/README.md +++ b/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 diff --git a/config/x11-host/20-dummy-headless.conf b/config/x11-host/20-dummy-headless.conf new file mode 100644 index 0000000..89aeeb1 --- /dev/null +++ b/config/x11-host/20-dummy-headless.conf @@ -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 diff --git a/config/x11-host/20-intel-virtual.conf b/config/x11-host/20-intel-virtual.conf deleted file mode 100644 index d412484..0000000 --- a/config/x11-host/20-intel-virtual.conf +++ /dev/null @@ -1,5 +0,0 @@ -Section "Device" - Identifier "Intel Graphics" - Driver "intel" - Option "VirtualHeads" "1" -EndSection diff --git a/config/x11-host/20-intel-virtual.conf.bak b/config/x11-host/20-intel-virtual.conf.bak new file mode 100644 index 0000000..a93e558 --- /dev/null +++ b/config/x11-host/20-intel-virtual.conf.bak @@ -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 diff --git a/install.sh b/install.sh index 3d4577e..390eb47 100644 --- a/install.sh +++ b/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"