commit 873a7cddf7a811784577800ba987df9ab84ddb79
Author: mrfox <jens.se@icloud.com>
AuthorDate: Mon Sep 21 07:00:14 2026 +0200
Commit: mrfox <jens.se@icloud.com>
CommitDate: Mon Sep 21 07:00:14 2026 +0200
screensaver: pause tte in place on DPMS blank instead of killing the window
241914c had screensaverd kill the screensaver window the moment DPMS
blanked the display, to stop it burning CPU into a screen nobody could
see. That backfired: confirmed live that creating *or* destroying an X
window forces the display live again on this hardware (mapping/killing
an unrelated window while `xset dpms force off` immediately flipped
"Monitor is Off" back to "On"; writing new content into an
already-mapped window did not). So killing the window woke the display
right back up, and with the machine still idle screensaverd relaunched
the screensaver -- which woke it again. The screen never actually
stayed off; the 5-minute DPMS timer kept getting reset.
Moved the DPMS check into bin/screensaver itself: it now pauses (kills
just the tte process) without touching its own window, and resumes when
DPMS wakes on its own from real input -- same window throughout, so
nothing ever forces the display live except genuine activity. screensaverd
goes back to only killing the window on real activity/pause/media, plus
keeps the DPMS check as a launch-guard (don't map a new window while
already blanked, for the same reason).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---
README.md | 19 +++++++++++++++----
bin/screensaver | 29 +++++++++++++++++++++++++++++
bin/screensaverd | 25 +++++++++++++++++--------
3 files changed, 61 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index d8d8c38..107c24e 100644
--- a/README.md
+++ b/README.md
@@ -396,10 +396,21 @@ Installed to `~/.local/bin` by `install.sh` and wired into `sxwmrc`/`~/.xinitrc`
The screensaver renders `bin/screensaver.txt` through random
[terminaltexteffects](https://github.com/ChrisBuilds/terminaltexteffects)
animations (installed via `pipx` during setup) inside a fullscreen st
-window, and exits the instant you touch the mouse or keyboard. It also stops
-itself the moment DPMS blanks the display or media starts playing (checked
-every second by `screensaverd`), so it never keeps animating — and burning
-CPU/GPU — into a screen nobody can see.
+window, and exits the instant you touch the mouse or keyboard.
+`screensaverd` also fully stops it (kills the window) if media starts
+playing or you're paused via `screensaver-toggle`, checked every second.
+
+Separately, the screensaver script *pauses itself* — stopping `tte` but
+leaving its window alone — the moment DPMS blanks the display, so it never
+keeps animating (and burning CPU/GPU) into a screen nobody can see. This has
+to happen from inside the script rather than `screensaverd` killing the
+window from outside: mapping *or* destroying an X window forces the display
+live again on this hardware (confirmed live with `xset dpms force off`, then
+mapping or killing an unrelated window — writing into an already-mapped
+window doesn't), so killing the window the instant it blanks just wakes it
+right back up, and with the machine still idle, `screensaverd` relaunches
+the screensaver, which wakes it again — the screen never actually stayed
+off.
Idle timeout, frame rate, cell size and art are all environment variables,
read fresh each time `screensaverd` launches it — set them in `~/.xinitrc`
diff --git a/bin/screensaver b/bin/screensaver
index 04205c0..3eca377 100755
--- a/bin/screensaver
+++ b/bin/screensaver
@@ -12,6 +12,23 @@ ART_FILE="${SCREENSAVER_ART:-$SCRIPT_DIR/screensaver.txt}"
FRAME_RATE="${SCREENSAVER_FPS:-30}"
WIN_CLASS="screensaver"
+# DPMS blanking (xset dpms, 300s by default -- see ~/.xinitrc) turns the
+# display off but doesn't stop this from rendering into its window, so
+# without this tte just kept animating into a screen nobody could see,
+# burning CPU/GPU for as long as the machine stayed idle.
+#
+# This has to pause tte in place rather than screensaverd killing the
+# window from outside: creating *or* destroying an X window forces the
+# display live again (confirmed live -- mapping or killing an unrelated
+# window while DPMS-off flips "Monitor is Off" back to "On" instantly;
+# writing new content into an already-mapped window does not), so killing
+# the window the moment it blanks just wakes it right back up, and with
+# screensaverd still seeing >IDLE_SECONDS of idle, relaunches the
+# screensaver -- which wakes it again. The screen never actually stayed off.
+monitor_off() {
+ xset q 2>/dev/null | grep -q "Monitor is \(Off\|Standby\|Suspend\)"
+}
+
screensaver_in_focus() {
local id
id=$(xdotool getactivewindow 2>/dev/null) || return 1
@@ -32,6 +49,14 @@ tput civis 2>/dev/null # hide text cursor
tty=$(tty 2>/dev/null)
while true; do
+ # Blanked: sit idle without touching the window at all (see monitor_off
+ # above for why) until either DPMS wakes on its own or screensaverd kills
+ # this whole window from outside because of real input.
+ if monitor_off; then
+ sleep 1
+ continue
+ fi
+
tte -i "$ART_FILE" \
--frame-rate "$FRAME_RATE" --canvas-width 0 --canvas-height 0 --reuse-canvas --anchor-canvas c --anchor-text c \
--random-effect --no-eol --no-restore-cursor &
@@ -40,5 +65,9 @@ while true; do
if read -n1 -t 1 || ! screensaver_in_focus; then
exit_screensaver
fi
+ if monitor_off; then
+ pkill -x tte 2>/dev/null
+ break
+ fi
done
done
diff --git a/bin/screensaverd b/bin/screensaverd
index e3e4080..0462806 100755
--- a/bin/screensaverd
+++ b/bin/screensaverd
@@ -1,7 +1,18 @@
#!/bin/bash
# Idle watcher: launches the screensaver after IDLE_SECONDS of inactivity
-# and kills it the moment real activity resumes, or the display blanks via
-# DPMS -- whichever comes first.
+# and kills it the moment real activity resumes.
+#
+# DPMS blanking is handled inside bin/screensaver itself (it pauses tte
+# without touching the window) rather than here -- destroying/creating the
+# fullscreen window from this end wakes the display right back up (creating
+# *or* destroying an X window forces the DRM connector live again to show
+# whatever's now on screen, confirmed live: `xset dpms force off` then
+# mapping or killing an unrelated window immediately flipped "Monitor is
+# Off" back to "On"; writing new content into an *already-mapped* window
+# does not). Killing the window here used to retrigger exactly that: blank
+# -> this script kills the screensaver window -> killing it wakes the
+# display -> still idle, so the screensaver relaunches -> new window wakes
+# it again -- the screen never actually stayed off.
set -u
# sxwm's `exec` runs commands via execvp with whatever PATH the X session
@@ -36,11 +47,9 @@ media_playing() {
[ "$(playerctl status 2>/dev/null)" = "Playing" ]
}
-# DPMS blanking (xset dpms, 300s by default -- see ~/.xinitrc) turns the
-# physical display off but doesn't stop X from rendering into windows, so
-# without this the screensaver just kept animating at --frame-rate 120 into
-# a screen nobody can see, burning CPU (and on this machine, spinning the
-# fans at 100%) for as long as the machine stayed idle.
+# Only used to avoid *launching* (mapping a new window) while already
+# blanked, which would wake the display right back up for no reason -- see
+# the file header for why this isn't also used to stop it.
monitor_off() {
xset q 2>/dev/null | grep -q "Monitor is \(Off\|Standby\|Suspend\)"
}
@@ -59,7 +68,7 @@ while true; do
if (( idle_s >= IDLE_SECONDS )) && ! is_running && ! media_playing && ! is_paused && ! monitor_off; then
"$SCRIPT_DIR/screensaver-launch"
- elif is_running && { (( idle_s < 1 )) || media_playing || is_paused || monitor_off; }; then
+ elif is_running && { (( idle_s < 1 )) || media_playing || is_paused; }; then
stop_screensaver
fi