wren-linux / bin / wallpaper
#!/bin/bash
# wallpaper: written by Wren Linux parts/wallpaper.sh
# Your own picture on the desktop: the newest JPEG or PNG in
# ~/Pictures/wren/desktop becomes the wallpaper; with none there, the default: a
# wren for the theme in use, so a theme switch (it reloads sway) swaps the wren.
# A background of your own in your sway config ("output * bg") stays instead.
# sway runs it at every start and reload (parts/wallpaper.sh).
# wallpaper put the picture up (or nothing, if there is none)
# wallpaper next the next picture in ~/Pictures/Wallpapers (Super+]),
# wallpaper previous or the one before (Super+[), up at once and kept
# wallpaper pick choose one from ~/Pictures/Wallpapers by name (Super+Shift+w),
# or "0 No wallpaper"
# wallpaper none no wallpaper (Super+\): the theme's plain background again
# wallpaper status which picture is used, and which files aren't, and why
# A picture chosen with next, previous or pick is copied into the desktop
# folder as chosen.jpg (or .png), so it is the newest there and stays after a
# reboot; a picture you copy in yourself later wins over it. "No wallpaper"
# stays the same way: until you choose one, or copy a newer picture in, or
# wrenctl remove wallpapers ends it (extras/wallpapers.sh).
set -uo pipefail
DIR=${WREN_WALLPAPER_DIR:-$HOME/Pictures/wren/desktop}
COLL=${WREN_WALLPAPERS:-$HOME/Pictures/Wallpapers}
CACHE=${XDG_CACHE_HOME:-$HOME/.cache}/wren
# the collection's file last chosen, for next and previous ("none": no
# wallpaper, since this file's time)
CURRENT=$CACHE/chosen-wallpaper
MAX_BYTES=$((50 * 1024 * 1024))
DEFAULTS=$HOME/.local/share/wren/wallpapers
# why_not FILE: empty if FILE can be the wallpaper, else the reason
why_not() {
local f=$1 magic
case ${f,,} in
*.jpg | *.jpeg | *.png) ;;
*.webp) echo "a WebP picture: only JPEG and PNG can be wallpapers (convert it, or save a JPEG)"; return ;;
*) echo "not a .jpg, .jpeg or .png"; return ;;
esac
[ -f "$f" ] && [ -r "$f" ] || { echo "can't be read"; return; }
[ "$(stat -L -c %s -- "$f")" -le "$MAX_BYTES" ] || { echo "bigger than 50 MB"; return; }
# what the file really is, by its first bytes, not its name
magic=$(head -c 8 -- "$f" | od -An -tx1 | tr -d ' \n')
case $magic in
89504e470d0a1a0a) ;;
ffd8ff*) ;;
*) echo "not really a JPEG or PNG picture" ;;
esac
}
# newest: the newest picture in DIR that can be used
newest() {
local f best=
for f in "$DIR"/*; do
[ -e "$f" ] || continue
[ -z "$(why_not "$f")" ] || continue
if [ -z "$best" ] || [ "$f" -nt "$best" ]; then best=$f; fi
done
[ -n "$best" ] && echo "$best"
}
# default_picture: the wren for the theme in use, unless your sway config sets a
# background of its own, or the defaults aren't here
default_picture() {
local theme f
if grep -qsE '^[[:space:]]*output[[:space:]].*[[:space:]]bg[[:space:]]' "$HOME/.config/sway/config"; then return 1; fi
theme=$(head -c 16 "${XDG_CONFIG_HOME:-$HOME/.config}/wren/theme" 2>/dev/null)
[ "$theme" = light ] || theme=dark
f=$DEFAULTS/default-$theme.jpg
[ -f "$f" ] && echo "$f"
}
# shown TEXT: TEXT without control characters (a file name can hold escape codes)
shown() { printf '%s' "$1" | tr -d '\000-\037\177'; }
# collection: the usable pictures in COLL, one a line, by name
collection() {
local f
for f in "$COLL"/*; do
[ -e "$f" ] || continue
case ${f##*/} in *$'\n'*) continue ;; esac
[ -z "$(why_not "$f")" ] && printf '%s\n' "$f"
done
}
# label FILE: what to call it: its title from CREDITS.txt, after its source
# ("Webb: Stars brewing in a nebulous cauldron"), else its tidied file name
label() {
local base=${1##*/} src="" title
# (the name on a line of its own, its title under it; or, from an install from before,
# all of it on one line: "NAME: TITLE. Credit: ...")
title=$(awk -v n="$base" -v b="$base: " '
$0 == n { getline; if (sub(/^ *Title: */, "")) print; exit }
index($0, b) == 1 { print substr($0, length(b) + 1); exit }' "$COLL/CREDITS.txt" 2>/dev/null)
title=${title%%. Credit:*}
case $base in
nasa_*) src=NASA ;; webb_*) src=Webb ;;
earth_*) src="Earth Observatory" ;; budgie_*) src=Budgie ;;
esac
if [ -z "$title" ]; then
title=${base%.*}
[ -z "$src" ] || title=${title#*_}
title=${title//[_-]/ }
fi
shown "${src:+$src: }$title"
}
# apply: the newest picture in DIR goes up, else the default
apply() {
local pic ext link old
if pic=$(newest); then
# "no wallpaper" was chosen after this picture: the plain background stays
if [ "$(cat "$CURRENT" 2>/dev/null)" = none ] && [ ! "$pic" -nt "$CURRENT" ]; then return 0; fi
else
[ "$(cat "$CURRENT" 2>/dev/null)" != none ] || return 0
pic=$(default_picture) || return 0
fi
# sway reads the path inside a command line, where a quote or a ; in a file
# name would end it: hand sway a link with a name made of digits only. The
# name changes with the picture, so sway starts the new one.
case $CACHE in *[\"\'\\\;]* | *$'\n'*) echo "wallpaper: can't use the folder $CACHE (odd characters in its name)" >&2; return 1 ;; esac
mkdir -p "$CACHE"
ext=${pic##*.}
ext=${ext,,}
[ "$ext" = jpeg ] && ext=jpg
link="$CACHE/wallpaper-$(stat -L -c '%i-%Y' -- "$pic").$ext"
# (only the links: wallpaper- and digits)
for old in "$CACHE"/wallpaper-[0-9]*; do
[ "$old" = "$link" ] || rm -f -- "$old"
done
ln -sfn -- "$pic" "$link"
swaymsg -q "output * bg \"$link\" fill"
}
# use FILE: FILE, from the collection, becomes the wallpaper and stays
use() {
local ext=${1##*.}
ext=${ext,,}
[ "$ext" = jpeg ] && ext=jpg
mkdir -p "$DIR" "$CACHE"
cp -- "$1" "$DIR/.chosen.part" || return 1
rm -f -- "$DIR/chosen.jpg" "$DIR/chosen.png"
mv -f -- "$DIR/.chosen.part" "$DIR/chosen.$ext"
printf '%s\n' "${1##*/}" >"$CURRENT"
apply
# its name, for a moment, if notifications are set up
if command -v notify-send >/dev/null; then notify-send -t 3000 Wallpaper "$(label "$1")" 2>/dev/null || true; fi
}
# step +1|-1: the next or previous picture in the collection
step() {
local pics=() cur i n=0
mapfile -t pics < <(collection)
n=${#pics[@]}
[ "$n" -gt 0 ] || { echo "wallpaper: no pictures in $COLL (wrenctl install wallpapers fetches some)" >&2; return 1; }
cur=$(cat "$CURRENT" 2>/dev/null)
i=-1
for ((k = 0; k < n; k++)); do [ "${pics[k]##*/}" = "$cur" ] && i=$k; done
if [ "$i" -lt 0 ]; then
# nothing chosen yet: start at the first (or, going back, the last)
if [ "$1" -gt 0 ]; then i=0; else i=$((n - 1)); fi
else
i=$(((i + $1 + n) % n))
fi
use "${pics[i]}"
}
# pick: a list of the collection by name, newest first, in Wren Linux's wmenu look
# none: no wallpaper: the chosen picture goes, and sway reloads its own
# background (its config's "output * bg" line, or its plain one); pictures of
# your own in the desktop folder stay, but don't go up until one is newer
none() {
mkdir -p "$CACHE"
rm -f -- "$DIR/chosen.jpg" "$DIR/chosen.png"
echo none >"$CURRENT"
swaymsg -q reload
if command -v notify-send >/dev/null; then notify-send -t 3000 Wallpaper "No wallpaper" 2>/dev/null || true; fi
}
pick() {
local pics=() f sel num bg fg
# the theme's menu colors (bin/theme), else black and white
bg=$("$HOME/.local/bin/theme" get WMENU_BG 2>/dev/null) || bg=000000
fg=$("$HOME/.local/bin/theme" get WMENU_TEXT 2>/dev/null) || fg=ffffff
mapfile -t pics < <(collection | while IFS= read -r f; do printf '%s\t%s\n' "$(stat -c %Y -- "$f")" "$f"; done | sort -rn | cut -f2-)
# each line starts with its number, which says which picture it is (0: none)
sel=$({ echo "0 No wallpaper"; for i in "${!pics[@]}"; do printf '%d %s\n' "$((i + 1))" "$(label "${pics[i]}")"; done; } |
wmenu -i -l 15 -p "Wallpaper:" -f 'monospace 12' -N "$bg" -n "$fg" -M "$bg" -m "$fg" -S "$fg" -s "$bg") || return 0
num=${sel%% *}
[[ $num =~ ^[0-9]{1,6}$ ]] && [ "$num" -le ${#pics[@]} ] || return 0
if [ "$num" -eq 0 ]; then none; else use "${pics[num - 1]}"; fi
}
case ${1:-} in
status)
if [ "$(cat "$CURRENT" 2>/dev/null)" = none ] && { ! pic=$(newest) || [ ! "$pic" -nt "$CURRENT" ]; }; then
echo "Wallpaper: none (the theme's plain background; Super+] or Super+Shift+w chooses one)"
elif pic=$(newest); then
if [ "${pic##*/}" = chosen.jpg ] || [ "${pic##*/}" = chosen.png ]; then
cur=$(cat "$CURRENT" 2>/dev/null)
if [ -n "$cur" ] && [ -f "$COLL/$cur" ]; then
echo "Wallpaper: $(label "$COLL/$cur") ($(shown "$cur"), from $COLL)"
else
echo "Wallpaper: ${pic##*/} (from $DIR)"
fi
else
echo "Wallpaper: $(shown "${pic##*/}") (from $DIR)"
fi
else
if pic=$(default_picture); then
pic=${pic##*/default-}
echo "Wallpaper: the default wren (${pic%.jpg}), with no picture in $DIR"
else
echo "Wallpaper: the default (no picture in $DIR)"
fi
fi
for d in "$DIR" "$COLL"; do
for f in "$d"/*; do
[ -f "$f" ] || continue
case ${f##*/} in *.txt) continue ;; esac
why=$(why_not "$f")
if [ -n "$why" ]; then printf ' not used: %s: %s\n' "$(shown "${f##*/}")" "$why"; fi
done
done
exit 0 ;;
next) step 1 ;;
previous | prev) step -1 ;;
pick) pick ;;
none) none ;;
"") apply ;;
*) echo "usage: wallpaper [next | previous | pick | none | status]" >&2; exit 2 ;;
esac