diff --git a/.ignore b/.ignore index e1b043b..a0a672d 100644 --- a/.ignore +++ b/.ignore @@ -1,4 +1,7 @@ # dotfiles/ .git/ flake.lock -frame12/framework-plymouth-theme +frame12/framework-plymouth-theme/framework/throbber-* + +*.png +*.jpeg diff --git a/frame12/linked-dotfiles/niri/config.kdl b/frame12/linked-dotfiles/niri/config.kdl index fdbd5eb..b9884ca 100644 --- a/frame12/linked-dotfiles/niri/config.kdl +++ b/frame12/linked-dotfiles/niri/config.kdl @@ -3,6 +3,7 @@ // // gui startup +spawn-at-startup "swaybg" "-i" "/home/nate/nixos/frame12/wallpaper.png" "-m" "fill" spawn-at-startup "waybar" spawn-at-startup "keepassxc" spawn-at-startup "flatpak" "run" "org.signal.Signal" diff --git a/jaci/desktop-configuration.nix b/jaci/desktop-configuration.nix index 6077416..8fdfb4f 100644 --- a/jaci/desktop-configuration.nix +++ b/jaci/desktop-configuration.nix @@ -65,6 +65,19 @@ in nixpkgs.config.allowUnfree = true; boot = { + plymouth = { + enable = true; + theme = "kiki"; + themePackages = [ + (pkgs.runCommand "plymouth-kiki-theme" { } '' + mkdir -p $out/share/plymouth/themes/kiki + cp -r ${./kiki-plymouth-theme/kiki}/* $out/share/plymouth/themes/kiki/ + substituteInPlace $out/share/plymouth/themes/kiki/kiki.plymouth \ + --replace-fail "@IMAGEDIR@" "$out/share/plymouth/themes/kiki" + '') + ]; + }; + # Enable "Silent Boot" consoleLogLevel = 0; initrd.verbose = false; diff --git a/jaci/kiki-plymouth-theme/kiki.gif b/jaci/kiki-plymouth-theme/kiki.gif new file mode 100644 index 0000000..68bb681 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki.gif differ diff --git a/jaci/kiki-plymouth-theme/kiki/bullet.png b/jaci/kiki-plymouth-theme/kiki/bullet.png new file mode 100644 index 0000000..5799dda Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/bullet.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/capslock.png b/jaci/kiki-plymouth-theme/kiki/capslock.png new file mode 100644 index 0000000..9d775f6 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/capslock.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/entry.png b/jaci/kiki-plymouth-theme/kiki/entry.png new file mode 100644 index 0000000..4a22e7f Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/entry.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/keyboard.png b/jaci/kiki-plymouth-theme/kiki/keyboard.png new file mode 100644 index 0000000..f532ad0 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/keyboard.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/keymap-render.png b/jaci/kiki-plymouth-theme/kiki/keymap-render.png new file mode 100644 index 0000000..4aaed5a Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/keymap-render.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/kiki.plymouth b/jaci/kiki-plymouth-theme/kiki/kiki.plymouth new file mode 100644 index 0000000..d80dc0a --- /dev/null +++ b/jaci/kiki-plymouth-theme/kiki/kiki.plymouth @@ -0,0 +1,54 @@ +[Plymouth Theme] +Name=Kiki +Description=Theme with animated Kiki from Kiki's Delivery Service lying in grass. +ModuleName=two-step + +[two-step] +Font=Cantarell 12 +TitleFont=Cantarell Light 30 +ImageDir=@IMAGEDIR@ +DialogHorizontalAlignment=.5 +DialogVerticalAlignment=.382 +TitleHorizontalAlignment=.5 +TitleVerticalAlignment=.382 +HorizontalAlignment=.5 +VerticalAlignment=.5 +WatermarkHorizontalAlignment=.5 +WatermarkVerticalAlignment=.96 +Transition=none +TransitionDuration=0.0 +BackgroundStartColor=0x498172 +BackgroundEndColor=0x759B65 +ProgressBarBackgroundColor=0x3a6b5e +ProgressBarForegroundColor=0xffffff +MessageBelowAnimation=true + +[boot-up] +UseEndAnimation=false + +[shutdown] +UseEndAnimation=false + +[reboot] +UseEndAnimation=false + +[updates] +SuppressMessages=true +ProgressBarShowPercentComplete=true +UseProgressBar=true +Title=Installing Updates... +SubTitle=Do not turn off your computer + +[system-upgrade] +SuppressMessages=true +ProgressBarShowPercentComplete=true +UseProgressBar=true +Title=Upgrading System... +SubTitle=Do not turn off your computer + +[firmware-upgrade] +SuppressMessages=true +ProgressBarShowPercentComplete=true +UseProgressBar=true +Title=Upgrading Firmware... +SubTitle=Do not turn off your computer diff --git a/jaci/kiki-plymouth-theme/kiki/lock.png b/jaci/kiki-plymouth-theme/kiki/lock.png new file mode 100644 index 0000000..c1538df Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/lock.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0001.png b/jaci/kiki-plymouth-theme/kiki/throbber-0001.png new file mode 100644 index 0000000..bc49b58 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0001.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0002.png b/jaci/kiki-plymouth-theme/kiki/throbber-0002.png new file mode 100644 index 0000000..bc49b58 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0002.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0003.png b/jaci/kiki-plymouth-theme/kiki/throbber-0003.png new file mode 100644 index 0000000..bc49b58 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0003.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0004.png b/jaci/kiki-plymouth-theme/kiki/throbber-0004.png new file mode 100644 index 0000000..bc49b58 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0004.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0005.png b/jaci/kiki-plymouth-theme/kiki/throbber-0005.png new file mode 100644 index 0000000..07ca18e Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0005.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0006.png b/jaci/kiki-plymouth-theme/kiki/throbber-0006.png new file mode 100644 index 0000000..07ca18e Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0006.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0007.png b/jaci/kiki-plymouth-theme/kiki/throbber-0007.png new file mode 100644 index 0000000..07ca18e Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0007.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0008.png b/jaci/kiki-plymouth-theme/kiki/throbber-0008.png new file mode 100644 index 0000000..07ca18e Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0008.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0009.png b/jaci/kiki-plymouth-theme/kiki/throbber-0009.png new file mode 100644 index 0000000..b9e2f3d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0009.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0010.png b/jaci/kiki-plymouth-theme/kiki/throbber-0010.png new file mode 100644 index 0000000..b9e2f3d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0010.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0011.png b/jaci/kiki-plymouth-theme/kiki/throbber-0011.png new file mode 100644 index 0000000..b9e2f3d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0011.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0012.png b/jaci/kiki-plymouth-theme/kiki/throbber-0012.png new file mode 100644 index 0000000..b9e2f3d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0012.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0013.png b/jaci/kiki-plymouth-theme/kiki/throbber-0013.png new file mode 100644 index 0000000..04f38cb Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0013.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0014.png b/jaci/kiki-plymouth-theme/kiki/throbber-0014.png new file mode 100644 index 0000000..04f38cb Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0014.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0015.png b/jaci/kiki-plymouth-theme/kiki/throbber-0015.png new file mode 100644 index 0000000..04f38cb Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0015.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0016.png b/jaci/kiki-plymouth-theme/kiki/throbber-0016.png new file mode 100644 index 0000000..04f38cb Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0016.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0017.png b/jaci/kiki-plymouth-theme/kiki/throbber-0017.png new file mode 100644 index 0000000..f76600d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0017.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0018.png b/jaci/kiki-plymouth-theme/kiki/throbber-0018.png new file mode 100644 index 0000000..f76600d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0018.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0019.png b/jaci/kiki-plymouth-theme/kiki/throbber-0019.png new file mode 100644 index 0000000..f76600d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0019.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0020.png b/jaci/kiki-plymouth-theme/kiki/throbber-0020.png new file mode 100644 index 0000000..f76600d Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0020.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0021.png b/jaci/kiki-plymouth-theme/kiki/throbber-0021.png new file mode 100644 index 0000000..4d29670 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0021.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0022.png b/jaci/kiki-plymouth-theme/kiki/throbber-0022.png new file mode 100644 index 0000000..4d29670 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0022.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0023.png b/jaci/kiki-plymouth-theme/kiki/throbber-0023.png new file mode 100644 index 0000000..4d29670 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0023.png differ diff --git a/jaci/kiki-plymouth-theme/kiki/throbber-0024.png b/jaci/kiki-plymouth-theme/kiki/throbber-0024.png new file mode 100644 index 0000000..4d29670 Binary files /dev/null and b/jaci/kiki-plymouth-theme/kiki/throbber-0024.png differ diff --git a/jaci/linked-dotfiles/niri/config.kdl b/jaci/linked-dotfiles/niri/config.kdl index fe6a541..0ae0a65 100644 --- a/jaci/linked-dotfiles/niri/config.kdl +++ b/jaci/linked-dotfiles/niri/config.kdl @@ -3,6 +3,7 @@ // // gui startup +spawn-at-startup "swaybg" "-i" "/home/jaci/nixos/jaci/kiki_background.jpg" "-m" "fill" spawn-at-startup "waybar" spawn-at-startup "keepassxc" // shell startup diff --git a/nate-work/default.nix b/nate-work/default.nix index ac282c7..309cb75 100644 --- a/nate-work/default.nix +++ b/nate-work/default.nix @@ -24,7 +24,7 @@ # Stylix theming - auto-generate color scheme from wallpaper stylix = { enable = true; - image = ../shared/modules/services/wallpapers/Dark/IU-Dark.jpg; + image = ./wallpaper.png; polarity = "dark"; # System-wide cursor diff --git a/nate-work/linked-dotfiles/niri/config.kdl b/nate-work/linked-dotfiles/niri/config.kdl index ff6edcd..24d766f 100644 --- a/nate-work/linked-dotfiles/niri/config.kdl +++ b/nate-work/linked-dotfiles/niri/config.kdl @@ -3,6 +3,7 @@ // // gui startup +spawn-at-startup "swaybg" "-i" "/home/nate/nixos/nate-work/wallpaper.png" "-m" "fill" spawn-at-startup "waybar" spawn-at-startup "keepassxc" spawn-at-startup "flatpak" "run" "org.signal.Signal" diff --git a/nate-work/wallpaper.png b/nate-work/wallpaper.png new file mode 100644 index 0000000..c7b99d1 Binary files /dev/null and b/nate-work/wallpaper.png differ diff --git a/nate/default.nix b/nate/default.nix index 0b4ad02..5d00f47 100644 --- a/nate/default.nix +++ b/nate/default.nix @@ -23,7 +23,7 @@ # Stylix theming - auto-generate color scheme from wallpaper stylix = { enable = true; - image = ../shared/modules/services/wallpapers/Dark/FSO-Dark.jpg; + image = ./wallpaper.png; polarity = "dark"; # System-wide cursor diff --git a/nate/wallpaper.png b/nate/wallpaper.png new file mode 100644 index 0000000..7b5094e Binary files /dev/null and b/nate/wallpaper.png differ diff --git a/shared/modules/services/theme_switcher/EXAMPLE_CONFIG.nix b/shared/modules/services/theme_switcher/EXAMPLE_CONFIG.nix deleted file mode 100644 index 9f584d9..0000000 --- a/shared/modules/services/theme_switcher/EXAMPLE_CONFIG.nix +++ /dev/null @@ -1,74 +0,0 @@ -# Example configuration for enabling the theme switcher module -# Add this to your host's desktop-configuration.nix or similar file - -{ - # Import the module (if not already in your imports list) - imports = [ - ../../shared/modules/services/theme_switcher - ]; - - # Enable and configure the theme switcher - services.themeSwitcher = { - enable = true; - user = "nate"; # Change to your username - - # Theme generation settings - backend = "haishoku"; # Recommended 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 - - # Palette generation strategy - paletteGeneration = "continuous"; # Options: continuous, light-dark - - # Wallpaper rotation settings - rotation = { - interval = "5m"; # Rotate wallpaper every 5 minutes - sorting = "random"; # Currently only random is supported - }; - - # Wpaperd wallpaper daemon settings - wpaperd = { - enable = true; - mode = "center"; # Options: center, fit, fit-border-color, stretch, tile - transition = { - effect = "fade"; # Transition effect (fade, simple, etc.) - duration = 300; # Transition duration in milliseconds - }; - }; - }; - - # IMPORTANT: Disable the old wallpaper-rotator module if you were using it - # services.wallpaperRotator.enable = false; # Comment out or remove - - # Note: You may want to comment out or remove static theme configurations - # when enabling the dynamic theme switcher. For example: - - # BEFORE (static theme) - COMMENT THESE OUT: - # home-manager.users.nate = { - # gtk.theme = { - # name = "catppuccin-macchiato-lavender-compact+rimless"; - # package = pkgs.catppuccin-gtk.override { ... }; - # }; - # - # xdg.configFile."Kvantum/kvantum.kvconfig".source = ...; - # }; - - # AFTER (dynamic theme): - # The theme switcher module handles GTK/Qt theming automatically - # You can still keep icon and cursor themes: - # home-manager.users.nate = { - # gtk.iconTheme = { - # name = "Papirus-Dark"; - # package = pkgs.catppuccin-papirus-folders; - # }; - # gtk.cursorTheme = { - # name = "Bibata-Modern-Classic"; - # package = pkgs.bibata-cursors; - # }; - # }; -} - -# For manual testing before enabling auto-switch: -# services.themeSwitcher.enableAutoSwitch = false; -# Then use: ~/.local/bin/apply-theme.sh --light (or --dark) diff --git a/shared/modules/services/theme_switcher/OUTLINE.md b/shared/modules/services/theme_switcher/OUTLINE.md deleted file mode 100644 index 13af94f..0000000 --- a/shared/modules/services/theme_switcher/OUTLINE.md +++ /dev/null @@ -1,399 +0,0 @@ -# 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/` diff --git a/shared/modules/services/theme_switcher/README.md b/shared/modules/services/theme_switcher/README.md deleted file mode 100644 index 8cbdc56..0000000 --- a/shared/modules/services/theme_switcher/README.md +++ /dev/null @@ -1,447 +0,0 @@ -# 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: - -```nix -# In your host's configuration.nix or desktop-configuration.nix -imports = [ - ../../shared/modules/services/theme_switcher -]; -``` - -### 2. Enable and Configure - -```nix -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 - -```bash -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/`: - -```bash -# 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 - -```bash -# 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 - -```bash -# 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) -```nix -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) -```nix -# 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 -```bash -# 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 -```bash -# 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: -```nix -services.themeSwitcher.backend = "modern_colorthief"; # or "fast_colorthief", "colorthief", etc. -``` - -### GTK Applications Not Updating -```bash -# 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/`: - -```bash -# 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: -```nix -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: - ```nix - # Comment out or remove: - # services.wallpaperRotator.enable = true; - ``` - -2. **Enable themeSwitcher** with equivalent settings: - ```nix - 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](https://github.com/eylles/pywal16) for color extraction -- Based on [pywal](https://github.com/dylanaraps/pywal) original concept diff --git a/shared/modules/services/theme_switcher/TESTING_GUIDE.md b/shared/modules/services/theme_switcher/TESTING_GUIDE.md deleted file mode 100644 index 087cbb3..0000000 --- a/shared/modules/services/theme_switcher/TESTING_GUIDE.md +++ /dev/null @@ -1,347 +0,0 @@ -# Theme Switcher Testing Guide - -This guide will help you test the theme switcher module on the nate-work host. - -## Pre-Testing Checklist - -### 1. Review Current Configuration - -Check your current nate-work configuration: - -```bash -# View current wallpaper rotator settings -grep -A 10 "wallpaperRotator" nate-work/desktop-configuration.nix - -# View current GTK/Qt theme settings -grep -A 10 "gtk\|kvantum" nate-work/modules/home-manager/home.nix -``` - -### 2. Files to Modify - -You'll need to modify these files: - -1. **nate-work/desktop-configuration.nix** (or wherever you import modules) - - Add themeSwitcher import - - Disable wallpaperRotator if enabled - - Configure themeSwitcher settings - -2. **nate-work/modules/home-manager/home.nix** - - Comment out static GTK theme configuration - - Comment out static Kvantum configuration - - Keep icon and cursor themes - -## Testing Phase 1: Manual Mode - -Start with manual mode to test theme generation before enabling automation. - -### Step 1: Add Module Configuration - -Edit `nate-work/desktop-configuration.nix`: - -```nix -{ - imports = [ - # ... existing imports ... - ../../shared/modules/services/theme_switcher - ]; - - # Disable old wallpaper rotator if present - # services.wallpaperRotator.enable = false; - - # Enable theme switcher in manual mode - services.themeSwitcher = { - enable = true; - user = "nate"; - - # Start with manual mode (no auto-switching) - enableAutoSwitch = false; - - # Use switch-only mode for testing (no continuous rotation yet) - rotation.mode = "switch-only"; - - # Configure wpaperd - wpaperd = { - enable = true; - mode = "center"; - transition.effect = "fade"; - transition.duration = 300; - }; - }; -} -``` - -### Step 2: Comment Out Static Themes - -Edit `nate-work/modules/home-manager/home.nix`: - -```nix -# Comment out these sections: -# gtk.theme = { ... }; -# xdg.configFile."Kvantum/kvantum.kvconfig" = ...; -# xdg.configFile."gtk-4.0/..." = ...; - -# Keep these (icon and cursor themes are independent): -gtk.iconTheme = { ... }; # Keep -gtk.cursorTheme = { ... }; # Keep -``` - -### Step 3: Rebuild System - -```bash -# From /home/nate/nixos directory -sudo nixos-rebuild switch --flake .#nate-work -``` - -This will: -- Install pywal16, haishoku, wpaperd -- Install apply-theme.sh to ~/.local/bin/ -- Install pywal templates -- Start wpaperd (but with no auto-rotation) -- NOT start any timers (manual mode) - -### Step 4: Manual Testing - -After rebuild, test the script manually: - -```bash -# Test light theme -~/.local/bin/apply-theme.sh --light - -# Verify: -# 1. Wallpaper changed to one from Light/ directory -# 2. Colors extracted: cat ~/.cache/wal/colors.json -# 3. GTK theme generated: ls ~/.config/gtk-3.0/gtk.css -# 4. Kvantum theme generated: ls ~/.config/Kvantum/PywalTheme/ -# 5. Open a GTK app (nautilus, calculator) - check colors -``` - -```bash -# Test dark theme -~/.local/bin/apply-theme.sh --dark - -# Verify same things with Dark wallpaper -``` - -```bash -# Test with specific wallpaper -~/.local/bin/apply-theme.sh --light --wallpaper-path ~/nixos/shared/modules/services/wallpapers/Light/IU-Light.jpg -``` - -### Step 5: Verify Wpaperd Integration - -```bash -# Check if wpaperd is running -pgrep wpaperd - -# Check wpaperd status -systemctl --user status wpaperd - -# Manually change wallpaper using wpaperctl -wpaperctl wallpaper ~/nixos/shared/modules/services/wallpapers/Dark/IU-Dark.jpg -``` - -## Testing Phase 2: Automatic Switching (Time-Based) - -Once manual testing works, enable automatic time-based switching. - -### Step 1: Enable Auto-Switch - -Edit `nate-work/desktop-configuration.nix`: - -```nix -services.themeSwitcher = { - enable = true; - user = "nate"; - - # Enable automatic switching - enableAutoSwitch = true; - - # Keep switch-only mode for now - rotation.mode = "switch-only"; - - # Optional: Adjust times for testing - lightTime = "08:00:00"; # Your preferred time - darkTime = "17:00:00"; # Your preferred time -}; -``` - -### Step 2: Rebuild and Check Timer - -```bash -sudo nixos-rebuild switch --flake .#nate-work - -# Verify timer is installed -systemctl --user list-timers | grep theme - -# Should show two entries (one for lightTime, one for darkTime) -systemctl --user status theme-switcher.timer -``` - -### Step 3: Test Timer Manually - -```bash -# Manually trigger the service -systemctl --user start theme-switcher.service - -# Check logs -journalctl --user -u theme-switcher.service -n 50 - -# Should show theme being applied based on current time -``` - -## Testing Phase 3: Continuous Rotation - -Once automatic switching works, enable continuous wallpaper rotation. - -### Step 1: Enable Continuous Mode - -Edit `nate-work/desktop-configuration.nix`: - -```nix -services.themeSwitcher = { - enable = true; - user = "nate"; - enableAutoSwitch = true; - - # Enable continuous rotation - rotation = { - mode = "continuous"; - interval = "5m"; # Rotate every 5 minutes - }; -}; -``` - -### Step 2: Rebuild and Monitor - -```bash -sudo nixos-rebuild switch --flake .#nate-work - -# Check rotation timer -systemctl --user list-timers | grep wallpaper -systemctl --user status wallpaper-rotation.timer - -# Watch logs in real-time -journalctl --user -u wallpaper-rotation.service -f -``` - -### Step 3: Verify Rotation - -Wait 5 minutes and verify: -- Wallpaper changes -- Theme regenerates -- Colors match new wallpaper - -## Troubleshooting - -### Theme Not Applying - -```bash -# Check if pywal generated files -ls -la ~/.cache/wal/ - -# Expected files: -# - colors.json -# - gtk-3.0.css -# - gtk-4.0.css -# - PywalTheme.kvconfig -# - ghostty.conf -``` - -### Wallpaper Not Changing - -```bash -# Check wpaperd status -systemctl --user status wpaperd -journalctl --user -u wpaperd -n 50 - -# Try manual change -wpaperctl wallpaper ~/path/to/wallpaper.jpg -``` - -### GTK Theme Not Loading - -```bash -# Check GTK config -ls -la ~/.config/gtk-3.0/ -ls -la ~/.config/gtk-4.0/ - -# Check if files are symlinks -readlink ~/.config/gtk-3.0/gtk.css -# Should point to ~/.cache/wal/gtk-3.0.css -``` - -### Kvantum Theme Not Loading - -```bash -# Check Kvantum directory -ls -la ~/.config/Kvantum/PywalTheme/ - -# Manually set theme -kvantummanager --set PywalTheme - -# Restart Qt applications -``` - -### Service Failures - -```bash -# Check service logs -journalctl --user -u theme-switcher.service --since today -journalctl --user -u wallpaper-rotation.service --since today - -# Check environment -systemctl --user show theme-switcher.service | grep PATH -``` - -## Performance Monitoring - -### Resource Usage (Continuous Mode) - -```bash -# Monitor theme regeneration -watch -n 60 'ls -lh ~/.cache/wal/colors.json' - -# Check pywal CPU usage -top -b -n 1 | grep wal -``` - -If continuous mode is too resource-intensive, switch to switch-only mode. - -## Success Criteria - -- ✓ Manual theme switching works (--light and --dark) -- ✓ Wallpapers change via wpaperctl -- ✓ GTK apps reflect new colors -- ✓ Qt apps reflect new Kvantum theme -- ✓ Ghostty terminal shows new colors (if installed) -- ✓ Timers appear in systemctl --user list-timers -- ✓ Automatic switching works at configured times -- ✓ Continuous rotation works (if enabled) -- ✓ No service failures in journalctl - -## Next Steps After Testing - -Once everything works on nate-work: - -1. Consider applying to other hosts (frame12, nate, scrappy) -2. Fine-tune rotation interval if needed -3. Try different pywal backends (modern_colorthief, etc.) -4. Customize wpaperd transition effects -5. Add custom pywal templates for other apps - -## Rollback Plan - -If something goes wrong: - -```bash -# Disable the module -# In nate-work/desktop-configuration.nix: -services.themeSwitcher.enable = false; - -# Re-enable old configuration -# services.wallpaperRotator.enable = true; -# Uncomment GTK/Qt theme settings in home.nix - -# Rebuild -sudo nixos-rebuild switch --flake .#nate-work -``` diff --git a/shared/modules/services/theme_switcher/apply_themes.py b/shared/modules/services/theme_switcher/apply_themes.py deleted file mode 100755 index eeb398d..0000000 --- a/shared/modules/services/theme_switcher/apply_themes.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env python3 -""" -Apply Themes Script - Applies GTK/Kvantum/Helix themes after pywal runs. - -This script: -1. Links GTK CSS files from pywal cache -2. Sets GTK color scheme preference based on light/dark mode -3. Copies and applies Kvantum theme -4. Copies Helix theme to config directory -5. Optionally runs color_mapper.py for semantic color mapping -""" - -import json -import shutil -import subprocess -import sys -from pathlib import Path -import logging - -# Setup logging -logging.basicConfig( - level=logging.INFO, - format='%(levelname)s: %(message)s' -) -logger = logging.getLogger(__name__) - - -class ThemeApplicator: - """Applies themes after pywal has generated color palette.""" - - def __init__(self): - self.home = Path.home() - self.cache_dir = self.home / ".cache/wal" - self.config_dir = self.home / ".config" - - def is_light_mode(self) -> bool: - """ - Determine if current theme is light mode by checking pywal cache. - - Returns: - True if light mode, False if dark mode - """ - colors_json = self.cache_dir / "colors.json" - - if not colors_json.exists(): - logger.warning("colors.json not found, assuming dark mode") - return False - - try: - with open(colors_json) as f: - data = json.load(f) - return data.get("special", {}).get("light", False) - except (json.JSONDecodeError, KeyError) as e: - logger.warning(f"Failed to parse colors.json: {e}, assuming dark mode") - return False - - def setup_gtk_themes(self) -> None: - """Link GTK CSS files from pywal cache.""" - gtk3_dir = self.config_dir / "gtk-3.0" - gtk4_dir = self.config_dir / "gtk-4.0" - - # Ensure directories exist - gtk3_dir.mkdir(parents=True, exist_ok=True) - gtk4_dir.mkdir(parents=True, exist_ok=True) - - # Link GTK 3.0 CSS - gtk3_source = self.cache_dir / "gtk-3.0.css" - gtk3_target = gtk3_dir / "gtk.css" - - if gtk3_source.exists(): - # Remove old symlink/file - if gtk3_target.exists() or gtk3_target.is_symlink(): - gtk3_target.unlink() - gtk3_target.symlink_to(gtk3_source) - logger.info(f"Linked GTK 3.0 theme: {gtk3_target}") - else: - logger.warning(f"GTK 3.0 CSS not found: {gtk3_source}") - - # Link GTK 4.0 CSS - gtk4_source = self.cache_dir / "gtk-4.0.css" - gtk4_target = gtk4_dir / "gtk.css" - - if gtk4_source.exists(): - # Remove old symlink/file - if gtk4_target.exists() or gtk4_target.is_symlink(): - gtk4_target.unlink() - gtk4_target.symlink_to(gtk4_source) - logger.info(f"Linked GTK 4.0 theme: {gtk4_target}") - else: - logger.warning(f"GTK 4.0 CSS not found: {gtk4_source}") - - def set_gtk_color_scheme(self, is_light: bool) -> None: - """ - Set GTK color scheme preference using gsettings. - - Args: - is_light: True for light mode, False for dark mode - """ - scheme = "prefer-light" if is_light else "prefer-dark" - - try: - subprocess.run( - ['gsettings', 'set', 'org.gnome.desktop.interface', 'color-scheme', scheme], - check=True, - timeout=5, - capture_output=True - ) - logger.info(f"Set GTK color scheme to: {scheme}") - except (subprocess.CalledProcessError, subprocess.TimeoutExpired, FileNotFoundError) as e: - logger.warning(f"Failed to set GTK color scheme (this is normal if not using GNOME): {e}") - - def setup_kvantum_theme(self) -> None: - """Copy and apply Kvantum theme from pywal cache.""" - kvantum_dir = self.config_dir / "Kvantum/PywalTheme" - kvantum_dir.mkdir(parents=True, exist_ok=True) - - # Copy Kvantum config - kvantum_source = self.cache_dir / "PywalTheme.kvconfig" - kvantum_target = kvantum_dir / "PywalTheme.kvconfig" - - if kvantum_source.exists(): - shutil.copy2(kvantum_source, kvantum_target) - logger.info(f"Copied Kvantum theme: {kvantum_target}") - - # Apply Kvantum theme - try: - subprocess.run( - ['kvantummanager', '--set', 'PywalTheme'], - check=True, - timeout=5, - capture_output=True - ) - logger.info("Applied Kvantum theme: PywalTheme") - except FileNotFoundError: - logger.warning("kvantummanager not found, skipping Kvantum theme application") - except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: - logger.warning(f"Failed to apply Kvantum theme: {e}") - else: - logger.warning(f"Kvantum theme not found: {kvantum_source}") - - def setup_helix_theme(self) -> None: - """Copy Helix theme from pywal cache to config directory.""" - helix_themes_dir = self.config_dir / "helix/themes" - helix_themes_dir.mkdir(parents=True, exist_ok=True) - - # Copy minimal Helix theme - helix_source = self.cache_dir / "helix_minimal.toml" - helix_target = helix_themes_dir / "pywal_minimal.toml" - - if helix_source.exists(): - shutil.copy2(helix_source, helix_target) - logger.info(f"Updated Helix theme: pywal_minimal") - else: - logger.warning(f"Helix theme not found: {helix_source}") - - # Copy semantic Helix theme if it exists - helix_semantic_source = self.cache_dir / "helix_semantic.toml" - helix_semantic_target = helix_themes_dir / "pywal_semantic.toml" - - if helix_semantic_source.exists(): - shutil.copy2(helix_semantic_source, helix_semantic_target) - logger.info(f"Updated Helix semantic theme: pywal_semantic") - - def run_color_mapper(self) -> None: - """ - Run color_mapper.py to generate semantic color mappings. - - This is optional and will only run if the script exists. - """ - # Look for color_mapper.py in the same directory as this script - script_dir = Path(__file__).parent - color_mapper = script_dir / "color_mapper.py" - - if not color_mapper.exists(): - logger.debug(f"color_mapper.py not found at {color_mapper}, skipping") - return - - try: - result = subprocess.run( - ['python3', str(color_mapper)], - capture_output=True, - text=True, - timeout=10, - check=True - ) - logger.info("Applied semantic color mapping") - # Log color mapper output for debugging - if result.stdout: - for line in result.stdout.strip().split('\n'): - logger.debug(f"color_mapper: {line}") - except FileNotFoundError: - logger.warning("python3 not found, skipping color mapper") - except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e: - logger.warning(f"Failed to run color_mapper.py: {e}") - - def run(self) -> int: - """ - Execute theme application process. - - Returns: - Exit code (0 for success, 1 for failure) - """ - try: - # Check if pywal has been run - if not self.cache_dir.exists(): - logger.error(f"Pywal cache not found: {self.cache_dir}") - logger.error("Please run pywal first") - return 1 - - # Determine mode - is_light = self.is_light_mode() - mode = "light" if is_light else "dark" - logger.info(f"Applying {mode} mode themes") - - # Setup GTK themes - self.setup_gtk_themes() - self.set_gtk_color_scheme(is_light) - - # Setup Kvantum theme - self.setup_kvantum_theme() - - # Setup Helix theme - self.setup_helix_theme() - - # Run color mapper for semantic themes - self.run_color_mapper() - - logger.info("All themes applied successfully") - return 0 - - except Exception as e: - logger.error(f"Theme application failed: {e}", exc_info=True) - return 1 - - -def main(): - """Main entry point.""" - applicator = ThemeApplicator() - return applicator.run() - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/shared/modules/services/theme_switcher/color_mapper.py b/shared/modules/services/theme_switcher/color_mapper.py deleted file mode 100644 index 71b2055..0000000 --- a/shared/modules/services/theme_switcher/color_mapper.py +++ /dev/null @@ -1,304 +0,0 @@ -#!/usr/bin/env python3 -""" -Intelligent color mapper for pywal themes. - -Analyzes generated pywal colors and maps them to semantic roles based on -hue, saturation, and lightness. Creates a mapping file that can be used -to apply consistent semantic meaning to colors regardless of wallpaper. -""" - -import json -import colorsys -from pathlib import Path -from typing import Dict, Tuple, List - -def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]: - """Convert hex color to RGB tuple.""" - hex_color = hex_color.lstrip('#') - return (int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)) - -def rgb_to_hsl(rgb: Tuple[int, int, int]) -> Tuple[float, float, float]: - """Convert RGB to HSL.""" - r, g, b = [x / 255.0 for x in rgb] - h, l, s = colorsys.rgb_to_hls(r, g, b) - return (h * 360, s * 100, l * 100) # Convert to degrees and percentages - -def analyze_color(hex_color: str) -> Dict: - """Analyze a color and return its properties.""" - rgb = hex_to_rgb(hex_color) - h, s, l = rgb_to_hsl(rgb) - - # Determine color category based on hue - if s < 10: - category = "neutral" - elif 0 <= h < 30 or 330 <= h <= 360: - category = "red" - elif 30 <= h < 60: - category = "orange" - elif 60 <= h < 90: - category = "yellow" - elif 90 <= h < 150: - category = "green" - elif 150 <= h < 210: - category = "cyan" - elif 210 <= h < 270: - category = "blue" - elif 270 <= h < 330: - category = "magenta" - else: - category = "neutral" - - return { - "hex": hex_color, - "rgb": rgb, - "hue": h, - "saturation": s, - "lightness": l, - "category": category - } - -def calculate_contrast(color1_hsl: Tuple[float, float, float], color2_hsl: Tuple[float, float, float]) -> float: - """Calculate perceived contrast between two colors based on lightness difference.""" - return abs(color1_hsl[2] - color2_hsl[2]) - -def find_best_color_for_role(colors: Dict[str, Dict], role: str, is_light_mode: bool, bg_lightness: float = 50.0) -> str: - """ - Find the best color from the palette for a given semantic role. - - Roles: error, warning, success, info, emphasis, secondary - """ - - # Define preferred characteristics for each role - role_preferences = { - "error": { - "categories": ["red", "orange"], - "min_saturation": 30, - "prefer_vibrant": True, - "prefer_contrast": True # Errors need high visibility - }, - "warning": { - "categories": ["orange", "yellow"], - "min_saturation": 30, - "prefer_vibrant": True, - "prefer_contrast": True - }, - "comment": { - "categories": ["yellow", "orange", "green", "cyan"], # Flexible for high contrast - "min_saturation": 25, - "prefer_vibrant": False, - "prefer_contrast": True # Comments should be very visible - }, - "string": { - "categories": ["green", "cyan"], - "min_saturation": 25, - "prefer_vibrant": False, - "prefer_contrast": False - }, - "keyword": { - "categories": ["magenta", "blue"], - "min_saturation": 20, - "prefer_vibrant": False, - "prefer_contrast": False - }, - "number": { - "categories": ["orange", "red", "magenta"], - "min_saturation": 30, - "prefer_vibrant": True, - "prefer_contrast": False - }, - "info": { - "categories": ["blue", "cyan"], - "min_saturation": 20, - "prefer_vibrant": False, - "prefer_contrast": False - } - } - - prefs = role_preferences.get(role, {}) - preferred_cats = prefs.get("categories", []) - min_sat = prefs.get("min_saturation", 0) - prefer_vibrant = prefs.get("prefer_vibrant", False) - prefer_contrast = prefs.get("prefer_contrast", False) - - # Score each color - best_score = -1 - best_color = None - - for color_name, props in colors.items(): - if props["category"] == "neutral": - continue - - score = 0 - - # Category match (highest priority) - if props["category"] in preferred_cats: - score += 100 - - # Saturation requirements - if props["saturation"] >= min_sat: - score += 50 - - # Contrast preference (for comments and errors) - if prefer_contrast: - # Calculate contrast with background - contrast = abs(props["lightness"] - bg_lightness) - score += contrast * 0.5 # Add up to 50 points for maximum contrast - - # Vibrancy preference - if prefer_vibrant: - # For vibrant colors, prefer higher saturation and medium lightness - score += props["saturation"] / 2 - if 40 <= props["lightness"] <= 60: - score += 20 - else: - # For less vibrant, prefer moderate saturation - if 30 <= props["saturation"] <= 70: - score += 20 - - # Light mode adjustments - prefer colors with good contrast - if is_light_mode: - # In light mode, prefer darker, more saturated colors for visibility - if props["lightness"] < 50: - score += 10 - else: - # In dark mode, prefer brighter colors - if props["lightness"] > 40: - score += 10 - - if score > best_score: - best_score = score - best_color = color_name - - # Fallback to first color if no match found - if best_color is None: - best_color = list(colors.keys())[0] - - return best_color - -def create_semantic_mapping(colors_json_path: Path) -> Dict: - """Create semantic color mapping from pywal colors.""" - - with open(colors_json_path) as f: - data = json.load(f) - - # Determine if light mode and get background lightness - bg_hex = data.get("special", {}).get("background", "#000000") - bg_rgb = hex_to_rgb(bg_hex) - bg_hsl = rgb_to_hsl(bg_rgb) - bg_lightness = bg_hsl[2] - is_light_mode = bg_lightness > 50 - - # Analyze all colors - analyzed = {} - for i in range(1, 8): # color1 through color7 (skip color0 which is usually bg) - color_key = f"color{i}" - hex_color = data["colors"][color_key] - analyzed[color_key] = analyze_color(hex_color) - - # Map semantic roles to best matching colors - mapping = { - "error": find_best_color_for_role(analyzed, "error", is_light_mode, bg_lightness), - "warning": find_best_color_for_role(analyzed, "warning", is_light_mode, bg_lightness), - "comment": find_best_color_for_role(analyzed, "comment", is_light_mode, bg_lightness), - "string": find_best_color_for_role(analyzed, "string", is_light_mode, bg_lightness), - "keyword": find_best_color_for_role(analyzed, "keyword", is_light_mode, bg_lightness), - "number": find_best_color_for_role(analyzed, "number", is_light_mode, bg_lightness), - "info": find_best_color_for_role(analyzed, "info", is_light_mode, bg_lightness), - } - - # Add the original color values for reference - result = { - "semantic_mapping": mapping, - "is_light_mode": is_light_mode, - "color_analysis": analyzed - } - - return result - -def apply_semantic_mapping(template_path: Path, output_path: Path, mapping: Dict): - """Apply semantic mapping to Helix template.""" - - with open(template_path) as f: - template = f.read() - - # Read pywal colors - colors_json = Path.home() / ".cache/wal/colors.json" - with open(colors_json) as f: - colors_data = json.load(f) - - # Replace color references with semantically mapped ones - semantic = mapping["semantic_mapping"] - - # First, do standard pywal substitutions - output = template - - # Replace special colors - for key, value in colors_data["special"].items(): - output = output.replace(f"{{{key}}}", value) - - # Replace palette colors - for key, value in colors_data["colors"].items(): - output = output.replace(f"{{{key}}}", value) - - # Now apply semantic color replacements - error_color = colors_data["colors"][semantic["error"]] - warning_color = colors_data["colors"][semantic["warning"]] - comment_color = colors_data["colors"][semantic["comment"]] - string_color = colors_data["colors"][semantic["string"]] - keyword_color = colors_data["colors"][semantic["keyword"]] - number_color = colors_data["colors"][semantic["number"]] - info_color = colors_data["colors"][semantic["info"]] - - # Replace semantic placeholders - output = output.replace("{{ERROR_COLOR}}", error_color) - output = output.replace("{{WARNING_COLOR}}", warning_color) - output = output.replace("{{COMMENT_COLOR}}", comment_color) - output = output.replace("{{STRING_COLOR}}", string_color) - output = output.replace("{{KEYWORD_COLOR}}", keyword_color) - output = output.replace("{{NUMBER_COLOR}}", number_color) - output = output.replace("{{INFO_COLOR}}", info_color) - - with open(output_path, 'w') as f: - f.write(output) - -def main(): - """Main entry point.""" - import sys - - colors_json = Path.home() / ".cache/wal/colors.json" - - if not colors_json.exists(): - print("Error: Pywal colors.json not found. Run pywal first.") - return 1 - - # Analyze colors and create mapping - mapping = create_semantic_mapping(colors_json) - - # Save mapping for debugging/inspection - mapping_output = Path.home() / ".cache/wal/semantic_mapping.json" - with open(mapping_output, 'w') as f: - json.dump(mapping, f, indent=2) - - print("Semantic color mapping created:") - for role, color in mapping["semantic_mapping"].items(): - props = mapping["color_analysis"][color] - print(f" {role:12} -> {color} ({props['category']:8} {props['hex']})") - - # Apply to helix template if it exists - helix_template = Path.home() / ".config/wal/templates/helix_semantic.toml" - if helix_template.exists(): - helix_output = Path.home() / ".cache/wal/helix_semantic.toml" - apply_semantic_mapping(helix_template, helix_output, mapping) - print(f"\nApplied semantic mapping to: {helix_output}") - - # Also copy to helix themes directory - helix_themes_dir = Path.home() / ".config/helix/themes" - helix_themes_dir.mkdir(parents=True, exist_ok=True) - final_output = helix_themes_dir / "pywal_semantic.toml" - apply_semantic_mapping(helix_template, final_output, mapping) - print(f"Copied to: {final_output}") - - return 0 - -if __name__ == "__main__": - exit(main()) diff --git a/shared/modules/services/theme_switcher/default.nix b/shared/modules/services/theme_switcher/default.nix deleted file mode 100644 index c9a8e1d..0000000 --- a/shared/modules/services/theme_switcher/default.nix +++ /dev/null @@ -1,348 +0,0 @@ -{ config, lib, pkgs, ... }: - -# Theme Switcher Module - Architecture Overview -# -# This module integrates wpaperd for wallpaper management with pywal for dynamic theming. -# wpaperd handles wallpaper rotation and transitions, while Python scripts handle palette -# generation and intelligent semantic color mapping. -# -# Python Scripts: -# - theme_switcher.py: Time-based light/dark theme switching -# - wallpaper_rotation.py: Continuous wallpaper rotation with theme regeneration -# - apply_themes.py: Applies GTK/Kvantum/Helix themes after pywal runs -# - color_mapper.py: Intelligent semantic color mapping (reds for errors, high contrast for comments) -# -# Two modes of operation: -# -# 1. Light/Dark Theme Switching (theme-switcher service, triggered by timer at configured times): -# - Updates wpaperd config to point to Light/ or Dark/ directory -# - Restarts wpaperd (which picks a random wallpaper with transition) -# - Generates pywal palette from the selected wallpaper -# - Runs semantic color mapper to assign colors based on hue analysis -# - Applies GTK/Kvantum/Helix themes with intelligent color assignments -# -# 2. Continuous Wallpaper Rotation (wallpaper-rotation service, triggered by timer every N minutes): -# - Pauses wpaperd's auto-rotation -# - Calls wpaperctl next-wallpaper (wpaperd handles the smooth transition) -# - Gets current wallpaper from wpaperd -# - Regenerates pywal palette from new wallpaper -# - Runs semantic color mapper for intelligent color assignment -# - Applies GTK/Kvantum/Helix themes -# - Resumes wpaperd's auto-rotation -# -# This approach leverages wpaperd's built-in features (transitions, random selection, timing) -# while keeping palette generation and theme application in sync with wallpaper changes. -# The semantic color mapper ensures consistent meaning (e.g., reds for errors) regardless of wallpaper. - -let - cfg = config.services.themeSwitcher; - - # Get home directory for the user - userConfig = config.home-manager.users.${cfg.user}; - homeDir = userConfig.home.homeDirectory or "/home/${cfg.user}"; - - # Create a custom pywal16 with haishoku backend available - pywal16WithBackends = pkgs.pywal16.overridePythonAttrs (old: { - propagatedBuildInputs = (old.propagatedBuildInputs or []) ++ [ - pkgs.python313Packages.haishoku - ]; - }); - - # Python scripts for theme management - themeSwitcherScript = pkgs.writeScript "theme-switcher" '' - #!${pkgs.python3}/bin/python3 - import subprocess - import sys - - # Execute the theme switcher Python script - result = subprocess.run([ - "${./theme_switcher.py}", - "--wallpaper-path", "${cfg.wallpaperPath}", - "--light-time", "${cfg.lightTime}", - "--dark-time", "${cfg.darkTime}", - "--backend", "${cfg.backend}", - "--rotation-interval", "${cfg.rotation.interval}", - "--wpaperd-mode", "${cfg.wpaperd.mode}", - "--sorting", "${cfg.rotation.sorting}", - "--apply-themes-script", "${applyThemesScript}" - ]) - sys.exit(result.returncode) - ''; - - # Python script to apply GTK/Kvantum/Helix themes - applyThemesScript = ./apply_themes.py; - - # Python script for wallpaper rotation - wallpaperRotationScript = pkgs.writeScript "wallpaper-rotation" '' - #!${pkgs.python3}/bin/python3 - import subprocess - import sys - - # Execute the wallpaper rotation Python script - result = subprocess.run([ - "${./wallpaper_rotation.py}", - "--light-time", "${cfg.lightTime}", - "--dark-time", "${cfg.darkTime}", - "--backend", "${cfg.backend}", - "--apply-themes-script", "${applyThemesScript}" - ]) - sys.exit(result.returncode) - ''; -in -{ - options.services.themeSwitcher = { - enable = lib.mkEnableOption "dynamic theme switching based on time and wallpapers with integrated wallpaper management"; - - user = lib.mkOption { - type = lib.types.str; - description = "Username for theme switching"; - }; - - wallpaperPath = lib.mkOption { - type = lib.types.str; - default = "${homeDir}/nixos/shared/modules/services/wallpapers"; - defaultText = lib.literalExpression ''"''${config.home-manager.users.''${cfg.user}.home.homeDirectory}/nixos/shared/modules/services/wallpapers"''; - description = "Path to wallpaper directories (Light/ and Dark/ subdirectories expected)"; - }; - - backend = lib.mkOption { - type = lib.types.enum [ "haishoku" "modern_colorthief" "fast_colorthief" "colorthief" "colorz" "wal" ]; - default = "haishoku"; - description = "Pywal color extraction backend"; - }; - - lightTime = lib.mkOption { - type = lib.types.str; - default = "09:00:00"; - description = "Time to switch to light theme (HH:MM:SS)"; - }; - - darkTime = lib.mkOption { - type = lib.types.str; - default = "16:30:00"; - description = "Time to switch to dark theme (HH:MM:SS)"; - }; - - enableThemeSwitch = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Enable automatic time-based theme switching via systemd timer"; - }; - - paletteGeneration = lib.mkOption { - type = lib.types.enum [ "light-dark" "continuous" ]; - default = "continuous"; - description = '' - When to regenerate color palettes from wallpapers: - - light-dark: Only regenerate at light/dark switch times (6 AM/6 PM) - - continuous: Regenerate palette every time wallpaper rotates - ''; - }; - - # Wallpaper rotation options - rotation = { - interval = lib.mkOption { - type = lib.types.str; - default = "5m"; - example = "30s"; - description = "How often to rotate wallpapers (e.g., 30s, 5m, 1h)"; - }; - - sorting = lib.mkOption { - type = lib.types.enum [ "random" ]; - default = "random"; - description = "Wallpaper selection method (currently only random is supported)"; - }; - }; - - # Wpaperd integration options - wpaperd = { - enable = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Enable wpaperd wallpaper daemon integration"; - }; - - mode = lib.mkOption { - type = lib.types.enum [ "center" "fit" "fit-border-color" "stretch" "tile" ]; - default = "center"; - description = "How to display wallpapers when size differs from display resolution"; - }; - - transition = { - effect = lib.mkOption { - type = lib.types.str; - default = "fade"; - example = "simple"; - description = "Transition effect name (fade, simple, etc.)"; - }; - - duration = lib.mkOption { - type = lib.types.int; - default = 300; - example = 2000; - description = "Transition duration in milliseconds"; - }; - }; - }; - }; - - config = lib.mkIf cfg.enable { - home-manager.users.${cfg.user} = { - home.packages = with pkgs; [ - pywal16WithBackends # pywal16 with haishoku backend included - imagemagick # Useful for image manipulation - wpaperd # Wallpaper daemon and CLI tool - ]; - - # Install manual theme switching script to user's local bin - home.file.".local/bin/switch-theme.sh" = { - source = pkgs.writeShellScript "switch-theme.sh" '' - # Manual theme switching script - # Usage: switch-theme.sh [light|dark] - - if [ $# -ne 1 ]; then - echo "Usage: $0 [light|dark]" - exit 1 - fi - - MODE="$1" - - if [ "$MODE" != "light" ] && [ "$MODE" != "dark" ]; then - echo "Error: Mode must be 'light' or 'dark'" - exit 1 - fi - - # Trigger the theme switcher service which will handle everything - ${pkgs.systemd}/bin/systemctl --user start theme-switcher.service - ''; - executable = true; - }; - - # Pywal custom templates for GTK, Kvantum, and Helix - 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/PywalTheme.kvconfig".source = ./templates/PywalTheme.kvconfig; - xdg.configFile."wal/templates/helix_minimal.toml".source = ./templates/helix_minimal.toml; - xdg.configFile."wal/templates/helix_semantic.toml".source = ./templates/helix_semantic.toml; - - # Start wpaperd daemon if enabled - # Note: We don't use services.wpaperd from home-manager because we need to - # dynamically update the config file, which would be read-only if managed by home-manager - systemd.user.services.wpaperd = lib.mkIf cfg.wpaperd.enable { - Unit = { - Description = "Wallpaper daemon for Wayland"; - After = [ "graphical-session.target" ]; - PartOf = [ "graphical-session.target" ]; - }; - Service = { - Type = "simple"; - # Create initial config before starting if it doesn't exist - ExecStartPre = pkgs.writeShellScript "wpaperd-init.sh" '' - mkdir -p ~/.config/wpaperd - if [ ! -f ~/.config/wpaperd/config.toml ] || [ -L ~/.config/wpaperd/config.toml ]; then - # Remove old symlink if exists and create initial config - rm -f ~/.config/wpaperd/config.toml - - # Determine initial directory based on current time - hour=$(${pkgs.coreutils}/bin/date +%H) - light_hour=$(echo "${cfg.lightTime}" | ${pkgs.coreutils}/bin/cut -d: -f1) - dark_hour=$(echo "${cfg.darkTime}" | ${pkgs.coreutils}/bin/cut -d: -f1) - - if [ "$hour" -ge "$light_hour" ] && [ "$hour" -lt "$dark_hour" ]; then - WALLPAPER_DIR="${cfg.wallpaperPath}/Light" - else - WALLPAPER_DIR="${cfg.wallpaperPath}/Dark" - fi - - cat > ~/.config/wpaperd/config.toml << EOF -[default] -path = "$WALLPAPER_DIR" -duration = "${cfg.rotation.interval}" -mode = "${cfg.wpaperd.mode}" -sorting = "${cfg.rotation.sorting}" -EOF - fi - ''; - ExecStart = "${pkgs.wpaperd}/bin/wpaperd"; - Restart = "on-failure"; - }; - Install.WantedBy = [ "graphical-session.target" ]; - }; - - # Systemd timer and service for theme switching at light/dark times - systemd.user.timers.theme-switcher = lib.mkIf cfg.enableThemeSwitch { - Unit.Description = "Dynamic Theme Switcher Timer"; - Timer = { - OnCalendar = [ "*-*-* ${cfg.lightTime}" "*-*-* ${cfg.darkTime}" ]; - Persistent = true; - }; - Install.WantedBy = [ "timers.target" ]; - }; - - systemd.user.services.theme-switcher = lib.mkIf cfg.enableThemeSwitch { - Unit.Description = "Dynamic Theme Switcher Service"; - Service = { - Type = "oneshot"; - ExecStart = "${themeSwitcherScript}"; - # Use home-manager profile to get pywal with all backends - Environment = [ - "PATH=${config.home-manager.users.${cfg.user}.home.profileDirectory}/bin:${lib.makeBinPath (with pkgs; [ - python3 - systemd - coreutils - findutils - glib - libsForQt5.qtstyleplugin-kvantum - bash - wpaperd - procps # for pgrep - ])}" - ]; - }; - }; - - # Wallpaper rotation timer and service (for continuous palette generation) - systemd.user.timers.wallpaper-rotation = lib.mkIf (cfg.paletteGeneration == "continuous") { - Unit.Description = "Wallpaper Rotation Timer (with Theme Regeneration)"; - Timer = { - OnBootSec = "1min"; # Start 1 minute after boot - OnUnitActiveSec = cfg.rotation.interval; - Persistent = false; - }; - Install.WantedBy = [ "timers.target" ]; - }; - - systemd.user.services.wallpaper-rotation = lib.mkIf (cfg.paletteGeneration == "continuous") { - Unit.Description = "Wallpaper Rotation Service (with Theme Regeneration)"; - Service = { - Type = "oneshot"; - ExecStart = "${wallpaperRotationScript}"; - # Use home-manager profile to get pywal with all backends - Environment = [ - "PATH=${config.home-manager.users.${cfg.user}.home.profileDirectory}/bin:${lib.makeBinPath (with pkgs; [ - python3 - systemd - coreutils - findutils - glib - libsForQt5.qtstyleplugin-kvantum - bash - wpaperd - procps # for pgrep - ])}" - ]; - }; - }; - - # Keep Qt/GTK enabled but allow pywal to override themes - qt = { - enable = true; - platformTheme.name = "kvantum"; - style.name = "kvantum"; - }; - - gtk.enable = true; - }; - }; -} diff --git a/shared/modules/services/theme_switcher/helix_theme_tester.py b/shared/modules/services/theme_switcher/helix_theme_tester.py deleted file mode 100755 index 2c87d78..0000000 --- a/shared/modules/services/theme_switcher/helix_theme_tester.py +++ /dev/null @@ -1,250 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple Helix Theme Tester - Generate Helix theme from image for testing. -Usage: - ./helix_theme_tester.py test image.jpg - ./helix_theme_tester.py test image.jpg --light - ./helix_theme_tester.py test image.jpg --output custom_name.toml -""" - -import argparse -import subprocess -import sys -from pathlib import Path -import json -# Import from existing color_mapper -from color_mapper import create_semantic_mapping, apply_semantic_mapping -def generate_palette_from_image(image_path: Path, is_light: bool = False) -> bool: - """Run pywal on image to generate colors.json""" - - cmd = ['wal', '-i', str(image_path), '--backend', 'haishoku', '-n'] - if is_light: - cmd.append('-l') - - try: - subprocess.run(cmd, check=True, timeout=30) - print(f"✓ Generated palette from {image_path.name}") - return True - except subprocess.CalledProcessError as e: - print(f"✗ Failed to generate palette: {e}") - return False - except FileNotFoundError: - print("✗ Error: 'wal' command not found. Is pywal16 installed?") - return False -def generate_helix_theme(output_name: str = "pywal_test") -> bool: - """Generate Helix theme using color_mapper logic""" - - colors_json = Path.home() / ".cache/wal/colors.json" - if not colors_json.exists(): - print(f"✗ colors.json not found at {colors_json}") - return False - - # Use existing color_mapper to create semantic mapping - print("✓ Analyzing colors and creating semantic mapping...") - mapping = create_semantic_mapping(colors_json) - - # Print the mapping for visibility - print("\nSemantic color mapping:") - for role, color in mapping["semantic_mapping"].items(): - props = mapping["color_analysis"][color] - print(f" {role:12} -> {color} ({props['category']:8} {props['hex']})") - - # Check for contrast issues - print("\nContrast analysis:") - bg_lightness = mapping.get("color_analysis", {}).get("color0", {}).get("lightness", 50) - for role, color_key in mapping["semantic_mapping"].items(): - color_props = mapping["color_analysis"][color_key] - contrast = abs(color_props["lightness"] - bg_lightness) - status = "✓" if contrast > 30 else "⚠" - print(f" {status} {role:12}: contrast {contrast:.1f}") - - # Apply to helix template - helix_template = Path(__file__).parent / "templates/helix_semantic.toml" - helix_output = Path.home() / f".config/helix/themes/{output_name}.toml" - helix_output.parent.mkdir(parents=True, exist_ok=True) - - apply_semantic_mapping(helix_template, helix_output, mapping) - print(f"\n✓ Helix theme created: {helix_output}") - print(f"\nTest in Helix:") - print(f" hx some_file.py") - print(f" :theme {output_name}") - - return True -def main(): - parser = argparse.ArgumentParser( - description='Generate Helix theme from image for testing' - ) - parser.add_argument('command', choices=['test'], help='Command to run') - parser.add_argument('image', type=Path, help='Path to image file') - parser.add_argument('--light', action='store_true', help='Generate light theme') - parser.add_argument('--output', default='pywal_test', help='Output theme name') - - args = parser.parse_args() - - if not args.image.exists(): - print(f"✗ Image not found: {args.image}") - return 1 - - print(f"Testing with image: {args.image}") - print(f"Mode: {'light' if args.light else 'dark'}\n") - - # Step 1: Generate palette - if not generate_palette_from_image(args.image, args.light): - return 1 - - # Step 2: Generate Helix theme - if not generate_helix_theme(args.output): - return 1 - - print("\n✓ Done! Now test the theme in Helix and iterate.") - return 0 -if __name__ == '__main__': - sys.exit(main()) -Enhanced color_mapper.py with Tweaking -Let me show you what minimal enhancements to add for tweaking: -# Add to color_mapper.py after existing imports -def adjust_color_lightness(hex_color: str, factor: float) -> str: - """ - Adjust color lightness by factor. - factor > 1.0 = lighter, factor < 1.0 = darker - """ - rgb = hex_to_rgb(hex_color) - h, s, l = rgb_to_hsl(rgb) - - # Adjust lightness - l = max(0, min(100, l * factor)) - - # Convert back to RGB then hex - r, g, b = colorsys.hls_to_rgb(h/360, l/100, s/100) - return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}" -def adjust_color_saturation(hex_color: str, factor: float) -> str: - """ - Adjust color saturation by factor. - factor > 1.0 = more saturated, factor < 1.0 = less saturated - """ - rgb = hex_to_rgb(hex_color) - h, s, l = rgb_to_hsl(rgb) - - # Adjust saturation - s = max(0, min(100, s * factor)) - - # Convert back - r, g, b = colorsys.hls_to_rgb(h/360, l/100, s/100) - return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}" -def calculate_contrast_ratio(color1_hex: str, color2_hex: str) -> float: - """Calculate WCAG contrast ratio between two colors""" - def relative_luminance(hex_color): - rgb = hex_to_rgb(hex_color) - r, g, b = [x / 255.0 for x in rgb] - - # Apply gamma correction - r = r / 12.92 if r <= 0.03928 else ((r + 0.055) / 1.055) ** 2.4 - g = g / 12.92 if g <= 0.03928 else ((g + 0.055) / 1.055) ** 2.4 - b = b / 12.92 if b <= 0.03928 else ((b + 0.055) / 1.055) ** 2.4 - - return 0.2126 * r + 0.7152 * g + 0.0722 * b - - l1 = relative_luminance(color1_hex) - l2 = relative_luminance(color2_hex) - - lighter = max(l1, l2) - darker = min(l1, l2) - - return (lighter + 0.05) / (darker + 0.05) -def enforce_minimum_contrast(fg_hex: str, bg_hex: str, min_ratio: float = 4.5) -> str: - """ - Adjust foreground color to meet minimum contrast ratio with background. - Preserves hue and saturation, only adjusts lightness. - """ - current_ratio = calculate_contrast_ratio(fg_hex, bg_hex) - - if current_ratio >= min_ratio: - return fg_hex # Already meets requirement - - # Determine if we need to lighten or darken - bg_rgb = hex_to_rgb(bg_hex) - bg_h, bg_s, bg_l = rgb_to_hsl(bg_rgb) - - fg_rgb = hex_to_rgb(fg_hex) - fg_h, fg_s, fg_l = rgb_to_hsl(fg_rgb) - - # If background is dark, lighten foreground; if light, darken foreground - step = 5 if bg_l < 50 else -5 - - # Iteratively adjust lightness - attempts = 0 - while attempts < 20: # Prevent infinite loop - fg_l += step - fg_l = max(0, min(100, fg_l)) - - # Convert back to hex - r, g, b = colorsys.hls_to_rgb(fg_h/360, fg_l/100, fg_s/100) - adjusted_hex = f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}" - - if calculate_contrast_ratio(adjusted_hex, bg_hex) >= min_ratio: - return adjusted_hex - - attempts += 1 - - # If we couldn't meet contrast, return best effort - return adjusted_hex -# Add parameters to apply_semantic_mapping -def apply_semantic_mapping(template_path: Path, output_path: Path, mapping: Dict, - adjustments: Dict = None): - """ - Apply semantic mapping to Helix template. - - adjustments: Optional dict with tweaking parameters: - { - 'comment': {'contrast': 4.5, 'saturation': 1.2}, - 'error': {'saturation': 1.3, 'force_hue': (0, 30)}, - } - """ - - with open(template_path) as f: - template = f.read() - - # Read pywal colors - colors_json = Path.home() / ".cache/wal/colors.json" - with open(colors_json) as f: - colors_data = json.load(f) - - # Get background for contrast calculations - bg_color = colors_data["special"]["background"] - - # Replace standard pywal colors first - output = template - for key, value in colors_data["special"].items(): - output = output.replace(f"{{{key}}}", value) - for key, value in colors_data["colors"].items(): - output = output.replace(f"{{{key}}}", value) - - # Now apply semantic colors with optional adjustments - semantic = mapping["semantic_mapping"] - - for role in ["error", "warning", "comment", "string", "keyword", "number", "info"]: - color_key = semantic[role] - color_hex = colors_data["colors"][color_key] - - # Apply adjustments if provided - if adjustments and role in adjustments: - adj = adjustments[role] - - # Apply saturation adjustment - if 'saturation' in adj: - color_hex = adjust_color_saturation(color_hex, adj['saturation']) - - # Apply lightness adjustment - if 'lightness' in adj: - color_hex = adjust_color_lightness(color_hex, adj['lightness']) - - # Enforce minimum contrast - if 'contrast' in adj: - color_hex = enforce_minimum_contrast(color_hex, bg_color, adj['contrast']) - - # Replace in template - placeholder = f"{{{role.upper()}_COLOR}}" - output = output.replace(placeholder, color_hex) - - with open(output_path, 'w') as f: - f.write(output) diff --git a/shared/modules/services/theme_switcher/templates/PywalTheme.kvconfig b/shared/modules/services/theme_switcher/templates/PywalTheme.kvconfig deleted file mode 100644 index 09b6eb5..0000000 --- a/shared/modules/services/theme_switcher/templates/PywalTheme.kvconfig +++ /dev/null @@ -1,360 +0,0 @@ -[%General] -author=Pywal Theme Generator -comment=A Kvantum theme generated from wallpaper colors -x11drag=menubar_and_primary_toolbar -alt_mnemonic=true -left_tabs=false -attach_active_tab=false -mirror_doc_tabs=true -group_toolbar_buttons=false -spread_progressbar=true -composite=true -menu_shadow_depth=6 -tooltip_shadow_depth=4 -scroll_width=12 -scroll_arrows=false -scroll_min_extent=40 -slider_width=6 -slider_handle_width=20 -slider_handle_length=20 -center_toolbar_handle=true -check_size=16 -textless_progressbar=false -progressbar_thickness=4 -menubar_mouse_tracking=true -toolbutton_style=0 -double_click=false -translucent_windows=false -blurring=false -popup_blurring=false -opaque=kaffeine,kmplayer,subtitlecomposer,kdenlive,vlc,smplayer,smplayer2,avidemux,avidemux2_qt4,avidemux3_qt4,avidemux3_qt5,kamoso,QtCreator,VirtualBox,VirtualBoxVM,trojita,dragon,digikam,lyx,calligra,ink -vertical_spin_indicators=false -spin_button_width=16 -fill_rubberband=false -merge_menubar_with_toolbar=true -small_icon_size=16 -large_icon_size=32 -button_icon_size=16 -toolbar_icon_size=22 -combo_as_lineedit=true -hide_combo_checkboxes=true -combo_menu=true -hide_spin_checkboxes=true -button_contents_shift=false -groupbox_top_label=true -inline_spin_indicators=true -joined_inactive_tabs=false - -[GeneralColors] -window.color={background} -base.color={background} -alt.base.color={color0} -button.color={color0} -light.color={color8} -mid.light.color={color8} -dark.color={color0} -mid.color={color8} -highlight.color={color4} -inactive.highlight.color={color8} -text.color={foreground} -window.text.color={foreground} -button.text.color={foreground} -disabled.text.color={color8} -tooltip.base.color={color0} -tooltip.text.color={foreground} -highlight.text.color={background} -link.color={color4} -link.visited.color={color5} -progress.indicator.text.color={background} - -[Hacks] -transparent_ktitle_label=true -transparent_dolphin_view=true -transparent_pcmanfm_sidepane=true -blur_translucent=false -transparent_menutitle=true -respect_darkness=true -kcapacitybar_as_progressbar=true -force_size_grip=true -iconless_pushbutton=false -iconless_menu=false -disabled_icon_opacity=70 -lxqtmainmenu_iconsize=22 -normal_default_pushbutton=true -single_top_toolbar=true -tint_on_mouseover=0 -transparent_pcmanfm_view=false -no_selection_tint=false - -[PanelButtonCommand] -frame=true -frame.element=button -frame.top=4 -frame.bottom=4 -frame.left=4 -frame.right=4 -interior=true -interior.element=button -indicator.size=10 -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} -text.toggle.color={background} -text.shadow=0 -text.margin=1 -text.iconspacing=4 -indicator.element=arrow -text.margin.top=2 -text.margin.bottom=2 -text.margin.left=2 -text.margin.right=2 -min_width=+0.3font -min_height=+0.3font -frame.expansion=0 - -[PanelButtonTool] -inherits=PanelButtonCommand - -[Toolbar] -inherits=PanelButtonCommand -interior.element=none -frame.element=none -text.normal.color={foreground} -text.focus.color={foreground} - -[ToolbarButton] -inherits=PanelButtonCommand -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} - -[DockTitle] -inherits=PanelButtonCommand -frame=false -interior=false -text.normal.color={foreground} -text.focus.color={background} - -[IndicatorSpinBox] -inherits=PanelButtonCommand -indicator.element=arrow -indicator.size=10 - -[RadioButton] -inherits=PanelButtonCommand -frame=false -interior.element=radio -text.normal.color={foreground} -text.focus.color={foreground} - -[CheckBox] -inherits=PanelButtonCommand -frame=false -interior.element=checkbox -text.normal.color={foreground} -text.focus.color={foreground} - -[GenericFrame] -inherits=PanelButtonCommand -frame=true -interior=false -frame.element=common -interior.element=common -frame.top=1 -frame.bottom=1 -frame.left=1 -frame.right=1 - -[LineEdit] -inherits=PanelButtonCommand -frame.element=lineedit -interior.element=lineedit -text.normal.color={foreground} -text.focus.color={foreground} - -[DropDownButton] -inherits=PanelButtonCommand -indicator.element=arrow-down - -[IndicatorArrow] -indicator.element=arrow -indicator.size=10 - -[ToolboxTab] -inherits=PanelButtonCommand -text.normal.color={foreground} -text.press.color={background} -text.focus.color={background} - -[Tab] -inherits=PanelButtonCommand -interior.element=tab -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} -frame.element=tab -indicator.element=tab -indicator.size=10 -frame.top=4 -frame.bottom=4 -frame.left=4 -frame.right=4 - -[TabFrame] -inherits=PanelButtonCommand -frame.element=tabframe -interior.element=tabframe -frame.top=2 -frame.bottom=2 -frame.left=2 -frame.right=2 - -[TreeExpander] -inherits=PanelButtonCommand -indicator.size=10 -indicator.element=tree - -[HeaderSection] -inherits=PanelButtonCommand -interior.element=header -frame.element=header -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} -text.toggle.color={background} -frame.top=1 -frame.bottom=1 -frame.left=1 -frame.right=1 - -[SizeGrip] -indicator.element=resize-grip - -[Scrollbar] -inherits=PanelButtonCommand -indicator.element=arrow -indicator.size=10 - -[ScrollbarSlider] -inherits=PanelButtonCommand -frame.element=scrollbarslider -interior=false -frame.left=6 -frame.right=6 -frame.top=6 -frame.bottom=6 -indicator.element=grip -indicator.size=13 - -[ScrollbarGroove] -inherits=PanelButtonCommand -interior=false -frame=false - -[Progressbar] -inherits=PanelButtonCommand -frame.element=progress -interior.element=progress -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} -text.toggle.color={background} -text.bold=false -frame.expansion=8 - -[ProgressbarContents] -inherits=PanelButtonCommand -frame=true -frame.element=progress-pattern -interior.element=progress-pattern - -[ItemView] -inherits=PanelButtonCommand -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} -text.toggle.color={background} -frame.element=itemview -interior.element=itemview -frame.top=2 -frame.bottom=2 -frame.left=2 -frame.right=2 - -[Splitter] -indicator.size=48 - -[Menu] -inherits=PanelButtonCommand -frame.top=3 -frame.bottom=3 -frame.left=3 -frame.right=3 -frame.element=menu -interior.element=menu -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} - -[MenuItem] -inherits=PanelButtonCommand -frame=true -frame.element=menuitem -interior.element=menuitem -indicator.element=menuitem -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} -text.toggle.color={background} -frame.top=3 -frame.bottom=3 -frame.left=3 -frame.right=3 - -[MenuBar] -inherits=PanelButtonCommand -frame.element=menubar -interior.element=menubar -frame.bottom=0 - -[MenuBarItem] -inherits=PanelButtonCommand -interior=true -interior.element=menubaritem -frame.element=menubaritem -text.normal.color={foreground} -text.focus.color={background} -text.press.color={background} -frame.top=2 -frame.bottom=2 -frame.left=2 -frame.right=2 - -[TitleBar] -inherits=PanelButtonCommand -frame=false -interior.element=titlebar -indicator.size=16 -indicator.element=mdi -text.normal.color={foreground} -text.focus.color={background} -text.bold=true - -[ComboBox] -inherits=PanelButtonCommand -interior.element=combo -frame.element=combo -indicator.element=carrow - -[ToolTip] -inherits=GenericFrame -frame.top=3 -frame.bottom=3 -frame.left=3 -frame.right=3 -interior=true -text.shadow=0 -text.margin=0 -interior.element=tooltip -frame.element=tooltip -frame.expansion=0 diff --git a/shared/modules/services/theme_switcher/templates/gtk-3.0.css b/shared/modules/services/theme_switcher/templates/gtk-3.0.css deleted file mode 100644 index 0d88a72..0000000 --- a/shared/modules/services/theme_switcher/templates/gtk-3.0.css +++ /dev/null @@ -1,255 +0,0 @@ -/* GTK 3.0 Theme - Generated by pywal */ -/* This theme uses colors extracted from wallpapers */ - -/* Define color variables */ -@define-color theme_bg_color {background}; -@define-color theme_fg_color {foreground}; -@define-color theme_base_color {background}; -@define-color theme_text_color {foreground}; -@define-color theme_selected_bg_color {color4}; -@define-color theme_selected_fg_color {background}; -@define-color insensitive_bg_color shade({background}, 0.95); -@define-color insensitive_fg_color shade({foreground}, 0.7); -@define-color insensitive_base_color {background}; -@define-color theme_unfocused_bg_color {background}; -@define-color theme_unfocused_fg_color {foreground}; -@define-color theme_unfocused_base_color {background}; -@define-color theme_unfocused_text_color {foreground}; -@define-color theme_unfocused_selected_bg_color {color4}; -@define-color theme_unfocused_selected_fg_color {background}; -@define-color borders shade({background}, 0.8); -@define-color unfocused_borders shade({background}, 0.85); - -/* Additional accent colors */ -@define-color accent_bg_color {color4}; -@define-color accent_fg_color {background}; -@define-color accent_color {color4}; -@define-color destructive_bg_color {color1}; -@define-color destructive_fg_color {background}; -@define-color destructive_color {color1}; -@define-color success_bg_color {color2}; -@define-color success_fg_color {background}; -@define-color success_color {color2}; -@define-color warning_bg_color {color3}; -@define-color warning_fg_color {background}; -@define-color warning_color {color3}; -@define-color error_bg_color {color1}; -@define-color error_fg_color {background}; -@define-color error_color {color1}; - -/* Window styling */ -window { - background-color: {background}; - color: {foreground}; -} - -/* Widget styling */ -* {{ - background-color: {background}; - color: {foreground}; -}} - -button { - background-color: {color0}; - color: {foreground}; - border: 1px solid {color8}; - border-radius: 4px; - padding: 6px 12px; -} - -button:hover { - background-color: {color8}; - border-color: {color4}; -} - -button:active, -button:checked { - background-color: {color4}; - color: {background}; -} - -button:disabled { - background-color: shade({background}, 0.95); - color: shade({foreground}, 0.7); -} - -/* Entry/Input fields */ -entry { - background-color: {color0}; - color: {foreground}; - border: 1px solid {color8}; - border-radius: 4px; - padding: 6px; -} - -entry:focus { - border-color: {color4}; -} - -entry:disabled { - background-color: shade({background}, 0.95); - color: shade({foreground}, 0.7); -} - -/* Selections */ -selection, -*:selected { - background-color: {color4}; - color: {background}; -} - -/* Headerbar */ -headerbar { - background-color: {color0}; - color: {foreground}; - border-bottom: 1px solid {color8}; -} - -headerbar button { - background-color: transparent; - border: none; -} - -headerbar button:hover { - background-color: {color8}; -} - -/* Sidebar */ -.sidebar { - background-color: {color0}; - border-right: 1px solid {color8}; -} - -/* Notebook/Tabs */ -notebook > header { - background-color: {color0}; - border-bottom: 1px solid {color8}; -} - -notebook > header > tabs > tab { - background-color: transparent; - color: {foreground}; - padding: 6px 12px; -} - -notebook > header > tabs > tab:checked { - background-color: {background}; - border-bottom: 2px solid {color4}; -} - -/* Menu */ -menu, -.menu { - background-color: {color0}; - color: {foreground}; - border: 1px solid {color8}; -} - -menuitem { - padding: 6px 12px; -} - -menuitem:hover { - background-color: {color4}; - color: {background}; -} - -/* Scrollbar */ -scrollbar { - background-color: {background}; -} - -scrollbar slider { - background-color: {color8}; - border-radius: 8px; - min-width: 8px; - min-height: 8px; -} - -scrollbar slider:hover { - background-color: {color4}; -} - -/* Progressbar */ -progressbar { - background-color: {color0}; - border-radius: 4px; -} - -progressbar progress { - background-color: {color4}; - border-radius: 4px; -} - -/* Switch */ -switch { - background-color: {color0}; - border: 1px solid {color8}; - border-radius: 12px; -} - -switch:checked { - background-color: {color4}; -} - -switch slider { - background-color: {foreground}; - border-radius: 50%; -} - -/* Checkbutton & Radiobutton */ -checkbutton check, -radiobutton radio { - background-color: {color0}; - border: 1px solid {color8}; -} - -checkbutton check:checked, -radiobutton radio:checked { - background-color: {color4}; - border-color: {color4}; -} - -/* Tooltip */ -tooltip { - background-color: {color0}; - color: {foreground}; - border: 1px solid {color8}; - border-radius: 4px; - padding: 6px; -} - -/* Popover */ -popover { - background-color: {color0}; - color: {foreground}; - border: 1px solid {color8}; - border-radius: 8px; - padding: 6px; -} - -/* Frame */ -frame { - border: 1px solid {color8}; - border-radius: 4px; -} - -/* List */ -list, -.view { - background-color: {background}; - color: {foreground}; -} - -list row { - padding: 6px; -} - -list row:hover { - background-color: {color8}; -} - -list row:selected { - background-color: {color4}; - color: {background}; -} diff --git a/shared/modules/services/theme_switcher/templates/gtk-4.0.css b/shared/modules/services/theme_switcher/templates/gtk-4.0.css deleted file mode 100644 index d0f1602..0000000 --- a/shared/modules/services/theme_switcher/templates/gtk-4.0.css +++ /dev/null @@ -1,397 +0,0 @@ -/* GTK 4.0 Theme - Generated by pywal */ -/* This theme uses colors extracted from wallpapers */ - -/* Define color variables */ -@define-color window_bg_color {background}; -@define-color window_fg_color {foreground}; -@define-color view_bg_color {background}; -@define-color view_fg_color {foreground}; -@define-color accent_bg_color {color4}; -@define-color accent_fg_color {background}; -@define-color accent_color {color4}; -@define-color destructive_bg_color {color1}; -@define-color destructive_fg_color {background}; -@define-color destructive_color {color1}; -@define-color success_bg_color {color2}; -@define-color success_fg_color {background}; -@define-color success_color {color2}; -@define-color warning_bg_color {color3}; -@define-color warning_fg_color {background}; -@define-color warning_color {color3}; -@define-color error_bg_color {color1}; -@define-color error_fg_color {background}; -@define-color error_color {color1}; -@define-color headerbar_bg_color {color0}; -@define-color headerbar_fg_color {foreground}; -@define-color headerbar_border_color {color8}; -@define-color sidebar_bg_color {color0}; -@define-color sidebar_fg_color {foreground}; -@define-color card_bg_color {color0}; -@define-color card_fg_color {foreground}; -@define-color popover_bg_color {color0}; -@define-color popover_fg_color {foreground}; -@define-color dialog_bg_color {background}; -@define-color dialog_fg_color {foreground}; - -/* Legacy GTK3 compatibility colors */ -@define-color theme_bg_color {background}; -@define-color theme_fg_color {foreground}; -@define-color theme_base_color {background}; -@define-color theme_text_color {foreground}; -@define-color theme_selected_bg_color {color4}; -@define-color theme_selected_fg_color {background}; -@define-color insensitive_bg_color alpha({foreground}, 0.1); -@define-color insensitive_fg_color alpha({foreground}, 0.5); -@define-color borders alpha({foreground}, 0.2); - -/* Window styling */ -window { - background-color: @window_bg_color; - color: @window_fg_color; -} - -/* Widget styling */ -* { - background-color: @window_bg_color; - color: @window_fg_color; -} - -/* Button */ -button { - background-color: @card_bg_color; - color: @window_fg_color; - border: 1px solid @borders; - border-radius: 6px; - padding: 6px 12px; - min-height: 24px; -} - -button:hover { - background-color: alpha(@accent_bg_color, 0.1); - border-color: @accent_color; -} - -button:active { - background-color: @accent_bg_color; - color: @accent_fg_color; -} - -button:disabled { - background-color: @insensitive_bg_color; - color: @insensitive_fg_color; -} - -button.suggested-action { - background-color: @accent_bg_color; - color: @accent_fg_color; -} - -button.destructive-action { - background-color: @destructive_bg_color; - color: @destructive_fg_color; -} - -/* Entry/Input fields */ -entry { - background-color: @view_bg_color; - color: @view_fg_color; - border: 1px solid @borders; - border-radius: 6px; - padding: 6px; - min-height: 32px; -} - -entry:focus { - border-color: @accent_color; - outline: 2px solid alpha(@accent_color, 0.3); - outline-offset: -1px; -} - -entry:disabled { - background-color: @insensitive_bg_color; - color: @insensitive_fg_color; -} - -/* Selections */ -selection, -*:selected { - background-color: @accent_bg_color; - color: @accent_fg_color; -} - -/* Headerbar */ -headerbar { - background-color: @headerbar_bg_color; - color: @headerbar_fg_color; - border-bottom: 1px solid @headerbar_border_color; - min-height: 46px; - padding: 0 6px; -} - -headerbar button { - background-color: transparent; - border: none; -} - -headerbar button:hover { - background-color: alpha(@accent_bg_color, 0.1); -} - -headerbar button:active { - background-color: alpha(@accent_bg_color, 0.2); -} - -/* Sidebar */ -.sidebar { - background-color: @sidebar_bg_color; - border-right: 1px solid @borders; -} - -.sidebar row { - padding: 6px; -} - -.sidebar row:hover { - background-color: alpha(@accent_bg_color, 0.1); -} - -.sidebar row:selected { - background-color: @accent_bg_color; - color: @accent_fg_color; -} - -/* Notebook/Tabs */ -notebook > header { - background-color: @card_bg_color; - border-bottom: 1px solid @borders; -} - -notebook > header > tabs > tab { - background-color: transparent; - color: @window_fg_color; - padding: 6px 12px; - min-height: 32px; -} - -notebook > header > tabs > tab:hover { - background-color: alpha(@accent_bg_color, 0.1); -} - -notebook > header > tabs > tab:checked { - background-color: @window_bg_color; - border-bottom: 2px solid @accent_color; -} - -/* Menu */ -menubar { - background-color: @headerbar_bg_color; - color: @headerbar_fg_color; -} - -menu, -.menu { - background-color: @popover_bg_color; - color: @popover_fg_color; - border: 1px solid @borders; - border-radius: 8px; - padding: 4px; -} - -menuitem { - padding: 6px 12px; - border-radius: 4px; - min-height: 28px; -} - -menuitem:hover { - background-color: @accent_bg_color; - color: @accent_fg_color; -} - -/* Scrollbar */ -scrollbar { - background-color: transparent; -} - -scrollbar slider { - background-color: alpha(@window_fg_color, 0.3); - border-radius: 8px; - min-width: 10px; - min-height: 10px; -} - -scrollbar slider:hover { - background-color: alpha(@window_fg_color, 0.5); -} - -scrollbar slider:active { - background-color: @accent_color; -} - -/* Progressbar */ -progressbar { - background-color: @view_bg_color; - border-radius: 4px; -} - -progressbar progress { - background-color: @accent_bg_color; - border-radius: 4px; -} - -progressbar trough { - background-color: alpha(@window_fg_color, 0.1); - border-radius: 4px; -} - -/* Switch */ -switch { - background-color: alpha(@window_fg_color, 0.2); - border-radius: 12px; - min-width: 48px; - min-height: 24px; -} - -switch:checked { - background-color: @accent_bg_color; -} - -switch slider { - background-color: @window_fg_color; - border-radius: 50%; - min-width: 20px; - min-height: 20px; - margin: 2px; -} - -/* Checkbutton & Radiobutton */ -checkbutton check, -radiobutton radio { - background-color: @view_bg_color; - border: 1px solid @borders; - min-width: 16px; - min-height: 16px; -} - -checkbutton check { - border-radius: 4px; -} - -radiobutton radio { - border-radius: 50%; -} - -checkbutton check:checked, -radiobutton radio:checked { - background-color: @accent_bg_color; - border-color: @accent_bg_color; - color: @accent_fg_color; -} - -/* Tooltip */ -tooltip { - background-color: @popover_bg_color; - color: @popover_fg_color; - border: 1px solid @borders; - border-radius: 6px; - padding: 6px 8px; -} - -tooltip label { - padding: 0; -} - -/* Popover */ -popover { - background-color: @popover_bg_color; - color: @popover_fg_color; - border: 1px solid @borders; - border-radius: 12px; - padding: 6px; -} - -popover > contents { - background-color: transparent; -} - -/* Frame */ -frame { - border: 1px solid @borders; - border-radius: 6px; -} - -/* List */ -list, -listview, -.view { - background-color: @view_bg_color; - color: @view_fg_color; - border-radius: 6px; -} - -list row, -listview row { - padding: 6px; - border-radius: 4px; -} - -list row:hover, -listview row:hover { - background-color: alpha(@accent_bg_color, 0.1); -} - -list row:selected, -listview row:selected { - background-color: @accent_bg_color; - color: @accent_fg_color; -} - -/* Card */ -.card { - background-color: @card_bg_color; - color: @card_fg_color; - border: 1px solid @borders; - border-radius: 12px; - padding: 12px; -} - -/* Dialog */ -dialog { - background-color: @dialog_bg_color; - color: @dialog_fg_color; -} - -/* Searchbar */ -searchbar { - background-color: @headerbar_bg_color; - border-bottom: 1px solid @borders; -} - -/* Toolbar */ -toolbar { - background-color: @headerbar_bg_color; - padding: 4px; - border-bottom: 1px solid @borders; -} - -/* Infobar */ -.info { - background-color: @accent_bg_color; - color: @accent_fg_color; -} - -.warning { - background-color: @warning_bg_color; - color: @warning_fg_color; -} - -.error { - background-color: @error_bg_color; - color: @error_fg_color; -} - -.success, -.question { - background-color: @success_bg_color; - color: @success_fg_color; -} diff --git a/shared/modules/services/theme_switcher/templates/helix_minimal.toml b/shared/modules/services/theme_switcher/templates/helix_minimal.toml deleted file mode 100644 index 35f85dc..0000000 --- a/shared/modules/services/theme_switcher/templates/helix_minimal.toml +++ /dev/null @@ -1,153 +0,0 @@ -# Pywal Minimal - Judicious Syntax Highlighting -# Uses only 4 colors for syntax -# Functions and variables remain plain text for reduced visual noise -# Colors dynamically generated from wallpaper via pywal - -"ui.background" = {{ bg = "base" }} -"ui.virtual" = {{ fg = "surface0" }} -"ui.virtual.ruler" = {{ bg = "surface0" }} -"ui.virtual.indent-guide" = {{ fg = "surface0" }} -"ui.virtual.inlay-hint" = {{ fg = "overlay1", bg = "mantle", modifiers = ["italic"] }} -"ui.virtual.jump-label" = {{ fg = "red", modifiers = ["bold"] }} - -"ui.selection" = {{ fg = "text", bg = "surface1" }} -"ui.selection.primary" = {{ fg = "text", bg = "surface2" }} - -"ui.cursor" = {{ fg = "base", bg = "cursor" }} -"ui.cursor.primary" = {{ fg = "base", bg = "cursor" }} -"ui.cursor.match" = {{ fg = "color1", modifiers = ["bold"] }} -"ui.cursorline.primary" = {{ bg = "surface0" }} -"ui.cursorcolumn.primary" = {{ bg = "surface0" }} - -"ui.linenr" = {{ fg = "surface1" }} -"ui.linenr.selected" = {{ fg = "cursor", modifiers = ["bold"] }} - -"ui.statusline" = {{ fg = "text", bg = "mantle" }} -"ui.statusline.inactive" = {{ fg = "overlay0", bg = "mantle" }} -"ui.statusline.normal" = {{ fg = "base", bg = "cursor", modifiers = ["bold"] }} -"ui.statusline.insert" = {{ fg = "base", bg = "color2", modifiers = ["bold"] }} -"ui.statusline.select" = {{ fg = "base", bg = "color5", modifiers = ["bold"] }} - -"ui.bufferline" = {{ fg = "overlay0", bg = "mantle" }} -"ui.bufferline.active" = {{ fg = "cursor", bg = "base", modifiers = ["bold"] }} - -"ui.help" = {{ fg = "text", bg = "surface0" }} -"ui.text" = "text" -"ui.text.focus" = {{ fg = "text", bg = "surface0" }} -"ui.text.inactive" = "overlay1" - -"ui.menu" = {{ fg = "text", bg = "surface0" }} -"ui.menu.selected" = {{ fg = "text", bg = "surface1", modifiers = ["bold"] }} -"ui.menu.scroll" = {{ fg = "overlay0", bg = "surface0" }} - -"ui.popup" = {{ fg = "text", bg = "surface0" }} -"ui.window" = {{ fg = "base" }} - -"diagnostic.error" = {{ underline = {{ color = "color1", style = "curl" }} }} -"diagnostic.warning" = {{ underline = {{ color = "color3", style = "curl" }} }} -"diagnostic.info" = {{ underline = {{ color = "color6", style = "curl" }} }} -"diagnostic.hint" = {{ underline = {{ color = "color4", style = "curl" }} }} -"diagnostic.unnecessary" = {{ modifiers = ["dim"] }} -"diagnostic.deprecated" = {{ modifiers = ["crossed_out"] }} - -"error" = "color1" -"warning" = "color3" -"info" = "color6" -"hint" = "color4" - -"diff.plus" = "color2" -"diff.minus" = "color1" -"diff.delta" = "color3" - -"markup.heading" = {{ fg = "cursor", modifiers = ["bold"] }} -"markup.list" = "color5" -"markup.bold" = {{ modifiers = ["bold"] }} -"markup.italic" = {{ modifiers = ["italic"] }} -"markup.strikethrough" = {{ modifiers = ["crossed_out"] }} -"markup.link.url" = {{ fg = "color4", modifiers = ["underlined"] }} -"markup.link.text" = "color5" -"markup.quote" = "color2" -"markup.raw" = "color2" - -# Minimal syntax highlighting - only 4 colors used -"comment" = "color3" # Comments pop (yellow/bright color) - -"keyword" = {{ fg = "color5", modifiers = ["italic"] }} # Keywords highlighted and italic (magenta) -"keyword.control" = {{ fg = "color5", modifiers = ["italic"] }} -"keyword.directive" = {{ fg = "color5", modifiers = ["italic"] }} -"keyword.function" = {{ fg = "color5", modifiers = ["italic"] }} -"keyword.operator" = {{ fg = "color5", modifiers = ["italic"] }} -"keyword.return" = {{ fg = "color5", modifiers = ["italic"] }} -"keyword.storage" = {{ fg = "color5", modifiers = ["italic"] }} - -"string" = "color2" # Strings highlighted (green) -"string.regexp" = "color1" -"string.special" = "color2" - -"constant.numeric" = "color1" # Numbers highlighted (orange/red) -"constant.builtin" = "color1" -"constant.character.escape" = "color1" - -# Everything else remains plain text -"function" = "text" # Functions are plain text -"function.builtin" = "text" -"function.method" = "text" -"function.macro" = "text" - -"variable" = "text" # Variables are plain text -"variable.builtin" = "text" -"variable.parameter" = "text" -"variable.other.member" = "text" - -"type" = {{ fg = "cursor", modifiers = ["italic"]}} # Types are slightly highlighted -"type.builtin" = "cursor" - -"constructor" = "text" # Constructors are plain text - -"attribute" = "text" -"label" = "text" -"namespace" = "text" -"tag" = "text" - -# Top-level declarations get accent color -"function.definition" = {{ fg = "color7", modifiers = ["bold"] }} -"type.definition" = {{ fg = "color7", modifiers = ["bold"] }} - -# Punctuation is slightly dimmed but still readable -"punctuation" = "subtext0" -"punctuation.bracket" = "subtext0" -"punctuation.delimiter" = "subtext0" -"punctuation.special" = "subtext0" - -"operator" = "subtext0" # Operators slightly dimmed - -[palette] -# Pywal generated colors - background to foreground gradient -base = "{background}" -mantle = "{color0}" -crust = "{color0}" - -surface0 = "{color8}" -surface1 = "{color8}" -surface2 = "{color7}" - -overlay0 = "{color8}" -overlay1 = "{color7}" -overlay2 = "{color7}" - -subtext0 = "{color7}" -subtext1 = "{color15}" -text = "{foreground}" - -# Core colors from pywal palette -red = "{color1}" -color1 = "{color1}" # Red/Orange - numbers, errors -color2 = "{color2}" # Green - strings -color3 = "{color3}" # Yellow - comments -color4 = "{color4}" # Blue - info -color5 = "{color5}" # Magenta - keywords -color6 = "{color6}" # Cyan - hints -color7 = "{color7}" # Light gray - definitions - -color15 = "{color15}" # Bright white -cursor = "{cursor}" # Cursor color diff --git a/shared/modules/services/theme_switcher/templates/helix_semantic.toml b/shared/modules/services/theme_switcher/templates/helix_semantic.toml deleted file mode 100644 index 305984b..0000000 --- a/shared/modules/services/theme_switcher/templates/helix_semantic.toml +++ /dev/null @@ -1,164 +0,0 @@ -# Pywal Semantic - Intelligent color mapping based on semantic meaning -# Colors are automatically mapped based on hue analysis: -# - Reds/oranges for errors and warnings -# - High contrast colors for comments -# - Appropriate semantic colors for syntax elements -# Colors dynamically generated from wallpaper via pywal with semantic mapping - -"ui.background" = {{ bg = "base" }} -"ui.virtual" = {{ fg = "surface0" }} -"ui.virtual.ruler" = {{ bg = "surface0" }} -"ui.virtual.indent-guide" = {{ fg = "surface0" }} -"ui.virtual.inlay-hint" = {{ fg = "overlay1", bg = "mantle", modifiers = ["italic"] }} -"ui.virtual.jump-label" = {{ fg = "error_color", modifiers = ["bold"] }} - -"ui.selection" = {{ fg = "text", bg = "surface1" }} -"ui.selection.primary" = {{ fg = "text", bg = "surface2" }} - -"ui.cursor" = {{ fg = "base", bg = "cursor" }} -"ui.cursor.primary" = {{ fg = "base", bg = "cursor" }} -"ui.cursor.match" = {{ fg = "keyword_color", modifiers = ["bold"] }} -"ui.cursorline.primary" = {{ bg = "surface0" }} -"ui.cursorcolumn.primary" = {{ bg = "surface0" }} - -"ui.linenr" = {{ fg = "surface1" }} -"ui.linenr.selected" = {{ fg = "cursor", modifiers = ["bold"] }} - -"ui.statusline" = {{ fg = "text", bg = "mantle" }} -"ui.statusline.inactive" = {{ fg = "overlay0", bg = "mantle" }} -"ui.statusline.normal" = {{ fg = "base", bg = "cursor", modifiers = ["bold"] }} -"ui.statusline.insert" = {{ fg = "base", bg = "string_color", modifiers = ["bold"] }} -"ui.statusline.select" = {{ fg = "base", bg = "keyword_color", modifiers = ["bold"] }} - -"ui.bufferline" = {{ fg = "overlay0", bg = "mantle" }} -"ui.bufferline.active" = {{ fg = "cursor", bg = "base", modifiers = ["bold"] }} - -"ui.help" = {{ fg = "text", bg = "surface0" }} -"ui.text" = "text" -"ui.text.focus" = {{ fg = "text", bg = "surface0" }} -"ui.text.inactive" = "overlay1" - -"ui.menu" = {{ fg = "text", bg = "surface0" }} -"ui.menu.selected" = {{ fg = "text", bg = "surface1", modifiers = ["bold"] }} -"ui.menu.scroll" = {{ fg = "overlay0", bg = "surface0" }} - -"ui.popup" = {{ fg = "text", bg = "surface0" }} -"ui.window" = {{ fg = "base" }} - -"diagnostic.error" = {{ underline = {{ color = "error_color", style = "curl" }} }} -"diagnostic.warning" = {{ underline = {{ color = "warning_color", style = "curl" }} }} -"diagnostic.info" = {{ underline = {{ color = "info_color", style = "curl" }} }} -"diagnostic.hint" = {{ underline = {{ color = "info_color", style = "curl" }} }} -"diagnostic.unnecessary" = {{ modifiers = ["dim"] }} -"diagnostic.deprecated" = {{ modifiers = ["crossed_out"] }} - -"error" = "error_color" -"warning" = "warning_color" -"info" = "info_color" -"hint" = "info_color" - -"diff.plus" = "string_color" -"diff.minus" = "error_color" -"diff.delta" = "warning_color" - -"markup.heading" = {{ fg = "cursor", modifiers = ["bold"] }} -"markup.list" = "keyword_color" -"markup.bold" = {{ modifiers = ["bold"] }} -"markup.italic" = {{ modifiers = ["italic"] }} -"markup.strikethrough" = {{ modifiers = ["crossed_out"] }} -"markup.link.url" = {{ fg = "info_color", modifiers = ["underlined"] }} -"markup.link.text" = "keyword_color" -"markup.quote" = "string_color" -"markup.raw" = "string_color" - -# Semantic syntax highlighting -"comment" = "comment_color" # Highest contrast color for visibility - -"keyword" = {{ fg = "keyword_color", modifiers = ["italic"] }} -"keyword.control" = {{ fg = "keyword_color", modifiers = ["italic"] }} -"keyword.directive" = {{ fg = "keyword_color", modifiers = ["italic"] }} -"keyword.function" = {{ fg = "keyword_color", modifiers = ["italic"] }} -"keyword.operator" = {{ fg = "keyword_color", modifiers = ["italic"] }} -"keyword.return" = {{ fg = "keyword_color", modifiers = ["italic"] }} -"keyword.storage" = {{ fg = "keyword_color", modifiers = ["italic"] }} - -"string" = "string_color" -"string.regexp" = "error_color" -"string.special" = "string_color" - -"constant.numeric" = "number_color" -"constant.builtin" = "number_color" -"constant.character.escape" = "number_color" - -# Everything else remains plain text -"function" = "text" -"function.builtin" = "text" -"function.method" = "text" -"function.macro" = "text" - -"variable" = "text" -"variable.builtin" = "text" -"variable.parameter" = "text" -"variable.other.member" = "text" - -"type" = {{ fg = "cursor", modifiers = ["italic"]}} -"type.builtin" = "cursor" - -"constructor" = "text" - -"attribute" = "text" -"label" = "text" -"namespace" = "text" -"tag" = "text" - -# Top-level declarations get accent color -"function.definition" = {{ fg = "color7", modifiers = ["bold"] }} -"type.definition" = {{ fg = "color7", modifiers = ["bold"] }} - -# Punctuation is slightly dimmed but still readable -"punctuation" = "subtext0" -"punctuation.bracket" = "subtext0" -"punctuation.delimiter" = "subtext0" -"punctuation.special" = "subtext0" - -"operator" = "subtext0" - -[palette] -# Pywal generated colors - background to foreground gradient -base = "{background}" -mantle = "{color0}" -crust = "{color0}" - -surface0 = "{color8}" -surface1 = "{color8}" -surface2 = "{color7}" - -overlay0 = "{color8}" -overlay1 = "{color7}" -overlay2 = "{color7}" - -subtext0 = "{color7}" -subtext1 = "{color15}" -text = "{foreground}" - -# Core colors from pywal palette -red = "{color1}" -color1 = "{color1}" -color2 = "{color2}" -color3 = "{color3}" -color4 = "{color4}" -color5 = "{color5}" -color6 = "{color6}" -color7 = "{color7}" - -color15 = "{color15}" -cursor = "{cursor}" - -# Semantic colors (will be replaced by color_mapper.py) -error_color = "{{ERROR_COLOR}}" -warning_color = "{{WARNING_COLOR}}" -comment_color = "{{COMMENT_COLOR}}" -string_color = "{{STRING_COLOR}}" -keyword_color = "{{KEYWORD_COLOR}}" -number_color = "{{NUMBER_COLOR}}" -info_color = "{{INFO_COLOR}}" diff --git a/shared/modules/services/theme_switcher/theme_switcher.py b/shared/modules/services/theme_switcher/theme_switcher.py deleted file mode 100755 index 0260424..0000000 --- a/shared/modules/services/theme_switcher/theme_switcher.py +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env python3 -""" -Theme Switcher Script - Time-based light/dark theme switching. - -This script: -1. Determines current theme mode (light/dark) based on time -2. Updates wpaperd config to use appropriate wallpaper directory -3. Restarts wpaperd to load new config -4. Gets the selected wallpaper from wpaperd -5. Generates pywal color palette from the wallpaper -6. Applies GTK/Kvantum/Helix themes -""" - -import os -import subprocess -import sys -import time -import shutil -from datetime import datetime -from pathlib import Path -from typing import Optional -import logging -import argparse - -# Setup logging -logging.basicConfig( - level=logging.INFO, - format='%(levelname)s: %(message)s' -) -logger = logging.getLogger(__name__) - - -class ThemeSwitcher: - """Handles time-based light/dark theme switching.""" - - def __init__(self, wallpaper_path: str, light_time: str, dark_time: str, - backend: str, rotation_interval: str, wpaperd_mode: str, - sorting: str, apply_themes_script: str): - self.wallpaper_path = Path(wallpaper_path) - self.light_hour = int(light_time.split(':')[0]) - self.dark_hour = int(dark_time.split(':')[0]) - self.backend = backend - self.rotation_interval = rotation_interval - self.wpaperd_mode = wpaperd_mode - self.sorting = sorting - self.apply_themes_script = apply_themes_script - self.home = Path.home() - - # Find command paths - self.systemctl = self._find_command('systemctl') - self.wpaperctl = self._find_command('wpaperctl') - self.wal = self._find_command('wal') - - def _find_command(self, cmd: str) -> str: - """Find full path to a command.""" - path = shutil.which(cmd) - if path is None: - # Try common NixOS locations - common_paths = [ - f'/run/current-system/sw/bin/{cmd}', - f'/etc/profiles/per-user/{os.environ.get("USER", "")}/bin/{cmd}', - f'{self.home}/.nix-profile/bin/{cmd}', - ] - for p in common_paths: - if Path(p).exists(): - return p - raise FileNotFoundError(f"Command not found: {cmd}") - return path - - def is_wpaperd_running(self) -> bool: - """Check if wpaperd process is running.""" - try: - pgrep = shutil.which('pgrep') or 'pgrep' - result = subprocess.run( - [pgrep, '-x', 'wpaperd'], - capture_output=True, - timeout=5 - ) - return result.returncode == 0 - except (subprocess.TimeoutExpired, FileNotFoundError): - return False - - def determine_mode(self) -> tuple[str, Path]: - """ - Determine current theme mode based on time. - - Returns: - Tuple of (mode_name, wallpaper_directory) - """ - current_hour = datetime.now().hour - - if self.light_hour <= current_hour < self.dark_hour: - mode = "light" - wallpaper_dir = self.wallpaper_path / "Light" - else: - mode = "dark" - wallpaper_dir = self.wallpaper_path / "Dark" - - return mode, wallpaper_dir - - def update_wpaperd_config(self, wallpaper_dir: Path) -> None: - """ - Update wpaperd configuration file. - - Args: - wallpaper_dir: Path to wallpaper directory (Light or Dark) - """ - config_dir = self.home / ".config/wpaperd" - config_file = config_dir / "config.toml" - - # Ensure config directory exists - config_dir.mkdir(parents=True, exist_ok=True) - - # Remove old config file - if config_file.exists(): - config_file.unlink() - - # Create new config - config_content = f"""[default] -path = "{wallpaper_dir}" -duration = "{self.rotation_interval}" -mode = "{self.wpaperd_mode}" -sorting = "{self.sorting}" -""" - - config_file.write_text(config_content) - logger.info(f"Updated wpaperd config: {config_file}") - - def restart_wpaperd(self) -> None: - """Restart wpaperd systemd service.""" - try: - subprocess.run( - [self.systemctl, '--user', 'restart', 'wpaperd.service'], - check=True, - timeout=10 - ) - logger.info("Restarted wpaperd service") - # Give wpaperd time to start and load wallpaper - time.sleep(2) - except subprocess.CalledProcessError as e: - logger.error(f"Failed to restart wpaperd: {e}") - raise - except subprocess.TimeoutExpired: - logger.error("Timeout restarting wpaperd") - raise - - def get_current_wallpaper(self) -> Path: - """ - Get the current wallpaper path from wpaperd. - - Returns: - Path to current wallpaper file - - Raises: - RuntimeError: If unable to get wallpaper path - """ - wallpaper_state_dir = self.home / ".local/state/wpaperd/wallpapers" - - if not wallpaper_state_dir.exists(): - raise RuntimeError(f"Wpaperd state directory not found: {wallpaper_state_dir}") - - # Get first monitor directory - try: - monitor_dirs = list(wallpaper_state_dir.iterdir()) - if not monitor_dirs: - raise RuntimeError("No monitor directories found in wpaperd state") - - monitor_name = monitor_dirs[0].name - - # Query wpaperd for current wallpaper - result = subprocess.run( - [self.wpaperctl, 'get-wallpaper', monitor_name], - capture_output=True, - text=True, - check=True, - timeout=5 - ) - - wallpaper_path = result.stdout.strip() - if not wallpaper_path: - raise RuntimeError("wpaperctl returned empty wallpaper path") - - return Path(wallpaper_path) - - except (subprocess.CalledProcessError, subprocess.TimeoutExpired, IndexError) as e: - raise RuntimeError(f"Failed to get current wallpaper: {e}") - - def generate_palette(self, wallpaper: Path, mode: str) -> None: - """ - Generate pywal color palette from wallpaper. - - Args: - wallpaper: Path to wallpaper image - mode: Theme mode ('light' or 'dark') - """ - cmd = [self.wal, '-i', str(wallpaper), '--backend', self.backend, '-n'] - - if mode == 'light': - cmd.append('-l') - - try: - subprocess.run(cmd, check=True, timeout=30) - logger.info(f"Generated {mode} palette from: {wallpaper}") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to generate palette: {e}") - raise - except subprocess.TimeoutExpired: - logger.error("Timeout while generating palette") - raise - - def apply_themes(self) -> None: - """Apply GTK and Kvantum themes using helper script.""" - try: - subprocess.run( - ['python3', self.apply_themes_script], - check=True, - timeout=30 - ) - logger.info("Applied themes successfully") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to apply themes: {e}") - raise - except subprocess.TimeoutExpired: - logger.error("Timeout while applying themes") - raise - - def run(self) -> int: - """ - Execute the theme switching process. - - Returns: - Exit code (0 for success, 1 for failure) - """ - try: - # Determine mode and wallpaper directory - mode, wallpaper_dir = self.determine_mode() - logger.info(f"Switching to {mode} theme with wallpapers from {wallpaper_dir}") - - # Check if wpaperd is running - if not self.is_wpaperd_running(): - logger.error("wpaperd is not running") - return 1 - - # Update wpaperd config - self.update_wpaperd_config(wallpaper_dir) - - # Restart wpaperd - self.restart_wpaperd() - - # Get current wallpaper - current_wallpaper = self.get_current_wallpaper() - logger.info(f"Current wallpaper: {current_wallpaper}") - - # Generate palette - self.generate_palette(current_wallpaper, mode) - - # Apply themes - self.apply_themes() - - logger.info(f"Theme switched to {mode} mode successfully!") - return 0 - - except Exception as e: - logger.error(f"Theme switching failed: {e}", exc_info=True) - return 1 - - -def main(): - """Main entry point.""" - parser = argparse.ArgumentParser(description='Time-based theme switcher') - parser.add_argument('--wallpaper-path', required=True, help='Path to wallpaper directories') - parser.add_argument('--light-time', default='09:00:00', help='Light theme start time (HH:MM:SS)') - parser.add_argument('--dark-time', default='16:30:00', help='Dark theme start time (HH:MM:SS)') - parser.add_argument('--backend', default='haishoku', help='Pywal color extraction backend') - parser.add_argument('--rotation-interval', default='5m', help='Wallpaper rotation interval') - parser.add_argument('--wpaperd-mode', default='center', help='Wpaperd display mode') - parser.add_argument('--sorting', default='random', help='Wallpaper sorting method') - parser.add_argument('--apply-themes-script', required=True, help='Path to apply-themes script') - - args = parser.parse_args() - - switcher = ThemeSwitcher( - wallpaper_path=args.wallpaper_path, - light_time=args.light_time, - dark_time=args.dark_time, - backend=args.backend, - rotation_interval=args.rotation_interval, - wpaperd_mode=args.wpaperd_mode, - sorting=args.sorting, - apply_themes_script=args.apply_themes_script - ) - - return switcher.run() - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/shared/modules/services/theme_switcher/wallpaper_rotation.py b/shared/modules/services/theme_switcher/wallpaper_rotation.py deleted file mode 100755 index 9f402d1..0000000 --- a/shared/modules/services/theme_switcher/wallpaper_rotation.py +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env python3 -""" -Wallpaper Rotation Script - Handles continuous wallpaper rotation with palette regeneration. - -This script: -1. Checks if wpaperd is running -2. Determines current theme mode (light/dark) for correct pywal flags -3. Pauses wpaperd's auto-rotation -4. Advances to next wallpaper -5. Gets the new wallpaper path -6. Regenerates pywal palette -7. Applies themes -8. Resumes wpaperd's auto-rotation -""" - -import os -import subprocess -import sys -import time -import shutil -from datetime import datetime -from pathlib import Path -import logging -import argparse - -# Setup logging -logging.basicConfig( - level=logging.INFO, - format='%(levelname)s: %(message)s' -) -logger = logging.getLogger(__name__) - - -class WallpaperRotator: - """Handles wallpaper rotation with theme regeneration.""" - - def __init__(self, light_time: str, dark_time: str, backend: str, - apply_themes_script: str): - self.light_hour = int(light_time.split(':')[0]) - self.dark_hour = int(dark_time.split(':')[0]) - self.backend = backend - self.apply_themes_script = apply_themes_script - self.home = Path.home() - - # Find command paths - self.wpaperctl = self._find_command('wpaperctl') - self.wal = self._find_command('wal') - - def _find_command(self, cmd: str) -> str: - """Find full path to a command.""" - path = shutil.which(cmd) - if path is None: - # Try common NixOS locations - common_paths = [ - f'/run/current-system/sw/bin/{cmd}', - f'/etc/profiles/per-user/{os.environ.get("USER", "")}/bin/{cmd}', - f'{self.home}/.nix-profile/bin/{cmd}', - ] - for p in common_paths: - if Path(p).exists(): - return p - raise FileNotFoundError(f"Command not found: {cmd}") - return path - - def is_wpaperd_running(self) -> bool: - """Check if wpaperd process is running.""" - try: - pgrep = shutil.which('pgrep') or 'pgrep' - result = subprocess.run( - [pgrep, '-x', 'wpaperd'], - capture_output=True, - timeout=5 - ) - return result.returncode == 0 - except (subprocess.TimeoutExpired, FileNotFoundError): - return False - - def determine_mode(self) -> tuple[str, list[str]]: - """ - Determine current theme mode and pywal flags based on time. - - Returns: - Tuple of (mode_name, pywal_flags_list) - """ - current_hour = datetime.now().hour - - if self.light_hour <= current_hour < self.dark_hour: - mode = "light" - pywal_flags = ['-l', '-n'] - else: - mode = "dark" - pywal_flags = ['-n'] - - return mode, pywal_flags - - def pause_wpaperd(self) -> None: - """Pause wpaperd's automatic wallpaper rotation.""" - try: - subprocess.run( - [self.wpaperctl, 'pause-wallpaper'], - check=True, - timeout=5, - capture_output=True - ) - logger.debug("Paused wpaperd rotation") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to pause wpaperd: {e}") - raise - except subprocess.TimeoutExpired: - logger.error("Timeout while pausing wpaperd") - raise - except FileNotFoundError: - logger.error("wpaperctl command not found") - raise - - def resume_wpaperd(self) -> None: - """Resume wpaperd's automatic wallpaper rotation.""" - try: - subprocess.run( - [self.wpaperctl, 'resume-wallpaper'], - check=True, - timeout=5, - capture_output=True - ) - logger.debug("Resumed wpaperd rotation") - except subprocess.CalledProcessError as e: - logger.warning(f"Failed to resume wpaperd: {e}") - except subprocess.TimeoutExpired: - logger.warning("Timeout while resuming wpaperd") - except FileNotFoundError: - logger.warning("wpaperctl command not found") - - def advance_wallpaper(self) -> None: - """Advance to next wallpaper using wpaperctl.""" - try: - subprocess.run( - [self.wpaperctl, 'next-wallpaper'], - check=True, - timeout=5, - capture_output=True - ) - logger.info("Advanced to next wallpaper") - # Give wpaperd time to complete transition - time.sleep(1) - except subprocess.CalledProcessError as e: - logger.error(f"Failed to advance wallpaper: {e}") - raise - except subprocess.TimeoutExpired: - logger.error("Timeout while advancing wallpaper") - raise - except FileNotFoundError: - logger.error("wpaperctl command not found") - raise - - def get_current_wallpaper(self) -> Path: - """ - Get the current wallpaper path from wpaperd. - - Returns: - Path to current wallpaper file - - Raises: - RuntimeError: If unable to get wallpaper path - """ - wallpaper_state_dir = self.home / ".local/state/wpaperd/wallpapers" - - if not wallpaper_state_dir.exists(): - raise RuntimeError(f"Wpaperd state directory not found: {wallpaper_state_dir}") - - # Get first monitor directory - try: - monitor_dirs = list(wallpaper_state_dir.iterdir()) - if not monitor_dirs: - raise RuntimeError("No monitor directories found in wpaperd state") - - monitor_name = monitor_dirs[0].name - - # Query wpaperd for current wallpaper - result = subprocess.run( - [self.wpaperctl, 'get-wallpaper', monitor_name], - capture_output=True, - text=True, - check=True, - timeout=5 - ) - - wallpaper_path = result.stdout.strip() - if not wallpaper_path: - raise RuntimeError("wpaperctl returned empty wallpaper path") - - return Path(wallpaper_path) - - except (subprocess.CalledProcessError, subprocess.TimeoutExpired, IndexError) as e: - raise RuntimeError(f"Failed to get current wallpaper: {e}") - - def generate_palette(self, wallpaper: Path, pywal_flags: list[str]) -> None: - """ - Generate pywal color palette from wallpaper. - - Args: - wallpaper: Path to wallpaper image - pywal_flags: List of additional pywal flags - """ - cmd = [self.wal, '-i', str(wallpaper), '--backend', self.backend] + pywal_flags - - try: - subprocess.run(cmd, check=True, timeout=30, capture_output=True) - logger.info(f"Generated palette from: {wallpaper.name}") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to generate palette: {e}") - raise - except subprocess.TimeoutExpired: - logger.error("Timeout while generating palette") - raise - except FileNotFoundError: - logger.error("wal command not found") - raise - - def apply_themes(self) -> None: - """Apply GTK and Kvantum themes using helper script.""" - try: - subprocess.run( - ['python3', self.apply_themes_script], - check=True, - timeout=30, - capture_output=True - ) - logger.debug("Applied themes successfully") - except subprocess.CalledProcessError as e: - logger.error(f"Failed to apply themes: {e}") - raise - except subprocess.TimeoutExpired: - logger.error("Timeout while applying themes") - raise - except FileNotFoundError as e: - logger.error(f"Theme application script not found: {e}") - raise - - def run(self) -> int: - """ - Execute the wallpaper rotation and theme regeneration process. - - Returns: - Exit code (0 for success, 1 for failure) - """ - # Check if wpaperd is running - if not self.is_wpaperd_running(): - logger.info("wpaperd is not running, skipping rotation") - return 0 - - try: - # Determine mode - mode, pywal_flags = self.determine_mode() - logger.info(f"Rotating wallpaper and regenerating {mode} palette...") - - # Pause wpaperd - self.pause_wpaperd() - - try: - # Advance to next wallpaper - self.advance_wallpaper() - - # Get new wallpaper path - current_wallpaper = self.get_current_wallpaper() - logger.info(f"New wallpaper: {current_wallpaper.name}") - - # Generate palette - self.generate_palette(current_wallpaper, pywal_flags) - - # Apply themes - self.apply_themes() - - logger.info("Wallpaper rotated and palette regenerated successfully!") - return 0 - - finally: - # Always resume wpaperd, even if something fails - self.resume_wpaperd() - - except Exception as e: - logger.error(f"Wallpaper rotation failed: {e}", exc_info=True) - return 1 - - -def main(): - """Main entry point.""" - parser = argparse.ArgumentParser(description='Wallpaper rotation with theme regeneration') - parser.add_argument('--light-time', default='09:00:00', help='Light theme start time (HH:MM:SS)') - parser.add_argument('--dark-time', default='16:30:00', help='Dark theme start time (HH:MM:SS)') - parser.add_argument('--backend', default='haishoku', help='Pywal color extraction backend') - parser.add_argument('--apply-themes-script', required=True, help='Path to apply-themes script') - - args = parser.parse_args() - - rotator = WallpaperRotator( - light_time=args.light_time, - dark_time=args.dark_time, - backend=args.backend, - apply_themes_script=args.apply_themes_script - ) - - return rotator.run() - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/shared/modules/services/wallpaper-rotator.nix b/shared/modules/services/wallpaper-rotator.nix deleted file mode 100644 index 4d5adb6..0000000 --- a/shared/modules/services/wallpaper-rotator.nix +++ /dev/null @@ -1,100 +0,0 @@ -{ config, lib, pkgs, ... }: - -let - cfg = config.services.wallpaperRotator; -in -{ - options.services.wallpaperRotator = { - enable = lib.mkEnableOption "wallpaper rotation service using wpaperd"; - - user = lib.mkOption { - type = lib.types.str; - description = "Username for the wallpaper service"; - }; - - wallpaperPath = lib.mkOption { - type = lib.types.str; - default = "/home/${cfg.user}/nixos/shared/modules/services/wallpapers"; - description = "Path to the wallpaper directory"; - }; - - duration = lib.mkOption { - type = lib.types.str; - default = "5m"; - example = "30s"; - description = "How long to display each wallpaper (e.g., 30s, 5m, 1h)"; - }; - - sorting = lib.mkOption { - type = lib.types.enum [ "random" "ascending" "descending" ]; - default = "random"; - description = "Order in which to display wallpapers"; - }; - - mode = lib.mkOption { - type = lib.types.enum [ "center" "fit" "fit-border-color" "stretch" "tile" ]; - default = "center"; - description = "How to display wallpapers when size differs from display resolution"; - }; - - transition = { - effect = lib.mkOption { - type = lib.types.str; - default = "fade"; - example = "hexagonalize"; - description = '' - Transition effect name. Available effects include: - fade, hexagonalize, simple-zoom, swirl, circle, wipe, slide, etc. - See wpaperd documentation for full list. - ''; - }; - - duration = lib.mkOption { - type = lib.types.int; - default = 300; - example = 2000; - description = "Transition duration in milliseconds"; - }; - }; - - queueSize = lib.mkOption { - type = lib.types.int; - default = 10; - description = "Size of wallpaper history queue for random mode"; - }; - - initialTransition = lib.mkOption { - type = lib.types.bool; - default = true; - description = "Enable transition effect at wpaperd startup"; - }; - }; - - config = lib.mkIf cfg.enable { - home-manager.users.${cfg.user} = { - services.wpaperd = { - enable = true; - settings = { - # Default configuration for all displays - default = { - path = cfg.wallpaperPath; - duration = cfg.duration; - sorting = cfg.sorting; - mode = cfg.mode; - transition-time = cfg.transition.duration; - queue-size = cfg.queueSize; - initial-transition = cfg.initialTransition; - }; - - # Apply transition effect - "default.transition.${cfg.transition.effect}" = {}; - }; - }; - - # Add wpaperd CLI tool to user packages for manual control - home.packages = with pkgs; [ - wpaperd - ]; - }; - }; -} diff --git a/shared/modules/services/wallpapers/Dark/FSO-Dark.jpg b/shared/modules/services/wallpapers/Dark/FSO-Dark.jpg deleted file mode 100644 index d46c0ea..0000000 Binary files a/shared/modules/services/wallpapers/Dark/FSO-Dark.jpg and /dev/null differ diff --git a/shared/modules/services/wallpapers/Dark/IU-Dark.jpg b/shared/modules/services/wallpapers/Dark/IU-Dark.jpg deleted file mode 100644 index d604238..0000000 Binary files a/shared/modules/services/wallpapers/Dark/IU-Dark.jpg and /dev/null differ diff --git a/shared/modules/services/wallpapers/README.md b/shared/modules/services/wallpapers/README.md deleted file mode 100644 index 4a08e07..0000000 --- a/shared/modules/services/wallpapers/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# Wallpapers - -This directory contains purchased wallpapers that are licensed for personal use only. - -## License - -These images are purchased and copyrighted by their respective creators. -They are **not included in this git repository** and are licensed for personal use only. - -No reproduction, distribution, or commercial use is permitted. - -## Usage - -Place your wallpaper images in this directory. The wallpaper rotation service -will automatically detect and rotate through them. - -Supported formats: -- JPEG (.jpg, .jpeg) -- PNG (.png) -- WebP (.webp) -- AVIF (.avif) - -## Configuration - -The wallpaper rotation service is configured via the `services.wallpaperRotator` -option in your host configuration. See the module documentation for available options.