Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 81 additions & 48 deletions scripts/stoa-screensaver.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ STOA_SS_CONF="${XDG_CONFIG_HOME:-$HOME/.config}/stoa/screensaver.conf"
STOA_SS_VIDEO="${STOA_SS_DIR}/marble-flow.mp4"
STOA_SS_LOCK="${XDG_RUNTIME_DIR:-/tmp}/stoa-screensaver-${UID}.lock"

# ── ImageMagick detection ──
# Support both IM7 (`magick`) and IM6 (`convert`). The plasma/CLUT/draw
# syntax used below is identical across both versions.
if command -v magick &>/dev/null; then
IM=(magick)
elif command -v convert &>/dev/null; then
IM=(convert)
else
IM=()
fi

# ── Stoa Palette ──
BG="#211e19"
BG_LIGHT="#2d2921"
Expand Down Expand Up @@ -51,33 +62,29 @@ _ss_get() {
# ── Generation ──

_gen_gradient_clut() {
# Create a color lookup table from Stoa palette
# This maps plasma grayscale → Stoa colors with smooth transitions
local clut_path="${STOA_SS_DIR}/stoa-clut.png"

magick -size 1x1 xc:"$BG" xc:"$BG_LIGHT" xc:"$STONE" xc:"$BRONZE" xc:"$GOLD" xc:"$OLIVE" \
"${IM[@]}" -size 1x1 xc:"$BG" xc:"$BG_LIGHT" xc:"$STONE" xc:"$BRONZE" xc:"$GOLD" xc:"$OLIVE" \
+append -filter Cubic -resize 256x1! \
"$clut_path"

echo "$clut_path"
}

_gen_warm_clut() {
# Warm variant: bg → terracotta → bronze → gold
local clut_path="${STOA_SS_DIR}/stoa-clut-warm.png"

magick -size 1x1 xc:"$BG" xc:"#1a1714" xc:"$TERRACOTTA" xc:"$BRONZE" xc:"$GOLD" xc:"$FG" \
"${IM[@]}" -size 1x1 xc:"$BG" xc:"#1a1714" xc:"$TERRACOTTA" xc:"$BRONZE" xc:"$GOLD" xc:"$FG" \
+append -filter Cubic -resize 256x1! \
"$clut_path"

echo "$clut_path"
}

_gen_cool_clut() {
# Cool variant: bg → stone → olive → bronze
local clut_path="${STOA_SS_DIR}/stoa-clut-cool.png"

magick -size 1x1 xc:"$BG" xc:"#1a1714" xc:"$STONE" xc:"$OLIVE" xc:"$BRONZE" xc:"$BG_LIGHT" \
"${IM[@]}" -size 1x1 xc:"$BG" xc:"#1a1714" xc:"$STONE" xc:"$OLIVE" xc:"$BRONZE" xc:"$BG_LIGHT" \
+append -filter Cubic -resize 256x1! \
"$clut_path"

Expand All @@ -88,6 +95,17 @@ generate() {
local style="${1:-marble}"
mkdir -p "$STOA_SS_DIR"

# Dependency check — fail loudly instead of producing an empty video.
local missing=()
[ ${#IM[@]} -eq 0 ] && missing+=("imagemagick (magick or convert)")
command -v ffmpeg &>/dev/null || missing+=("ffmpeg")
if [ ${#missing[@]} -gt 0 ]; then
echo " ERROR: missing dependencies:" >&2
printf ' - %s\n' "${missing[@]}" >&2
echo " Install on Arch: sudo pacman -S imagemagick ffmpeg" >&2
return 1
fi

echo " Generating Stoa screensaver ($style)..."
echo ""

Expand Down Expand Up @@ -138,7 +156,7 @@ generate() {
# 3. Map grayscale to Stoa colors via CLUT
# 4. Add subtle vignette for depth
# 5. Scale to full HD
magick -size ${gen_w}x${gen_h} -seed "$seed" plasma: \
if ! "${IM[@]}" -size ${gen_w}x${gen_h} -seed "$seed" plasma: \
-blur 0x${blur_sigma} \
-modulate "$((85 + phase % 20)),70,100" \
"$current_clut" -clut \
Expand All @@ -149,7 +167,13 @@ generate() {
-compose multiply -composite \
-resize 1920x1080! \
-quality 92 \
"$(printf '%s/frame_%04d.jpg' "$frame_dir" "$i")"
"$(printf '%s/frame_%04d.jpg' "$frame_dir" "$i")"; then
echo "" >&2
echo " ERROR: ImageMagick failed rendering frame $i." >&2
echo " Check /etc/ImageMagick-*/policy.xml for disabled coders (plasma)." >&2
rm -rf "$frame_dir"
return 1
fi

# Progress
progress=$(( (i + 1) * 100 / total_frames ))
Expand All @@ -161,13 +185,18 @@ generate() {
echo ""
echo " Encoding video..."

# Encode with ffmpeg — high quality, smooth looping
ffmpeg -y -framerate "$fps" \
# Encode with ffmpeg — high quality, smooth looping.
# Keep stderr visible so codec/policy issues are diagnosable.
if ! ffmpeg -y -hide_banner -loglevel error -framerate "$fps" \
-i "${frame_dir}/frame_%04d.jpg" \
-c:v libx264 -preset slow -crf 18 \
-pix_fmt yuv420p \
-movflags +faststart \
"$STOA_SS_VIDEO" 2>/dev/null
"$STOA_SS_VIDEO"; then
echo " ERROR: ffmpeg failed encoding $STOA_SS_VIDEO" >&2
rm -rf "$frame_dir"
return 1
fi

# Clean up frames
rm -rf "$frame_dir"
Expand All @@ -188,52 +217,56 @@ start() {
return
fi

if ! command -v mpv &>/dev/null; then
echo "stoa-screensaver: mpv not installed." >&2
return 1
fi

# Generate if not exists
if [ ! -f "$STOA_SS_VIDEO" ]; then
generate
if ! generate; then
return 1
fi
fi

# Dim the screen slightly before starting
local original_brightness
original_brightness=$(brightnessctl get 2>/dev/null)

if [ -n "$WAYLAND_DISPLAY" ]; then
# Wayland: mpv fullscreen, any key/mouse exits
mpv --fullscreen --loop-file=inf --no-audio \
--no-input-default-bindings \
--input-conf=/dev/null \
--osd-level=0 \
--no-osc --no-terminal \
--input-vo-keyboard=yes \
--force-window=yes \
--idle=no \
"$STOA_SS_VIDEO" &
local mpv_pid=$!
echo "$mpv_pid" > "$STOA_SS_LOCK"

# Wait for any input event to kill it
# mpv will catch keyboard via its window
wait "$mpv_pid" 2>/dev/null
else
# Xorg: same approach
mpv --fullscreen --loop-file=inf --no-audio \
--no-input-default-bindings \
--input-conf=/dev/null \
--osd-level=0 \
--no-osc --no-terminal \
--input-vo-keyboard=yes \
--force-window=yes \
--idle=no \
"$STOA_SS_VIDEO" &
local mpv_pid=$!
echo "$mpv_pid" > "$STOA_SS_LOCK"
wait "$mpv_pid" 2>/dev/null
local original_brightness=""
if command -v brightnessctl &>/dev/null; then
original_brightness=$(brightnessctl get 2>/dev/null)
fi

# Minimal keybindings: any key (or mouse click) exits.
# We use --input-conf with a tiny generated file rather than disabling
# bindings entirely (the previous version made the screensaver impossible
# to dismiss with the keyboard).
local mpv_conf="${STOA_SS_DIR}/mpv-input.conf"
cat > "$mpv_conf" <<'EOF'
ANY_UNICODE quit
ESC quit
ENTER quit
SPACE quit
MBTN_LEFT quit
MBTN_RIGHT quit
MBTN_MID quit
EOF

mpv --fullscreen --loop-file=inf --no-audio \
--no-config \
--input-conf="$mpv_conf" \
--osd-level=0 \
--no-osc --no-terminal \
--cursor-autohide=always \
--force-window=yes \
--idle=no \
"$STOA_SS_VIDEO" &
local mpv_pid=$!
echo "$mpv_pid" > "$STOA_SS_LOCK"
wait "$mpv_pid" 2>/dev/null

rm -f "$STOA_SS_LOCK"

# Restore brightness
if [ -n "$original_brightness" ]; then
if [ -n "$original_brightness" ] && command -v brightnessctl &>/dev/null; then
brightnessctl set "$original_brightness" -q 2>/dev/null
fi
}
Expand Down
45 changes: 31 additions & 14 deletions scripts/stoa-walls.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,53 @@ mkdir -p "$WALLDIR"
WIDTH=1920
HEIGHT=1080

# Require ImageMagick 7 (`magick`). ImageMagick 6 (`convert`) is not
# supported because the rectangle/gradient syntax we use below differs.
if ! command -v magick &>/dev/null; then
echo "stoa-walls: 'magick' (ImageMagick 7) not found." >&2
# Use ImageMagick 7 (`magick`) when available, fall back to IM6 (`convert`).
# The rectangle/gradient/CLUT syntax we use below is identical in both.
if command -v magick &>/dev/null; then
IM=(magick)
elif command -v convert &>/dev/null; then
IM=(convert)
else
echo "stoa-walls: ImageMagick not found (neither 'magick' nor 'convert')." >&2
echo " Install: sudo pacman -S imagemagick" >&2
exit 1
fi

# Pick a usable mono font for the 'memento' wallpaper. ImageMagick matches
# fontconfig families: 'JetBrains Mono' (space) is the canonical name; the
# hyphenated form some docs show is not always resolved. Fall back gracefully.
_pick_font() {
local candidates=("JetBrains Mono" "JetBrainsMono-Regular" "JetBrains-Mono" \
"DejaVu Sans Mono" "DejaVu-Sans-Mono" "monospace")
for f in "${candidates[@]}"; do
if "${IM[@]}" -list font 2>/dev/null | grep -qiE "^[[:space:]]*(Font:[[:space:]]*)?${f}\$"; then
echo "$f"; return
fi
done
# Last resort: let IM pick its default
echo ""
}
FONT=$(_pick_font)

echo "Generating Stoic wallpapers..."

# 1. Marble — subtle gradient
magick -size ${WIDTH}x${HEIGHT} \
"${IM[@]}" -size ${WIDTH}x${HEIGHT} \
gradient:"#2d2921-#1a1714" \
-blur 0x2 \
"$WALLDIR/marble.png"
echo " [+] marble.png"

# 2. Parchment — warm tone
magick -size ${WIDTH}x${HEIGHT} \
"${IM[@]}" -size ${WIDTH}x${HEIGHT} \
gradient:"#211e19-#1a1714" \
-fill "#c49a5c" -draw "rectangle 0,$((HEIGHT/2-1)),${WIDTH},$((HEIGHT/2+1))" \
-blur 0x40 \
"$WALLDIR/parchment.png"
echo " [+] parchment.png"

# 3. Columns — minimalist vertical lines
magick -size ${WIDTH}x${HEIGHT} \
"${IM[@]}" -size ${WIDTH}x${HEIGHT} \
xc:"#1a1714" \
-fill "#2d2921" \
-draw "rectangle $((WIDTH/2-60)),0,$((WIDTH/2-58)),${HEIGHT}" \
Expand All @@ -49,13 +69,10 @@ magick -size ${WIDTH}x${HEIGHT} \
echo " [+] columns.png"

# 4. Minimalist with text
magick -size ${WIDTH}x${HEIGHT} \
xc:"#1a1714" \
-gravity center \
-font "JetBrains-Mono" -pointsize 18 \
-fill "#4a4540" \
-annotate +0+0 "MEMENTO MORI" \
"$WALLDIR/memento.png"
memento_args=(-size ${WIDTH}x${HEIGHT} xc:"#1a1714" -gravity center)
[ -n "$FONT" ] && memento_args+=(-font "$FONT")
memento_args+=(-pointsize 18 -fill "#4a4540" -annotate +0+0 "MEMENTO MORI" "$WALLDIR/memento.png")
"${IM[@]}" "${memento_args[@]}"
echo " [+] memento.png"

echo ""
Expand Down
Loading