104 lines
3.5 KiB
Nix
104 lines
3.5 KiB
Nix
{ config, lib, pkgs, options, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.dov.dynamic-theme;
|
|
schemes = "${pkgs.base16-schemes}/share/themes";
|
|
|
|
# Single source of truth for builtin themes
|
|
builtinThemes = {
|
|
gruvbox = {
|
|
dark = "${schemes}/gruvbox-dark-hard.yaml";
|
|
light = "${schemes}/gruvbox-light-hard.yaml";
|
|
};
|
|
catppuccin = {
|
|
dark = "${schemes}/catppuccin-mocha.yaml";
|
|
light = "${schemes}/catppuccin-latte.yaml";
|
|
};
|
|
};
|
|
|
|
# Generate all theme-variant combinations
|
|
themeVariants = concatMapAttrs (name: theme: {
|
|
"${name}-dark" = { scheme = theme.dark; polarity = "dark"; };
|
|
"${name}-light" = { scheme = theme.light; polarity = "light"; };
|
|
}) cfg.themes;
|
|
|
|
# Base is gruvbox-dark, everything else is a specialisation
|
|
baseVariant = "gruvbox-dark";
|
|
specialisationVariants = filterAttrs (name: _: name != baseVariant) themeVariants;
|
|
|
|
availableVariants = concatStringsSep " " (attrNames themeVariants);
|
|
|
|
# Userspace theme switcher: activates a pre-built Home Manager
|
|
# specialisation generation. No sudo, no OS rebuild — just runs the
|
|
# specialisation's activation script in the base HM generation.
|
|
#
|
|
# The script logic lives in ./theme-switch.sh (plain shell, no Nix
|
|
# escaping); we only inject the two config-derived values it needs.
|
|
theme-switch = pkgs.writeShellScriptBin "theme-switch" ''
|
|
baseVariant="${baseVariant}"
|
|
availableVariants="${availableVariants}"
|
|
${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 {
|
|
options.dov.dynamic-theme = {
|
|
enable = mkEnableOption "dynamic theme switching";
|
|
|
|
themes = mkOption {
|
|
type = types.attrsOf (types.submodule {
|
|
options = {
|
|
dark = mkOption {
|
|
type = types.path;
|
|
description = "Path to dark variant base16 scheme";
|
|
};
|
|
light = mkOption {
|
|
type = types.path;
|
|
description = "Path to light variant base16 scheme";
|
|
};
|
|
};
|
|
});
|
|
default = builtinThemes;
|
|
description = "Available themes with dark and light variants";
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable (
|
|
{
|
|
home.packages = [ theme-switch ];
|
|
}
|
|
# Only reference the `stylix` option where it is actually declared.
|
|
# `mkIf false` is NOT enough: the module system still rejects a
|
|
# definition (even a disabled one) for an option that does not exist,
|
|
# which breaks every host that doesn't import stylix. `optionalAttrs`
|
|
# removes the key structurally, so non-stylix hosts evaluate cleanly.
|
|
// optionalAttrs hasStylix {
|
|
stylix = {
|
|
enable = true;
|
|
autoEnable = true;
|
|
# Base scheme/polarity; overridable per specialisation below.
|
|
base16Scheme = mkDefault themeVariants.${baseVariant}.scheme;
|
|
polarity = mkDefault themeVariants.${baseVariant}.polarity;
|
|
};
|
|
|
|
# Generate Home Manager specialisations for every non-base variant.
|
|
# Each is pre-built into the nix store during the single rebuild and
|
|
# activated userspace via `theme-switch` (no sudo, no OS rebuild).
|
|
specialisation = mapAttrs (name: variant: {
|
|
configuration = {
|
|
stylix = {
|
|
base16Scheme = mkForce variant.scheme;
|
|
polarity = mkForce variant.polarity;
|
|
};
|
|
};
|
|
}) specialisationVariants;
|
|
}
|
|
);
|
|
}
|