#!/bin/bash
# Idle watcher: launches the screensaver after IDLE_SECONDS of inactivity
# and kills it the moment real activity resumes.
set -u
# sxwm's `exec` runs commands via execvp with whatever PATH the X session
# started with, which may not include a pipx-installed `tte`. Make sure it's
# found regardless.
export PATH="$HOME/.local/bin:$PATH"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
IDLE_SECONDS="${SCREENSAVER_IDLE_SECONDS:-150}"
WIN_CLASS="screensaver"
PAUSE_FILE="${XDG_RUNTIME_DIR:-/tmp}/screensaver-paused"
is_running() {
xdotool search --class "^${WIN_CLASS}\$" >/dev/null 2>&1
}
# Set by screensaver-toggle. Checked fresh every loop iteration (not just
# at launch time) so toggling pause while the screensaver is already
# showing kills it immediately, same as real input would.
is_paused() {
[[ -e "$PAUSE_FILE" ]]
}
# xssstate only tracks keyboard/mouse input, so a video playing quietly
# (no input for the whole runtime) still counts as "idle" and would get
# covered by the screensaver. playerctl (already a dependency for the
# media keybindings) reports MPRIS playback state for players that
# support it (mpv, VLC, Firefox/Chromium, ...), so use that as an extra
# "really idle" check.
media_playing() {
command -v playerctl >/dev/null 2>&1 || return 1
[ "$(playerctl status 2>/dev/null)" = "Playing" ]
}
stop_screensaver() {
pkill -f "$SCRIPT_DIR/screensaver\$" 2>/dev/null
pkill -x tte 2>/dev/null
for win in $(xdotool search --class "^${WIN_CLASS}\$" 2>/dev/null); do
xdotool windowkill "$win" 2>/dev/null
done
}
while true; do
idle_ms=$(xssstate -i)
idle_s=$((idle_ms / 1000))
if (( idle_s >= IDLE_SECONDS )) && ! is_running && ! media_playing && ! is_paused; then
"$SCRIPT_DIR/screensaver-launch"
elif is_running && { (( idle_s < 1 )) || media_playing || is_paused; }; then
stop_screensaver
fi
sleep 1
done