# Hyprland HDMI Revive Automatically recover a Wayland/Hyprland desktop after switching a physical monitor input away from an Arch Linux workstation and back again. This project was built for a setup where one external HDMI monitor is shared between: - an Arch Linux workstation running Hyprland/Wayland - a Windows laptop connected to another HDMI input on the same monitor On some monitors/GPU combinations, switching back to the Arch input leaves the display black or showing `No source`, even though Hyprland still reports the monitor as enabled, focused, and DPMS-on. The workaround that reliably restores the image is a short VT switch away from the active Hyprland VT and back again. This repository automates that workaround only when the kernel reports a DRM hotplug event and only while an active Wayland session exists. ## How it works ```text Monitor input switch | v Kernel DRM HOTPLUG=1 event | v User service receives the event | v Is there an active Wayland session for this user? | | no yes | | ignore detect active VT | v wait for HDMI sync | v Hyprland DPMS/focus refresh | v temporary VT switch | v switch back to Hyprland ``` The important detail is that the recovery is **event-driven**. There is no polling timer and no periodic DPMS off/on cycle, so the screen does not blink during normal desktop use. ## Repository layout ```text . ├── config/ │ └── revive-monitor.env.example ├── docs/ │ ├── architecture.md │ └── troubleshooting.md ├── scripts/ │ ├── install.sh │ ├── revive-monitor-drm-watch.sh │ ├── revive-monitor-fix.sh │ └── uninstall.sh ├── sudoers/ │ ├── revive-monitor-chvt.example │ └── revive-monitor-chvt.strict.example ├── systemd/ │ └── user/ │ └── revive-monitor-drm-watch.service ├── .gitea/ │ ├── issue_template/ │ └── pull_request_template.md ├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── SECURITY.md └── README.md ``` ## Requirements - Arch Linux or another systemd-based Linux distribution - Hyprland - `systemd` / `systemd-logind` - `udevadm` - `hyprctl` - `flock` from `util-linux` - `chvt` from `kbd` - `sudo` On Arch Linux, the relevant packages are typically already present. If needed: ```bash sudo pacman -S --needed hyprland systemd util-linux kbd sudo ``` ## 1. Clone the repository Example for Gitea: ```bash git clone ssh://git@gitea.example.com/your-user/hyprland-hdmi-revive.git cd hyprland-hdmi-revive ``` ## 2. Configure the monitor Copy the example configuration: ```bash mkdir -p ~/.config/revive-monitor cp config/revive-monitor.env.example ~/.config/revive-monitor/config.env ``` Determine the Hyprland output name: ```bash hyprctl monitors ``` Example: ```text Monitor HDMI-A-2 (ID 0): ``` Then edit: ```bash nano ~/.config/revive-monitor/config.env ``` Example: ```bash MONITOR=HDMI-A-2 DEBOUNCE_SECONDS=30 HDMI_SETTLE_SECONDS=5 VT_SWITCH_DELAY_SECONDS=1 VT_RETURN_SETTLE_SECONDS=2 ``` ## 3. Install the scripts and systemd user service Run: ```bash ./scripts/install.sh ``` The installer copies the scripts to: ```text ~/.local/bin/revive-monitor-fix ~/.local/bin/revive-monitor-drm-watch ``` and installs the user service at: ```text ~/.config/systemd/user/revive-monitor-drm-watch.service ``` The service is intentionally attached to `default.target`, because not every Hyprland setup activates `graphical-session.target`. The watcher itself performs an additional safety check and refuses to switch VTs unless it finds an **active Wayland session for the current user**. This prevents DRM hotplug events during boot from switching the display manager away from its VT. ## 4. Configure sudo safely `chvt` requires root privileges. Do **not** give the script unrestricted passwordless sudo. ### Recommended general rule Open a dedicated sudoers file: ```bash sudo visudo -f /etc/sudoers.d/revive-monitor-chvt ``` Add: ```text YOUR_USER ALL=(root) NOPASSWD: /usr/bin/chvt ``` Replace `YOUR_USER` with your username. This grants passwordless sudo only for `/usr/bin/chvt`, not for arbitrary root commands. ### More restrictive rule If your graphical session always uses VT1 and the temporary VT is always VT2, use: ```text YOUR_USER ALL=(root) NOPASSWD: /usr/bin/chvt 1, /usr/bin/chvt 2 ``` A template is included in: ```text sudoers/revive-monitor-chvt.strict.example ``` The scripts use `sudo -n`, so they never block waiting for a password prompt. Test the rule manually: ```bash sudo -n /usr/bin/chvt 2 sleep 1 sudo -n /usr/bin/chvt 1 ``` ## 5. Start the service The installer enables it automatically. To do it manually: ```bash systemctl --user daemon-reload systemctl --user enable --now revive-monitor-drm-watch.service ``` Check status: ```bash systemctl --user status revive-monitor-drm-watch.service ``` Watch logs: ```bash journalctl --user -u revive-monitor-drm-watch.service -f ``` ## Why not xrandr? `xrandr` is an X11 tool. Under Hyprland/Wayland it is not the correct interface for controlling physical outputs and may only see Xwayland state. Typical symptoms include: ```text Authorization required, but no authorization protocol specified Can't open display :0 ``` This project uses `hyprctl` for compositor-side state and DRM/udev events for the actual hotplug trigger. ## Why not poll every 20 or 30 seconds? A polling approach can accidentally run disruptive operations while the desktop is already working. In early versions, a periodic DPMS toggle caused the monitor to go dark and come back every few seconds. This implementation does not use a timer. Recovery is triggered only by: ```text SUBSYSTEM=drm HOTPLUG=1 ``` and only if an active Wayland session exists. ## Why the active Wayland-session check matters During boot, GPUs and displays can generate DRM hotplug events before the display manager or Hyprland session is ready. Without a session guard, a recovery script that always runs: ```bash chvt 2 chvt 1 ``` can leave the machine on tty1 while the display manager is actually running on tty2. The watcher therefore queries `systemd-logind` via `loginctl` and only runs recovery when it finds a session matching all of these conditions: ```text Name= Type=wayland Active=yes VTNr>0 ``` The current graphical VT is detected dynamically and passed to the recovery script. No VT is hard-coded. ## Example successful log ```text revive-monitor-drm-watch: DRM hotplug detected - Wayland session 3 on VT 1 revive-monitor-fix: Recovery started for HDMI-A-2 on VT 1 revive-monitor-fix: VT refresh: 2 -> 1 sudo: COMMAND=/usr/bin/chvt 2 sudo: COMMAND=/usr/bin/chvt 1 revive-monitor-fix: Recovery completed for HDMI-A-2 revive-monitor-drm-watch: Recovery after DRM hotplug finished ``` ## Verify the boot-safety fix After rebooting, check: ```bash journalctl -b --no-pager | grep -E 'revive-monitor|COMMAND=/usr/bin/chvt|chvt' ``` Before logging into Hyprland, you should either see no recovery at all or a message similar to: ```text No active Wayland session - DRM hotplug ignored ``` There should be no `chvt` call before the graphical user session exists. ## Uninstall Run: ```bash ./scripts/uninstall.sh ``` Then remove the sudoers file manually: ```bash sudo rm /etc/sudoers.d/revive-monitor-chvt ``` The uninstall script intentionally does not remove sudoers configuration automatically. ## Troubleshooting See [docs/troubleshooting.md](docs/troubleshooting.md). ## Security notes - The service runs as a normal systemd **user service**. - Only `chvt` requires root privileges. - `sudo -n` prevents unattended password prompts. - A lock file prevents overlapping recovery runs. - Debouncing suppresses repeated DRM hotplug events. - The recovery runs only for an active Wayland session belonging to the current user. - The sudoers rule should be limited to `/usr/bin/chvt`, or ideally only the specific VTs used on the machine. ## License MIT. See [LICENSE](LICENSE).