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

13 KiB

Dynamic Theme Switcher Module

A NixOS module that automatically generates and applies color themes from wallpapers using pywal16, with integrated wpaperd wallpaper management and support for GTK, Qt/Kvantum, Ghostty, and other applications.

Features

  • Integrated Wallpaper Management: Built-in wpaperd integration with configurable rotation modes
  • Automatic Color Extraction: Uses pywal16 with configurable backends (haishoku, colorthief, etc.) to extract color palettes from wallpapers
  • Multi-Application Support: Generates themes for GTK 3.0, GTK 4.0, Qt/Kvantum, Ghostty terminal, and more
  • Time-Based Switching: Automatically switches between light and dark themes based on configured times
  • Flexible Rotation Modes: Choose between continuous rotation, switch-only, or hybrid modes
  • Manual Control: Provides scripts for manual theme switching
  • Synchronized Wallpaper & Theme: Wallpaper and color theme always match

Directory Structure

theme_switcher/
├── default.nix                    # Main module configuration
├── templates/
│   ├── gtk-3.0.css               # GTK 3.0 theme template
│   ├── gtk-4.0.css               # GTK 4.0 theme template
│   └── PywalTheme.kvconfig       # Kvantum Qt theme template
├── OUTLINE.md                     # Implementation specification
└── README.md                      # This file

Installation

1. Import the Module

Add the module to your host configuration:

# In your host's configuration.nix or desktop-configuration.nix
imports = [
  ../../shared/modules/services/theme_switcher
];

2. Enable and Configure

services.themeSwitcher = {
  enable = true;
  user = "yourUsername";
  
  # Optional: Customize settings
  backend = "haishoku";  # Color extraction backend
  lightTime = "06:00:00";  # Switch to light theme at 6 AM
  darkTime = "18:00:00";   # Switch to dark theme at 6 PM
  enableAutoSwitch = true; # Enable automatic time-based switching
  
  # Wallpaper rotation settings
  rotation = {
    mode = "continuous";  # Options: continuous, switch-only, hybrid
    interval = "5m";      # Rotation interval (for continuous mode)
  };
  
  # Wpaperd configuration
  wpaperd = {
    enable = true;
    mode = "center";      # Options: center, fit, stretch, tile
    transition.effect = "fade";
    transition.duration = 300;  # milliseconds
  };
};

Note: If you were previously using the separate wallpaper-rotator module, you should disable it to avoid conflicts.

3. Wallpaper Directory Structure

Ensure your wallpaper directory has Light and Dark subdirectories:

wallpapers/
├── Light/
│   ├── wallpaper1.jpg
│   ├── wallpaper2.jpg
│   └── ...
└── Dark/
    ├── wallpaper1.jpg
    ├── wallpaper2.jpg
    └── ...

4. Rebuild Your System

sudo nixos-rebuild switch --flake .#hostname

Configuration Options

Core Options

services.themeSwitcher.enable

  • Type: boolean
  • Default: false
  • Description: Enable the dynamic theme switcher with integrated wallpaper management

services.themeSwitcher.user

  • Type: string
  • Required: yes
  • Description: Username for which to apply theme switching

services.themeSwitcher.wallpaperPath

  • Type: string
  • Default: ${homeDirectory}/nixos/shared/modules/services/wallpapers
  • Description: Path to wallpaper directories (must contain Light/ and Dark/ subdirectories)

services.themeSwitcher.backend

  • Type: enum
  • Default: "haishoku"
  • Options: "haishoku", "modern_colorthief", "fast_colorthief", "colorthief", "colorz", "wal"
  • Description: Pywal color extraction backend algorithm

services.themeSwitcher.lightTime

  • Type: string
  • Default: "06:00:00"
  • Description: Time to switch to light theme (HH:MM:SS format)

services.themeSwitcher.darkTime

  • Type: string
  • Default: "18:00:00"
  • Description: Time to switch to dark theme (HH:MM:SS format)

services.themeSwitcher.enableAutoSwitch

  • Type: boolean
  • Default: true
  • Description: Enable automatic time-based theme switching via systemd timer

Rotation Options

services.themeSwitcher.rotation.mode

  • Type: enum
  • Default: "continuous"
  • Options:
    • "continuous": Rotate wallpapers every interval AND regenerate theme each time
    • "switch-only": Only change wallpaper at light/dark switch times (6 AM/6 PM)
    • "hybrid": Rotate wallpapers but only regenerate theme at switch times (not yet implemented)
  • Description: Wallpaper rotation and theme generation behavior

