wren-linux / bin / theme
#!/bin/bash
# theme: written by Wren Linux parts/theme.sh
# Wren Linux's colors, light or dark, from one place:
# theme which theme is set (dark unless you chose light)
# theme light | dark switch to it (the Wren menu's Theme line too)
# theme toggle the other one
# theme get NAME one of its colors (e.g. LOCK), for the lock screen
# theme env all of them as NAME='value' lines, for Wren Linux's
# commands (wren-menu, nmtui, powermenu, wallpaper)
# theme apply write the current theme's colors again
# Switching writes the bar's colors, the plain background (no wallpaper) and
# the window borders in a sway snippet, foot's [colors], mako's colors and the launcher's
# (Super+d), GTK apps' light or dark (settings.ini, and the color-scheme
# setting libadwaita apps read), then reloads sway. The sandboxed Firefox and
# incognito are told at their next start (bin/firefox, bin/incognito). Windows open now keep their colors until they
# open again. The text console (tmux) stays dark. Only Wren Linux's own files are
# changed: a foot.ini or mako config you wrote yourself is left alone.
# The colors are data/themes/dark and light, read strictly: NAME=colour lines
# (rrggbb or rrggbbaa), lists of them, nothing else.
set -uo pipefail
STATE=${XDG_CONFIG_HOME:-$HOME/.config}/wren/theme
THEMES=$HOME/.local/share/wren/themes
SWAY_CONF=$HOME/.config/sway/config
SNIPPET=$HOME/.config/sway/config.d/theme.conf
FOOT=$HOME/.config/foot/foot.ini
MAKO=$HOME/.config/mako/config
NEEDED=(DESKTOP_BG BAR_BG BAR_TEXT BAR_INACTIVE_TEXT LOCK NOTIFY_BG NOTIFY_TEXT NOTIFY_BORDER
WMENU_BG WMENU_TEXT MENU_FG MENU_BG MENU_REGULAR MENU_BRIGHT NEWT_TEXT NEWT_REVERSED NEWT_DIM
FOOT_FG FOOT_BG FOOT_REGULAR FOOT_BRIGHT FOOT_SEL_FG FOOT_SEL_BG
BORDER_ACTIVE BORDER_TEXT BORDER_INACTIVE BORDER_INACTIVE_TEXT)
declare -A C
die() { echo "theme: $*" >&2; exit 1; }
# current: the theme set (dark if none, or if the file says something else)
current() {
local t
t=$(head -c 16 "$STATE" 2>/dev/null)
case $t in light | dark) echo "$t" ;; *) echo dark ;; esac
}
# load NAME: its colors into C; fails on a missing file, a line that isn't a
# color, or a color missing
load() {
local f=$THEMES/$1 line key val k
[ -f "$f" ] || return 1
while IFS= read -r line || [ -n "$line" ]; do
case $line in '' | '#'*) continue ;; esac
[[ $line =~ ^([A-Z_]+)=([0-9a-fA-F]{6}([0-9a-fA-F]{2})?( [0-9a-fA-F]{6}([0-9a-fA-F]{2})?)*)$ ]] || return 1
key=${BASH_REMATCH[1]} val=${BASH_REMATCH[2]}
C[$key]=${val,,}
done <"$f"
for k in "${NEEDED[@]}"; do [ -n "${C[$k]:-}" ] || return 1; done
for k in MENU_REGULAR MENU_BRIGHT FOOT_REGULAR FOOT_BRIGHT; do
[ "$(wc -w <<<"${C[$k]}")" = 8 ] || return 1
done
}
# snippet: the bar's colors, the plain background and the window borders, for
# sway. Borders: 1 pixel on floating windows (the menu, pop-ups), the active
# one in the theme's color; none on tiled windows. sway gives default_floating_border
# only to windows that open floating, so the pop-ups a rule floats get theirs by
# app_id, and Super+Shift+space sets the border as it floats or tiles a window.
# This file, not install-base.sh's, because update.sh runs parts/theme.sh again
# when this changes. (Your own sway file after this one changes them:
# config.d/zz-mine.conf, say.)
snippet() {
local a=${C[BORDER_ACTIVE]} at=${C[BORDER_TEXT]} i=${C[BORDER_INACTIVE]} it=${C[BORDER_INACTIVE_TEXT]}
cat <<EOF
# Wren Linux's theme ($1): written by bin/theme; "theme light" or "theme dark" rewrites it
output * bg #${C[DESKTOP_BG]} solid_color
bar bar-0 colors {
background #${C[BAR_BG]}
statusline #${C[BAR_TEXT]}
inactive_workspace #00000000 #00000000 #${C[BAR_INACTIVE_TEXT]}
}
default_border none
default_floating_border pixel 1
for_window [app_id="^wren-(keys|run|nmtui|timer)$"] border pixel 1
# (--no-warn: sway's own config binds these keys first; without it, sway shows a warning bar at every reload)
bindsym --no-warn \$mod+Shift+space floating toggle; [con_id=__focused__ floating] border pixel 1; [con_id=__focused__ tiling] border none
client.focused #$a #$a #$at #$a #$a
client.focused_inactive #$i #$i #$it #$i #$i
client.unfocused #$i #$i #$it #$i #$i
EOF
}
# foot_colors: foot.ini's [colors] section
foot_colors() {
local i=0 c
printf '[colors]\nalpha=1.0\nforeground=%s\nbackground=%s\n' "${C[FOOT_FG]}" "${C[FOOT_BG]}"
for c in ${C[FOOT_REGULAR]}; do printf 'regular%d=%s\n' "$i" "$c"; i=$((i + 1)); done
i=0
for c in ${C[FOOT_BRIGHT]}; do printf 'bright%d=%s\n' "$i" "$c"; i=$((i + 1)); done
printf 'selection-foreground=%s\nselection-background=%s\n' "${C[FOOT_SEL_FG]}" "${C[FOOT_SEL_BG]}"
}
# gtk LIGHT|DARK: GTK 3 and 4 apps (Mousepad, Thunar...) prefer that: one line
# in each settings.ini, the rest kept; and the desktop's color-scheme
# setting, which libadwaita apps read (when gsettings is there)
gtk() {
local dark=false f d
[ "$1" = dark ] && dark=true
for d in gtk-3.0 gtk-4.0; do
f=${XDG_CONFIG_HOME:-$HOME/.config}/$d/settings.ini
mkdir -p "${f%/*}"
[ -f "$f" ] || printf '[Settings]\n' >"$f"
grep -q '^\[Settings\]' "$f" || printf '\n[Settings]\n' >>"$f"
if grep -q '^gtk-application-prefer-dark-theme=' "$f"; then
sed -i "s/^gtk-application-prefer-dark-theme=.*/gtk-application-prefer-dark-theme=$dark/" "$f"
else
sed -i "/^\[Settings\]/a gtk-application-prefer-dark-theme=$dark" "$f"
fi
done
if command -v gsettings >/dev/null; then
gsettings set org.gnome.desktop.interface color-scheme "prefer-$1" 2>/dev/null || true
# Mousepad: Wren Linux's scheme to match (extras/mousepad.sh), while it
# is one of Wren Linux's two; one you chose yourself stays
if gsettings list-schemas 2>/dev/null | grep -qx org.xfce.mousepad.preferences.view; then
case $(gsettings get org.xfce.mousepad.preferences.view color-scheme 2>/dev/null) in
"'wren-dark'" | "'wren-light'")
gsettings set org.xfce.mousepad.preferences.view color-scheme "wren-$1" 2>/dev/null || true ;;
esac
fi
fi
}
# apply: the current theme's colors into Wren Linux's files, then sway reloads
apply() {
local t tmp
t=$(current)
load "$t" || die "the $t theme's colors ($THEMES/$t) can't be read; run wrenctl part theme"
mkdir -p "${SNIPPET%/*}"
snippet "$t" >"$SNIPPET.new" && mv -f "$SNIPPET.new" "$SNIPPET"
# foot: only Wren Linux's foot.ini; its [colors] section, up to the next section
if grep -qs 'Written by Wren Linux' "$FOOT"; then
tmp=$(mktemp)
awk -v colors="$(foot_colors)" '
/^\[colors\]/ { print colors; skip = 1; next }
/^\[/ { skip = 0 }
!skip { print }' "$FOOT" >"$tmp" && cat "$tmp" >"$FOOT"
rm -f "$tmp"
fi
# mako: only Wren Linux's config
if grep -qsxF '# Written by Wren Linux parts/mako.sh' "$MAKO"; then
sed -i -E '/^(background-color|text-color|border-color)=/d' "$MAKO"
printf 'background-color=#%s\ntext-color=#%s\nborder-color=#%s\n' \
"${C[NOTIFY_BG]}" "${C[NOTIFY_TEXT]}" "${C[NOTIFY_BORDER]}" >>"$MAKO"
if pgrep -x mako >/dev/null; then makoctl reload >/dev/null 2>&1 || true; fi
fi
# the launcher (Super+d): only the line Wren Linux styled (lib/sway.sh)
if [ -f "$SWAY_CONF" ]; then
sed -i -E "s/^(set \\\$menu wmenu-run -f 'monospace 12') -N [0-9a-f]{6} -n [0-9a-f]{6} -M [0-9a-f]{6} -m [0-9a-f]{6} -S [0-9a-f]{6} -s [0-9a-f]{6}\$/\\1 -N ${C[WMENU_BG]} -n ${C[WMENU_TEXT]} -M ${C[WMENU_BG]} -m ${C[WMENU_TEXT]} -S ${C[WMENU_TEXT]} -s ${C[WMENU_BG]}/" "$SWAY_CONF"
fi
gtk "$t"
if [ -n "${SWAYSOCK:-}" ]; then swaymsg -q reload 2>/dev/null || true; fi
}
case ${1:-} in
"" | status)
echo "Theme: $(current)" ;;
light | dark)
load "$1" || die "the $1 theme's colors ($THEMES/$1) can't be read; run wrenctl part theme"
mkdir -p "${STATE%/*}"
echo "$1" >"$STATE"
apply
if command -v notify-send >/dev/null; then notify-send -t 3000 Theme "${1^}: new windows open in it" 2>/dev/null || true; fi ;;
toggle)
if [ "$(current)" = dark ]; then exec "$0" light; else exec "$0" dark; fi ;;
get)
# one color, or nothing and failure: never an empty or odd value (the
# lock screen falls back to black on failure)
[[ ${2:-} =~ ^[A-Z_]+$ ]] || exit 2
load "$(current)" || exit 1
[ -n "${C[$2]:-}" ] || exit 1
echo "${C[$2]}" ;;
env)
load "$(current)" || exit 1
for k in "${NEEDED[@]}"; do printf "%s='%s'\n" "$k" "${C[$k]}"; done ;;
apply) apply ;;
*) echo "usage: theme [light | dark | toggle | get NAME | env | apply]" >&2; exit 2 ;;
esac