hyprland: run screensaver as layer overlay

This commit is contained in:
2026-04-29 13:34:04 -07:00
parent 8ccf5fb7de
commit bb32668387
3 changed files with 99 additions and 146 deletions

View File

@@ -1,7 +1,6 @@
general { general {
lock_cmd = pidof hyprlock || hyprlock lock_cmd = pidof hyprlock || hyprlock
before_sleep_cmd = loginctl lock-session before_sleep_cmd = loginctl lock-session
after_sleep_cmd = hyprctl dispatch dpms on
} }
listener { listener {
@@ -9,9 +8,3 @@ listener {
on-timeout = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver start on-timeout = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver start
on-resume = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop on-resume = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop
} }
listener {
timeout = 900
on-timeout = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop && hyprctl dispatch dpms off
on-resume = hyprctl dispatch dpms on && /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop
}

View File

@@ -518,10 +518,10 @@ simplifiedScreensaverWidget =
then return False then return False
else case button of else case button of
1 -> do 1 -> do
void $ spawnCommand "hypr-screensaver toggle >/dev/null 2>&1" void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver toggle >/dev/null 2>&1"
return True return True
3 -> do 3 -> do
void $ spawnCommand "hypr-screensaver stop >/dev/null 2>&1" void $ spawnCommand "/home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver stop >/dev/null 2>&1"
return True return True
_ -> return False _ -> return False
Gtk.widgetShowAll ebox Gtk.widgetShowAll ebox

View File

@@ -2,8 +2,9 @@
set -euo pipefail set -euo pipefail
script_path="$(readlink -f "${BASH_SOURCE[0]}")"
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver" state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
pid_file="$state_dir/mpvpaper.pid"
event_log="$state_dir/events.log"
mkdir -p "$state_dir" mkdir -p "$state_dir"
title_prefix="hypr-screensaver:" title_prefix="hypr-screensaver:"
@@ -15,11 +16,11 @@ usage() {
Usage: hypr-screensaver <start|stop|toggle|status|session> Usage: hypr-screensaver <start|stop|toggle|status|session>
Commands: Commands:
start Launch the screensaver on every Hyprland monitor. start Launch the screensaver as a Wayland layer-shell overlay.
stop Stop any running screensaver windows. stop Stop any running screensaver overlay.
toggle Start if stopped, otherwise stop. toggle Start if stopped, otherwise stop.
status Exit 0 if any screensaver window is running, otherwise exit 1. status Exit 0 if the screensaver overlay is running, otherwise exit 1.
session Run the configured screensaver payload for one monitor. session Compatibility alias for start.
By default, start chooses a random media file from: By default, start chooses a random media file from:
/var/lib/syncthing/sync/Screensaver/use /var/lib/syncthing/sync/Screensaver/use
@@ -33,6 +34,10 @@ example:
You can also override the rotation directory: You can also override the rotation directory:
HYPR_SCREENSAVER_USE_DIR='/path/to/use' HYPR_SCREENSAVER_USE_DIR='/path/to/use'
Layer-shell/output overrides:
HYPR_SCREENSAVER_OUTPUT='ALL'
HYPR_SCREENSAVER_LAYER='overlay'
HDR handling defaults to matching Hyprland's monitor color-management preset. HDR handling defaults to matching Hyprland's monitor color-management preset.
Only monitors with preset "hdr" or "hdredid" get HDR colorspace hints. Override Only monitors with preset "hdr" or "hdredid" get HDR colorspace hints. Override
with: with:
@@ -46,28 +51,11 @@ monitors_json() {
hyprctl -j monitors hyprctl -j monitors
} }
monitor_names() { log_event() {
monitors_json | jq -r '.[].name' printf '%s %s\n' "$(date --iso-8601=seconds)" "$*" >>"$event_log"
} }
monitor_specs() { legacy_screensaver_window_pids() {
monitors_json | jq -c '.[] | { name, width, height }'
}
focused_monitor() {
monitors_json | jq -r '.[] | select(.focused) | .name'
}
monitor_color_management_preset() {
local monitor="$1"
monitors_json | jq -r --arg monitor "$monitor" '
.[]
| select(.name == $monitor)
| .colorManagementPreset // "srgb"
'
}
screensaver_window_pids() {
hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" ' hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" '
.[] .[]
| select((.title // "") | startswith($prefix)) | select((.title // "") | startswith($prefix))
@@ -77,27 +65,28 @@ screensaver_window_pids() {
is_running() { is_running() {
local pid local pid
for pid in $(screensaver_window_pids); do if [ -f "$pid_file" ]; then
if kill -0 "$pid" 2>/dev/null; then
return 0
fi
done
shopt -s nullglob
local pid_file
for pid_file in "$state_dir"/*.pid; do
pid="$(<"$pid_file")" pid="$(<"$pid_file")"
if kill -0 "$pid" 2>/dev/null; then if kill -0 "$pid" 2>/dev/null; then
return 0 return 0
fi fi
done rm -f "$pid_file"
fi
return 1 return 1
} }
default_source() { default_source() {
local width="$1" local size width height
local height="$2" size="$(
monitors_json 2>/dev/null \
| jq -r 'max_by((.width // 0) * (.height // 0)) | "\(.width // 1920)x\(.height // 1080)"' 2>/dev/null \
|| true
)"
size="${size:-1920x1080}"
width="${size%x*}"
height="${size#*x}"
printf 'av://lavfi:life=s=%sx%s:r=60:mold=10:ratio=0.065:death_color=#101414:life_color=#7dd3fc:mold_color=#1e3a5f,format=yuv420p' \ printf 'av://lavfi:life=s=%sx%s:r=60:mold=10:ratio=0.065:death_color=#101414:life_color=#7dd3fc:mold_color=#1e3a5f,format=yuv420p' \
"$width" "$height" "$width" "$height"
} }
@@ -128,10 +117,8 @@ random_source() {
printf '%s\n' "${candidates[$((RANDOM % ${#candidates[@]}))]}" printf '%s\n' "${candidates[$((RANDOM % ${#candidates[@]}))]}"
} }
monitor_uses_hdr() { screensaver_uses_hdr() {
local monitor="$1"
local mode="${HYPR_SCREENSAVER_HDR_MODE:-auto}" local mode="${HYPR_SCREENSAVER_HDR_MODE:-auto}"
local preset
case "$mode" in case "$mode" in
hdr) hdr)
@@ -141,15 +128,8 @@ monitor_uses_hdr() {
return 1 return 1
;; ;;
auto) auto)
preset="$(monitor_color_management_preset "$monitor" 2>/dev/null || true)" monitors_json 2>/dev/null \
case "$preset" in | jq -e 'any(.[]; (.colorManagementPreset // "srgb") == "hdr" or (.colorManagementPreset // "srgb") == "hdredid")' >/dev/null 2>&1
hdr|hdredid)
return 0
;;
*)
return 1
;;
esac
;; ;;
*) *)
printf 'Invalid HYPR_SCREENSAVER_HDR_MODE=%s; expected auto, sdr, or hdr\n' "$mode" >&2 printf 'Invalid HYPR_SCREENSAVER_HDR_MODE=%s; expected auto, sdr, or hdr\n' "$mode" >&2
@@ -158,119 +138,99 @@ monitor_uses_hdr() {
esac esac
} }
mpv_color_args() { mpv_color_options() {
local monitor="$1" if screensaver_uses_hdr; then
printf '%s ' \
if monitor_uses_hdr "$monitor"; then target-colorspace-hint=yes \
printf '%s\0' \ target-colorspace-hint-mode=source
--target-colorspace-hint=yes \
--target-colorspace-hint-mode=source
return return
fi fi
printf '%s\0' \ printf '%s ' \
--target-colorspace-hint=no \ target-colorspace-hint=no \
--target-prim=bt.709 \ target-prim=bt.709 \
--target-trc=srgb \ target-trc=srgb \
--target-gamut=bt.709 \ target-gamut=bt.709 \
--target-peak=80 \ target-peak=80 \
--inverse-tone-mapping=no inverse-tone-mapping=no
}
mpv_options() {
if [ -n "${HYPR_SCREENSAVER_MPV_OPTIONS:-}" ]; then
printf '%s\n' "$HYPR_SCREENSAVER_MPV_OPTIONS"
return
fi
printf '%s %s\n' \
"no-audio loop-file=inf osc=no osd-level=0 input-default-bindings=no terminal=no image-display-duration=inf keep-open=yes" \
"$(mpv_color_options)"
}
run_mpvpaper() {
if command -v mpvpaper >/dev/null 2>&1; then
exec mpvpaper "$@"
fi
exec nix shell nixpkgs#mpvpaper --command mpvpaper "$@"
} }
start() { start() {
local current_monitor spec monitor width height pid source local source output layer options pid
if is_running; then if is_running; then
log_event "start ignored: already running pid=$(<"$pid_file")"
exit 0 exit 0
fi fi
current_monitor="$(focused_monitor || true)" stop
source="${HYPR_SCREENSAVER_SOURCE:-}" source="${HYPR_SCREENSAVER_SOURCE:-}"
if [ -z "$source" ]; then if [ -z "$source" ]; then
source="$(random_source || true)" source="$(random_source || true)"
fi fi
if [ -z "$source" ]; then
while IFS= read -r spec; do source="$(default_source)"
monitor="$(jq -r '.name' <<<"$spec")"
width="$(jq -r '.width' <<<"$spec")"
height="$(jq -r '.height' <<<"$spec")"
[ -n "$monitor" ] || continue
HYPR_SCREENSAVER_MONITOR="$monitor" \
HYPR_SCREENSAVER_WIDTH="$width" \
HYPR_SCREENSAVER_HEIGHT="$height" \
HYPR_SCREENSAVER_SOURCE="$source" \
"$script_path" session >/dev/null 2>&1 &
pid=$!
printf '%s\n' "$pid" > "$state_dir/${monitor}.pid"
sleep 0.15
done < <(monitor_specs)
if [ -n "$current_monitor" ]; then
hyprctl dispatch focusmonitor "$current_monitor" >/dev/null 2>&1 || true
fi fi
output="${HYPR_SCREENSAVER_OUTPUT:-ALL}"
layer="${HYPR_SCREENSAVER_LAYER:-overlay}"
options="$(mpv_options)"
log_event "start output=$output layer=$layer source=$source"
(
exec </dev/null
run_mpvpaper --layer "$layer" --mpv-options "$options" "$output" "$source"
) >>"$state_dir/mpvpaper.log" 2>&1 &
pid=$!
printf '%s\n' "$pid" > "$pid_file"
sleep 0.2
if ! kill -0 "$pid" 2>/dev/null; then
rm -f "$pid_file"
log_event "start failed: process exited early pid=$pid"
return 1
fi
log_event "start ok pid=$pid"
} }
stop() { stop() {
local pid pid_file local pid legacy_pid
for pid in $(screensaver_window_pids); do if [ -f "$pid_file" ]; then
kill "$pid" >/dev/null 2>&1 || true
done
shopt -s nullglob
for pid_file in "$state_dir"/*.pid; do
pid="$(<"$pid_file")" pid="$(<"$pid_file")"
log_event "stop pid=$pid"
kill "$pid" >/dev/null 2>&1 || true kill "$pid" >/dev/null 2>&1 || true
pkill -TERM -P "$pid" >/dev/null 2>&1 || true
rm -f "$pid_file" rm -f "$pid_file"
else
log_event "stop with no pid file"
fi
for legacy_pid in $(legacy_screensaver_window_pids); do
log_event "stop legacy pid=$legacy_pid"
kill "$legacy_pid" >/dev/null 2>&1 || true
done done
} }
session() {
local monitor="${HYPR_SCREENSAVER_MONITOR:?missing HYPR_SCREENSAVER_MONITOR}"
local width="${HYPR_SCREENSAVER_WIDTH:-1920}"
local height="${HYPR_SCREENSAVER_HEIGHT:-1080}"
local source="${HYPR_SCREENSAVER_SOURCE:-}"
local -a color_args=()
if [ -z "$source" ]; then
source="$(random_source || true)"
fi
if [ -z "$source" ]; then
source="$(default_source "$width" "$height")"
fi
while IFS= read -r -d '' arg; do
color_args+=("$arg")
done < <(mpv_color_args "$monitor")
local -a mpv_args=(
--no-config
--really-quiet
--fullscreen
--fs-screen-name="$monitor"
--screen-name="$monitor"
--force-window=immediate
--border=no
--title-bar=no
--ontop
--keep-open=yes
--loop-file=inf
--audio=no
--osc=no
--osd-level=0
--input-default-bindings=no
--wayland-app-id=hypr-screensaver
--title="${title_prefix}${monitor}"
--image-display-duration=inf
"${color_args[@]}"
"$source"
)
if command -v mpv >/dev/null 2>&1; then
exec mpv "${mpv_args[@]}"
fi
exec nix shell nixpkgs#mpv --command mpv "${mpv_args[@]}"
}
status() { status() {
is_running is_running
} }
@@ -293,7 +253,7 @@ case "${1:-}" in
status status
;; ;;
session) session)
session start
;; ;;
""|-h|--help|help) ""|-h|--help|help)
usage usage