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 {
lock_cmd = pidof hyprlock || hyprlock
before_sleep_cmd = loginctl lock-session
after_sleep_cmd = hyprctl dispatch dpms on
}
listener {
@@ -9,9 +8,3 @@ listener {
on-timeout = /home/imalison/dotfiles/dotfiles/lib/bin/hypr-screensaver start
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
else case button of
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
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 False
Gtk.widgetShowAll ebox

View File

@@ -2,8 +2,9 @@
set -euo pipefail
script_path="$(readlink -f "${BASH_SOURCE[0]}")"
state_dir="${XDG_RUNTIME_DIR:-/tmp}/hypr-screensaver"
pid_file="$state_dir/mpvpaper.pid"
event_log="$state_dir/events.log"
mkdir -p "$state_dir"
title_prefix="hypr-screensaver:"
@@ -15,11 +16,11 @@ usage() {
Usage: hypr-screensaver <start|stop|toggle|status|session>
Commands:
start Launch the screensaver on every Hyprland monitor.
stop Stop any running screensaver windows.
start Launch the screensaver as a Wayland layer-shell overlay.
stop Stop any running screensaver overlay.
toggle Start if stopped, otherwise stop.
status Exit 0 if any screensaver window is running, otherwise exit 1.
session Run the configured screensaver payload for one monitor.
status Exit 0 if the screensaver overlay is running, otherwise exit 1.
session Compatibility alias for start.
By default, start chooses a random media file from:
/var/lib/syncthing/sync/Screensaver/use
@@ -33,6 +34,10 @@ example:
You can also override the rotation directory:
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.
Only monitors with preset "hdr" or "hdredid" get HDR colorspace hints. Override
with:
@@ -46,28 +51,11 @@ monitors_json() {
hyprctl -j monitors
}
monitor_names() {
monitors_json | jq -r '.[].name'
log_event() {
printf '%s %s\n' "$(date --iso-8601=seconds)" "$*" >>"$event_log"
}
monitor_specs() {
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() {
legacy_screensaver_window_pids() {
hyprctl -j clients 2>/dev/null | jq -r --arg prefix "$title_prefix" '
.[]
| select((.title // "") | startswith($prefix))
@@ -77,27 +65,28 @@ screensaver_window_pids() {
is_running() {
local pid
for pid in $(screensaver_window_pids); do
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
if [ -f "$pid_file" ]; then
pid="$(<"$pid_file")"
if kill -0 "$pid" 2>/dev/null; then
return 0
fi
done
rm -f "$pid_file"
fi
return 1
}
default_source() {
local width="$1"
local height="$2"
local size width height
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' \
"$width" "$height"
}
@@ -128,10 +117,8 @@ random_source() {
printf '%s\n' "${candidates[$((RANDOM % ${#candidates[@]}))]}"
}
monitor_uses_hdr() {
local monitor="$1"
screensaver_uses_hdr() {
local mode="${HYPR_SCREENSAVER_HDR_MODE:-auto}"
local preset
case "$mode" in
hdr)
@@ -141,15 +128,8 @@ monitor_uses_hdr() {
return 1
;;
auto)
preset="$(monitor_color_management_preset "$monitor" 2>/dev/null || true)"
case "$preset" in
hdr|hdredid)
return 0
;;
*)
return 1
;;
esac
monitors_json 2>/dev/null \
| jq -e 'any(.[]; (.colorManagementPreset // "srgb") == "hdr" or (.colorManagementPreset // "srgb") == "hdredid")' >/dev/null 2>&1
;;
*)
printf 'Invalid HYPR_SCREENSAVER_HDR_MODE=%s; expected auto, sdr, or hdr\n' "$mode" >&2
@@ -158,119 +138,99 @@ monitor_uses_hdr() {
esac
}
mpv_color_args() {
local monitor="$1"
if monitor_uses_hdr "$monitor"; then
printf '%s\0' \
--target-colorspace-hint=yes \
--target-colorspace-hint-mode=source
mpv_color_options() {
if screensaver_uses_hdr; then
printf '%s ' \
target-colorspace-hint=yes \
target-colorspace-hint-mode=source
return
fi
printf '%s\0' \
--target-colorspace-hint=no \
--target-prim=bt.709 \
--target-trc=srgb \
--target-gamut=bt.709 \
--target-peak=80 \
--inverse-tone-mapping=no
printf '%s ' \
target-colorspace-hint=no \
target-prim=bt.709 \
target-trc=srgb \
target-gamut=bt.709 \
target-peak=80 \
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() {
local current_monitor spec monitor width height pid source
local source output layer options pid
if is_running; then
log_event "start ignored: already running pid=$(<"$pid_file")"
exit 0
fi
current_monitor="$(focused_monitor || true)"
stop
source="${HYPR_SCREENSAVER_SOURCE:-}"
if [ -z "$source" ]; then
source="$(random_source || true)"
fi
while IFS= read -r spec; do
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
if [ -z "$source" ]; then
source="$(default_source)"
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() {
local pid pid_file
local pid legacy_pid
for pid in $(screensaver_window_pids); do
kill "$pid" >/dev/null 2>&1 || true
done
shopt -s nullglob
for pid_file in "$state_dir"/*.pid; do
if [ -f "$pid_file" ]; then
pid="$(<"$pid_file")"
log_event "stop pid=$pid"
kill "$pid" >/dev/null 2>&1 || true
pkill -TERM -P "$pid" >/dev/null 2>&1 || true
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
}
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() {
is_running
}
@@ -293,7 +253,7 @@ case "${1:-}" in
status
;;
session)
session
start
;;
""|-h|--help|help)
usage