NixDaemon

NixOS pentest workstation as one flake — IceBreaker's successor
git clone https://git.daemon-sec.xyz/NixDaemon.git
Log | Files | Refs | README

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 }