Now we can avoid editing the Nix files directly.
A3KCM3RK3HEFXYM7PQMMFKNYZLPFGOP7GZHWKRDP37O67TIFV7MAC 6W4BUQ5XYSICW6QBKPYFQQ73RSZUHHIFMYVX5OPCZCGNPCGWGRKQC FCKKA6DLJ6SY6IVTPRCCAXDLDF5VV24MVJBNLFHT66OTUSBW57HQC RVV2KSCIXFU4HLXELZV67QDRTKG7OU6FZHFMPYO6HN6ISXPALX6QC 5G7WRBMWKG6DMCOHE6WQHTYZACUHO2UPBZRWN72CFH7P45NN5E7QC GB7WCUSTYHJ642OLGSZXO34V3KDZ23WQGTMNYMJ6FMOHDGDR445QC QGSSRFA57VPLML5JN23BSJRXSYKTIALASQ3NFCVDWAXVZGP7JUKAC Y6NY2LPV67XLNG26FLTYIMRLNVBJN3HVHXF6NIOB4Q6X43LE3NMAC IC3YV3RSECMIT3CCPYLUKLGWHHDST2LMVELG6TBQNJCRF524F6UQC DQKCPBYIW34BPR3BNMCBID7HL3MHGKR47LGBJ6B7VSIKMCOV3EFAC ANMDKHKOF4TK66SMHVWBKCWKVJZFVPHT3GBNE365AJQD7GW3BW7AC YGLYNBOJLMAK6AA6FK6EVSV6HXZNLSRG3XR2NHGEZTCUXWQWU57AC I24IYFMOTYSSI3LAZ2PWUZP5LU4ULNPBHZDVIFDMI4WTIBGLEZJQC POWANTMVHF4TOAJ2QBEEOW7BZ3MT7IBUEOXJQXPC33LZKNKPBTQQC ZXGWQ2XANWUMPZ3WKAG37WCK5PGJFEFGOGPOO4J6Q74I5VWNBNHAC I4HWXLAAKJHZUQFV23XBAAEW6VTCOXO2F3AB5R6RTOHHZBZLK2VAC RXJH46XLP6AFSIZZD4WEQA5A5YZLKHZ54ZUGCRH7TRAOH2Z4Q2VQC HBSVMHMQ2CQFZXFPIECPGNT7DB46YEHSE72BB4XGHKPS4IPNKYLQC 3BWU34HZYLFGQ7SDXHIHMXWK5SPJ3DAUWTL4GR3YX4V5IFKKQPJAC IBNPMUOIC6ZWL5D2YK5HNBQ23FSLCKGDD4B7B77VDXHHW4E7FY3AC HQ4UTQK5UQXMEUZRYQSCTEKW73UZLJQHPA4YD75FCKBKHS5NMJOAC DLMU5B7JBMU5J7HSRMPD7OYA7AY5OCT2ZWUKKF2X3SFHE6OTV6TAC OUPAW7IENNTRLHI5ET2ZV4235FCCHNPVRGRE5HGQNDHIK63IMK6QC EIFSYSBVD56R2PMNUZGNUQY5G4FT3XIJ2XYXVTWAUBSZ2UVHVA4QC QRPPLHLL6YZU3Z35JNJFXHMBY3BX4THHN4AMWDFCUSFHYAHWTRHAC WRZ7ISE3YY7EIZPX3YOQOGZ3R6O3E5XEFB7SV74JC3WBSWZZAEVQC SCB7IK7YNYAR3WTV2XYGPYCGCUW2GPJSWAA5S2ZGCYSZ5AZKXIDAC ONJIY3TNGUJSQ4HRL2AIMDKYWYFICVZAHRU2GVY7LZ7ULWPY7NWQC S3V46SMTWXBTBKVOKCWLZG6PDQ6ODP7NNT5SWFS6D6BBAPY65OOQC 3JRUTC653DE7TT2LWTTXOG4YT6ZGVBS67T4Y67D4HHVDMRSQ33KQC HNASWHQYV237BFQZ2H7LO76APITXXWFIPTH4N4HXQPLTSWENPBGQC {"alpha": "100","special": {},"colors": {}}"color0": "#fefdfd","color1": "#bc2f54","color2": "#d35951","color3": "#3e30c2","color4": "#d14985","color5": "#e79fb7","color6": "#e69caa","color7": "#1C1226","color8": "#979492","color9": "#bc2f54","color10": "#d35951","color11": "#3e30c2","color12": "#d14985","color13": "#e79fb7","color14": "#e69caa","color15": "#1C1226""background": "#fefdfd","foreground": "#1C1226","cursor": "#1C1226""wallpaper": "/home/jam/wallpapers/017.jpg",
let wal_args = if $is_dark {["-n" "--saturate" "0.5" "-i" $selected]
# 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"]
^${pkgs.pywal}/bin/wal --backend wal ...$wal_args err> /dev/null^cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/pywal-colors.json"
^${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"
{ 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] ($message)"# Progress notifications persist, completion/error notifications auto-dismiss.let is_complete = $progress == 100let is_error = ($message | str downcase | str contains "error")# Dismiss all previous notifications before showing completion.if $is_complete {^${pkgs.mako}/bin/makoctl dismiss --all}let timeout = if $is_error {30000} else if $is_complete {5000} else {0 # Persist until replaced.}let urgency = if $is_error { "critical" } else { "normal" }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] {# Determine current theme from environment variable.let current_theme = try {$env.THEME_MODE} catch {"light"}# Check if using pywal scheme.let using_pywal = try {$env.THEME_SCHEME == "pywal"} catch {false}# 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"{ mode: $new_theme, scheme: $env.THEME_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."# If switching to pywal, generate colors from current wallpaper.if $scheme == "pywal" {print-notify "Generating pywal colors from current wallpaper..." 25let is_dark = try {$env.THEME_MODE == "dark"} catch {false}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/common/theme/theme.json"{ mode: $env.THEME_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.isDesktop [themeToggleScript];}
let base_args = ["--replace-id=1002" "--print-id"]
# Progress notifications persist, completion/error notifications auto-dismiss.let is_complete = $progress == 100let is_error = ($message | str downcase | str contains "error")# Dismiss all previous notifications before showing completion.if $is_complete and (which makoctl | is-not-empty) {^makoctl dismiss --all}let timeout = if $is_error {30000} else if $is_complete {5000} else {0 # Persist until replaced.}
let timeout = if ($message | str downcase | str contains "error") { 30000 } else { 15000 }let urgency = if ($message | str downcase | str contains "error") { "critical" } else { "normal" }
^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.^rm -rf ~/.cache/wal
^notify-send ...$args --urgency=($urgency) --expire-time=($timeout) "Theme Switcher" $"($message)"
# 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"]
let dark_mode_file = $"($env.HOME)/.config/dark-mode"# Determine current theme from nix theme file.let theme_file = $"($env.HOME)/nixos/modules/common/theme.nix"
# Determine current theme from environment variable.
# Clear pywal cache to force regeneration from current wallpaper.^rm -rf ~/.cache/wallet wal_args = if $new_theme == "dark" {["-n" "--saturate" "0.5" "-i" $wallpaper]} else {["-n" "--saturate" "0.6" "-l" "-i" $wallpaper]}^wal --backend wal ...$wal_args err> /dev/null^cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/pywal-colors.json"
generate-pywal-colors $wallpaper ($new_theme == "dark")
# Update centralized theme file.try {let content = open $theme_file
# Update environment and persist to theme.json.print-notify "Updating theme configuration..." 40$env.THEME_MODE = $new_theme
let updated = if $new_theme == "dark" {$content | str replace "is_dark = false;" "is_dark = true;"} else {$content | str replace "is_dark = true;" "is_dark = false;"}$updated | save $theme_file --forceprint $"updated theme to ($new_theme)"} catch { |e|print-notify $"Failed to switch theme: ($e.msg)"return}
let theme_json = $"($env.HOME)/nixos/modules/common/theme/theme.json"{ mode: $new_theme, scheme: $env.THEME_SCHEME } | to json | save $theme_json --force
# Update system dark mode marker and environment variable.print-notify "Updating environment..." 40if $new_theme == "dark" {touch $dark_mode_file$env.THEME_MODE = "dark"print-notify "Dark mode activated." 50} else {if ($dark_mode_file | path exists) {rm $dark_mode_file}$env.THEME_MODE = "light"print-notify "Light mode activated." 50}
print-notify $"($new_theme | str capitalize) mode activated." 50
# Clear pywal cache to force regeneration from current wallpaper.^rm -rf ~/.cache/wallet wal_args = if $is_dark {["-n" "--saturate" "0.5" "-i" $wallpaper]} else {["-n" "--saturate" "0.6" "-l" "-i" $wallpaper]}^wal --backend wal ...$wal_args err> /dev/null^cp ~/.cache/wal/colors.json $"($env.HOME)/nixos/pywal-colors.json"
generate-pywal-colors $wallpaper $is_dark
let updated = if $scheme == "pywal" {$content | str replace 'color_scheme = "gruvbox";' 'color_scheme = "pywal";'} else {$content | str replace 'color_scheme = "pywal";' 'color_scheme = "gruvbox";'}
let theme_json = $"($env.HOME)/nixos/modules/common/theme/theme.json"{ mode: $env.THEME_MODE, scheme: $scheme } | to json | save $theme_json --force
{"mode": "dark","scheme": "pywal"}
{"wallpaper": "/home/jam/wallpapers/057.png","alpha": "100","special": {"background": "#1b2326","foreground": "#e3eaed","cursor": "#e3eaed"},"colors": {"color0": "#1b2326","color1": "#93b4db","color2": "#e0a2c1","color3": "#a4cfe0","color4": "#b4cfe6","color5": "#b7dae7","color6": "#c9d8ed","color7": "#e3eaed","color8": "#9ea3a5","color9": "#93b4db","color10": "#e0a2c1","color11": "#a4cfe0","color12": "#b4cfe6","color13": "#b7dae7","color14": "#c9d8ed","color15": "#e3eaed"}}
xdg.desktopEntries.dark-mode = {name = "Dark Mode";exec = ''nu -l -c "tt dark"'';terminal = false;};xdg.desktopEntries.light-mode = {name = "Light Mode";exec = ''nu -l -c "tt light"'';terminal = false;};})];
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;};})];};
is_dark = if builtins.pathExists (self + /modules/common/theme/dark-mode) then true else false;color_scheme = "pywal"; # "gruvbox" or "pywal"
# 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;
# Load theme state from theme.jsonlet theme_json = $"($env.HOME)/nixos/modules/common/theme/theme.json"if ($theme_json | path exists) {let theme = (open $theme_json)$env.THEME_MODE = $theme.mode$env.THEME_SCHEME = $theme.scheme} else {$env.THEME_MODE = "${config.theme.variant}"$env.THEME_SCHEME = "${config.theme.color_scheme}"}
# Load theme state from theme.jsonif [ -f "$HOME/nixos/modules/common/theme/theme.json" ]; thenexport THEME_MODE=$(grep -o '"mode":"[^"]*"' "$HOME/nixos/modules/common/theme/theme.json" | cut -d'"' -f4)export THEME_SCHEME=$(grep -o '"scheme":"[^"]*"' "$HOME/nixos/modules/common/theme/theme.json" | cut -d'"' -f4)elseexport THEME_MODE="${config.theme.variant}"export THEME_SCHEME="${config.theme.color_scheme}"fi
# Don't add progress hint for completion (progress=100) so it times out.let args = if $progress >= 0 and $progress < 100 {$base_args | append ["--hint" $"int:value:($progress)"]} else {$base_args}
# Progress notifications persist, completion/error notifications auto-dismiss.let is_complete = $progress == 100let is_error = ($message | str downcase | str contains "error")# Dismiss all previous notifications before showing completion.if $is_complete {^${pkgs.mako}/bin/makoctl dismiss --all}let timeout = if $is_error {30000} else if $is_complete {5000} else {0 # Persist until replaced.}