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

400 lines
13 KiB
Markdown

# 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:
```nix
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:**
```bash
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:**
```bash
if [ "$MODE" = "light" ]; then
wal -i "$WALLPAPER" --backend haishoku -l -n
else
wal -i "$WALLPAPER" --backend haishoku -n
fi
```
3. **Apply GTK theme:**
```bash
# 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:**
```bash
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:**
```bash
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:**
```nix
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:**
```nix
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:**
```bash
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`**
```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:**
```bash
# 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:**
```bash
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:**
```bash
~/.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:**
```bash
kvantummanager --set PywalTheme
# Or check: cat ~/.config/Kvantum/kvantum.kvconfig
```
6. **Test systemd automation:**
```bash
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/`