Files
hyprland-hdmi-revive/README.md
T

342 lines
8.4 KiB
Markdown

# 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=<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
```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).