Files
hyprland-hdmi-revive/README.md
T

8.4 KiB

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

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

.
├── 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:

sudo pacman -S --needed hyprland systemd util-linux kbd sudo

1. Clone the repository

Example for Gitea:

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:

mkdir -p ~/.config/revive-monitor
cp config/revive-monitor.env.example ~/.config/revive-monitor/config.env

Determine the Hyprland output name:

hyprctl monitors

Example:

Monitor HDMI-A-2 (ID 0):

Then edit:

nano ~/.config/revive-monitor/config.env

Example:

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:

./scripts/install.sh

The installer copies the scripts to:

~/.local/bin/revive-monitor-fix
~/.local/bin/revive-monitor-drm-watch

and installs the user service at:

~/.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.

Open a dedicated sudoers file:

sudo visudo -f /etc/sudoers.d/revive-monitor-chvt

Add:

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:

YOUR_USER ALL=(root) NOPASSWD: /usr/bin/chvt 1, /usr/bin/chvt 2

A template is included in:

sudoers/revive-monitor-chvt.strict.example

The scripts use sudo -n, so they never block waiting for a password prompt.

Test the rule manually:

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:

systemctl --user daemon-reload
systemctl --user enable --now revive-monitor-drm-watch.service

Check status:

systemctl --user status revive-monitor-drm-watch.service

Watch logs:

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:

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:

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:

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:

Name=<current user>
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

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:

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:

No active Wayland session - DRM hotplug ignored

There should be no chvt call before the graphical user session exists.

Uninstall

Run:

./scripts/uninstall.sh

Then remove the sudoers file manually:

sudo rm /etc/sudoers.d/revive-monitor-chvt

The uninstall script intentionally does not remove sudoers configuration automatically.

Troubleshooting

See 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.