Add wallpaper switch
This commit is contained in:
@@ -30,11 +30,53 @@ let
|
|||||||
|
|
||||||
availableVariants = concatStringsSep " " (attrNames themeVariants);
|
availableVariants = concatStringsSep " " (attrNames themeVariants);
|
||||||
|
|
||||||
|
# `variant:schemeFile` pairs so the wallpaper picker can read each variant's
|
||||||
|
# base16 palette at runtime.
|
||||||
|
variantSchemes = concatStringsSep " "
|
||||||
|
(mapAttrsToList (variant: v: "${variant}:${v.scheme}") themeVariants);
|
||||||
|
|
||||||
# variant:doomtheme pairs consumed by the theme-switch script's
|
# variant:doomtheme pairs consumed by the theme-switch script's
|
||||||
# best-effort Doom Emacs hook (see ./theme-switch.sh).
|
# best-effort Doom Emacs hook (see ./theme-switch.sh).
|
||||||
doomThemeMap = concatStringsSep " "
|
doomThemeMap = concatStringsSep " "
|
||||||
(mapAttrsToList (variant: theme: "${variant}:${theme}") cfg.doomThemes);
|
(mapAttrsToList (variant: theme: "${variant}:${theme}") cfg.doomThemes);
|
||||||
|
|
||||||
|
# Stylix is only declared on hosts that import its Home Manager module
|
||||||
|
# (e.g. `fujin`). Servers (susano, izanagi, amaterasu, ...) don't, so
|
||||||
|
# `stylix` isn't declared there. We probe option *declarations* (not
|
||||||
|
# config values) to avoid an evaluation cycle.
|
||||||
|
hasStylix = options ? stylix;
|
||||||
|
|
||||||
|
# --- Wallpaper handling (runtime-cloned repo + palette matching) ----------
|
||||||
|
wallpapersEnabled = cfg.wallpaperRepo != null;
|
||||||
|
wallpapersDir = "${config.home.homeDirectory}/Wallpapers";
|
||||||
|
# hyprpaper's config points here; set-wallpaper rebinds this symlink, so we
|
||||||
|
# never have to write hyprpaper config ourselves.
|
||||||
|
wallpaperSymlink = "${config.xdg.stateHome}/theme/wallpaper-current";
|
||||||
|
|
||||||
|
wallpaper-pick = pkgs.writeShellScriptBin "wallpaper-pick" ''
|
||||||
|
SCRIPT_TAG="wallpaper-pick"
|
||||||
|
${builtins.readFile ./log.sh}
|
||||||
|
wallpapersDir="${wallpapersDir}"
|
||||||
|
wallpaperRepo="${cfg.wallpaperRepo or ""}"
|
||||||
|
variantSchemes="${variantSchemes}"
|
||||||
|
MAGICK="${getExe pkgs.imagemagick}"
|
||||||
|
GIT="${getExe pkgs.git}"
|
||||||
|
${builtins.readFile ./wallpaper-pick.sh}
|
||||||
|
'';
|
||||||
|
|
||||||
|
wallpaper-set = pkgs.writeShellScriptBin "wallpaper-set" ''
|
||||||
|
SCRIPT_TAG="wallpaper-set"
|
||||||
|
${builtins.readFile ./log.sh}
|
||||||
|
wallpaperSymlink="${wallpaperSymlink}"
|
||||||
|
${builtins.readFile ./set-wallpaper.sh}
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Applied once at login (after hyprpaper) to seed a base-variant wallpaper.
|
||||||
|
wallpaper-init = pkgs.writeShellScript "wallpaper-init" ''
|
||||||
|
wp="$(${getExe wallpaper-pick} ${baseVariant} 2>/dev/null)" || exit 0
|
||||||
|
[ -n "$wp" ] && ${getExe wallpaper-set} "$wp" || true
|
||||||
|
'';
|
||||||
|
|
||||||
# Userspace theme switcher: activates a pre-built Home Manager
|
# Userspace theme switcher: activates a pre-built Home Manager
|
||||||
# specialisation generation. No sudo, no OS rebuild — just runs the
|
# specialisation generation. No sudo, no OS rebuild — just runs the
|
||||||
# specialisation's activation script in the base HM generation.
|
# specialisation's activation script in the base HM generation.
|
||||||
@@ -42,17 +84,16 @@ let
|
|||||||
# The script logic lives in ./theme-switch.sh (plain shell, no Nix
|
# The script logic lives in ./theme-switch.sh (plain shell, no Nix
|
||||||
# escaping); we only inject the config-derived values it needs.
|
# escaping); we only inject the config-derived values it needs.
|
||||||
theme-switch = pkgs.writeShellScriptBin "theme-switch" ''
|
theme-switch = pkgs.writeShellScriptBin "theme-switch" ''
|
||||||
|
SCRIPT_TAG="theme-switch"
|
||||||
|
${builtins.readFile ./log.sh}
|
||||||
baseVariant="${baseVariant}"
|
baseVariant="${baseVariant}"
|
||||||
availableVariants="${availableVariants}"
|
availableVariants="${availableVariants}"
|
||||||
doomThemes="${doomThemeMap}"
|
doomThemes="${doomThemeMap}"
|
||||||
|
wallpapersEnabled="${if wallpapersEnabled then "1" else "0"}"
|
||||||
|
wallpaperPickBin="${if wallpapersEnabled then getExe wallpaper-pick else ""}"
|
||||||
|
wallpaperSetBin="${if wallpapersEnabled then getExe wallpaper-set else ""}"
|
||||||
${builtins.readFile ./theme-switch.sh}
|
${builtins.readFile ./theme-switch.sh}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
# Stylix is only declared on hosts that import its Home Manager module
|
|
||||||
# (e.g. `fujin`). Servers (susano, izanagi, amaterasu, ...) don't, so
|
|
||||||
# `stylix` isn't declared there. We probe option *declarations* (not
|
|
||||||
# config values) to avoid an evaluation cycle.
|
|
||||||
hasStylix = options ? stylix;
|
|
||||||
in {
|
in {
|
||||||
options.dov.dynamic-theme = {
|
options.dov.dynamic-theme = {
|
||||||
enable = mkEnableOption "dynamic theme switching";
|
enable = mkEnableOption "dynamic theme switching";
|
||||||
@@ -95,24 +136,43 @@ in {
|
|||||||
map the variants to `catppuccin-mocha` / `catppuccin-latte`.
|
map the variants to `catppuccin-mocha` / `catppuccin-latte`.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
wallpaperRepo = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
example = "https://example.org/me/wallpapers.git";
|
||||||
|
description = ''
|
||||||
|
Git URL of a wallpaper repository. When set, theme-switch also picks
|
||||||
|
a random wallpaper whose palette matches the current variant and
|
||||||
|
applies it via hyprpaper. The repo is cloned to
|
||||||
|
`~/Wallpapers` on first use (a one-time, runtime cost — it is NOT
|
||||||
|
fetched at build time, so `nix build` stays fast).
|
||||||
|
'';
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable (
|
config = mkIf cfg.enable (mkMerge [
|
||||||
{
|
{
|
||||||
home.packages = [ theme-switch ];
|
home.packages = [ theme-switch ]
|
||||||
|
++ optionals wallpapersEnabled [ wallpaper-pick wallpaper-set ];
|
||||||
}
|
}
|
||||||
|
|
||||||
# Only reference the `stylix` option where it is actually declared.
|
# Only reference the `stylix` option where it is actually declared.
|
||||||
# `mkIf false` is NOT enough: the module system still rejects a
|
# `mkIf false` is NOT enough: the module system still rejects a
|
||||||
# definition (even a disabled one) for an option that does not exist,
|
# definition (even a disabled one) for an option that does not exist,
|
||||||
# which breaks every host that doesn't import stylix. `optionalAttrs`
|
# which breaks every host that doesn't import stylix. `optionalAttrs`
|
||||||
# removes the key structurally, so non-stylix hosts evaluate cleanly.
|
# (keyed on `options ?`, i.e. *declarations* — never a config value) is
|
||||||
// optionalAttrs hasStylix {
|
# the recursion-free way to gate structure on stylix presence.
|
||||||
|
(optionalAttrs hasStylix {
|
||||||
stylix = {
|
stylix = {
|
||||||
enable = true;
|
enable = true;
|
||||||
autoEnable = true;
|
autoEnable = true;
|
||||||
# Base scheme/polarity; overridable per specialisation below.
|
# Base scheme/polarity; overridable per specialisation below.
|
||||||
base16Scheme = mkDefault themeVariants.${baseVariant}.scheme;
|
base16Scheme = mkDefault themeVariants.${baseVariant}.scheme;
|
||||||
polarity = mkDefault themeVariants.${baseVariant}.polarity;
|
polarity = mkDefault themeVariants.${baseVariant}.polarity;
|
||||||
|
# When we manage hyprpaper's wallpaper ourselves, disable stylix's
|
||||||
|
# hyprpaper target so the two don't fight over the wallpaper path.
|
||||||
|
targets.hyprpaper.enable = mkForce (!wallpapersEnabled);
|
||||||
};
|
};
|
||||||
|
|
||||||
# Generate Home Manager specialisations for every non-base variant.
|
# Generate Home Manager specialisations for every non-base variant.
|
||||||
@@ -126,6 +186,32 @@ in {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}) specialisationVariants;
|
}) specialisationVariants;
|
||||||
|
})
|
||||||
|
|
||||||
|
# Wallpaper handling. These options always exist structurally (HM ships
|
||||||
|
# services.hyprpaper and a freeform systemd.user.services), so we gate
|
||||||
|
# the *values* with mkIf/optionals rather than shaping structure — that
|
||||||
|
# avoids the config-depends-on-config recursion that optionalAttrs on a
|
||||||
|
# config value would cause.
|
||||||
|
{
|
||||||
|
services.hyprpaper.enable = wallpapersEnabled;
|
||||||
|
services.hyprpaper.settings = mkIf wallpapersEnabled {
|
||||||
|
splash = false;
|
||||||
|
wallpaper = [{ monitor = ""; path = wallpaperSymlink; }];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.user.services.wallpaper-init = mkIf wallpapersEnabled {
|
||||||
|
Unit = {
|
||||||
|
Description = "Pick and apply a wallpaper matching the base theme";
|
||||||
|
After = [ "hyprpaper.service" ];
|
||||||
|
PartOf = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
|
Service = {
|
||||||
|
Type = "oneshot";
|
||||||
|
ExecStart = "${wallpaper-init}";
|
||||||
|
};
|
||||||
|
Install.WantedBy = [ "graphical-session.target" ];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
# Colorful, TTY-aware logging helpers: log_info / log_warn / log_error.
|
||||||
|
#
|
||||||
|
# Everything is written to *stderr*, so a script's stdout stays a clean data
|
||||||
|
# channel (e.g. wallpaper-pick prints the chosen image path on stdout while
|
||||||
|
# its progress goes to stderr). Set SCRIPT_TAG to prefix every line with the
|
||||||
|
# originating script, e.g. "[theme-switch] selecting wallpaper...".
|
||||||
|
#
|
||||||
|
# Colors are auto-disabled when stderr isn't a terminal or NO_COLOR is set.
|
||||||
|
__theme_log_init() {
|
||||||
|
if [ -t 2 ] && [ -z "${NO_COLOR:-}" ]; then
|
||||||
|
__C_RESET=$'\033[0m'
|
||||||
|
__C_INFO=$'\033[1;34m' # blue
|
||||||
|
__C_WARN=$'\033[1;33m' # yellow
|
||||||
|
__C_ERR=$'\033[1;31m' # red
|
||||||
|
else
|
||||||
|
__C_RESET=""; __C_INFO=""; __C_WARN=""; __C_ERR=""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
__theme_log_init
|
||||||
|
|
||||||
|
log_info() { printf '%s%s%s%s\n' "${__C_INFO}" "${SCRIPT_TAG:+[$SCRIPT_TAG] }" "$*" "${__C_RESET}" >&2; }
|
||||||
|
log_warn() { printf '%s%s%s%s\n' "${__C_WARN}" "${SCRIPT_TAG:+[$SCRIPT_TAG] }" "warning: $*" "${__C_RESET}" >&2; }
|
||||||
|
log_error() { printf '%s%s%s%s\n' "${__C_ERR}" "${SCRIPT_TAG:+[$SCRIPT_TAG] }" "error: $*" "${__C_RESET}" >&2; }
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
# set-wallpaper — bind the runtime wallpaper symlink to $1 and restart hyprpaper
|
||||||
|
# so it picks up the new image. hyprpaper's config (managed by Home Manager)
|
||||||
|
# points at the symlink, so we never write hyprpaper config ourselves.
|
||||||
|
#
|
||||||
|
# Env (injected by the Nix wrapper):
|
||||||
|
# wallpaperSymlink path to the symlink hyprpaper's `wallpaper.path` reads
|
||||||
|
# Arg: image path
|
||||||
|
|
||||||
|
: "${wallpaperSymlink:?set-wallpaper: wallpaperSymlink not set}"
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
target="${1:?usage: set-wallpaper <image-path>}"
|
||||||
|
[ -f "$target" ] || { log_error "not a file: $target"; exit 1; }
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$wallpaperSymlink")"
|
||||||
|
ln -sfn "$target" "$wallpaperSymlink"
|
||||||
|
|
||||||
|
# Apply live by (re)starting the user hyprpaper service. `systemctl restart`
|
||||||
|
# starts the unit even if it is inactive — important because a freshly-added
|
||||||
|
# hyprpaper service won't be running until the first switch. Tolerate the
|
||||||
|
# service being absent (e.g. on a host without hyprpaper).
|
||||||
|
if command -v systemctl >/dev/null 2>&1 && systemctl --user restart hyprpaper 2>/dev/null; then
|
||||||
|
log_info "applied $(basename "$target") (hyprpaper started)"
|
||||||
|
else
|
||||||
|
log_warn "could not start hyprpaper; wallpaper symlink set but not displayed"
|
||||||
|
fi
|
||||||
@@ -57,7 +57,7 @@ do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ -z "$gen" ]; then
|
if [ -z "$gen" ]; then
|
||||||
echo "error: could not resolve Home Manager base generation" >&2
|
log_error "could not resolve Home Manager base generation"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -72,18 +72,16 @@ case "$variant" in
|
|||||||
esac
|
esac
|
||||||
|
|
||||||
if [ ! -x "$activate" ]; then
|
if [ ! -x "$activate" ]; then
|
||||||
echo "error: variant '$variant' not found at $activate" >&2
|
log_error "variant '$variant' not found at $activate"
|
||||||
echo "Available: ${availableVariants}"
|
log_info "available: ${availableVariants}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Switching theme to $variant..."
|
log_info "switching theme to ${variant}"
|
||||||
|
|
||||||
# Best-effort: live-switch the running Doom Emacs daemon too. No config
|
# Best-effort: live-switch the running Doom Emacs daemon too. No config
|
||||||
# files are touched; if the daemon isn't running or emacsclient isn't
|
# files are touched; if the daemon isn't running or emacsclient isn't
|
||||||
# installed (e.g. on a server), this is silently skipped. A theme symbol
|
# installed (e.g. on a server), this is silently skipped.
|
||||||
# that isn't installed in Doom makes emacsclient return non-zero, which we
|
|
||||||
# also treat as skip.
|
|
||||||
doom_theme=""
|
doom_theme=""
|
||||||
if [ -n "$doomThemes" ]; then
|
if [ -n "$doomThemes" ]; then
|
||||||
for pair in $doomThemes; do
|
for pair in $doomThemes; do
|
||||||
@@ -92,12 +90,26 @@ if [ -n "$doomThemes" ]; then
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
if [ -n "$doom_theme" ] && command -v emacsclient >/dev/null 2>&1; then
|
if [ -n "$doom_theme" ]; then
|
||||||
if emacsclient --eval "(progn (mapc (function disable-theme) custom-enabled-themes) (load-theme (quote ${doom_theme}) t))" >/dev/null 2>&1; then
|
if command -v emacsclient >/dev/null 2>&1 \
|
||||||
echo "Doom: ${doom_theme}"
|
&& emacsclient --eval "(progn (mapc (function disable-theme) custom-enabled-themes) (load-theme (quote ${doom_theme}) t))" >/dev/null 2>&1; then
|
||||||
|
log_info "doom: ${doom_theme}"
|
||||||
else
|
else
|
||||||
echo "note: Doom theme switch skipped (daemon not running or '${doom_theme}' not installed)" >&2
|
log_warn "doom theme not applied (daemon not running or '${doom_theme}' not installed)"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Pick a palette-matching wallpaper and apply it. This BLOCKS — on the very
|
||||||
|
# first run wallpaper-pick clones the wallpaper repo and builds the palette
|
||||||
|
# index, which can take a few minutes; progress is logged on stderr by the
|
||||||
|
# pick/set scripts. Failures fall back to leaving the wallpaper unchanged.
|
||||||
|
if [ "${wallpapersEnabled:-0}" = "1" ] && [ -n "${wallpaperPickBin:-}" ] && [ -n "${wallpaperSetBin:-}" ]; then
|
||||||
|
if wp="$("$wallpaperPickBin" "$variant")" && [ -n "$wp" ]; then
|
||||||
|
"$wallpaperSetBin" "$wp" || log_warn "wallpaper apply failed"
|
||||||
|
else
|
||||||
|
log_warn "wallpaper selection failed; leaving wallpaper unchanged"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_info "activating Home Manager specialisation"
|
||||||
exec "$activate"
|
exec "$activate"
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
# shellcheck shell=bash
|
||||||
|
# wallpaper-pick — print a random wallpaper from $wallpapersDir whose palette
|
||||||
|
# best matches the given theme variant. Per-image palettes are extracted once
|
||||||
|
# (ImageMagick) and cached, keyed by the (immutable) source path so a rebuild
|
||||||
|
# that changes the wallpaper set reindexes automatically.
|
||||||
|
#
|
||||||
|
# Matching uses several key colors on both sides: the variant's base16
|
||||||
|
# base00–base05 (the background→foreground neutral range) are each matched to
|
||||||
|
# their nearest wallpaper dominant color; the sum of those nearest distances
|
||||||
|
# is the score. A random image is picked from the lowest-score tier.
|
||||||
|
#
|
||||||
|
# Env (injected by the Nix wrapper):
|
||||||
|
# wallpapersDir directory of wallpaper images (~/Wallpapers, a git clone)
|
||||||
|
# wallpaperRepo git URL cloned into wallpapersDir on first use
|
||||||
|
# variantSchemes space-separated `variant:schemeFile` pairs (base16 YAML)
|
||||||
|
# MAGICK path to the ImageMagick `magick` binary
|
||||||
|
# GIT path to the `git` binary
|
||||||
|
# Arg: variant name (e.g. gruvbox-dark)
|
||||||
|
|
||||||
|
: "${wallpapersDir:?wallpaper-pick: wallpapersDir not set}"
|
||||||
|
: "${wallpaperRepo:?wallpaper-pick: wallpaperRepo not set}"
|
||||||
|
: "${variantSchemes:?wallpaper-pick: variantSchemes not set}"
|
||||||
|
: "${MAGICK:?wallpaper-pick: MAGICK not set}"
|
||||||
|
: "${GIT:?wallpaper-pick: GIT not set}"
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
variant="${1:?usage: wallpaper-pick <variant>}"
|
||||||
|
|
||||||
|
# Ensure the wallpaper repo is cloned locally. The clone is a one-time cost
|
||||||
|
# paid on first use (not at build time), keeping `nix build` fast.
|
||||||
|
if [ ! -d "$wallpapersDir/.git" ]; then
|
||||||
|
log_info "cloning wallpaper repo (one-time, may take a few minutes)..."
|
||||||
|
mkdir -p "$(dirname "$wallpapersDir")"
|
||||||
|
"$GIT" clone --depth=1 --single-branch "$wallpaperRepo" "$wallpapersDir" >&2
|
||||||
|
log_info "clone ready"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/wallpaper-pick"
|
||||||
|
palettes="$cache_dir/palettes.tsv"
|
||||||
|
stamp_file="$cache_dir/source"
|
||||||
|
|
||||||
|
# Cache key: the clone's current commit, so a `git pull` reindexes. Falls back
|
||||||
|
# to the resolved path if git fails for some reason.
|
||||||
|
src_stamp="$("$GIT" -C "$wallpapersDir" rev-parse HEAD 2>/dev/null || readlink -f "$wallpapersDir")"
|
||||||
|
|
||||||
|
rebuild_cache() {
|
||||||
|
img_count="$(find "$wallpapersDir" -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.webp' \) -printf . 2>/dev/null | wc -c)"
|
||||||
|
log_info "indexing ${img_count} wallpapers (one-time, building palette cache)..."
|
||||||
|
mkdir -p "$cache_dir"
|
||||||
|
tmp="$palettes.tmp"
|
||||||
|
: > "$tmp"
|
||||||
|
jobs="$(nproc 2>/dev/null || echo 4)"
|
||||||
|
|
||||||
|
# For each image: top dominant colors (by pixel count) as space-separated #hex.
|
||||||
|
find "$wallpapersDir" -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.webp' \) -print0 \
|
||||||
|
| xargs -0 -P "$jobs" -n1 sh -c '
|
||||||
|
MAGICK="$0" img="$1"
|
||||||
|
"$MAGICK" "$img" -resize 128x128^ -gravity center -extent 128x128 +dither -colors 8 -format "%c" histogram:info: 2>/dev/null \
|
||||||
|
| grep -E "^[[:space:]]*[0-9]+:" | sort -rn | grep -oE "#[0-9A-Fa-f]{6}" | head -6 | paste -sd" " - \
|
||||||
|
| { read -r hex; [ -n "$hex" ] && printf "%s\t%s\n" "$img" "$hex"; }
|
||||||
|
' "$MAGICK" >> "$tmp" || true
|
||||||
|
|
||||||
|
mv "$tmp" "$palettes"
|
||||||
|
printf '%s\n' "$src_stamp" > "$stamp_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f "$palettes" ] || [ ! -f "$stamp_file" ] || [ "$(cat "$stamp_file")" != "$src_stamp" ]; then
|
||||||
|
rebuild_cache
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve the base16 scheme file for the requested variant.
|
||||||
|
scheme=""
|
||||||
|
for pair in $variantSchemes; do
|
||||||
|
case "$pair" in
|
||||||
|
"${variant}":*) scheme="${pair#*:}"; break ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ -n "$scheme" ] || { log_error "no scheme mapped for variant '$variant'"; exit 2; }
|
||||||
|
|
||||||
|
# Theme key colors: base00–base05 hex (one per line, with leading #).
|
||||||
|
theme_colors="$(grep -E '^[[:space:]]*base0[0-5]:' "$scheme" | grep -oE '#[0-9A-Fa-f]{6}')"
|
||||||
|
[ -n "$theme_colors" ] || { log_error "could not parse base00–05 from $scheme"; exit 2; }
|
||||||
|
|
||||||
|
# hex (no #) -> decimal rgb on stdout
|
||||||
|
hex2dec() { printf '%d %d %d\n' "$((16#${1:0:2}))" "$((16#${1:2:2}))" "$((16#${1:4:2}))"; }
|
||||||
|
|
||||||
|
# Theme colors as parallel arrays.
|
||||||
|
t_r=(); t_g=(); t_b=()
|
||||||
|
while read -r h; do
|
||||||
|
read -r r g b < <(hex2dec "${h#\#}")
|
||||||
|
t_r+=("$r"); t_g+=("$g"); t_b+=("$b")
|
||||||
|
done <<< "$theme_colors"
|
||||||
|
|
||||||
|
# Score every indexed wallpaper: sum over theme colors of nearest wallpaper color.
|
||||||
|
scores="$cache_dir/scores.tmp"
|
||||||
|
: > "$scores"
|
||||||
|
while IFS=$'\t' read -r path whexes; do
|
||||||
|
w_r=(); w_g=(); w_b=()
|
||||||
|
for wh in $whexes; do
|
||||||
|
read -r wr wg wb < <(hex2dec "${wh#\#}")
|
||||||
|
w_r+=("$wr"); w_g+=("$wg"); w_b+=("$wb")
|
||||||
|
done
|
||||||
|
if [ "${#w_r[@]}" -eq 0 ]; then continue; fi
|
||||||
|
total=0
|
||||||
|
for i in "${!t_r[@]}"; do
|
||||||
|
mind=1000000000
|
||||||
|
for j in "${!w_r[@]}"; do
|
||||||
|
dr=$(( t_r[i] - w_r[j] )); dg=$(( t_g[i] - w_g[j] )); db=$(( t_b[i] - w_b[j] ))
|
||||||
|
cd=$(( dr*dr + dg*dg + db*db ))
|
||||||
|
if [ "$cd" -lt "$mind" ]; then mind=$cd; fi
|
||||||
|
done
|
||||||
|
total=$(( total + mind ))
|
||||||
|
done
|
||||||
|
printf '%s\t%s\n' "$total" "$path"
|
||||||
|
done < "$palettes" | sort -n -k1 > "$scores"
|
||||||
|
|
||||||
|
[ -s "$scores" ] || { log_error "no wallpapers indexed"; exit 3; }
|
||||||
|
|
||||||
|
# Matched tier: within 1.4x of the best score (fall back to closest 20).
|
||||||
|
best="$(head -n1 "$scores" | cut -f1)"
|
||||||
|
threshold=$(( best * 14 / 10 ))
|
||||||
|
matched="$(awk -F'\t' -v t="$threshold" '$1 <= t {print $2}' "$scores")"
|
||||||
|
if [ "$(printf '%s\n' "$matched" | grep -c .)" -lt 5 ]; then
|
||||||
|
matched="$(head -n20 "$scores" | cut -f2)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Print the chosen PATH on stdout (data channel for callers) and log the
|
||||||
|
# human-readable choice on stderr.
|
||||||
|
chosen="$(printf '%s\n' "$matched" | shuf -n1)"
|
||||||
|
log_info "picked $(basename "$chosen") for ${variant}"
|
||||||
|
printf '%s\n' "$chosen"
|
||||||
@@ -70,7 +70,10 @@
|
|||||||
|
|
||||||
notification.mako.enable = true;
|
notification.mako.enable = true;
|
||||||
|
|
||||||
dynamic-theme.enable = true;
|
dynamic-theme = {
|
||||||
|
enable = true;
|
||||||
|
wallpaperRepo = "https://gitea.susano-homelab.duckdns.org/fujin/gruvbox-wallpapers.git";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
programs = {
|
programs = {
|
||||||
@@ -88,13 +91,6 @@
|
|||||||
directory = ["/home/${username}/nixos-dotfiles" "/home/${username}/.cache/nix"];
|
directory = ["/home/${username}/nixos-dotfiles" "/home/${username}/.cache/nix"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
hooks = {
|
|
||||||
commit-msg = pkgs.writeScript "commit-msg" ''
|
|
||||||
#!${pkgs.bash}/bin/bash
|
|
||||||
# Remove Co-Authored-by lines from commit messages (Claude likes to add these)
|
|
||||||
${pkgs.gnused}/bin/sed -i '/^Co-[Aa]uthored-[Bb]y:/d' "$1"
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user