155 lines
5.0 KiB
Bash
Executable File
155 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
LOGTAG=revive-monitor-fix
|
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
# Source-tree and installed layouts.
|
|
COMMON="$SCRIPT_DIR/../lib/hyprland-hdmi-revive/common.sh"
|
|
[[ -f "$COMMON" ]] || COMMON="$SCRIPT_DIR/../lib/common.sh"
|
|
# shellcheck source=lib/common.sh
|
|
source "$COMMON" || exit 1
|
|
load_config || exit 1
|
|
require_commands timeout loginctl hyprctl jq sudo flock stat sleep || exit 1
|
|
prepare_runtime || exit 1
|
|
|
|
GRAPHICAL_VT="${1:-}"
|
|
if [[ ! "$GRAPHICAL_VT" =~ ^[1-9][0-9]?$ ]] || (( GRAPHICAL_VT > 63 )); then
|
|
log 'No valid graphical VT supplied (expected 1..63)'
|
|
exit 1
|
|
fi
|
|
|
|
exec 9>"$STATE_DIR/fix.lock" || exit 1
|
|
flock -n -E 75 9
|
|
lock_status=$?
|
|
if (( lock_status == 75 )); then
|
|
log 'Recovery already running - skipping'
|
|
exit 0
|
|
elif (( lock_status != 0 )); then
|
|
log 'Cannot acquire recovery lock'
|
|
exit 1
|
|
fi
|
|
|
|
SESSION="$(get_active_wayland_session)" || { log 'No active Wayland session - recovery skipped'; exit 0; }
|
|
if [[ "${SESSION##*:}" != "$GRAPHICAL_VT" ]]; then
|
|
log 'Requested VT does not belong to the active Wayland session'
|
|
exit 1
|
|
fi
|
|
if [[ -z "${HYPRLAND_INSTANCE_SIGNATURE:-}" ]]; then
|
|
log 'Missing HYPRLAND_INSTANCE_SIGNATURE; import the Hyprland environment into systemd --user'
|
|
exit 1
|
|
fi
|
|
|
|
monitor_present() {
|
|
timeout 5 hyprctl -j monitors |
|
|
jq -e --arg name "$MONITOR" 'any(.[]; .name == $name)' >/dev/null 2>&1
|
|
}
|
|
|
|
select_dispatch_syntax() {
|
|
local status provider
|
|
DISPATCH_SYNTAX=hyprlang
|
|
# The active config provider, not the version, determines dispatch syntax.
|
|
# Older Hyprland versions do not expose `status`; their dispatch is legacy.
|
|
if status="$(timeout 5 hyprctl -j status 2>/dev/null)" &&
|
|
provider="$(jq -er '.configProvider | select(type == "string")' <<< "$status" 2>/dev/null)"; then
|
|
case "$provider" in
|
|
lua|hyprlang) DISPATCH_SYNTAX="$provider" ;;
|
|
*) log "Unsupported Hyprland config provider: $provider"; return 1 ;;
|
|
esac
|
|
else
|
|
log 'Hyprland config provider unavailable; trying legacy dispatch syntax'
|
|
fi
|
|
log "Using $DISPATCH_SYNTAX dispatch syntax"
|
|
}
|
|
|
|
dispatch_checked() {
|
|
local action="$1" reply status
|
|
shift
|
|
reply="$(timeout 5 hyprctl dispatch "$@" 2>&1)"
|
|
status=$?
|
|
# Some hyprctl versions return exit 0 even for an IPC error response.
|
|
if (( status != 0 )) || [[ "$reply" != ok ]]; then
|
|
log "Hyprland $action failed (exit $status): ${reply:-no response}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
refresh_monitor() {
|
|
if [[ "$DISPATCH_SYNTAX" == lua ]]; then
|
|
# MONITOR is restricted to alphanumeric characters, underscores and '-'.
|
|
dispatch_checked DPMS "hl.dsp.dpms({ action = \"on\", monitor = \"$MONITOR\" })" &&
|
|
dispatch_checked focus "hl.dsp.focus({ monitor = \"$MONITOR\" })"
|
|
else
|
|
dispatch_checked DPMS dpms on "$MONITOR" &&
|
|
dispatch_checked focus focusmonitor "$MONITOR"
|
|
fi
|
|
}
|
|
|
|
log "Recovery started for $MONITOR on VT $GRAPHICAL_VT"
|
|
sleep "$HDMI_SETTLE_SECONDS" || exit 1
|
|
if ! monitor_present; then
|
|
log "Hyprland IPC unavailable or configured output $MONITOR absent - recovery aborted"
|
|
exit 1
|
|
fi
|
|
select_dispatch_syntax || exit 1
|
|
if ! refresh_monitor; then
|
|
log 'Hyprland refresh failed - recovery aborted'
|
|
exit 1
|
|
fi
|
|
|
|
# Recheck after settling/IPC, immediately before the privileged switch.
|
|
if [[ "$(get_active_wayland_session)" != "$SESSION" ]]; then
|
|
log 'Active session changed while settling - recovery skipped'
|
|
exit 0
|
|
fi
|
|
TEMP_VT=1
|
|
[[ "$GRAPHICAL_VT" == 1 ]] && TEMP_VT=2
|
|
# Check both directions before leaving the desktop (actual sudo can still fail).
|
|
for vt in "$TEMP_VT" "$GRAPHICAL_VT"; do
|
|
if ! timeout 5 sudo -n -l /usr/bin/chvt "$vt" >/dev/null 2>&1; then
|
|
log "sudo permission check failed for VT $vt - recovery aborted"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [[ "$(get_active_wayland_session)" != "$SESSION" ]]; then
|
|
log 'Active session changed during permission checks - recovery skipped'
|
|
exit 0
|
|
fi
|
|
NEEDS_RETURN=0
|
|
cleanup() {
|
|
local status=$?
|
|
trap - EXIT HUP INT TERM
|
|
if (( NEEDS_RETURN )); then
|
|
log "Attempting emergency return to graphical VT $GRAPHICAL_VT"
|
|
if ! timeout 5 sudo -n /usr/bin/chvt "$GRAPHICAL_VT"; then
|
|
log "Emergency return failed; manually switch to VT $GRAPHICAL_VT"
|
|
status=1
|
|
fi
|
|
fi
|
|
exit "$status"
|
|
}
|
|
trap cleanup EXIT
|
|
trap 'exit 129' HUP
|
|
trap 'exit 130' INT
|
|
trap 'exit 143' TERM
|
|
|
|
log "VT refresh: $TEMP_VT -> $GRAPHICAL_VT"
|
|
# Set before chvt to cover a signal arriving as the command completes.
|
|
NEEDS_RETURN=1
|
|
if ! timeout 5 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" || exit 1
|
|
if ! timeout 5 sudo -n /usr/bin/chvt "$GRAPHICAL_VT"; then
|
|
log "Failed to return to graphical VT $GRAPHICAL_VT"
|
|
exit 1
|
|
fi
|
|
NEEDS_RETURN=0
|
|
sleep "$VT_RETURN_SETTLE_SECONDS" || exit 1
|
|
if ! refresh_monitor; then
|
|
log 'Final Hyprland refresh failed'
|
|
exit 1
|
|
fi
|
|
log "Recovery completed for $MONITOR"
|