tte screensaver

This commit is contained in:
2026-09-23 23:31:12 +02:00
parent 30619b00ce
commit aed6d46846
10 changed files with 183 additions and 5 deletions
+4 -4
View File
@@ -1,4 +1,4 @@
# hypridle.conf — idle stages: hyprsaver (5min) -> Noctalia lock (10min) -> DPMS off. # hypridle.conf — idle stages: screensaver (300s, backend in modules/screensaver.lua) -> Noctalia lock (10min) -> DPMS off.
# Managed by dotfiles (stow hypr). # Managed by dotfiles (stow hypr).
# #
# NOTE: this Hyprland (hyprmod Lua runtime, 0.56.x) evaluates `hyprctl dispatch` # NOTE: this Hyprland (hyprmod Lua runtime, 0.56.x) evaluates `hyprctl dispatch`
@@ -11,11 +11,11 @@ general {
after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms(true)' after_sleep_cmd = hyprctl dispatch 'hl.dsp.dpms(true)'
} }
# 5min: GPU screensaver (wlr-layer-shell overlay, any input dismisses it). # Screensaver via backend wrappers (switch backend in modules/screensaver.lua).
listener { listener {
timeout = 300 timeout = 300
on-timeout = hyprsaver on-timeout = ~/.config/hypr/scripts/screensaver-start.sh
on-resume = hyprsaver --quit on-resume = ~/.config/hypr/scripts/screensaver-stop.sh
} }
# 10min: lock via Noctalia (SUPER+L runs the same command). # 10min: lock via Noctalia (SUPER+L runs the same command).
+13
View File
@@ -0,0 +1,13 @@
-- Screensaver commands for idle (hypridle on-timeout/on-resume) and manual
-- (SUPER+SHIFT+L) use. scripts/screensaver-start.sh and
-- scripts/screensaver-stop.sh exec the .start / .stop command below.
--
-- Active backend: tte (fullscreen kitty + terminal-text-effects).
return {
start = "~/.config/hypr/scripts/launch-screensaver.sh",
stop = "~/.config/hypr/scripts/screensaver-stop.sh",
-- Alternative backend: hyprsaver (GPU fractal shaders, wlr-layer-shell).
-- Swap the commented pair in to switch (no other file changes needed).
-- start = "hyprsaver",
-- stop = "hyprsaver --quit",
}
+1 -1
View File
@@ -7,7 +7,7 @@ return {
settings = "noctalia msg settings-toggle", settings = "noctalia msg settings-toggle",
control_center = "noctalia msg panel-toggle control-center", control_center = "noctalia msg panel-toggle control-center",
lock = "noctalia msg session lock", lock = "noctalia msg session lock",
screensaver = "hyprsaver", screensaver = "sh ~/.config/hypr/scripts/screensaver-start.sh force",
wallpaper_select = "noctalia msg panel-toggle wallpaper", wallpaper_select = "noctalia msg panel-toggle wallpaper",
keybinds_show = "sh ~/.config/hypr/scripts/show-keybinds.sh", keybinds_show = "sh ~/.config/hypr/scripts/show-keybinds.sh",
} }
@@ -93,6 +93,16 @@ hl.window_rule({
border_size = 0, border_size = 0,
}) })
-- Manual tte screensaver preview (SUPER+SHIFT+L): fullscreen kitty.
hl.window_rule({
name = "screensaver-fullscreen",
match = {
class = "org.screensaver",
},
fullscreen = true,
float = true,
})
-- windowrule { -- windowrule {
-- name = no-rounding-steam-games -- name = no-rounding-steam-games
+2
View File
@@ -0,0 +1,2 @@
hyprland
// idle looks good on you
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# launch-screensaver.sh — idle-triggered tte screensaver (hypridle on-timeout).
# Spawns one fullscreen kitty running screensaver-tte.sh per monitor.
# Usage: launch-screensaver.sh [force]
# force ignore the screensaver-off toggle file.
# Toggle off: touch ~/.local/state/hypr/screensaver-off
#
# Lessons baked in: no focus-restore after spawn (it raced the new window and
# caused flash-then-nothing), no pkill -f patterns anywhere (teardown is done
# by screensaver-stop.sh via exact client PIDs).
set -euo pipefail
SCREENSAVER_CLASS="org.screensaver"
TOGGLE_FILE="$HOME/.local/state/hypr/screensaver-off"
command -v tte >/dev/null 2>&1 || exit 1
command -v kitty >/dev/null 2>&1 || exit 1
# Already running (precise client check).
if hyprctl clients -j 2>/dev/null | jq -e '.[] | select(.class=="org.screensaver")' >/dev/null 2>&1; then
exit 0
fi
# Opt-out toggle (unless forced, e.g. manual invocation).
if [[ -f "$TOGGLE_FILE" ]] && [[ "${1:-}" != "force" ]]; then
exit 1
fi
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# hyprctl prints plain-text errors (not JSON) when HYPRLAND_INSTANCE_SIGNATURE
# is stale (e.g. shell/tmux predates the current compositor). Fail clearly
# instead of dumping a jq parse error.
monitors_json="$(hyprctl monitors -j 2>/dev/null)" || {
echo "launch-screensaver: hyprctl failed - is HYPRLAND_INSTANCE_SIGNATURE stale? (try a fresh terminal)" >&2
exit 1
}
echo "$monitors_json" | jq -e . >/dev/null 2>&1 || {
echo "launch-screensaver: hyprctl did not return JSON - stale HYPRLAND_INSTANCE_SIGNATURE?" >&2
exit 1
}
# NOTE: hyprmod Lua runtime (Hyprland 0.56.x) evaluates `hyprctl dispatch` args
# as Lua — classic `dispatch focusmonitor eDP-1` fails. Dispatchers must be
# hl.dsp.* expressions. Focus each monitor before exec so the saver kitty
# spawns there; deliberately no focus-restore afterwards (spawn race).
for m in $(echo "$monitors_json" | jq -r '.[] | .name'); do
hyprctl dispatch "hl.dsp.focus({monitor=\"$m\"})" >/dev/null 2>&1
hyprctl dispatch "hl.dsp.exec_cmd('kitty --class=\"$SCREENSAVER_CLASS\" --override font_size=18 --override window_padding_width=0 --override background_opacity=1 --override mouse_hide_wait=1.0 -e \"$SCRIPT_DIR/screensaver-tte.sh\"')" >/dev/null 2>&1
done
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# screensaver-start.sh — exec the start command from modules/screensaver.lua.
# Usage: screensaver-start.sh [force] (forwarded to .sh targets, e.g. the tte
# launcher's toggle bypass; dropped for bare commands like `hyprsaver`).
set -euo pipefail
CMD="$(lua -e 'print(dofile(os.getenv("HOME").."/.config/hypr/modules/screensaver.lua").start)' 2>/dev/null)" || {
echo "screensaver-start: cannot read modules/screensaver.lua" >&2
exit 1
}
CMD="${CMD/#\~/$HOME}"
[[ -n "$CMD" ]] || {
echo "screensaver-start: empty start command" >&2
exit 1
}
if [[ "$CMD" == *.sh ]]; then
exec "$CMD" "$@"
else
# Bare command, may carry flags (e.g. "hyprsaver --quit").
# shellcheck disable=SC2086
exec $CMD
fi
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# screensaver-stop.sh — exec the stop command from modules/screensaver.lua.
# When it points back here (tte backend), run the built-in teardown below:
# kill saver windows by Hyprland client PID (class org.screensaver) plus
# stray tte processes (exact comm match). Never `pkill -f <pattern>`: a
# full-cmdline pattern also matches bystander shells merely mentioning it.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ME="$(realpath "$SCRIPT_DIR/screensaver-stop.sh")"
WANT="$(lua -e 'print(dofile(os.getenv("HOME").."/.config/hypr/modules/screensaver.lua").stop)' 2>/dev/null)" || {
echo "screensaver-stop: cannot read modules/screensaver.lua" >&2
exit 1
}
WANT="${WANT/#\~/$HOME}"
# Alternative backend configured (e.g. "hyprsaver --quit"): run it instead.
if [[ "$(realpath -m "$WANT")" != "$ME" ]]; then
# shellcheck disable=SC2086
exec $WANT
fi
# --- tte teardown ---
pids="$(hyprctl clients -j 2>/dev/null | jq -r '.[] | select(.class=="org.screensaver") | .pid' 2>/dev/null)" || pids=""
if [[ -n "${pids:-}" ]]; then
# shellcheck disable=SC2086
kill $pids 2>/dev/null || true
fi
# tte runs under comm "tte" (verified); -x matches the comm only, so this
# cannot hit bystander shells.
pkill -x tte 2>/dev/null || true
exit 0
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# screensaver-tte.sh — runs inside each fullscreen tte kitty.
# Loops random tte effects until any keypress, then exits (kitty closes).
# Child teardown uses the exact PID ($!) — never pkill -f patterns, which
# would also match bystander shells mentioning the class name.
set -euo pipefail
SCREENSAVER_TEXT="$HOME/.config/hypr/screensaver.txt"
trap 'exit 0' SIGINT SIGTERM SIGHUP SIGQUIT
printf '\033]11;rgb:00/00/00\007' # black background
input="$SCREENSAVER_TEXT"
[[ -f "$input" ]] || input="/etc/hostname"
while true; do
tte -i "$input" \
--frame-rate 60 --canvas-width 0 --canvas-height 0 \
--anchor-canvas c --anchor-text c \
--random-effect --exclude-effects dev_worm \
--no-eol --no-restore-cursor &
TPID=$!
# Wait for tte to appear (Python startup) before polling it.
for _ in $(seq 1 10); do
kill -0 "$TPID" 2>/dev/null || break
pgrep -x tte >/dev/null 2>&1 && break
sleep 0.1
done
while kill -0 "$TPID" 2>/dev/null; do
if read -r -n1 -t 1; then
kill "$TPID" 2>/dev/null || true
wait "$TPID" 2>/dev/null || true
exit 0
fi
done
wait "$TPID" 2>/dev/null || true
# Throttle respawn if tte exits instantly (e.g. a sub-second effect).
sleep 0.2
done
+1
View File
@@ -25,3 +25,4 @@ noctalia
@aur @aur
hyprmod hyprmod
hyprsaver hyprsaver
python-terminaltexteffects