13 KiB
NixOS Dynamic Theme Switcher - Implementation Instructions
Objective
Build a time-based dynamic theme switching system on NixOS that:
- Extracts color palettes from wallpapers using pywal16 with haishoku backend
- Generates and applies themes for GTK, Qt/Kvantum, Ghostty, and other applications
- Switches between light/dark themes based on time of day
- Integrates with existing wpaperd wallpaper rotation service
Current System State
Existing Theming Configuration
GTK Configuration:
- Current theme:
catppuccin-macchiato-lavender-compact+rimless - Settings location:
~/.config/gtk-3.0/settings.ini,~/.config/gtk-4.0/settings.ini - Icon theme:
Papirus-Dark - Cursor:
Bibata-Modern-Classic - Files are symlinked from Nix store via home-manager
Qt/Kvantum Configuration:
- Current theme:
catppuccin-macchiato-lavender - Location:
~/.config/Kvantum/kvantum.kvconfig - Style: kvantum (both Qt5 and Qt6)
- Environment variable:
QT_STYLE_OVERRIDE=kvantum
Wallpaper Service:
- Using wpaperd with home-manager integration
- Wallpapers in:
/home/nate/nixos/shared/modules/services/wallpapers/ - Organized into
Light/andDark/subdirectories - Service configured via
services.wallpaperRotatormodule
Actions Required:
- Disable/modify static GTK theme configuration in home.nix
- Disable/modify static Kvantum theme configuration
- Integrate with wpaperd to trigger theme changes on wallpaper rotation
- Remove hardcoded Catppuccin theme packages when pywal themes are working
Implementation Components
3. Install Required Packages
Add to module configuration:
home.packages = with pkgs; [
pywal16 # Color scheme generator with 16-color support
python3Packages.haishoku # Recommended backend for pywal16
wpaperd # Already installed via wallpaper service
];
Available pywal16 backends: modern_colorthief, wal, fast_colorthief, haishoku, colorthief, colorz, okthief, schemer2
4. Color Extraction & Theme Generation
4.1 Pywal16 Built-in Templates
Pywal16 already includes templates for:
- Ghostty:
ghostty.conftemplate (tested and confirmed) - CSS variables:
colors.csstemplate - Waybar:
colors-waybar.css - Hyprland:
colors-hyprland.conf - Rofi:
colors-rofi-dark.rasiandcolors-rofi-light.rasi - Sway:
colors-sway - Mako:
colors-mako - Foot, Kitty, Alacritty terminal configs
- Many others (47 templates total)
Output location: ~/.cache/wal/ after running wal -i /path/to/wallpaper
Usage:
wal -i /path/to/wallpaper --backend haishoku -n
# -n flag skips setting wallpaper (wpaperd handles that)
# --backend haishoku uses the haishoku color extraction
# -l flag for light themes
4.2 GTK Theme Generation
Goal: Create custom GTK 3.0 and GTK 4.0 CSS themes from pywal colors
Note: Pywal16 includes colors.css template with CSS variables, but NOT full GTK themes.
Required files:
~/.config/gtk-3.0/gtk.css- Custom CSS overlay~/.config/gtk-4.0/gtk.css- Custom CSS overlay
Approach: Create pywal template to generate GTK CSS using colors.json output
- Map pywal colors to GTK color definitions
- Use @define-color for GTK theme variables
- Apply to common widgets
4.3 Qt/Kvantum Theme Generation
Goal: Generate Kvantum theme from pywal colors
Kvantum structure (.kvconfig file):
[General]section with metadata[GeneralColors]section with color definitions (window.color, base.color, button.color, etc.)[Hacks]section with tweaks- Per-widget sections (PanelButtonCommand, etc.)
Location: ~/.config/Kvantum/PywalTheme/PywalTheme.kvconfig
Required: Create pywal template that maps colors.json to .kvconfig format Optional: SVG file can be minimal or omitted for basic themes
Application: Use kvantummanager --set PywalTheme to activate
5. Theme Application Script
Script: apply-theme.sh
Parameters:
--lightor--dark(theme mode)- Optional:
--wallpaper-path(specific wallpaper, otherwise use wpaperd current)
Script Logic:
-
Determine wallpaper source:
- If
--wallpaper-pathprovided, use it - Otherwise, let wpaperd select based on mode (Light/ or Dark/ directory)
- Use
wpaperctlto interact with wpaperd if needed
- If
-
Generate color scheme:
if [ "$MODE" = "light" ]; then wal -i "$WALLPAPER" --backend haishoku -l -n else wal -i "$WALLPAPER" --backend haishoku -n fi -
Apply GTK theme:
# pywal doesn't generate full GTK themes, use custom CSS ln -sf ~/.cache/wal/gtk-3.0.css ~/.config/gtk-3.0/gtk.css ln -sf ~/.cache/wal/gtk-4.0.css ~/.config/gtk-4.0/gtk.css gsettings set org.gnome.desktop.interface color-scheme "prefer-$MODE" -
Apply Qt/Kvantum theme:
kvantummanager --set PywalTheme -
Reload configurations:
- Ghostty: Auto-reloads config on file change (no action needed)
- GTK apps: Automatically detect gsettings changes
- Qt apps: May need restart for Kvantum changes
- Hyprland/Sway: Reload via IPC if using pywal templates
-
Optional: Update Flatpak:
flatpak override --user --filesystem=~/.cache/wal:ro
6. Wallpaper Integration & Time-Based Switching
Option A: Integrate with wpaperd events (Recommended)
- Modify wallpaper-rotator module to support Light/Dark subdirectories
- Change wallpaper directory based on time of day
- Trigger theme generation on wallpaper change
Option B: Independent systemd timer
Timer configuration:
systemd.user.timers.theme-switcher = {
Unit.Description = "Dynamic Theme Switcher Timer";
Timer = {
OnCalendar = [ "*-*-* 06:00:00" "*-*-* 18:00:00" ]; # 6am light, 6pm dark
Persistent = true;
};
Install.WantedBy = [ "timers.target" ];
};
Service configuration:
systemd.user.services.theme-switcher = {
Unit.Description = "Dynamic Theme Switcher";
Service = {
Type = "oneshot";
ExecStart = "${pkgs.bash}/bin/bash ${./scripts/theme-switcher.sh}";
Environment = "PATH=${lib.makeBinPath [ pkgs.pywal16 pkgs.coreutils ]}";
};
};
Script determines light/dark based on current hour:
hour=$(date +%H)
if [ $hour -ge 6 ] && [ $hour -lt 18 ]; then
MODE="light"
WALLPAPER_DIR="Light"
else
MODE="dark"
WALLPAPER_DIR="Dark"
fi
7. NixOS Module Structure
Module: theme_switcher/default.nix
{ config, lib, pkgs, ... }:
let
cfg = config.services.themeSwitcher;
in
{
options.services.themeSwitcher = {
enable = lib.mkEnableOption "dynamic theme switching based on time and wallpapers";
user = lib.mkOption {
type = lib.types.str;
description = "Username for theme switching";
};
wallpaperPath = lib.mkOption {
type = lib.types.str;
default = "/home/${cfg.user}/nixos/shared/modules/services/wallpapers";
description = "Path to wallpaper directories (Light/ and Dark/)";
};
backend = lib.mkOption {
type = lib.types.enum [ "haishoku" "modern_colorthief" "fast_colorthief" ];
default = "haishoku";
description = "Pywal color extraction backend";
};
lightTime = lib.mkOption {
type = lib.types.str;
default = "06:00:00";
description = "Time to switch to light theme (HH:MM:SS)";
};
darkTime = lib.mkOption {
type = lib.types.str;
default = "18:00:00";
description = "Time to switch to dark theme (HH:MM:SS)";
};
};
config = lib.mkIf cfg.enable {
home-manager.users.${cfg.user} = {
home.packages = with pkgs; [
pywal16
python3Packages.haishoku
];
# Install theme application scripts
home.file.".local/bin/apply-theme.sh" = {
source = ./scripts/apply-theme.sh;
executable = true;
};
# Pywal custom templates for GTK and Kvantum
xdg.configFile."wal/templates/gtk-3.0.css".source = ./templates/gtk-3.0.css;
xdg.configFile."wal/templates/gtk-4.0.css".source = ./templates/gtk-4.0.css;
xdg.configFile."wal/templates/kvantum.kvconfig".source = ./templates/kvantum.kvconfig;
# Systemd timer and service
systemd.user.timers.theme-switcher = { /* ... */ };
systemd.user.services.theme-switcher = { /* ... */ };
# Keep Qt/GTK enabled but don't set static themes
qt = {
enable = true;
platformTheme.name = "kvantum";
style.name = "kvantum";
};
gtk.enable = true;
};
};
}
Template files needed:
templates/gtk-3.0.css- GTK3 theme using pywal variablestemplates/gtk-4.0.css- GTK4 theme using pywal variablestemplates/kvantum.kvconfig- Kvantum config using pywal colors
Testing & Validation
8. Test Plan
-
Test pywal color extraction:
# Test with light wallpaper wal -i ~/nixos/shared/modules/services/wallpapers/Light/IU-Light.jpg --backend haishoku -l -n cat ~/.cache/wal/colors.json # Verify colors extracted # Test with dark wallpaper wal -i ~/nixos/shared/modules/services/wallpapers/Dark/IU-Dark.jpg --backend haishoku -n cat ~/.cache/wal/colors.json # Verify colors extracted -
Verify pywal template output:
ls -la ~/.cache/wal/ # Should see: colors.json, ghostty.conf, colors.css, and custom templates cat ~/.cache/wal/ghostty.conf # Check ghostty colors -
Manual theme application:
~/.local/bin/apply-theme.sh --light # Check: GTK apps, Qt apps, Ghostty colors update ~/.local/bin/apply-theme.sh --dark # Check: Theme switches to dark -
Verify theme files:
~/.cache/wal/colors.json- Generated by pywal~/.cache/wal/gtk-3.0.css- From custom template~/.cache/wal/gtk-4.0.css- From custom template~/.cache/wal/kvantum.kvconfig- From custom template~/.cache/wal/ghostty.conf- From built-in template
-
Test Kvantum theme:
kvantummanager --set PywalTheme # Or check: cat ~/.config/Kvantum/kvantum.kvconfig -
Test systemd automation:
systemctl --user start theme-switcher.service systemctl --user status theme-switcher.timer systemctl --user list-timers | grep theme -
Test with live applications:
- Open GTK app (nautilus, gnome-calculator) - verify colors
- Open Qt app (if available) - verify Kvantum theme
- Open Ghostty terminal - verify color palette matches wallpaper
- Check waybar/rofi if using pywal templates for those
Implementation Steps Summary
Phase 1: Setup pywal with existing wallpapers
- Create theme_switcher module with pywal16 package
- Test color extraction on Light/ and Dark/ wallpapers
- Verify built-in templates (ghostty.conf, colors.css) generate correctly
Phase 2: Create custom templates
- Create
templates/gtk-3.0.cssusing pywal variables - Create
templates/gtk-4.0.cssusing pywal variables - Create
templates/kvantum.kvconfigmapping colors.json to Kvantum format - Install templates to
~/.config/wal/templates/
Phase 3: Theme application script
- Write
apply-theme.shscript that:- Runs pywal with appropriate flags (-l for light)
- Symlinks generated CSS to GTK config directories
- Copies Kvantum config to
~/.config/Kvantum/PywalTheme/ - Runs
kvantummanager --set PywalTheme - Updates gsettings for color-scheme preference
Phase 4: Time-based automation
- Create systemd timer for light/dark switching times
- Create systemd service that calls apply-theme.sh
- Script determines mode based on current time
- Selects random wallpaper from Light/ or Dark/ directory
Phase 5: Integration and cleanup
- Modify existing home.nix files to disable static themes when module enabled
- Keep icon themes, cursor themes (not wallpaper-dependent)
- Test on one host before deploying to all
- Document wpaperd integration approach for future enhancement
Key Technical Details
Pywal template syntax:
{background} -> #081212
{foreground} -> #c1c3c3
{color0} -> #081212
...
{color15} -> #c1c3c3
{cursor} -> #c1c3c3
{wallpaper} -> /path/to/wallpaper.jpg
Template location: ~/.config/wal/templates/filename.ext
Output location: ~/.cache/wal/filename.ext
Ghostty: Automatically reloads config when files change - no manual reload needed
GTK: Changes propagate via gsettings/dconf - running apps update automatically
Kvantum: May require app restart to see changes (Qt limitation)
Wallpaper directories:
- Light:
/home/nate/nixos/shared/modules/services/wallpapers/Light/ - Dark:
/home/nate/nixos/shared/modules/services/wallpapers/Dark/