#!/bin/sh # Idle-timeout photo slideshow. Installed to /usr/local/bin/idle-gallery, invoked # directly by swayidle's `timeout`/`resume` commands in configs/sway/config — not a # sway `exec_always`, so the PID-lock-guard idiom in fullscreen-watcher.sh and # gesture-control.sh (needed there to survive repeated `swaymsg reload`) does not # apply here; swayidle only ever calls this on its own timeout/resume edges, so plain # idempotent checks (`mountpoint -q`, `pgrep -f`) are enough. # # Two-tier fallback, both ways: no gallery credentials configured, an unreachable # share, or an empty share must all fall back to the plain output-blank that this # script's caller (configs/sway/config) previously did unconditionally — a half-lit # fullscreen error is worse than blanking, and this is the ONLY thing standing between # an idle kiosk and screen burn-in, so it must never just silently do nothing. set -eu CONFIG_ENV=/etc/thinclient-agent/config.env CREDENTIALS=/etc/thinclient-agent/gallery-credentials MOUNTPOINT=/mnt/gallery PLAYLIST="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}/idle-gallery-playlist.m3u" # Distinguishes this mpv instance from one a person (or thinclient-agent) launched # deliberately, so `stop` can pkill exactly it and nothing else — see mpv.conf's own # --input-ipc-server for why a marker in argv, not a socket, is the simpler match here. MARKER="--playlist=$PLAYLIST" DISPLAY_SECONDS=10 blank() { swaymsg "output * power off" >/dev/null 2>&1 || true } # Weather/clock overlay (configs/eww/eww.yuck's weather-overlay window) — this # script is the sole owner of its visibility, the same way fullscreen-watcher.sh # owns the now-playing widget's. Best-effort throughout: an image built without # eww (0800-eww-widget.hook.chroot's documented placeholder path) or a session # where the daemon has not started yet must never turn a slideshow failure into a # script failure, so every step here is allowed to silently no-op. show_weather_overlay() { command -v eww >/dev/null 2>&1 || return 0 eww daemon >/dev/null 2>&1 || true eww open weather-overlay >/dev/null 2>&1 || true } hide_weather_overlay() { command -v eww >/dev/null 2>&1 || return 0 eww close weather-overlay >/dev/null 2>&1 || true } stop_slideshow() { pkill -u "$(id -u)" -f "mpv .*${MARKER}" 2>/dev/null || true hide_weather_overlay } if [ "${1:-start}" = "stop" ]; then stop_slideshow exit 0 fi # Already running (e.g. a second timeout fired before the first resume) — nothing to do. if pgrep -u "$(id -u)" -f "mpv .*${MARKER}" >/dev/null 2>&1; then exit 0 fi if [ ! -r "$CREDENTIALS" ]; then echo "idle-gallery: $CREDENTIALS not set up yet — see hosts/thin-client/README.md. Blanking instead." >&2 blank exit 0 fi # shellcheck disable=SC1090 . "$CONFIG_ENV" 2>/dev/null || true if [ -z "${GALLERY_SMB_HOST:-}" ]; then echo "idle-gallery: GALLERY_SMB_HOST is unset in $CONFIG_ENV. Blanking instead." >&2 blank exit 0 fi if ! mountpoint -q "$MOUNTPOINT" 2>/dev/null; then sudo mkdir -p "$MOUNTPOINT" # soft + a short connect timeout: a share that is merely unreachable right now (the # container host is off, the network hiccuped) must fail in a few seconds, not hang # the whole idle-transition — same "never block on a remote service" rule Phase # 11.10 already applies to thinclient-agent's own MQTT connection. if ! sudo mount -t cifs "//${GALLERY_SMB_HOST}/gallery" "$MOUNTPOINT" \ -o "credentials=${CREDENTIALS},ro,vers=3.0,soft,retry=0,echo_interval=5,uid=$(id -u),gid=$(id -g)" \ 2>/tmp/idle-gallery-mount.err then echo "idle-gallery: mount failed — $(cat /tmp/idle-gallery-mount.err 2>/dev/null). Blanking instead." >&2 blank exit 0 fi fi find "$MOUNTPOINT" -type f \( \ -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \ \) 2>/dev/null | shuf > "$PLAYLIST" if [ ! -s "$PLAYLIST" ]; then echo "idle-gallery: $MOUNTPOINT has no images. Blanking instead." >&2 rm -f "$PLAYLIST" blank exit 0 fi # Backgrounded, not exec'd: swayidle expects its timeout command to return, not to # become the slideshow for as long as it runs. --fs, not --fullscreen: sway's own # `for_window [app_id="mpv"] fullscreen enable` already applies, this just tells mpv # not to draw window decorations in the instant before that rule lands. app_id stays # the plain mpv one on purpose — fullscreen-watcher.sh's now-playing widget already # treats any fullscreen mpv window as "visual content on screen" and hides itself, # which is exactly the right behaviour here too. mpv --fs --no-osc --idle=no --image-display-duration="$DISPLAY_SECONDS" \ --loop-playlist=inf --shuffle "--playlist=$PLAYLIST" \ >/tmp/idle-gallery-mpv.log 2>&1 & disown # After mpv is actually launched, not before — a failed launch above (this whole # block is under `set -e`, so mpv itself failing to start would already have exited # the script) must never leave the overlay showing over a blank/off panel. show_weather_overlay