caelestia.nix (11606B)
1 # modules/home/caelestia.nix — Caelestia Shell as bar, launcher, notifications, 2 # OSD, lock and session menu; its CLI; the static Rosé Pine (M3) schemes. 3 # 4 # Look (2026-10-08): Rosé Pine dark (main, not moon) everywhere, translucent 5 # shell layers over blur, and Caelestia's own framed bar: the screen edge is a 6 # 10 px frame with 25 px rounded inner corners, the clock and tray sit in pill 7 # backgrounds, occupied workspaces are filled, the Nix snowflake is the logo. 8 # The sidebar, utilities and notification panels are narrower than stock 9 # (./caelestia/shell-tokens.json: 340 / 340 / 360 px instead of 430). 10 # A Rosé Pine Dawn mapping is registered too: caelestia scheme set -n rose-pine -f rose-pine-dawn 11 # and back: caelestia scheme set -n rose-pine -f rose-pine-dark 12 # 13 { ... }: 14 { 15 flake.homeModules.caelestia = 16 { config, pkgs, lib, inputs, osConfig, identity, ... }: 17 let 18 system = pkgs.stdenv.hostPlatform.system; 19 hyprPkg = inputs.hyprland.packages.${system}.hyprland; 20 21 # The CLI knows schemes by name and re-reads their colours whenever the 22 # wallpaper changes, so the two hand-mapped Rosé Pine → M3 palettes are 23 # registered as a real scheme (name rose-pine; flavours rose-pine-dark, mode 24 # dark, and rose-pine-dawn, mode light). The CLI's own `rosepine/dawn` is a 25 # Material palette generated from a gold seed, not the Rosé Pine colours. 26 cli = (inputs.caelestia-cli.packages.${system}.default).overrideAttrs (old: { 27 patchPhase = (old.patchPhase or "") + '' 28 install -Dm644 ${./caelestia/rose-pine-dark.txt} src/caelestia/data/schemes/rose-pine/rose-pine-dark/dark.txt 29 install -Dm644 ${./caelestia/rose-pine-dawn.txt} src/caelestia/data/schemes/rose-pine/rose-pine-dawn/light.txt 30 ''; 31 }); 32 33 shell = 34 ((inputs.caelestia-shell.packages.${system}.with-cli).override { 35 caelestia-cli = cli; 36 hyprland = hyprPkg; # hyprctl from the same Hyprland the compositor runs 37 }).overrideAttrs (old: { 38 # bar.activeWindow.compact shows the program (desktop-entry name for the 39 # window class) instead of the window title. Upstream's compact mode only 40 # trims the title at its last " - ". Rebuilds the shell locally. 41 patches = (old.patches or [ ]) ++ [ ./caelestia/active-window-program-name.patch ]; 42 }); 43 44 # ~/Pictures/Wallpapers/{rose-pine,rose-pine-dark,rose-pine-mid-walls}; the picker scans subfolders. 45 wallpaperDir = "${config.home.homeDirectory}/Pictures/Wallpapers"; 46 in 47 # Only when the Hyprland desktop is switched on (modules/features/desktop/options.nix). 48 lib.mkIf osConfig.daemon.desktop.hyprland.enable { 49 programs.caelestia = { 50 enable = true; 51 package = shell; 52 53 # The module's default target is graphical-session.target, which Niri 54 # starts too (niri.service BindsTo it), so Caelestia would come up beside 55 # Noctalia in a Niri login. uwsm's Hyprland session target only exists 56 # under Hyprland. 57 systemd.target = "wayland-session@hyprland.desktop.target"; 58 59 cli = { 60 enable = true; 61 package = cli; 62 # Theming is static Rosé Pine from this repo; the CLI must not rewrite 63 # kitty, GTK, Hyprland or anything else when the wallpaper changes. 64 settings.theme = { 65 enableTerm = false; 66 enableHypr = false; 67 enableDiscord = false; 68 enableSpicetify = false; 69 enablePandora = false; 70 enableFuzzel = false; 71 enableBtop = false; 72 enableNvtop = false; 73 enableHtop = false; 74 enableGtk = false; 75 enableQt = false; 76 enableWarp = false; 77 enableChromium = false; 78 enableZed = false; 79 enableCava = false; 80 }; 81 }; 82 83 settings = { 84 appearance = { 85 font = { 86 clock = "Rubik"; 87 workspaces = "Rubik"; 88 headline.family = "Noto Sans"; 89 title.family = "Noto Sans"; 90 body.family = "Noto Sans"; 91 label.family = "Noto Sans"; 92 mono.family = "DMMono Nerd Font"; 93 }; 94 anim.durations.scale = 1.15; 95 transparency = { 96 enabled = true; 97 base = 0.85; 98 layers = 0.4; 99 }; 100 }; 101 general = { 102 # The Nix snowflake in the bar, dashboard and lock screen (empty = Caelestia's own logo). 103 logo = "/run/current-system/sw/share/icons/hicolor/scalable/apps/nix-snowflake.svg"; 104 apps = { 105 terminal = [ "kitty" ]; 106 audio = [ "caelestia" "shell" "drawers" "toggle" "sidebar" ]; # was the Omarchy audio popup 107 playback = [ "mpv" ]; 108 explorer = [ "nautilus" ]; 109 }; 110 idle = { 111 lockBeforeSleep = false; 112 inhibitWhenAudio = true; 113 # Lock after 15 min idle, screen off after 20; audio playing or a 114 # fullscreen app with an idle inhibitor holds both off. 115 timeouts = [ 116 { timeout = 900; idleAction = "lock"; respectInhibitors = true; } 117 { timeout = 1200; idleAction = "dpms off"; returnAction = "dpms on"; } 118 ]; 119 }; 120 }; 121 # Omarchy drew the wallpaper; here Caelestia does. Colours stay static. 122 background = { 123 enabled = true; 124 wallpaperEnabled = true; 125 # Big clock on the wallpaper, bottom right, with a soft shadow. 126 desktopClock = { 127 enabled = true; 128 position = "bottom-right"; 129 scale = 1.0; 130 shadow.enabled = true; 131 }; 132 # Audio visualiser along the bottom of the wallpaper while something plays (cava is built in). 133 visualiser = { 134 enabled = true; 135 autoHide = true; 136 blur = false; 137 rounding = 1; 138 spacing = 1; 139 }; 140 }; 141 bar = { 142 persistent = true; 143 showOnHover = true; 144 workspaces = { 145 shown = 5; 146 activeIndicator = true; 147 occupiedBg = true; # filled pills behind workspaces that have windows 148 showWindows = true; 149 activeTrail = true; 150 }; 151 activeWindow = { 152 compact = true; # the program's name (patched, see `shell` above), not the title 153 inverted = true; # on a primary-coloured pill 154 }; 155 clock = { 156 showDate = true; 157 showIcon = true; 158 background = true; # clock in its own pill 159 }; 160 tray = { 161 background = true; # tray in its own pill 162 recolour = true; # tray icons tinted to the scheme 163 }; 164 statusIcons = [ 165 { id = "lockStatus"; enabled = true; } 166 { id = "audio"; enabled = true; } 167 { id = "microphone"; enabled = true; } # shows when something is capturing 168 { id = "kbLayout"; enabled = false; } 169 { id = "network"; enabled = true; } 170 { id = "bluetooth"; enabled = true; } 171 { id = "battery"; enabled = true; } 172 ]; 173 }; 174 # Caelestia's frame: the bar is one side of a border around the screen 175 # whose inner corners are rounded. Stock values; the carried 5/0/0 was a flat strip. 176 border = { 177 thickness = 10; 178 rounding = 25; 179 smoothing = 20; 180 }; 181 dashboard = { 182 enabled = true; 183 showWeather = true; 184 performance.showGpu = true; 185 }; 186 launcher = { 187 enabled = true; 188 maxShown = 8; 189 vimKeybinds = true; # ctrl+j/k move, like everywhere else here 190 actions = [ 191 { name = "Calculator"; icon = "calculate"; description = "Do simple math equations (powered by Qalc)"; command = [ "autocomplete" "calc" ]; enabled = true; dangerous = false; } 192 { name = "Wallpaper"; icon = "image"; description = "Pick a wallpaper"; command = [ "caelestia" "wallpaper" ]; enabled = true; dangerous = false; } 193 { name = "Lock"; icon = "lock"; description = "Lock the session"; command = [ "caelestia" "shell" "lock" "lock" ]; enabled = true; dangerous = false; } 194 { name = "Sleep"; icon = "bedtime"; description = "Suspend"; command = [ "systemctl" "suspend" ]; enabled = true; dangerous = false; } 195 { name = "Settings"; icon = "settings"; description = "Configure the shell"; command = [ "caelestia" "shell" "nexus" "open" ]; enabled = true; dangerous = false; } 196 { name = "Logout"; icon = "exit_to_app"; description = "Log out of the current session"; command = [ "uwsm" "stop" ]; enabled = true; dangerous = true; } 197 { name = "Reboot"; icon = "cached"; description = "Reboot the system"; command = [ "systemctl" "reboot" ]; enabled = true; dangerous = true; } 198 { name = "Shutdown"; icon = "power_settings_new"; description = "Shutdown the system"; command = [ "systemctl" "poweroff" ]; enabled = true; dangerous = true; } 199 ]; 200 }; 201 lock = { 202 enabled = true; # Caelestia is the lock screen here (Omarchy had hyprlock) 203 useWallpaper = true; # the wallpaper behind the lock, not a flat colour 204 }; 205 notifs = { 206 expire = true; 207 defaultExpireTimeout = 6000; 208 actionOnClick = true; 209 }; 210 osd = { 211 enabled = true; 212 enableBrightness = true; 213 }; 214 services = { 215 gpuType = "Generic"; # must be a string 216 smartScheme = false; # never derive colours from the wallpaper 217 defaultPlayer = "rmpc"; 218 weatherLocation = identity.weather.coords; # dashboard weather card (modules/hosts/laptop/_identity.nix) 219 }; 220 session = { 221 enabled = true; 222 icons.hibernate = "bedtime"; 223 commands = { 224 logout = [ "uwsm" "stop" ]; 225 shutdown = [ "systemctl" "poweroff" ]; 226 hibernate = [ "systemctl" "suspend" ]; 227 reboot = [ "systemctl" "reboot" ]; 228 }; 229 }; 230 sidebar.enabled = true; 231 utilities = { 232 enabled = true; 233 toasts.nowPlaying = true; # a toast when the track changes 234 }; 235 paths.wallpaperDir = wallpaperDir; 236 }; 237 }; 238 239 # The scheme the shell reads at start: Rosé Pine dark (scheme.json; 240 # scheme-dawn.json is the light mapping). `caelestia scheme set …` rewrites 241 # this file (home-manager backs the symlink up as .hm-bak when that happens). 242 home.file.".local/state/caelestia/scheme.json".source = ./caelestia/scheme.json; 243 244 # Internal size tokens: the shell reads this file (defaults in its source, 245 # plugin/src/Caelestia/Config/tokens.hpp: sidebar, utilities and 246 # notification width 430, bar innerWidth 40). Here: sidebar and utilities 247 # 340, notifications 360, bar 36. Rounding, padding and spacing stay stock. 248 home.file.".config/caelestia/shell-tokens.json".source = ./caelestia/shell-tokens.json; 249 250 # First wallpaper, only if none was ever chosen (Caelestia keeps the choice in state). 251 home.activation.caelestiaWallpaper = lib.hm.dag.entryAfter [ "writeBoundary" ] '' 252 st="$HOME/.local/state/caelestia/wallpaper" 253 if [ ! -e "$st/path.txt" ]; then 254 wp=$(ls "${wallpaperDir}"/rose-pine-dark/*.png 2>/dev/null | head -1 || true) 255 if [ -n "$wp" ]; then 256 mkdir -p "$st" && printf '%s' "$wp" > "$st/path.txt" && ln -sfn "$wp" "$st/current" 257 fi 258 fi 259 ''; 260 } 261 ; 262 }