HYWEG52CLGAF54BUZO53SGBVT6UKVPMW2BN5WX35OJSJ4RW4F6RAC 6LUHNGGS5UCQCJ4GZRVXZ6Q7C5W63VNUTYIVZUFTALGDAK27YVLAC 3T7XRDT5ECQAQXAVQW4JADVH6P53CPWZFTMEA46KIE5I7BHB6W7QC W62EKVNDJKWCLCQVP47P5THP3LSYC4IX6Y3A5DKORTQDLR3QLQYQC GJ2MOI2OMUX4R22BIG3WGEXPOOLHY7BZXBVNGCBPRSKCVIQZZV4QC A2MKNXX27RFP5RP52XCOYOHQKS2LCOAYFUP3YW7DKVFSQILQQX7QC NGEV4IEE3SAM5RUYEJIWFWAARRLATSJPIEZELHUJ5R7WJJ3WBEUQC MQT74AV47PUYNCX27OMFK6BFN7PP4DX46JAACN2EPRYXUXV7EL3AC RTBMBSBABSGTRICJ4AWBKWO3JJHBRKV6FGOMYPDD7X6SS6X35ZIQC RVV2KSCIXFU4HLXELZV67QDRTKG7OU6FZHFMPYO6HN6ISXPALX6QC A3KCM3RK3HEFXYM7PQMMFKNYZLPFGOP7GZHWKRDP37O67TIFV7MAC QOY7D3GZ3WF7HAMEHAW2CIUB5TDQCFE3YDZEO23R5MFQEBN635WAC 4R5XJ6M6G3A67RQ3BPA6OSHMUEDCWOSDT7BCUMIKT4MPYHGTYLIAC HQ4UTQK5UQXMEUZRYQSCTEKW73UZLJQHPA4YD75FCKBKHS5NMJOAC 5G7WRBMWKG6DMCOHE6WQHTYZACUHO2UPBZRWN72CFH7P45NN5E7QC 52JZLFIA4TLBPX2OACDOZ4BKNX3TJ33W5O2ZP7ACKI34N7RBZQVQC HBSVMHMQ2CQFZXFPIECPGNT7DB46YEHSE72BB4XGHKPS4IPNKYLQC USA5NQJAPQIM3Y64IIHREDDOBZWAM6VDI4JTCFFU5X3TENEDILXQC GB7WCUSTYHJ642OLGSZXO34V3KDZ23WQGTMNYMJ6FMOHDGDR445QC QRPPLHLL6YZU3Z35JNJFXHMBY3BX4THHN4AMWDFCUSFHYAHWTRHAC LFQQPAE4OQEWK4MRAB4VDAWRXRHWKSACGRL3XDGGXC3UMWPW2J3QC SCB7IK7YNYAR3WTV2XYGPYCGCUW2GPJSWAA5S2ZGCYSZ5AZKXIDAC EIFSYSBVD56R2PMNUZGNUQY5G4FT3XIJ2XYXVTWAUBSZ2UVHVA4QC IBNPMUOIC6ZWL5D2YK5HNBQ23FSLCKGDD4B7B77VDXHHW4E7FY3AC OMXTKMQWU63DLVBH4PPK3RN6NHLE4E77T7LM6VGHWQZBXLMG5GVQC AHAA7UNL2RRXP7KERGVCC42GB564WS5BYSSFEOXW3EKVMB2UVEOAC Y6NY2LPV67XLNG26FLTYIMRLNVBJN3HVHXF6NIOB4Q6X43LE3NMAC 5PACT4YBGSPNI7A3TV3YUZBSN2JVIM35EMQLDL5RW37TCIUB44AAC E5WANV7PYNPZFSJE4AMZABQ76TUNKVZN3M6ZFD5V6CKAFKG444NAC QGSSRFA57VPLML5JN23BSJRXSYKTIALASQ3NFCVDWAXVZGP7JUKAC TGXVRQU53E3KY7XVAXCIZIKSJMHQXMK4QKWZQ7UJHRFEFGZEAAKQC MZ64CKVYIZTIHIYNKUG53NRPNDSLGFVFVLMOJWU4ZMEFT2ADPTYQC 4SBHPC3PZB66O3Y6UU2IZDF5Y5XJCD7WV4QTOVAGUSY5FT3VPZGQC TZYEERAM2COSECETY2NQLKWXZIPZMWZVLZPT4XZNITCWBQKCEJEQC UQUY3KEQEXOWI4OAOEDE7CHP5H5QJL2E3L2GMYCCOOBC6NQVDJLAC E73EOPIVJFQOMGPM26MNOMYPMR23CE43MZTVKUZONZF4IT4YU46AC NC7UU7NY53VETOZLU2DEE34J2DA2SLQXD6LZM3XIHT6SKB6EPKKQC 75A6YI434T2KGSS4PFIWDAS55PA2CVKGVDWVDZIIHJ3CF5JSI4RAC WNVGS7JEPUDFU3HSFYYSFEFK6UUJIQCON576YTLXNUCTGWFDNPHAC ANMDKHKOF4TK66SMHVWBKCWKVJZFVPHT3GBNE365AJQD7GW3BW7AC DQKCPBYIW34BPR3BNMCBID7HL3MHGKR47LGBJ6B7VSIKMCOV3EFAC 6W4BUQ5XYSICW6QBKPYFQQ73RSZUHHIFMYVX5OPCZCGNPCGWGRKQC { lib, self, ... }:let# Global theme configuration - use `tt dark`/`tt light` to switch light/dark.# Use `tt pywal`/`tt gruvbox` to switch color scheme.# Both are controlled via theme.json file that gets updated by theme commands.themeConfig = builtins.fromJSON (builtins.readFile ./theme.json);is_dark = themeConfig.mode == "dark";color_scheme = themeConfig.scheme;# Pywal colors cache - single file updated when theme changes.# Stored in flake directory so Nix can access it (Nix can't read ~/.cache).pywal_cache = ./pywal-colors.json;# Parse pywal colors and convert to base16 format.# Pywal provides: background, foreground, color0-15.parse_pywal_colors = json: letcolors = builtins.fromJSON json;strip_hash = s: builtins.substring 1 6 s;in {base00 = strip_hash colors.colors.color0; # Background 0.base01 = strip_hash colors.colors.color1; # Background 1.base02 = strip_hash colors.colors.color2; # Background 2.base03 = strip_hash colors.colors.color3; # Background 3.base04 = strip_hash colors.colors.color4; # Foreground 3.base05 = strip_hash colors.colors.color5; # Foreground 2.base06 = strip_hash colors.colors.color6; # Foreground 1.base07 = strip_hash colors.colors.color7; # Foreground 0.base08 = strip_hash colors.colors.color8; # Main colour 1.base09 = strip_hash colors.colors.color9; # Main colour 2.base0A = strip_hash colors.colors.color10; # Main colour 3.base0B = strip_hash colors.colors.color11; # Main colour 4.base0C = strip_hash colors.colors.color12; # Main colour 5.base0D = strip_hash colors.colors.color13; # Main colour 6.base0E = strip_hash colors.colors.color14; # Main colour 7.base0F = strip_hash colors.colors.color15; # Main colour 8.};# Read pywal colors if cache exists and scheme is pywal.pywal_colors_raw = if builtins.pathExists pywal_cachethen builtins.readFile pywal_cacheelse null;pywal_colors = if pywal_colors_raw != nullthen parse_pywal_colors pywal_colors_rawelse gruvbox_colors.dark; # Fallback to gruvbox dark.# Gruvbox hard Base16 color definitions.gruvbox_colors = {dark = {base00 = "1d2021"; # Background.base01 = "3c3836"; # Background 1.base02 = "504945"; # Background 2.base03 = "665c54"; # Background 3.base04 = "bdae93"; # Foreground 2.base05 = "d5c4a1"; # Foreground 1.base06 = "ebdbb2"; # Foreground 0.base07 = "fbf1c7"; # Foreground.base08 = "fb4934"; # Red.base09 = "fe8019"; # Orange.base0A = "fabd2f"; # Yellow.base0B = "b8bb26"; # Green.base0C = "8ec07c"; # Aqua.base0D = "83a598"; # Blue.base0E = "d3869b"; # Purple.base0F = "d65d0e"; # Brown.};light = {base00 = "f9f5d7"; # Background.base01 = "ebdbb2"; # Background 1.base02 = "d5c4a1"; # Background 2.base03 = "bdae93"; # Background 3.base04 = "665c54"; # Foreground 2.base05 = "504945"; # Foreground 1.base06 = "3c3836"; # Foreground 0.base07 = "282828"; # Foreground.base08 = "9d0006"; # Red.base09 = "af3a03"; # Orange.base0A = "b57614"; # Yellow.base0B = "79740e"; # Green.base0C = "427b58"; # Aqua.base0D = "076678"; # Blue.base0E = "8f3f71"; # Purple.base0F = "d65d0e"; # Brown.};};# Current color scheme (two-dimensional: scheme + light/dark).# NOTE: pywal colors are pre-generated in the correct mode (light/dark) by tt command.colors = if color_scheme == "pywal"then pywal_colorselse (if is_dark then gruvbox_colors.dark else gruvbox_colors.light);variant = if is_dark then "dark" else "light";# Convert hex to RGB array helper for colors.hexToRgb = hex: letr = lib.fromHexString (builtins.substring 0 2 hex);g = lib.fromHexString (builtins.substring 2 2 hex);b = lib.fromHexString (builtins.substring 4 2 hex);in [ r g b ];in{config.theme = {inherit# Core theme state.is_darkcolor_schemevariant# Base16 color scheme.colors;# Color helpers with prefixes.withHash = lib.mapAttrs (name: value: "#${value}") colors;with0x = lib.mapAttrs (name: value: "0x${value}") colors;withRgb = lib.mapAttrs (name: value: hexToRgb value) colors;};}
{ lib, config, ... }:letinherit (lib) mkOption types mkIf;in{imports = lib.collectNix ./.|> lib.remove ./default.nix;# Define theme as a top-level option that all modules can access via config.theme.options.theme = mkOption {type = types.attrs;default = {};description = "Global theme configuration";};options.useTheme = lib.mkEnableOption "use theming";config = mkIf config.useTheme {theme = {# Shared design system.radius = {small = 2;normal = 4;big = 6;verybig = 8;};border = {small = 2;normal = 4;big = 6;};margin = {small = 4;normal = 8;big = 32;};padding = {small = 4;normal = 8;};opacity = {opaque = 1.00;veryhigh = 0.99;high = 0.97;medium = 0.94;low = 0.90;verylow = 0.80;};duration = {s = {short = 0.5;normal = 1.0;long = 1.5;};ms = {short = 150;normal = 200;long = 300;};};};# home-manager.sharedModules = mkIf config.isDesktopNotWsl [# (homeArgs: {# programs.pywal = enabled;# xdg.desktopEntries.dark-mode = {# name = "Dark Mode";# icon = "preferences-color-symbolic";# exec = ''tt dark'';# terminal = false;# };# xdg.desktopEntries.light-mode = {# name = "Light Mode";# icon = "preferences-color-symbolic";# exec = ''tt light'';# terminal = false;# };# xdg.desktopEntries.pywal-mode = {# name = "Pywal Mode";# icon = "preferences-color-symbolic";# exec = ''tt pywal'';# terminal = false;# };# xdg.desktopEntries.gruvbox-mode = {# name = "Gruvbox Mode";# icon = "preferences-color-symbolic";# exec = ''tt gruvbox'';# terminal = false;# };# })# ];};}
{ config, pkgs, lib, ... }: letinherit (lib) mkIf;in{config.theme.font = {size.small = 12;size.term = 12;size.normal = 16;size.big = 20;# mono.name = "JetBrainsMono Nerd Font Mono";# mono.family = "JetBrainsMono Nerd Font";# mono.package = pkgs.nerd-fonts.jetbrains-mono;# mono.name = "Fira Code Nerd Font Mono";# mono.family = "Fira Code Nerd Font";# mono.package = pkgs.nerd-fonts.fira-code;# mono.name = "Hasklug Nerd Font Mono";# mono.family = "Hasklug Nerd Font";# mono.package = pkgs.nerd-fonts.hasklug;# mono.name = "Iosevka Nerd Font Mono";# mono.family = "Iosevka Nerd Font";# mono.package = pkgs.nerd-fonts.iosevka;# mono.name = "Cascadia Code NF";# mono.family = "Cascadia Code";# mono.package = pkgs.cascadia-code;# mono.name = "Hack Nerd Font Mono";# mono.family = "Hack Nerd Font";# mono.package = pkgs.nerd-fonts.hack;mono.name = "Maple Mono NF";mono.family = "Maple Mono";mono.package = pkgs.maple-mono.NF;sans.name = "Lexend";sans.family = "Lexend";sans.package = pkgs.lexend;};# Virtual console for login.config.console = {earlySetup = true;font = "Lat2-Terminus16";packages = [ pkgs.terminus_font ];};config.fonts.fontconfig.enable = mkIf (!config.useTheme) true;config.fonts.packages = mkIf config.useTheme [config.theme.font.mono.packageconfig.theme.font.sans.package# Fallback for emojis and icons.pkgs.noto-fontspkgs.noto-fonts-cjk-sanspkgs.noto-fonts-lgc-pluspkgs.noto-fonts-color-emoji];}
{"wallpaper": "/home/jam/wallpapers/044.jpg","alpha": "100","special": {"background": "#211b29","foreground": "#f2ece1","cursor": "#f2ece1"},"colors": {"color0": "#211b29","color1": "#c145ba","color2": "#d37d7d","color3": "#deb49c","color4": "#e7d2b8","color5": "#e1a5c5","color6": "#ebcec3","color7": "#f2ece1","color8": "#a9a59d","color9": "#c145ba","color10": "#d37d7d","color11": "#deb49c","color12": "#e7d2b8","color13": "#e1a5c5","color14": "#ebcec3","color15": "#f2ece1"}}
{ pkgs, config, ... }:letinherit (config.theme) is_dark;themes = {alacritty.dark = "gruvbox_material_hard_dark";alacritty.light = "gruvbox_material_hard_light";ghostty.dark = "Gruvbox Dark Hard";ghostty.light = "Gruvbox Light Hard";rio.dark = "gruvbox-dark-hard";rio.light = "gruvbox-light-hard";zellij.dark = "gruvbox-dark";zellij.light = "gruvbox-light";starship.dark = "dark_theme";starship.light = "light_theme";vivid.dark = "gruvbox-dark";vivid.light = "gruvbox-light";nushell.dark = "dark-theme";nushell.light = "light-theme";helix.dark = "gruvbox_dark_hard";helix.light = "gruvbox_light_hard";gtk.dark = {name = "Gruvbox-Dark";package = pkgs.gruvbox-gtk-theme;};gtk.light = {name = "Adwaita";package = pkgs.gnome-themes-extra;};qt.dark = {name = "adwaita-dark";platformTheme = "adwaita";};qt.light = {name = "adwaita";platformTheme = "adwaita";};icons.dark = {name = "Gruvbox-Plus-Dark";package = pkgs.gruvbox-plus-icons;};icons.light = {name = "Papirus-Light";package = pkgs.papirus-icon-theme;};};# Helper for the current theme.get_theme = program: if is_dark then themes.${program}.dark else themes.${program}.light;in{config.theme = {icons = get_theme "icons";# Program-specific theme names.alacritty = get_theme "alacritty";ghostty = get_theme "ghostty";rio = get_theme "rio";zellij = get_theme "zellij";starship = get_theme "starship";vivid = get_theme "vivid";nushell = get_theme "nushell";helix = get_theme "helix";gtk = get_theme "gtk";qt = get_theme "qt";# Expose raw theme definitions for flexibility.themes = themes;};}
{ pkgs, config, lib, ... }: letinherit (lib) mkIf;themeToggleScript = pkgs.writeScriptBin "tt" /* nu */ ''#!${pkgs.nushell}/bin/nudef print-notify [message: string, progress: int = -1] {print $"(ansi purple)[Theme Switcher](ansi rst) ($message)"let is_error = ($message | str downcase | str contains "error")let urgency = if $is_error { "critical" } else { "normal" }let timeout = 5000let args = if $progress >= 0 and $progress < 100 {["--hint" $"int:value:($progress)"]} else {[]}^${pkgs.libnotify}/bin/notify-send ...$args --urgency=($urgency) --expire-time=($timeout) "Theme Switcher" $"($message)"}def generate-pywal-colors [wallpaper: string, is_dark: bool] {# Clear pywal cache to force regeneration.^${pkgs.coreutils}/bin/rm -rf ~/.cache/wal# Build args: start with base args, then append mode-specific ones.let base_args = ["-n" "--backend" "wal" "-i" $wallpaper]let mode_args = if $is_dark {["--saturate" "0.5"]} else {["--saturate" "0.75" "-l"]}^${pkgs.pywal}/bin/wal ...($base_args | append $mode_args) err> /dev/null^${pkgs.coreutils}/bin/cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/modules/theme/pywal-colors.json"}def toggle-theme [theme?: string] {# Determine current theme and scheme from theme.json file.let theme_config = try {open $"($env.HOME)/nixos/modules/theme/theme.json"} catch {{ mode: "light", scheme: "pywal" }}let current_theme = $theme_config.modelet using_pywal = $theme_config.scheme == "pywal"# Use provided theme or error if not provided.let new_theme = if $theme != null {if $theme in ["light", "dark"] {$theme} else {print-notify $"Invalid theme: '($theme)'. Use 'light' or 'dark'."return}} else {print-notify "Theme argument required. Use 'light' or 'dark'."return}print-notify $"Switching to ($new_theme) theme."# If using pywal, regenerate colors from current wallpaper.if $using_pywal {print-notify "Regenerating pywal colors..." 20let wallpaper = try {^${pkgs.swww}/bin/swww query | lines | first | parse "{monitor}: image: {path}" | get path.0} catch {null}if $wallpaper != null and ($wallpaper | path exists) {try {generate-pywal-colors $wallpaper ($new_theme == "dark")print-notify $"Regenerated ($new_theme) mode pywal colors." 30} catch { |e|print-notify $"Warning: Failed to regenerate pywal colors: ($e.msg)" 30}} else {print-notify "Warning: Could not detect current wallpaper" 30}}# Update environment and persist to theme.json.print-notify "Updating theme configuration..." 40$env.THEME_MODE = $new_themelet theme_json = $"($env.HOME)/nixos/modules/theme/theme.json"{ mode: $new_theme, scheme: $theme_config.scheme } | to json | save $theme_json --forceprint-notify $"($new_theme | str capitalize) mode activated." 50# Rebuild configuration to apply themes.print-notify $"Rebuilding configuration to apply ($new_theme) theme." 75try {^rebuild --quiet} catch { |e|print-notify "Error: Rebuild failed, run manually in a terminal."exit 1}print-notify $"Switch to the ($new_theme) theme completed!" 100}def switch-scheme [scheme: string] {# Validate scheme.if $scheme not-in ["pywal", "gruvbox"] {print-notify $"Invalid scheme: '($scheme)'. Use 'pywal' or 'gruvbox'."return}print-notify $"Switching to ($scheme) color scheme."# Get current theme configuration from theme.json file.let theme_config = try {open $"($env.HOME)/nixos/modules/theme/theme.json"} catch {{ mode: "light", scheme: "pywal" }}# If switching to pywal, generate colors from current wallpaper.if $scheme == "pywal" {print-notify "Generating pywal colors from current wallpaper..." 25let is_dark = $theme_config.mode == "dark"let wallpaper = try {^${pkgs.swww}/bin/swww query | lines | first | parse "{monitor}: image: {path}" | get path.0} catch {null}if $wallpaper != null and ($wallpaper | path exists) {try {generate-pywal-colors $wallpaper $is_darkprint-notify "Generated pywal colors." 50} catch { |e|print-notify $"Warning: Failed to generate colors: ($e.msg)" 50}} else {print-notify "Warning: Could not detect current wallpaper" 50}}# Update environment and persist to theme.json.$env.THEME_SCHEME = $schemelet theme_json = $"($env.HOME)/nixos/modules/theme/theme.json"{ mode: $theme_config.mode, scheme: $scheme } | to json | save $theme_json --forceprint $"Updated THEME_SCHEME to ($scheme)"# Rebuild configuration to apply new scheme.print-notify $"Rebuilding configuration to apply ($scheme) scheme..." 75try {^rebuild --quiet} catch { |e|print-notify "Error: Rebuild failed, run manually in a terminal."exit 1}print-notify $"Switch to ($scheme) scheme completed!" 100}# Main tt command - handles both light/dark and scheme switching.def --wrapped main [arg?: string # Theme mode (dark/light) or color scheme (pywal/gruvbox)....rest: string # Arbitrary arguments.]: nothing -> nothing {if $arg == null {print "Usage: tt <dark|light|pywal|gruvbox>"return}match $arg {"dark" | "light" => { toggle-theme $arg }"pywal" | "gruvbox" => { switch-scheme $arg }_ => { print $"Invalid option: '($arg)'. Use: dark, light, pywal, or gruvbox." }}}'';in {environment.systemPackages = mkIf config.useTheme [themeToggleScript];}
{ config, lib, pkgs, ... }: letinherit (lib) mkIf;set-wallpaper = pkgs.writeTextFile {name = "set-wallpaper";text = ''#!/usr/bin/env nudef main [url_or_path: string] {if ($url_or_path | str starts-with "http") {let wallpaper_dir = $"($env.HOME)/wallpapers"mkdir $wallpaper_dir# Extract filename from URLlet filename = ($url_or_path | url parse | get path | path basename)let wallpaper_file = $"($wallpaper_dir)/($filename)"if ($wallpaper_file | path exists) {print $"Wallpaper already exists: ($wallpaper_file)"^${pkgs.swww}/bin/swww img $wallpaper_file o+e>| ignoreprint "Using existing wallpaper"return}print $"Downloading wallpaper to ($wallpaper_file)..."try {${pkgs.curl}/bin/curl -L -o $wallpaper_file $url_or_path^${pkgs.swww}/bin/swww img $wallpaper_file o+e>| ignoreprint $"Wallpaper downloaded and set: ($wallpaper_file)"} catch {print "Failed to download wallpaper"exit 1}} else {^${pkgs.swww}/bin/swww img $url_or_path o+e>| ignore}}'';executable = true;destination = "/bin/set-wallpaper";};pick-wallpaper = pkgs.writeTextFile {name = "pick-wallpaper";text = /* nu */ ''#!/usr/bin/env nudef main [] {let wallpaper_dir = $"($env.HOME)/wallpapers"mkdir $wallpaper_dirlet wallpapers = (ls $wallpaper_dir | where type == file | where name =~ '\.(jpg|png|jpeg|webp|gif)$')if ($wallpapers | is-empty) {print $"No wallpapers found in ($wallpaper_dir)"exit 1}# Use fzf with chafa for previewlet selected = ($wallpapers| get name| str join "\n"| ^${pkgs.fzf}/bin/fzf --preview $"${pkgs.chafa}/bin/chafa --size 40x20 {}" --preview-window=right:50% --prompt="Select wallpaper: ")if not ($selected | is-empty) {^${pkgs.swww}/bin/swww img $selected o+e>| ignoreprint $"Wallpaper set: (($selected | path basename))"# Regenerate pywal colors if using pywal schemelet theme_config = try {open $"($env.HOME)/nixos/modules/theme/theme.json"} catch {{ mode: "light", scheme: "pywal" }}let using_pywal = $theme_config.scheme == "pywal"if $using_pywal {print "Regenerating pywal colors..."let is_dark = $theme_config.mode == "dark"try {# Clear pywal cache to force regeneration^rm -rf ~/.cache/wal# Build args: start with base, then append mode-specific oneslet base_args = ["-n" "--backend" "wal" "-i" $selected]let mode_args = if $is_dark {["--saturate" "0.5"]} else {["--saturate" "0.75" "-l"]}^${pkgs.pywal}/bin/wal ...($base_args | append $mode_args) err> /dev/null^cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/modules/theme/pywal-colors.json"print "Colors regenerated!"try {^rebuild --quiet} catch { |e|print "Failed to rebuild."}print "Rebuilt system to apply colors."} catch { |e|print $"Warning: Failed to regenerate colors: ($e.msg)"}}}}'';executable = true;destination = "/bin/pick-wallpaper";};save-wallpaper = pkgs.writeTextFile {name = "save-wallpaper";text = ''#!/usr/bin/env nudef main [url: string] {if not ($url | str starts-with "http") {print "Error: Please provide a valid URL"exit 1}let wallpaper_dir = $"($env.HOME)/wallpapers"mkdir $wallpaper_dir# Extract filename from URLlet filename = ($url | url parse | get path | path basename)let wallpaper_file = $"($wallpaper_dir)/($filename)"if ($wallpaper_file | path exists) {print $"Wallpaper already exists: ($wallpaper_file)"return}print $"Downloading wallpaper to ($wallpaper_file)..."try {${pkgs.curl}/bin/curl -L -o $wallpaper_file $urlprint $"Wallpaper saved: ($wallpaper_file)"} catch {print "Failed to download wallpaper"exit 1}}'';executable = true;destination = "/bin/save-wallpaper";};in {environment.systemPackages = [pkgs.swwwpkgs.chafa # Terminal image viewer for previews.set-wallpaperpick-wallpapersave-wallpaper];# home-manager.sharedModules = [{# # Desktop entry for fuzzel.# xdg.desktopEntries.pick-wallpaper = {# name = "Pick Wallpaper";# icon = "preferences-desktop-wallpaper";# exec = "pick-wallpaper";# terminal = true;# };# }];}
{ lib, ... }:letinherit (lib)mkEnableOptionmkIfmkOptiontypes;# Shared theme configuration logicmkThemeConfig ={ lib, pkgs }:letthemeConfig = builtins.fromJSON (builtins.readFile ./theme.json);is_dark = themeConfig.mode == "dark";color_scheme = themeConfig.scheme;pywal_cache = ./theme-pywal-colors.json;parse_pywal_colors =json:letcolors = builtins.fromJSON json;strip_hash = s: builtins.substring 1 6 s;in{base00 = strip_hash colors.colors.color0;base01 = strip_hash colors.colors.color1;base02 = strip_hash colors.colors.color2;base03 = strip_hash colors.colors.color3;base04 = strip_hash colors.colors.color4;base05 = strip_hash colors.colors.color5;base06 = strip_hash colors.colors.color6;base07 = strip_hash colors.colors.color7;base08 = strip_hash colors.colors.color8;base09 = strip_hash colors.colors.color9;base0A = strip_hash colors.colors.color10;base0B = strip_hash colors.colors.color11;base0C = strip_hash colors.colors.color12;base0D = strip_hash colors.colors.color13;base0E = strip_hash colors.colors.color14;base0F = strip_hash colors.colors.color15;};pywal_colors_raw = if builtins.pathExists pywal_cache then builtins.readFile pywal_cache else null;pywal_colors =if pywal_colors_raw != null then parse_pywal_colors pywal_colors_raw else gruvbox_colors.dark;gruvbox_colors = {dark = {base00 = "1d2021";base01 = "3c3836";base02 = "504945";base03 = "665c54";base04 = "bdae93";base05 = "d5c4a1";base06 = "ebdbb2";base07 = "fbf1c7";base08 = "fb4934";base09 = "fe8019";base0A = "fabd2f";base0B = "b8bb26";base0C = "8ec07c";base0D = "83a598";base0E = "d3869b";base0F = "d65d0e";};light = {base00 = "f9f5d7";base01 = "ebdbb2";base02 = "d5c4a1";base03 = "bdae93";base04 = "665c54";base05 = "504945";base06 = "3c3836";base07 = "282828";base08 = "9d0006";base09 = "af3a03";base0A = "b57614";base0B = "79740e";base0C = "427b58";base0D = "076678";base0E = "8f3f71";base0F = "d65d0e";};};colors =if color_scheme == "pywal" thenpywal_colorselse(if is_dark then gruvbox_colors.dark else gruvbox_colors.light);variant = if is_dark then "dark" else "light";hexToRgb =hex:letr = lib.fromHexString (builtins.substring 0 2 hex);g = lib.fromHexString (builtins.substring 2 2 hex);b = lib.fromHexString (builtins.substring 4 2 hex);in[rgb];design_system = {radius = {small = 2;normal = 4;big = 6;verybig = 8;};border = {small = 2;normal = 4;big = 6;};margin = {small = 4;normal = 8;big = 32;};padding = {small = 4;normal = 8;};opacity = {opaque = 1.00;veryhigh = 0.99;high = 0.97;medium = 0.94;low = 0.90;verylow = 0.80;};duration = {s = {short = 0.5;normal = 1.0;long = 1.5;};ms = {short = 150;normal = 200;long = 300;};};};themes = {alacritty.dark = "gruvbox_material_hard_dark";alacritty.light = "gruvbox_material_hard_light";ghostty.dark = "Gruvbox Dark Hard";ghostty.light = "Gruvbox Light Hard";rio.dark = "gruvbox-dark-hard";rio.light = "gruvbox-light-hard";zellij.dark = "gruvbox-dark";zellij.light = "gruvbox-light";starship.dark = "dark_theme";starship.light = "light_theme";vivid.dark = "gruvbox-dark";vivid.light = "gruvbox-light";nushell.dark = "dark-theme";nushell.light = "light-theme";helix.dark = "gruvbox_dark_hard";helix.light = "gruvbox_light_hard";gtk.dark = {name = "Gruvbox-Dark";package = pkgs.gruvbox-gtk-theme;};gtk.light = {name = "Adwaita";package = pkgs.gnome-themes-extra;};qt.dark = {name = "adwaita-dark";platformTheme = "adwaita";};qt.light = {name = "adwaita";platformTheme = "adwaita";};icons.dark = {name = "Gruvbox-Plus-Dark";package = pkgs.gruvbox-plus-icons;};icons.light = {name = "Papirus-Light";package = pkgs.papirus-icon-theme;};};get_theme = program: if is_dark then themes.${program}.dark else themes.${program}.light;in{inheritis_darkcolor_schemevariantcolorsdesign_systemthemesget_themehexToRgb;};in{config.flake.theme ={ config, pkgs, ... }:lettheme = mkThemeConfig { inherit lib pkgs; };in{options.theme = mkOption {type = types.attrs;default = { };description = "Global theme configuration";};options.useTheme = mkEnableOption "use theming";config = mkIf config.useTheme {theme = theme.design_system // {is_dark = theme.is_dark;color_scheme = theme.color_scheme;variant = theme.variant;colors = theme.colors;withHash = lib.mapAttrs (name: value: "#${value}") theme.colors;with0x = lib.mapAttrs (name: value: "0x${value}") theme.colors;withRgb = lib.mapAttrs (name: value: theme.hexToRgb value) theme.colors;icons = theme.get_theme "icons";alacritty = theme.get_theme "alacritty";ghostty = theme.get_theme "ghostty";rio = theme.get_theme "rio";zellij = theme.get_theme "zellij";starship = theme.get_theme "starship";vivid = theme.get_theme "vivid";nushell = theme.get_theme "nushell";helix = theme.get_theme "helix";gtk = theme.get_theme "gtk";qt = theme.get_theme "qt";themes = theme.themes;font = {size.small = 12;size.term = 12;size.normal = 16;size.big = 20;# mono.name = "JetBrainsMono Nerd Font Mono";# mono.family = "JetBrainsMono Nerd Font";# mono.package = pkgs.nerd-fonts.jetbrains-mono;# mono.name = "Fira Code Nerd Font Mono";# mono.family = "Fira Code Nerd Font";# mono.package = pkgs.nerd-fonts.fira-code;# mono.name = "Hasklug Nerd Font Mono";# mono.family = "Hasklug Nerd Font";# mono.package = pkgs.nerd-fonts.hasklug;# mono.name = "Iosevka Nerd Font Mono";# mono.family = "Iosevka Nerd Font";# mono.package = pkgs.nerd-fonts.iosevka;# mono.name = "Cascadia Code NF";# mono.family = "Cascadia Code";# mono.package = pkgs.cascadia-code;# mono.name = "Hack Nerd Font Mono";# mono.family = "Hack Nerd Font";# mono.package = pkgs.nerd-fonts.hack;mono.name = "Maple Mono NF";mono.family = "Maple Mono";mono.package = pkgs.maple-mono.NF;sans.name = "Lexend";sans.family = "Lexend";sans.package = pkgs.lexend;};};};};config.flake.modules.nixos.theme ={ config, pkgs, ... }:lettheme = mkThemeConfig { inherit lib pkgs; };in{options.theme = mkOption {type = types.attrs;default = { };description = "Global theme configuration";};options.useTheme = mkEnableOption "use theming";config = mkIf config.useTheme {theme = theme.design_system // {is_dark = theme.is_dark;color_scheme = theme.color_scheme;variant = theme.variant;colors = theme.colors;withHash = lib.mapAttrs (name: value: "#${value}") theme.colors;with0x = lib.mapAttrs (name: value: "0x${value}") theme.colors;withRgb = lib.mapAttrs (name: value: theme.hexToRgb value) theme.colors;icons = theme.get_theme "icons";alacritty = theme.get_theme "alacritty";ghostty = theme.get_theme "ghostty";rio = theme.get_theme "rio";zellij = theme.get_theme "zellij";starship = theme.get_theme "starship";vivid = theme.get_theme "vivid";nushell = theme.get_theme "nushell";helix = theme.get_theme "helix";gtk = theme.get_theme "gtk";qt = theme.get_theme "qt";themes = theme.themes;font = {size.small = 12;size.term = 12;size.normal = 16;size.big = 20;mono.name = "Maple Mono NF";mono.family = "Maple Mono";mono.package = pkgs.maple-mono.NF;sans.name = "Lexend";sans.family = "Lexend";sans.package = pkgs.lexend;};};};};config.flake.modules.nixos.theme-fonts ={ config, pkgs, ... }:{console = {earlySetup = true;font = "Lat2-Terminus16";packages = [ pkgs.terminus_font ];};fonts.fontconfig.enable = true;fonts.packages = [config.theme.font.mono.packageconfig.theme.font.sans.packagepkgs.noto-fontspkgs.noto-fonts-cjk-sanspkgs.noto-fonts-lgc-pluspkgs.noto-fonts-color-emoji];};config.flake.modules.nixos.theme-scripts ={ pkgs, ... }:letpickWallpaper = pkgs.writeTextFile {name = "pick-wallpaper";text = /* nu */ ''#!/usr/bin/env nudef main [] {let wallpaper_dir = $"($env.HOME)/wallpapers"mkdir $wallpaper_dirlet wallpapers = (ls $wallpaper_dir | where type == file | where name =~ '\.(jpg|png|jpeg|webp|gif)$')if ($wallpapers | is-empty) {print $"No wallpapers found in ($wallpaper_dir)"exit 1}let selected = ($wallpapers| get name| str join "\n"| ^${pkgs.fzf}/bin/fzf --preview $"${pkgs.chafa}/bin/chafa --size 40x20 {}" --preview-window=right:50% --prompt="Select wallpaper: ")if not ($selected | is-empty) {^${pkgs.swww}/bin/swww img $selected o+e>| ignoreprint $"Wallpaper set: (($selected | path basename))"let theme_config = try {open $"($env.HOME)/nixos/modules/theme.json"} catch {{ mode: "light", scheme: "pywal" }}let using_pywal = $theme_config.scheme == "pywal"if $using_pywal {print "Regenerating pywal colors..."let is_dark = $theme_config.mode == "dark"try {^rm -rf ~/.cache/wallet base_args = ["-n" "--backend" "wal" "-i" $selected]let mode_args = if $is_dark {["--saturate" "0.5"]} else {["--saturate" "0.75" "-l"]}^${pkgs.pywal}/bin/wal ...($base_args | append $mode_args) err> /dev/null^cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/modules/theme-pywal-colors.json"print "Colors regenerated!"try {^rebuild --quiet} catch { |e|print "Failed to rebuild."}print "Rebuilt system to apply colors."} catch { |e|print $"Warning: Failed to regenerate colors: ($e.msg)"}}}}'';executable = true;destination = "/bin/pick-wallpaper";};# Theme toggle scriptthemeToggleScript = pkgs.writeScriptBin "tt" /* nu */ ''#!${pkgs.nushell}/bin/nudef print-notify [message: string, progress: int = -1] {print $"(ansi purple)[Theme Switcher](ansi rst) ($message)"let is_error = ($message | str downcase | str contains "error")let urgency = if $is_error { "critical" } else { "normal" }let timeout = 5000let args = if $progress >= 0 and $progress < 100 {["--hint" $"int:value:($progress)"]} else {[]}^${pkgs.libnotify}/bin/notify-send ...$args --urgency=($urgency) --expire-time=($timeout) "Theme Switcher" $"($message)"}def generate-pywal-colors [wallpaper: string, is_dark: bool] {^${pkgs.coreutils}/bin/rm -rf ~/.cache/wallet base_args = ["-n" "--backend" "wal" "-i" $wallpaper]let mode_args = if $is_dark {["--saturate" "0.5"]} else {["--saturate" "0.75" "-l"]}^${pkgs.pywal}/bin/wal ...($base_args | append $mode_args) err> /dev/null^${pkgs.coreutils}/bin/cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/modules/theme-pywal-colors.json"}def toggle-theme [theme?: string] {let theme_config = try {open $"($env.HOME)/nixos/modules/theme.json"} catch {{ mode: "light", scheme: "pywal" }}let current_theme = $theme_config.modelet using_pywal = $theme_config.scheme == "pywal"let new_theme = if $theme != null {if $theme in ["light", "dark"] {$theme} else {print-notify $"Invalid theme: '($theme)'. Use 'light' or 'dark'."return}} else {print-notify "Theme argument required. Use 'light' or 'dark'."return}print-notify $"Switching to ($new_theme) theme."if $using_pywal {print-notify "Regenerating pywal colors..." 20let wallpaper = try {^${pkgs.swww}/bin/swww query | lines | first | parse "{monitor}: image: {path}" | get path.0} catch {null}if $wallpaper != null and ($wallpaper | path exists) {try {generate-pywal-colors $wallpaper ($new_theme == "dark")print-notify $"Regenerated ($new_theme) mode pywal colors." 30} catch { |e|print-notify $"Warning: Failed to regenerate pywal colors: ($e.msg)" 30}} else {print-notify "Warning: Could not detect current wallpaper" 30}}print-notify "Updating theme configuration..." 40$env.THEME_MODE = $new_themelet theme_json = $"($env.HOME)/nixos/modules/theme.json"{ mode: $new_theme, scheme: $theme_config.scheme } | to json | save $theme_json --forceprint-notify $"($new_theme | str capitalize) mode activated." 50print-notify $"Rebuilding configuration to apply ($new_theme) theme." 75try {^rebuild --quiet} catch { |e|print-notify "Error: Rebuild failed, run manually in a terminal."exit 1}print-notify $"Switch to the ($new_theme) theme completed!" 100}def switch-scheme [scheme: string] {if $scheme not-in ["pywal", "gruvbox"] {print-notify $"Invalid scheme: '($scheme)'. Use 'pywal' or 'gruvbox'."return}print-notify $"Switching to ($scheme) color scheme."let theme_config = try {open $"($env.HOME)/nixos/modules/theme.json"} catch {{ mode: "light", scheme: "pywal" }}if $scheme == "pywal" {print-notify "Generating pywal colors from current wallpaper..." 25let is_dark = $theme_config.mode == "dark"let wallpaper = try {^${pkgs.swww}/bin/swww query | lines | first | parse "{monitor}: image: {path}" | get path.0} catch {null}if $wallpaper != null and ($wallpaper | path exists) {try {generate-pywal-colors $wallpaper $is_darkprint-notify "Generated pywal colors." 50} catch { |e|print-notify $"Warning: Failed to generate colors: ($e.msg)" 50}} else {print-notify "Warning: Could not detect current wallpaper" 50}}$env.THEME_SCHEME = $schemelet theme_json = $"($env.HOME)/nixos/modules/theme.json"{ mode: $theme_config.mode, scheme: $scheme } | to json | save $theme_json --forceprint $"Updated THEME_SCHEME to ($scheme)"print-notify $"Rebuilding configuration to apply ($scheme) scheme..." 75try {^rebuild --quiet} catch { |e|print-notify "Error: Rebuild failed, run manually in a terminal."exit 1}print-notify $"Switch to ($scheme) scheme completed!" 100}def --wrapped main [arg?: string...rest: string]: nothing -> nothing {if $arg == null {print "Usage: tt <dark|light|pywal|gruvbox>"return}match $arg {"dark" | "light" => { toggle-theme $arg }"pywal" | "gruvbox" => { switch-scheme $arg }_ => { print $"Invalid option: '($arg)'. Use: dark, light, pywal, or gruvbox." }}}'';in{environment.systemPackages = [themeToggleScriptpickWallpaper];};}
{"alpha": "100","special": {},"colors": {}}"color0": "#211b29","color1": "#c145ba","color2": "#d37d7d","color3": "#deb49c","color4": "#e7d2b8","color5": "#e1a5c5","color6": "#ebcec3","color7": "#f2ece1","color8": "#a9a59d","color9": "#c145ba","color10": "#d37d7d","color11": "#deb49c","color12": "#e7d2b8","color13": "#e1a5c5","color14": "#ebcec3","color15": "#f2ece1""background": "#211b29","foreground": "#f2ece1","cursor": "#f2ece1""wallpaper": "/home/jam/wallpapers/044.jpg",
let# Pywal colors cache - single file updated when theme changes.# Stored in flake directory so Nix can access it (Nix can't read ~/.cache).# Parse pywal colors and convert to base16 format.# Pywal provides: background, foreground, color0-15.parse_pywal_colors = json: letcolors = builtins.fromJSON json;strip_hash = s: builtins.substring 1 6 s;in {base00 = strip_hash colors.colors.color0; # Background 0.base01 = strip_hash colors.colors.color1; # Background 1.base02 = strip_hash colors.colors.color2; # Background 2.base03 = strip_hash colors.colors.color3; # Background 3.base04 = strip_hash colors.colors.color4; # Foreground 3.base05 = strip_hash colors.colors.color5; # Foreground 2.base06 = strip_hash colors.colors.color6; # Foreground 1.base07 = strip_hash colors.colors.color7; # Foreground 0.base08 = strip_hash colors.colors.color8; # Main colour 1.base09 = strip_hash colors.colors.color9; # Main colour 2.base0A = strip_hash colors.colors.color10; # Main colour 3.base0B = strip_hash colors.colors.color11; # Main colour 4.base0C = strip_hash colors.colors.color12; # Main colour 5.base0D = strip_hash colors.colors.color13; # Main colour 6.base0E = strip_hash colors.colors.color14; # Main colour 7.base0F = strip_hash colors.colors.color15; # Main colour 8.};pywal_cache = ./pywal-colors.json;# Global theme configuration - use `tt dark`/`tt light` to switch light/dark.# Use `tt pywal`/`tt gruvbox` to switch color scheme.# Both are controlled via theme.json file that gets updated by theme commands.themeConfig = builtins.fromJSON (builtins.readFile ./theme.json);is_dark = themeConfig.mode == "dark";color_scheme = themeConfig.scheme;variant = if is_dark then "dark" else "light";in{config.theme = {# Color helpers with prefixes.withHash = lib.mapAttrs (name: value: "#${value}") colors;with0x = lib.mapAttrs (name: value: "0x${value}") colors;withRgb = lib.mapAttrs (name: value: hexToRgb value) colors;};}inherit# Core theme state.is_darkcolor_schemevariant# Base16 color scheme.colors;# Convert hex to RGB array helper for colors.hexToRgb = hex: letr = lib.fromHexString (builtins.substring 0 2 hex);g = lib.fromHexString (builtins.substring 2 2 hex);b = lib.fromHexString (builtins.substring 4 2 hex);in [ r g b ];# Gruvbox hard Base16 color definitions.gruvbox_colors = {dark = {base00 = "1d2021"; # Background.base01 = "3c3836"; # Background 1.base02 = "504945"; # Background 2.base03 = "665c54"; # Background 3.base04 = "bdae93"; # Foreground 2.base05 = "d5c4a1"; # Foreground 1.base06 = "ebdbb2"; # Foreground 0.base07 = "fbf1c7"; # Foreground.base08 = "fb4934"; # Red.base09 = "fe8019"; # Orange.base0A = "fabd2f"; # Yellow.base0B = "b8bb26"; # Green.base0C = "8ec07c"; # Aqua.base0D = "83a598"; # Blue.base0E = "d3869b"; # Purple.base0F = "d65d0e"; # Brown.};light = {base00 = "f9f5d7"; # Background.base01 = "ebdbb2"; # Background 1.base02 = "d5c4a1"; # Background 2.base03 = "bdae93"; # Background 3.base04 = "665c54"; # Foreground 2.base05 = "504945"; # Foreground 1.base06 = "3c3836"; # Foreground 0.base07 = "282828"; # Foreground.base08 = "9d0006"; # Red.base09 = "af3a03"; # Orange.base0A = "b57614"; # Yellow.base0B = "79740e"; # Green.base0C = "427b58"; # Aqua.base0D = "076678"; # Blue.base0E = "8f3f71"; # Purple.base0F = "d65d0e"; # Brown.};};colors = if color_scheme == "pywal"then pywal_colorselse (if is_dark then gruvbox_colors.dark else gruvbox_colors.light);# Current color scheme (two-dimensional: scheme + light/dark).# NOTE: pywal colors are pre-generated in the correct mode (light/dark) by tt command.# Read pywal colors if cache exists and scheme is pywal.pywal_colors_raw = if builtins.pathExists pywal_cachethen builtins.readFile pywal_cacheelse null;pywal_colors = if pywal_colors_raw != nullthen parse_pywal_colors pywal_colors_rawelse gruvbox_colors.dark; # Fallback to gruvbox dark.{ lib, self, ... }:
{ lib, config, ... }:letinherit (lib) mkOption types mkIf enabled;in{imports = lib.collectNix ./.|> lib.remove ./default.nix;# Define theme as a top-level option that all modules can access via config.theme.options.theme = mkOption {type = types.attrs;default = {};description = "Global theme configuration";};# Shared design system.radius = {small = 2;normal = 4;big = 6;verybig = 8;};border = {small = 2;normal = 4;big = 6;};margin = {small = 4;normal = 8;big = 32;};padding = {small = 4;normal = 8;};opacity = {opaque = 1.00;high = 0.97;medium = 0.94;low = 0.90;verylow = 0.80;};duration = {s = {short = 0.5;normal = 1.0;long = 1.5;};ms = {short = 150;normal = 200;long = 300;};};}xdg.desktopEntries.dark-mode = {name = "Dark Mode";icon = "preferences-color-symbolic";exec = ''tt dark'';terminal = false;};xdg.desktopEntries.light-mode = {name = "Light Mode";icon = "preferences-color-symbolic";exec = ''tt light'';terminal = false;};xdg.desktopEntries.pywal-mode = {name = "Pywal Mode";icon = "preferences-color-symbolic";exec = ''tt pywal'';terminal = false;};xdg.desktopEntries.gruvbox-mode = {name = "Gruvbox Mode";icon = "preferences-color-symbolic";exec = ''tt gruvbox'';terminal = false;};})];};home-manager.sharedModules = mkIf config.isDesktopNotWsl [(homeArgs: {programs.pywal = enabled;};veryhigh = 0.99;config = mkIf config.isDesktop {theme = {
{];}config.theme.font.mono.packageconfig.theme.font.sans.package# Fallback for emojis and icons.pkgs.noto-fontspkgs.noto-fonts-cjk-sanspkgs.noto-fonts-lgc-pluspkgs.noto-fonts-color-emojiconfig.theme.font = {size.small = 12;size.normal = 16;size.big = 20;sans.name = "Lexend";sans.package = pkgs.lexend;};sans.family = "Lexend";# mono.name = "Iosevka Nerd Font Mono";# mono.family = "Iosevka Nerd Font";# mono.package = pkgs.nerd-fonts.iosevka;# mono.name = "Hack Nerd Font Mono";# mono.family = "Hack Nerd Font";# mono.package = pkgs.nerd-fonts.hack;mono.name = "Maple Mono NF";mono.family = "Maple Mono";mono.package = pkgs.maple-mono.NF;# mono.name = "Cascadia Code NF";# mono.family = "Cascadia Code";# mono.package = pkgs.cascadia-code;# mono.name = "Hasklug Nerd Font Mono";# mono.family = "Hasklug Nerd Font";# mono.package = pkgs.nerd-fonts.hasklug;# mono.name = "Fira Code Nerd Font Mono";# mono.family = "Fira Code Nerd Font";# mono.package = pkgs.nerd-fonts.fira-code;# mono.name = "JetBrainsMono Nerd Font Mono";# mono.family = "JetBrainsMono Nerd Font";# mono.package = pkgs.nerd-fonts.jetbrains-mono;size.term = 12;# Virtual console for login.earlySetup = true;font = "Lat2-Terminus16";packages = [ pkgs.terminus_font ];};config.fonts.packages = mkIf config.useTheme [config.fonts.fontconfig = mkIf (!config.useTheme) false;config.console = {{ config, pkgs, lib, ... }: letininherit (lib) mkIf disabled;
{ pkgs, config, ... }:letinherit (config.theme) is_dark;themes = {alacritty.dark = "gruvbox_material_hard_dark";alacritty.light = "gruvbox_material_hard_light";zellij.dark = "gruvbox-dark";zellij.light = "gruvbox-light";starship.dark = "dark_theme";starship.light = "light_theme";vivid.dark = "gruvbox-dark";vivid.light = "gruvbox-light";nushell.dark = "dark-theme";nushell.light = "light-theme";helix.dark = "gruvbox_dark_hard";helix.light = "gruvbox_light_hard";gtk.dark = {};gtk.light = {};qt.dark = {name = "adwaita-dark";platformTheme = "adwaita";};qt.light = {name = "adwaita";platformTheme = "adwaita";};icons.dark = {name = "Gruvbox-Plus-Dark";package = pkgs.gruvbox-plus-icons;};icons.light = {name = "Papirus-Light";package = pkgs.papirus-icon-theme;};};get_theme = program: if is_dark then themes.${program}.dark else themes.${program}.light;in{config.theme = {icons = get_theme "icons";# Program-specific theme names.alacritty = get_theme "alacritty";zellij = get_theme "zellij";starship = get_theme "starship";vivid = get_theme "vivid";nushell = get_theme "nushell";helix = get_theme "helix";gtk = get_theme "gtk";qt = get_theme "qt";# Expose raw theme definitions for flexibility.themes = themes;};}ghostty = get_theme "ghostty";rio = get_theme "rio";# Helper for the current theme.name = "Adwaita";package = pkgs.gnome-themes-extra;name = "Gruvbox-Dark";package = pkgs.gruvbox-gtk-theme;ghostty.dark = "Gruvbox Dark Hard";ghostty.light = "Gruvbox Light Hard";rio.dark = "gruvbox-dark-hard";rio.light = "gruvbox-light-hard";
{ pkgs, config, lib, ... }: letinherit (lib) mkIf;themeToggleScript = pkgs.writeScriptBin "tt" /* nu */ ''#!${pkgs.nushell}/bin/nudef print-notify [message: string, progress: int = -1] {let is_error = ($message | str downcase | str contains "error")let args = if $progress >= 0 and $progress < 100 {["--hint" $"int:value:($progress)"]} else {[]}^${pkgs.libnotify}/bin/notify-send ...$args --urgency=($urgency) --expire-time=($timeout) "Theme Switcher" $"($message)"}def generate-pywal-colors [wallpaper: string, is_dark: bool] {# Clear pywal cache to force regeneration.^${pkgs.coreutils}/bin/rm -rf ~/.cache/wal# Build args: start with base args, then append mode-specific ones.let base_args = ["-n" "--backend" "wal" "-i" $wallpaper]let mode_args = if $is_dark {["--saturate" "0.5"]} else {["--saturate" "0.75" "-l"]}^${pkgs.pywal}/bin/wal ...($base_args | append $mode_args) err> /dev/null^${pkgs.coreutils}/bin/cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/modules/common/theme/pywal-colors.json"}def toggle-theme [theme?: string] {} catch {}# Use provided theme or error if not provided.let new_theme = if $theme != null {if $theme in ["light", "dark"] {$theme} else {print-notify $"Invalid theme: '($theme)'. Use 'light' or 'dark'."return}} else {print-notify "Theme argument required. Use 'light' or 'dark'."return}print-notify $"Switching to ($new_theme) theme."# If using pywal, regenerate colors from current wallpaper.if $using_pywal {print-notify "Regenerating pywal colors..." 20let wallpaper = try {^${pkgs.swww}/bin/swww query | lines | first | parse "{monitor}: image: {path}" | get path.0} catch {null}if $wallpaper != null and ($wallpaper | path exists) {try {generate-pywal-colors $wallpaper ($new_theme == "dark")print-notify $"Regenerated ($new_theme) mode pywal colors." 30} catch { |e|print-notify $"Warning: Failed to regenerate pywal colors: ($e.msg)" 30}} else {print-notify "Warning: Could not detect current wallpaper" 30}}# Update environment and persist to theme.json.print-notify "Updating theme configuration..." 40$env.THEME_MODE = $new_themelet theme_json = $"($env.HOME)/nixos/modules/common/theme/theme.json"print-notify $"($new_theme | str capitalize) mode activated." 50# Rebuild configuration to apply themes.print-notify $"Rebuilding configuration to apply ($new_theme) theme." 75try {^rebuild --quiet} catch { |e|print-notify "Error: Rebuild failed, run manually in a terminal."exit 1}print-notify $"Switch to the ($new_theme) theme completed!" 100}def switch-scheme [scheme: string] {# Validate scheme.if $scheme not-in ["pywal", "gruvbox"] {print-notify $"Invalid scheme: '($scheme)'. Use 'pywal' or 'gruvbox'."return}print-notify $"Switching to ($scheme) color scheme."# If switching to pywal, generate colors from current wallpaper.if $scheme == "pywal" {print-notify "Generating pywal colors from current wallpaper..." 25let wallpaper = try {^${pkgs.swww}/bin/swww query | lines | first | parse "{monitor}: image: {path}" | get path.0} catch {null}if $wallpaper != null and ($wallpaper | path exists) {try {generate-pywal-colors $wallpaper $is_darkprint-notify "Generated pywal colors." 50} catch { |e|print-notify $"Warning: Failed to generate colors: ($e.msg)" 50}} else {print-notify "Warning: Could not detect current wallpaper" 50}}# Update environment and persist to theme.json.$env.THEME_SCHEME = $schemelet theme_json = $"($env.HOME)/nixos/modules/common/theme/theme.json"print $"Updated THEME_SCHEME to ($scheme)"# Rebuild configuration to apply new scheme.print-notify $"Rebuilding configuration to apply ($scheme) scheme..." 75try {^rebuild --quiet} catch { |e|print-notify "Error: Rebuild failed, run manually in a terminal."exit 1}print-notify $"Switch to ($scheme) scheme completed!" 100}# Main tt command - handles both light/dark and scheme switching.def --wrapped main [arg?: string # Theme mode (dark/light) or color scheme (pywal/gruvbox)....rest: string # Arbitrary arguments.]: nothing -> nothing {if $arg == null {print "Usage: tt <dark|light|pywal|gruvbox>"return}match $arg {"dark" | "light" => { toggle-theme $arg }"pywal" | "gruvbox" => { switch-scheme $arg }_ => { print $"Invalid option: '($arg)'. Use: dark, light, pywal, or gruvbox." }}}'';in {environment.systemPackages = mkIf config.isDesktop [themeToggleScript];}{ mode: $theme_config.mode, scheme: $scheme } | to json | save $theme_json --forcelet is_dark = $theme_config.mode == "dark"# Get current theme configuration from theme.json file.let theme_config = try {open $"($env.HOME)/nixos/modules/common/theme/theme.json"} catch {{ mode: "light", scheme: "pywal" }}{ mode: $new_theme, scheme: $theme_config.scheme } | to json | save $theme_json --forcelet current_theme = $theme_config.modelet using_pywal = $theme_config.scheme == "pywal"{ mode: "light", scheme: "pywal" }# Determine current theme and scheme from theme.json file.let theme_config = try {open $"($env.HOME)/nixos/modules/common/theme/theme.json"let timeout = 5000let urgency = if $is_error { "critical" } else { "normal" }print $"(ansi purple)[Theme Switcher](ansi rst) ($message)"
{ config, lib, pkgs, ... }: letinherit (lib) mkIf;set-wallpaper = pkgs.writeTextFile {name = "set-wallpaper";text = ''#!/usr/bin/env nudef main [url_or_path: string] {if ($url_or_path | str starts-with "http") {let wallpaper_dir = $"($env.HOME)/wallpapers"mkdir $wallpaper_dir# Extract filename from URLlet filename = ($url_or_path | url parse | get path | path basename)let wallpaper_file = $"($wallpaper_dir)/($filename)"if ($wallpaper_file | path exists) {print $"Wallpaper already exists: ($wallpaper_file)"^${pkgs.swww}/bin/swww img $wallpaper_file o+e>| ignoreprint "Using existing wallpaper"return}print $"Downloading wallpaper to ($wallpaper_file)..."try {${pkgs.curl}/bin/curl -L -o $wallpaper_file $url_or_path^${pkgs.swww}/bin/swww img $wallpaper_file o+e>| ignoreprint $"Wallpaper downloaded and set: ($wallpaper_file)"} catch {print "Failed to download wallpaper"exit 1}} else {^${pkgs.swww}/bin/swww img $url_or_path o+e>| ignore}}'';executable = true;destination = "/bin/set-wallpaper";};pick-wallpaper = pkgs.writeTextFile {name = "pick-wallpaper";#!/usr/bin/env nudef main [] {let wallpaper_dir = $"($env.HOME)/wallpapers"mkdir $wallpaper_dirif ($wallpapers | is-empty) {print $"No wallpapers found in ($wallpaper_dir)"exit 1}# Use fzf with chafa for previewlet selected = ($wallpapers| get name| str join "\n"| ^${pkgs.fzf}/bin/fzf --preview $"${pkgs.chafa}/bin/chafa --size 40x20 {}" --preview-window=right:50% --prompt="Select wallpaper: ")if not ($selected | is-empty) {^${pkgs.swww}/bin/swww img $selected o+e>| ignoreprint $"Wallpaper set: (($selected | path basename))"}}'';executable = true;destination = "/bin/pick-wallpaper";};save-wallpaper = pkgs.writeTextFile {name = "save-wallpaper";text = ''#!/usr/bin/env nudef main [url: string] {if not ($url | str starts-with "http") {print "Error: Please provide a valid URL"exit 1}let wallpaper_dir = $"($env.HOME)/wallpapers"mkdir $wallpaper_dir# Extract filename from URLlet filename = ($url | url parse | get path | path basename)let wallpaper_file = $"($wallpaper_dir)/($filename)"if ($wallpaper_file | path exists) {print $"Wallpaper already exists: ($wallpaper_file)"return}print $"Downloading wallpaper to ($wallpaper_file)..."try {${pkgs.curl}/bin/curl -L -o $wallpaper_file $urlprint $"Wallpaper saved: ($wallpaper_file)"} catch {print "Failed to download wallpaper"exit 1}}'';executable = true;destination = "/bin/save-wallpaper";};environment.systemPackages = [pkgs.swwwpkgs.chafa # Terminal image viewer for previews.set-wallpaperpick-wallpapersave-wallpaper];home-manager.sharedModules = [{# Desktop entry for fuzzel.xdg.desktopEntries.pick-wallpaper = {name = "Pick Wallpaper";exec = "pick-wallpaper";terminal = true;};}];}icon = "preferences-desktop-wallpaper";in mkIf (config.isDesktopNotWsl && !config.isDarwin) {# Regenerate pywal colors if using pywal scheme} catch {}if $using_pywal {print "Regenerating pywal colors..."try {# Clear pywal cache to force regeneration^rm -rf ~/.cache/wal} else {}print "Colors regenerated!"try {} catch { |e|print "Failed to rebuild."}print "Rebuilt system to apply colors."} catch { |e|print $"Warning: Failed to regenerate colors: ($e.msg)"}}^rebuild --quiet^${pkgs.pywal}/bin/wal ...($base_args | append $mode_args) err> /dev/null^cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/modules/common/theme/pywal-colors.json"["--saturate" "0.75" "-l"]# Build args: start with base, then append mode-specific oneslet base_args = ["-n" "--backend" "wal" "-i" $selected]let mode_args = if $is_dark {["--saturate" "0.5"]let is_dark = $theme_config.mode == "dark"let using_pywal = $theme_config.scheme == "pywal"{ mode: "light", scheme: "pywal" }let theme_config = try {open $"($env.HOME)/nixos/modules/common/theme/theme.json"let wallpapers = (ls $wallpaper_dir | where type == file | where name =~ '\.(jpg|png|jpeg|webp|gif)$')text = /* nu */ ''