services.themeSwitcher.rotation.interval

  • Type: string
  • Default: "5m"
  • Example: "30s", "10m", "1h"
  • Description: How often to rotate wallpapers in continuous mode

services.themeSwitcher.rotation.sorting

  • Type: enum
  • Default: "random"
  • Options: "random"
  • Description: Wallpaper selection method (currently only random is supported)

Wpaperd Integration Options

services.themeSwitcher.wpaperd.enable

  • Type: boolean
  • Default: true
  • Description: Enable wpaperd wallpaper daemon integration

services.themeSwitcher.wpaperd.mode

  • Type: enum
  • Default: "center"
  • Options: "center", "fit", "fit-border-color", "stretch", "tile"
  • Description: How to display wallpapers when size differs from display resolution

services.themeSwitcher.wpaperd.transition.effect

  • Type: string
  • Default: "fade"
  • Example: "simple", "fade"
  • Description: Wallpaper transition effect name

services.themeSwitcher.wpaperd.transition.duration

  • Type: integer
  • Default: 300
  • Description: Transition duration in milliseconds

Manual Usage

Apply Theme Manually

The module installs apply-theme.sh to ~/.local/bin/:

# Apply light theme with random wallpaper from Light/ directory
~/.local/bin/apply-theme.sh --light

# Apply dark theme with random wallpaper from Dark/ directory
~/.local/bin/apply-theme.sh --dark

# Apply theme with specific wallpaper
~/.local/bin/apply-theme.sh --light --wallpaper-path /path/to/wallpaper.jpg

Check Systemd Timer Status

# View timer status
systemctl --user status theme-switcher.timer

# List upcoming timer events
systemctl --user list-timers | grep theme

# Manually trigger theme switch
systemctl --user start theme-switcher.service

# View service logs
journalctl --user -u theme-switcher.service

Test Pywal Color Extraction

# Test light theme generation
wal -i ~/nixos/shared/modules/services/wallpapers/Light/IU-Light.jpg --backend haishoku -l -n

# Test dark theme generation
wal -i ~/nixos/shared/modules/services/wallpapers/Dark/IU-Dark.jpg --backend haishoku -n

# View generated colors
cat ~/.cache/wal/colors.json

# View generated templates
ls -la ~/.cache/wal/

Generated Files

After running the theme switcher, pywal generates the following files:

Pywal Cache (~/.cache/wal/)

  • colors.json - Extracted color palette in JSON format
  • colors.sh - Shell script with color variables
  • ghostty.conf - Ghostty terminal theme (auto-reloads)
  • gtk-3.0.css - GTK 3.0 theme (from custom template)
  • gtk-4.0.css - GTK 4.0 theme (from custom template)
  • PywalTheme.kvconfig - Kvantum Qt theme (from custom template)

User Configuration

  • ~/.config/gtk-3.0/gtk.css - Symlink to pywal-generated GTK3 theme
  • ~/.config/gtk-4.0/gtk.css - Symlink to pywal-generated GTK4 theme
  • ~/.config/Kvantum/PywalTheme/PywalTheme.kvconfig - Kvantum theme copy

Integration with Existing Themes

When enabling this module, you should adjust your existing theme configuration:

Before (Static Catppuccin Theme)

gtk = {
  enable = true;
  theme = {
    name = "catppuccin-macchiato-lavender-compact+rimless";
    package = pkgs.catppuccin-gtk.override { ... };
  };
};

qt = {
  enable = true;
  platformTheme.name = "kvantum";
  style.name = "kvantum";
};

xdg.configFile."Kvantum/kvantum.kvconfig".source = ...;

After (Dynamic Pywal Theme)

# The theme switcher module automatically sets:
# - gtk.enable = true
# - qt.enable = true
# - qt.platformTheme.name = "kvantum"
# - qt.style.name = "kvantum"

# You can still keep static icon and cursor themes:
gtk.iconTheme = {
  name = "Papirus-Dark";
  package = pkgs.catppuccin-papirus-folders;
};

gtk.cursorTheme = {
  name = "Bibata-Modern-Classic";
  package = pkgs.bibata-cursors;
};

Troubleshooting

Theme Not Applying

# Check if pywal generated files
ls -la ~/.cache/wal/

