Import initial Hyprland HDMI recovery implementation

This commit is contained in:
admin committed 2026-09-23 09:10:57 +02:00
commit 73e727ef4c
19 files changed
+1041

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.sh]
indent_style = space
indent_size = 4
[*.md]
trim_trailing_whitespace = false
+48
View File
@@ -0,0 +1,48 @@
---
name: Bug report
about: Report a problem with HDMI recovery or session handling
title: "[Bug] "
labels: bug
---
## Environment
- Distribution:
- Hyprland version (`hyprctl version`):
- Kernel (`uname -r`):
- GPU / driver:
- Display manager:
- Monitor model:
- Output name (`hyprctl monitors`):
## What happened?
Describe the observed behavior.
## What did you expect?
Describe the expected behavior.
## Relevant logs
```text
journalctl --user -u revive-monitor-drm-watch.service -n 100 --no-pager
```
```text
journalctl --user -t revive-monitor-fix -n 100 --no-pager
```
## DRM hotplug test
Does this produce `HOTPLUG=1` while switching monitor inputs?
```bash
udevadm monitor --kernel --property --subsystem-match=drm
```
## Active session information
```bash
loginctl list-sessions
```
+18
View File
@@ -0,0 +1,18 @@
---
name: Feature request
about: Suggest an improvement
title: "[Feature] "
labels: enhancement
---
## Use case
Describe the problem or setup the feature would address.
## Proposed behavior
Describe how you think it should work.
## Additional context
Include relevant hardware, compositor, DRM, or systemd details.
+15
View File
@@ -0,0 +1,15 @@
## Summary
Describe the change and why it is needed.
## Testing
- [ ] Shell syntax validated with `bash -n`
- [ ] Tested with an active Hyprland Wayland session
- [ ] Confirmed that no VT switch occurs before login
- [ ] Confirmed that HDMI recovery still works after switching inputs
- [ ] Updated documentation where necessary
## Security impact
Describe any change involving `sudo`, `chvt`, systemd permissions, or privilege boundaries.
+24
View File
@@ -0,0 +1,24 @@
# Local configuration
config.env
*.local
*.env.local
# Runtime / logs
*.log
*.pid
*.lock
*.tmp
# Editors / IDEs
.vscode/
.idea/
*.swp
*.swo
*~
# OS metadata
.DS_Store
Thumbs.db
# Shell tooling
.shellcheckrc.local
+19
View File
@@ -0,0 +1,19 @@
# Changelog
All notable changes to this project will be documented here.
## 0.1.0 - 2026-09-23
### Added
- DRM `HOTPLUG=1` event watcher using `udevadm monitor`.
- Dynamic detection of the active Wayland session and its VT via `loginctl`.
- VT-based HDMI recovery using `chvt`.
- Hyprland DPMS and monitor-focus refresh before and after the VT switch.
- Debounce protection for duplicate DRM events.
- `flock` protection against overlapping recovery executions.
- systemd user service.
- Installation and uninstall scripts.
- Configurable monitor name and timing values.
- Restricted sudoers examples.
- Boot-safety guard to avoid switching away from the display manager before login.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Johannes Rest
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+341
View File
@@ -0,0 +1,341 @@
# 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).
+38
View File
@@ -0,0 +1,38 @@
# Security
This project intentionally keeps almost all functionality unprivileged.
## Privileged operation
Only `chvt` requires root privileges. The recommended sudoers rule is limited to:
```text
/usr/bin/chvt
```
or, when the machine uses fixed VTs, to explicit arguments such as:
```text
/usr/bin/chvt 1
/usr/bin/chvt 2
```
Do not configure unrestricted passwordless sudo for the user or for the recovery scripts.
## Non-interactive sudo
The recovery script always invokes sudo with `-n`:
```bash
sudo -n /usr/bin/chvt ...
```
This prevents the systemd user service from hanging on a password prompt.
## Boot safety
DRM hotplug events can occur during boot. The watcher therefore verifies that the current user has an active Wayland session with a valid VT before any recovery or VT switch is attempted.
## Reporting security issues
If this repository is published publicly, report security-sensitive findings privately through the repository owner's preferred security contact rather than opening a public issue containing exploitable details.
+14
View File
@@ -0,0 +1,14 @@
# Hyprland output name. Check with: hyprctl monitors
MONITOR=HDMI-A-2
# Suppress repeated DRM hotplug events within this interval.
DEBOUNCE_SECONDS=30
# Allow the monitor/HDMI handshake to settle before the VT refresh.
HDMI_SETTLE_SECONDS=5
# Time spent on the temporary VT before returning to Hyprland.
VT_SWITCH_DELAY_SECONDS=1
# Time to wait after returning to the graphical VT.
VT_RETURN_SETTLE_SECONDS=2
+81
View File
@@ -0,0 +1,81 @@
# Architecture
## Problem
A monitor shared between multiple physical HDMI inputs can keep its EDID/output state visible to Linux even while the monitor is displaying another source. When switching back, Hyprland may report the output as completely healthy while the physical monitor remains black or reports no source.
Typical Hyprland state in the broken condition:
```text
focused: yes
dpmsStatus: 1
disabled: false
active workspace: 1
```
Commands such as `hyprctl reload`, `hyprctl dispatch dpms on`, and `hyprctl dispatch focusmonitor` may all return `ok` without restoring the image.
## Discovery
Listening to Hyprland's event socket did not provide a reliable monitor add/remove event for the physical input switch.
The kernel DRM subsystem did, however, emit:
```text
SUBSYSTEM=drm
HOTPLUG=1
```
via:
```bash
udevadm monitor --kernel --property --subsystem-match=drm
```
A manual VT switch restored the image, indicating that the required reset happens below the compositor level.
## Final design
The long-running watcher consumes kernel DRM events. For every `HOTPLUG=1` event it:
1. checks `systemd-logind` for an active Wayland session belonging to the current user;
2. ignores the event if no such session exists;
3. dynamically determines that session's VT;
4. applies debounce protection;
5. calls the recovery script with the detected VT.
The recovery script:
1. uses `flock` to prevent overlap;
2. waits for the HDMI handshake to settle;
3. asks Hyprland to enable/focus the output;
4. uses `chvt` to move to another VT;
5. returns to the dynamically detected graphical VT;
6. refreshes Hyprland DPMS/focus once more.
## Why the Wayland-session guard is critical
GPU initialization during boot can generate DRM hotplug events. If the recovery script blindly executes a hard-coded `chvt 2` followed by `chvt 1`, the machine can end up showing tty1 even though the display manager is running on tty2.
The current implementation prevents this by requiring:
```text
Name=<current user>
Type=wayland
Active=yes
VTNr>0
```
before performing any recovery.
## Privilege boundary
The watcher and recovery script run as the normal user. Only `chvt` needs elevated privileges.
The recommended sudoers rule limits passwordless sudo to:
```text
/usr/bin/chvt
```
The script always calls it through `sudo -n`, which fails instead of prompting for a password.
+139
View File
@@ -0,0 +1,139 @@
# Troubleshooting
## Check the Hyprland output name
```bash
hyprctl monitors
```
Update:
```text
~/.config/revive-monitor/config.env
```
if the output is not `HDMI-A-2`.
## Check the user service
```bash
systemctl --user status revive-monitor-drm-watch.service
```
## Follow logs
```bash
journalctl --user -u revive-monitor-drm-watch.service -f
```
For the recovery script tag:
```bash
journalctl --user -t revive-monitor-fix -f
```
## Verify DRM hotplug events manually
```bash
udevadm monitor --kernel --property --subsystem-match=drm
```
Switch the physical monitor input away and back. Look for:
```text
HOTPLUG=1
```
If no DRM hotplug event is emitted, this project has no reliable event to trigger from.
## Verify the active Wayland session
```bash
loginctl list-sessions
```
Then:
```bash
for s in $(loginctl list-sessions --no-legend | awk '{print $1}'); do
echo "=== Session $s ==="
loginctl show-session "$s" -p Name -p Type -p Active -p VTNr
done
```
A normal Hyprland session should look similar to:
```text
Name=your-user
VTNr=1
Type=wayland
Active=yes
```
## Display manager appears only after manually switching TTY
Check the boot journal:
```bash
journalctl -b --no-pager | grep -E 'revive-monitor|COMMAND=/usr/bin/chvt|chvt'
```
If `chvt` runs before login, verify that you installed the current watcher script. It must ignore hotplug events when there is no active Wayland session.
Expected pre-login behavior:
```text
No active Wayland session - DRM hotplug ignored
```
## sudo / chvt fails
Test non-interactively:
```bash
sudo -n /usr/bin/chvt 2
```
If sudo asks for a password or exits with an error, verify:
```text
/etc/sudoers.d/revive-monitor-chvt
```
Always edit sudoers files with `visudo`.
## Recovery runs multiple times
Increase:
```bash
DEBOUNCE_SECONDS=30
```
in:
```text
~/.config/revive-monitor/config.env
```
For noisy hardware, `60` may be more appropriate.
## Recovery runs but the monitor still stays black
Increase:
```bash
HDMI_SETTLE_SECONDS=5
```
to `7` or `10` and test again.
Also manually verify that a VT switch actually restores the image:
```bash
sudo chvt 2
sleep 1
sudo chvt 1
```
Adjust the numbers to match the machine's graphical VT.
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BIN_DIR="$HOME/.local/bin"
SYSTEMD_DIR="$HOME/.config/systemd/user"
CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/revive-monitor"
mkdir -p "$BIN_DIR" "$SYSTEMD_DIR" "$CONFIG_DIR"
install -m 0755 "$REPO_ROOT/scripts/revive-monitor-fix.sh" "$BIN_DIR/revive-monitor-fix"
install -m 0755 "$REPO_ROOT/scripts/revive-monitor-drm-watch.sh" "$BIN_DIR/revive-monitor-drm-watch"
install -m 0644 "$REPO_ROOT/systemd/user/revive-monitor-drm-watch.service" "$SYSTEMD_DIR/revive-monitor-drm-watch.service"
if [[ ! -e "$CONFIG_DIR/config.env" ]]; then
install -m 0644 "$REPO_ROOT/config/revive-monitor.env.example" "$CONFIG_DIR/config.env"
printf 'Created %s\n' "$CONFIG_DIR/config.env"
printf 'Edit MONITOR before using the service if your output is not HDMI-A-2.\n'
else
printf 'Keeping existing config: %s\n' "$CONFIG_DIR/config.env"
fi
systemctl --user daemon-reload
systemctl --user enable --now revive-monitor-drm-watch.service
cat <<'MSG'
Installed successfully.
Next steps:
1. Verify ~/.config/revive-monitor/config.env
2. Configure /etc/sudoers.d/revive-monitor-chvt using one of the templates in sudoers/
3. Test with: systemctl --user status revive-monitor-drm-watch.service
4. Watch logs: journalctl --user -u revive-monitor-drm-watch.service -f
MSG
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
set -u
LOGTAG="revive-monitor-drm-watch"
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/revive-monitor/config.env"
FIX_SCRIPT="${REVIVE_MONITOR_FIX_SCRIPT:-$HOME/.local/bin/revive-monitor-fix}"
# Defaults. Override in ~/.config/revive-monitor/config.env
DEBOUNCE_SECONDS="30"
if [[ -r "$CONFIG_FILE" ]]; then
# shellcheck disable=SC1090
source "$CONFIG_FILE"
fi
STATE_DIR="${XDG_RUNTIME_DIR:-/tmp}/revive-monitor"
LAST_RUN_FILE="$STATE_DIR/last-drm-hotplug-run"
mkdir -p "$STATE_DIR"
log() {
logger -t "$LOGTAG" -- "$*"
}
get_active_wayland_session() {
local sid
local name
local type
local active
local vt
while read -r sid; do
[[ -n "$sid" ]] || continue
name="$(loginctl show-session "$sid" -p Name --value 2>/dev/null)"
[[ "$name" == "$USER" ]] || continue
type="$(loginctl show-session "$sid" -p Type --value 2>/dev/null)"
[[ "$type" == "wayland" ]] || continue
active="$(loginctl show-session "$sid" -p Active --value 2>/dev/null)"
[[ "$active" == "yes" ]] || continue
vt="$(loginctl show-session "$sid" -p VTNr --value 2>/dev/null)"
if [[ "$vt" =~ ^[1-9][0-9]*$ ]]; then
printf '%s:%s\n' "$sid" "$vt"
return 0
fi
done < <(
loginctl list-sessions --no-legend --no-pager 2>/dev/null | awk '{print $1}'
)
return 1
}
run_recovery() {
local session_info
local session_id
local graphical_vt
local now
local last="0"
local diff
if ! session_info="$(get_active_wayland_session)"; then
log "No active Wayland session - DRM hotplug ignored"
return 0
fi
session_id="${session_info%%:*}"
graphical_vt="${session_info##*:}"
now="$(date +%s)"
if [[ -r "$LAST_RUN_FILE" ]]; then
last="$(cat "$LAST_RUN_FILE" 2>/dev/null || printf '0')"
fi
[[ "$last" =~ ^[0-9]+$ ]] || last="0"
diff=$((now - last))
if (( diff < DEBOUNCE_SECONDS )); then
log "DRM hotplug ignored because of debounce (${diff}s)"
return 0
fi
printf '%s\n' "$now" > "$LAST_RUN_FILE"
log "DRM hotplug detected - Wayland session $session_id on VT $graphical_vt"
"$FIX_SCRIPT" "$graphical_vt"
log "Recovery after DRM hotplug finished"
}
if ! command -v udevadm >/dev/null 2>&1; then
log "udevadm not found"
exit 1
fi
if [[ ! -x "$FIX_SCRIPT" ]]; then
log "Recovery script is not executable: $FIX_SCRIPT"
exit 1
fi
log "Listening for DRM hotplug events"
stdbuf -oL udevadm monitor --kernel --property --subsystem-match=drm |
while IFS= read -r line; do
case "$line" in
HOTPLUG=1)
run_recovery
;;
esac
done
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -u
LOGTAG="revive-monitor-fix"
CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/revive-monitor/config.env"
# Defaults. Override in ~/.config/revive-monitor/config.env
MONITOR="HDMI-A-2"
HDMI_SETTLE_SECONDS="5"
VT_SWITCH_DELAY_SECONDS="1"
VT_RETURN_SETTLE_SECONDS="2"
if [[ -r "$CONFIG_FILE" ]]; then
# shellcheck disable=SC1090
source "$CONFIG_FILE"
fi
GRAPHICAL_VT="${1:-}"
STATE_DIR="${XDG_RUNTIME_DIR:-/tmp}/revive-monitor"
LOCK_FILE="$STATE_DIR/fix.lock"
mkdir -p "$STATE_DIR"
log() {
logger -t "$LOGTAG" -- "$*"
}
run_quiet() {
"$@" >/dev/null 2>&1
}
if ! [[ "$GRAPHICAL_VT" =~ ^[1-9][0-9]*$ ]]; then
log "No valid graphical VT supplied - recovery aborted"
exit 0
fi
if [[ "$GRAPHICAL_VT" -eq 1 ]]; then
TEMP_VT=2
else
TEMP_VT=1
fi
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
log "Recovery already running - skipping"
exit 0
fi
log "Recovery started for $MONITOR on VT $GRAPHICAL_VT"
sleep "$HDMI_SETTLE_SECONDS"
run_quiet hyprctl dispatch dpms on "$MONITOR"
run_quiet hyprctl dispatch focusmonitor "$MONITOR"
sleep 1
log "VT refresh: $TEMP_VT -> $GRAPHICAL_VT"
if ! sudo -n /usr/bin/chvt "$TEMP_VT"; then
log "Failed to switch to temporary VT $TEMP_VT"
exit 1
fi
sleep "$VT_SWITCH_DELAY_SECONDS"
if ! sudo -n /usr/bin/chvt "$GRAPHICAL_VT"; then
log "Failed to return to graphical VT $GRAPHICAL_VT"
exit 1
fi
sleep "$VT_RETURN_SETTLE_SECONDS"
run_quiet hyprctl dispatch dpms on "$MONITOR"
run_quiet hyprctl dispatch focusmonitor "$MONITOR"
log "Recovery completed for $MONITOR"
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
BIN_DIR="$HOME/.local/bin"
SYSTEMD_DIR="$HOME/.config/systemd/user"
systemctl --user disable --now revive-monitor-drm-watch.service 2>/dev/null || true
rm -f "$SYSTEMD_DIR/revive-monitor-drm-watch.service"
rm -f "$BIN_DIR/revive-monitor-fix"
rm -f "$BIN_DIR/revive-monitor-drm-watch"
systemctl --user daemon-reload
systemctl --user reset-failed
cat <<'MSG'
Removed the user service and installed scripts.
Not removed automatically:
~/.config/revive-monitor/config.env
/etc/sudoers.d/revive-monitor-chvt
Remove those manually if you no longer need them.
MSG
+3
View File
@@ -0,0 +1,3 @@
# Replace YOUR_USER with the Linux username running Hyprland.
# This permits passwordless sudo only for /usr/bin/chvt.
YOUR_USER ALL=(root) NOPASSWD: /usr/bin/chvt
@@ -0,0 +1,3 @@
# Replace YOUR_USER with the Linux username running Hyprland.
# Use this stricter version only if the machine always switches between VT1 and VT2.
YOUR_USER ALL=(root) NOPASSWD: /usr/bin/chvt 1, /usr/bin/chvt 2
@@ -0,0 +1,12 @@
[Unit]
Description=Watch DRM hotplug events and revive HDMI monitor
[Service]
Type=simple
ExecStart=%h/.local/bin/revive-monitor-drm-watch
Restart=on-failure
RestartSec=2
Environment=XDG_RUNTIME_DIR=%t
[Install]
WantedBy=default.target