nixos/shared/modules/services/theme_switcher/OUTLINE.md
2026-01-23 13:44:07 -07:00

13 KiB

NixOS Dynamic Theme Switcher - Implementation Instructions

Objective

Build a time-based dynamic theme switching system on NixOS that:

  1. Extracts color palettes from wallpapers using pywal16 with haishoku backend
  2. Generates and applies themes for GTK, Qt/Kvantum, Ghostty, and other applications
  3. Switches between light/dark themes based on time of day
  4. 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/ and Dark/ subdirectories
  • Service configured via services.wallpaperRotator module

Actions Required:

  1. Disable/modify static GTK theme configuration in home.nix
  2. Disable/modify static Kvantum theme configuration
  3. Integrate with wpaperd to trigger theme changes on wallpaper rotation
  4. 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.conf template (tested and confirmed)
  • CSS variables: colors.css template
  • Waybar: colors-waybar.css
  • Hyprland: colors-hyprland.conf
  • Rofi: colors-rofi-dark.rasi and colors-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:

  • --light or --dark (theme mode)
  • Optional: --wallpaper-path (specific wallpaper, otherwise use wpaperd current)

Script Logic:

  1. Determine wallpaper source:

    • If --wallpaper-path provided, use it
    • Otherwise, let wpaperd select based on mode (Light/ or Dark/ directory)
    • Use wpaperctl to interact with wpaperd if needed
  2. Generate color scheme:

    if [ "$MODE" = "light" ]; then
        wal -i "$WALLPAPER" --backend haishoku -l -n
    else
        wal -i "$WALLPAPER" --backend haishoku -n
    fi
    
  3. 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"
    
  4. Apply Qt/Kvantum theme:

    kvantummanager --set PywalTheme
    
  5. 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
  6. 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 variables
  • templates/gtk-4.0.css - GTK4 theme using pywal variables
  • templates/kvantum.kvconfig - Kvantum config using pywal colors

Testing & Validation

8. Test Plan

  1. 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
    
  2. 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
    
  3. 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
    
  4. 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
  5. Test Kvantum theme:

    kvantummanager --set PywalTheme
    # Or check: cat ~/.config/Kvantum/kvantum.kvconfig
    
  6. Test systemd automation:

    systemctl --user start theme-switcher.service
    systemctl --user status theme-switcher.timer
    systemctl --user list-timers | grep theme
    
  7. 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

  1. Create theme_switcher module with pywal16 package
  2. Test color extraction on Light/ and Dark/ wallpapers
  3. Verify built-in templates (ghostty.conf, colors.css) generate correctly

Phase 2: Create custom templates

  1. Create templates/gtk-3.0.css using pywal variables
  2. Create templates/gtk-4.0.css using pywal variables
  3. Create templates/kvantum.kvconfig mapping colors.json to Kvantum format
  4. Install templates to ~/.config/wal/templates/

Phase 3: Theme application script

  1. Write apply-theme.sh script 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

  1. Create systemd timer for light/dark switching times
  2. Create systemd service that calls apply-theme.sh
  3. Script determines mode based on current time
  4. Selects random wallpaper from Light/ or Dark/ directory

Phase 5: Integration and cleanup

  1. Modify existing home.nix files to disable static themes when module enabled
  2. Keep icon themes, cursor themes (not wallpaper-dependent)
  3. Test on one host before deploying to all
  4. 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/