# Check systemd service status
systemctl --user status theme-switcher.service

# View detailed logs
journalctl --user -u theme-switcher.service -n 50

# Manually run apply-theme.sh with verbose output
~/.local/bin/apply-theme.sh --light

Kvantum Theme Not Working

# Check if Kvantum theme was created
ls -la ~/.config/Kvantum/PywalTheme/

# Manually set Kvantum theme
kvantummanager --set PywalTheme

# Check if Qt applications are using Kvantum
echo $QT_STYLE_OVERRIDE  # Should be "kvantum"

Colors Don't Match Wallpaper

Try a different pywal backend:

services.themeSwitcher.backend = "modern_colorthief";  # or "fast_colorthief", "colorthief", etc.

GTK Applications Not Updating

# Check if GTK CSS files exist
ls -la ~/.config/gtk-3.0/gtk.css
ls -la ~/.config/gtk-4.0/gtk.css

# Restart GTK applications for changes to take effect

Supported Applications

Automatically Themed

  • GTK 3 Applications: Nautilus, GNOME Calculator, etc.
  • GTK 4 Applications: GNOME Text Editor, newer GNOME apps
  • Qt Applications: KDE apps when using Kvantum
  • Ghostty Terminal: Auto-reloads on config change
  • Waybar: If using pywal template (built-in)
  • Rofi: If using pywal template (built-in)
  • Hyprland/Sway: If using pywal template (built-in)

Manual Integration Required

  • Firefox: Use pywal-firefox extension
  • VSCode: Use pywal theme extension
  • Other Applications: Check if pywal templates exist in ~/.cache/wal/

Advanced Usage

Custom Pywal Templates

To add your own templates, create files in ~/.config/wal/templates/:

# Template file: ~/.config/wal/templates/myapp.conf
# Use pywal template variables
background = {background}
foreground = {foreground}
color0 = {color0}
...
color15 = {color15}

After running apply-theme.sh, find your generated file in ~/.cache/wal/myapp.conf.

Disable Automatic Switching

To use manual control only:

services.themeSwitcher = {
  enable = true;
  user = "username";
  enableAutoSwitch = false;  # Disable timer
};

Then use ~/.local/bin/apply-theme.sh manually as needed.

Rotation Modes Explained

Continuous Mode (Default)

  • Wallpaper rotates every interval (e.g., 5 minutes)
  • Theme is regenerated from each new wallpaper
  • Wallpaper and theme always match
  • Resource usage: Moderate (pywal runs every interval)
  • Best for: Users who want frequently changing, perfectly matched themes

Switch-Only Mode

  • Wallpaper changes only at light/dark switch times (6 AM / 6 PM)
  • Theme is generated once per switch
  • Wallpaper and theme always match
  • Resource usage: Minimal (pywal runs twice per day)
  • Best for: Users who prefer consistent themes throughout the day

Hybrid Mode (Future)

  • Wallpaper rotates every interval
  • Theme is generated only at switch times
  • Wallpaper and theme may not match mid-day
  • Not yet implemented

Migration from wallpaper-rotator Module

If you were previously using the separate wallpaper-rotator module:

  1. Disable the old module in your configuration:

    # Comment out or remove:
    # services.wallpaperRotator.enable = true;
    
  2. Enable themeSwitcher with equivalent settings:

    services.themeSwitcher = {
      enable = true;
      user = "yourUsername";
      rotation.interval = "5m";  # Same as old duration setting
      wpaperd.mode = "center";   # Same as old mode setting
    };
    
  3. Rebuild your system - wpaperd will now be managed by themeSwitcher

Technical Details

  • Pywal Template Syntax: {background}, {foreground}, {color0}-{color15}, {cursor}, {wallpaper}
  • Template Location: ~/.config/wal/templates/filename.ext
  • Output Location: ~/.cache/wal/filename.ext
  • Color Extraction: 16-color palette using pywal16
  • GTK Reload: Automatic via gsettings changes
  • Kvantum Reload: May require app restart
  • Ghostty Reload: Automatic on config file change

Future Enhancements

  • Direct wpaperd integration (trigger theme change on wallpaper rotation)
  • Per-monitor theme configuration
  • Additional application templates (Firefox, VSCode, etc.)
  • Theme preview before applying
  • Fallback color schemes for monochrome wallpapers

Credits

  • Built for NixOS using home-manager
  • Uses pywal16 for color extraction
  • Based on pywal original concept