Files
hyprland-hdmi-revive/README.md
T

174 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Hyprland HDMI Revive
Recover a black HDMI display after switching a shared physical monitor away from
an Arch Linux / Hyprland workstation and back. A DRM hotplug event triggers a
short switch away from the active graphical VT and back. There is no periodic
DPMS toggle or polling timer.
**Version 0.2.0.** The initial implementation and the review fixes are separate
commits. See [the code review](docs/code-review.md) and [changelog](CHANGELOG.md).
## Requirements
A systemd-based Linux desktop with Hyprland, a local `seat0` Wayland session,
Bash, coreutils (`timeout`, `stdbuf`, `stat`), `jq`, `flock`, `sudo` and
`/usr/bin/chvt`. On Arch Linux:
```bash
sudo pacman -S --needed hyprland systemd bash coreutils jq util-linux kbd sudo
```
The intended setup is one local Hyprland session per user. The monitor must emit
DRM `HOTPLUG=1` events when its physical input changes. No event means no automatic
recovery. Hyprland must still expose the configured monitor over its IPC socket.
## Install from the package
Download `hyprland-hdmi-revive-0.2.0.tar.gz` and `SHA256SUMS` from the
[Gitea release](https://gitea.jr-it-services.de/johannes/hyprland-hdmi-revive/releases).
Run as your desktop user, **without sudo**:
```bash
sha256sum --ignore-missing -c SHA256SUMS
tar -xzf hyprland-hdmi-revive-0.2.0.tar.gz
cd hyprland-hdmi-revive-0.2.0
./scripts/install.sh
```
This is a portable user installer bundle, not a pacman system package. It installs:
- executables under `~/.local/bin/`;
- shared code under `~/.local/lib/hyprland-hdmi-revive/`;
- the unit under `${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/`;
- a new config under `${XDG_CONFIG_HOME:-$HOME/.config}/revive-monitor/config.env`.
Existing configuration is preserved. A running instance is stopped before its
files are replaced. Installation alone does not enable or start the service.
Alternatively, clone the source and use the same installer:
```bash
git clone https://gitea.jr-it-services.de/johannes/hyprland-hdmi-revive.git
cd hyprland-hdmi-revive
./scripts/install.sh
```
## Configure
Find the monitor name from a terminal inside Hyprland:
```bash
hyprctl monitors
nano "${XDG_CONFIG_HOME:-$HOME/.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
```
Timing values are whole seconds: 0–300 for waits and 0–3600 for debounce. Config is
trusted Bash sourced as your user; do not copy untrusted shell code into it.
### Allow the VT switch
Edit with `sudo visudo -f /etc/sudoers.d/revive-monitor-chvt`. Prefer explicit VT
arguments when the machine always uses VT1 and VT2:
```sudoers
YOUR_USER ALL=(root) NOPASSWD: /usr/bin/chvt 1, /usr/bin/chvt 2
```
Replace `YOUR_USER`. For changing graphical VTs, a broader chvt-only rule is in
`sudoers/revive-monitor-chvt.example`. Never grant sudo access to these scripts.
The helper checks permission for both directions before leaving the desktop and
uses `sudo -n` with command timeouts. A return failure gets one emergency retry.
### Connect the Hyprland session
The user service manager does not automatically inherit a terminal's Hyprland
environment. Add this line to your Hyprland configuration (commonly
`~/.config/hypr/hyprland.conf`):
```ini
exec-once = sh -c 'export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"; systemctl --user import-environment HYPRLAND_INSTANCE_SIGNATURE XDG_CONFIG_HOME && systemctl --user restart revive-monitor-drm-watch.service'
```
It imports the current IPC instance at each Hyprland login, including after
logging out and back in. A custom `XDG_CONFIG_HOME` must also be consistent with
the user manager's unit search path. See the official
[hyprctl documentation](https://wiki.hypr.land/configuring/core/advanced-configuration/using-hyprctl/)
and [systemd environment documentation](https://www.freedesktop.org/software/systemd/man/latest/environment.d.html).
For the current login, after configuring the output and sudoers:
```bash
./scripts/install.sh --start
systemctl --user status revive-monitor-drm-watch.service
journalctl --user -u revive-monitor-drm-watch.service -b
```
`--start` imports the current instance, reloads systemd, enables and restarts the
unit. Use it for updates as well. The service remains attached to `default.target`
because not every Hyprland setup activates `graphical-session.target`; session
and IPC guards prevent pre-login VT switching.
## Behavior and limits
- Events are accepted only for this UID's active local Wayland session on seat0.
A reported non-Hyprland desktop is excluded.
- The helper independently verifies the requested VT and rechecks the session
after settling and permission checks.
- A private `XDG_RUNTIME_DIR` is required. Locks prevent duplicate watchers and
overlapping recoveries; there is no shared `/tmp` fallback.
- Missing IPC, invalid monitor names, command failures and lost event streams
produce error logs. A terminated event stream causes systemd to restart the watcher.
- Signals trigger a best-effort return to the graphical VT. SIGKILL, power loss
and persistent sudo/kernel failures cannot be recovered by a shell trap.
- DRM events are not reliably attributed to a particular monitor. Another
output's hotplug can trigger this workaround too.
- Debounce is a cooldown measured after a recovery attempt. It suppresses queued
events, including events caused by the VT switch. It can also suppress a real
rapid input return; if that happens, try a shorter `DEBOUNCE_SECONDS` or run
the helper manually with the correct graphical VT.
## Uninstall
From the unpacked bundle or checkout:
```bash
./scripts/uninstall.sh
```
The user manager must be reachable. Config and sudoers remain intact. Remove the
Hyprland startup line and, if no longer needed, the dedicated sudoers entry with
`visudo`. Failure state for unrelated services is preserved.
## Build and validate
Python 3 is needed only for tests; ShellCheck is a development dependency.
```bash
for script in scripts/*.sh lib/*.sh; do bash -n "$script" || exit; done
shellcheck -x scripts/*.sh lib/*.sh
python3 -B -m unittest discover -s tests -v
./scripts/build-release.sh
```
The release builder requires clean tracked files and packages **committed HEAD**.
It creates a `.tar.gz` installer bundle, source ZIP, Base64 ZIP and `SHA256SUMS` in
`dist/`. Untracked files and local config are excluded. The Base64 ZIP can also
be decoded on Windows with `certutil -decode <file.zip.b64> <file.zip>`.
Tests mock loginctl, Hyprland IPC, udev, sudo and systemd. They never switch the
real display. A hardware input-switch test after installation remains necessary.
CI runs the shell checks and behavioral tests on a configured Gitea Actions runner.
See [architecture](docs/architecture.md), [troubleshooting](docs/troubleshooting.md)
and [security notes](SECURITY.md). MIT license: [LICENSE](LICENSE).