diff --git a/modules/misc/news/2026/08/2026-08-01_09-29-44.nix b/modules/misc/news/2026/08/2026-08-01_09-29-44.nix new file mode 100644 index 000000000..cf69252a5 --- /dev/null +++ b/modules/misc/news/2026/08/2026-08-01_09-29-44.nix @@ -0,0 +1,10 @@ +{ pkgs, ... }: +{ + time = "2026-08-01T09:29:44+00:00"; + condition = pkgs.stdenv.hostPlatform.isLinux; + message = '' + A new module is available `programs.noctalia`. + + Noctalia is a sleek, customizable shell crafted for Wayland. + ''; +} diff --git a/modules/programs/noctalia.nix b/modules/programs/noctalia.nix new file mode 100644 index 000000000..defdea652 --- /dev/null +++ b/modules/programs/noctalia.nix @@ -0,0 +1,153 @@ +{ + config, + pkgs, + lib, + ... +}: +let + cfg = config.programs.noctalia; + jsonFormat = pkgs.formats.json { }; + tomlFormat = pkgs.formats.toml { }; + + generateConfig = + format: name: value: + if lib.isString value then + pkgs.writeText name value + else if builtins.isPath value || lib.isStorePath value then + value + else + format.generate name value; + + generateToml = generateConfig tomlFormat; + generateJson = generateConfig jsonFormat; +in +{ + meta.maintainers = with lib.maintainers; [ + rachitvrma + ]; + + options.programs.noctalia = { + enable = lib.mkEnableOption "noctalia, a lightweight Wayland shell and bar"; + + systemd.enable = lib.mkEnableOption "a systemd user service for noctalia"; + + package = lib.mkPackageOption pkgs "noctalia" { nullable = true; }; + + checkConfig = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Validate the configuration file at build time."; + }; + + settings = lib.mkOption { + type = + with lib.types; + oneOf [ + tomlFormat.type + str + path + ]; + default = { }; + description = '' + Default settings for noctalia, can be written as: + - A Nix attrset (converted to TOML via nixpkgs' tomlFormat) + - A raw TOML string + - A path to a `.toml` file + + See for more information and examples. + + Note: these settings can still be overwritten at runtime via the settings menu. + ''; + example = lib.literalExpression '' + { + shell = { + font = "JetBrainsMono Nerd Font"; + settings_show_advanced = true; + }; + + theme = { + mode = "dark"; + source = "builtin"; + builtin = "Catppuccin"; + }; + } + ''; + }; + + customPalettes = lib.mkOption { + type = + with lib.types; + attrsOf (oneOf [ + jsonFormat.type + str + path + ]); + default = { }; + description = '' + Custom color palettes, keyed by name. Each value can be: + - A Nix attrset (converted to JSON) + - A raw JSON string + - A path to a {file}`.json` file + + See . + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "programs.noctalia" pkgs lib.platforms.linux) + + { + assertion = !cfg.systemd.enable || cfg.package != null; + message = "programs.noctalia.package cannot be null when programs.noctalia.systemd.enable is true"; + } + ]; + systemd.user.services.noctalia = lib.mkIf cfg.systemd.enable { + Unit = { + Description = "Noctalia - A lightweight Wayland shell and bar"; + Documentation = "https://docs.noctalia.dev/v5/"; + PartOf = [ config.wayland.systemd.target ]; + After = [ config.wayland.systemd.target ]; + X-Restart-Triggers = + lib.optional (cfg.settings != { }) "${config.xdg.configFile."noctalia/config.toml".source}" + ++ lib.mapAttrsToList ( + name: _: "${config.xdg.configFile."noctalia/palettes/${name}.json".source}" + ) cfg.customPalettes; + }; + + Service = { + ExecStart = lib.getExe cfg.package; + Restart = "on-failure"; + }; + + Install.WantedBy = [ config.wayland.systemd.target ]; + }; + + home.packages = lib.optional (cfg.package != null) cfg.package; + + xdg = { + configFile = lib.mkMerge [ + (lib.mkIf (cfg.settings != { }) { + "noctalia/config.toml".source = + let + rawConfig = generateToml "config.toml" cfg.settings; + in + if cfg.checkConfig && cfg.package != null then + pkgs.runCommand "noctalia-config.toml" { } '' + ${lib.getExe cfg.package} config validate ${rawConfig} + cp ${rawConfig} $out + '' + else + rawConfig; + }) + (lib.mapAttrs' ( + name: palette: + lib.nameValuePair "noctalia/palettes/${name}.json" { + source = generateJson "${name}-palette.json" palette; + } + ) cfg.customPalettes) + ]; + }; + }; +} diff --git a/tests/modules/programs/noctalia/custom-palette.nix b/tests/modules/programs/noctalia/custom-palette.nix new file mode 100644 index 000000000..37c4f9f69 --- /dev/null +++ b/tests/modules/programs/noctalia/custom-palette.nix @@ -0,0 +1,106 @@ +{ + programs.noctalia = { + enable = true; + checkConfig = false; + customPalettes.example_gruvbox = { + dark = { + mError = "#ea6962"; + mHover = "#89b482"; + mOnError = "#141617"; + mOnHover = "#141617"; + mOnPrimary = "#141617"; + mOnSecondary = "#141617"; + mOnSurface = "#ddc7a1"; + mOnSurfaceVariant = "#bdae93"; + mOnTertiary = "#141617"; + mOutline = "#5a524c"; + mPrimary = "#d3869b"; + mSecondary = "#7daea3"; + mShadow = "#141617"; + mSurface = "#141617"; + mSurfaceVariant = "#1d2021"; + mTertiary = "#89b482"; + terminal = { + background = "#141617"; + bright = { + black = "#5a524c"; + blue = "#7daea3"; + cyan = "#89b482"; + green = "#a9b665"; + magenta = "#d3869b"; + red = "#ea6962"; + white = "#ebdbb2"; + yellow = "#d8a657"; + }; + cursor = "#ddc7a1"; + cursorText = "#141617"; + foreground = "#ddc7a1"; + normal = { + black = "#1d2021"; + blue = "#7daea3"; + cyan = "#89b482"; + green = "#a9b665"; + magenta = "#d3869b"; + red = "#ea6962"; + white = "#ddc7a1"; + yellow = "#d8a657"; + }; + selectionBg = "#5a524c"; + selectionFg = "#ddc7a1"; + }; + }; + light = { + mError = "#cc241d"; + mHover = "#427b58"; + mOnError = "#fbf1c7"; + mOnHover = "#fbf1c7"; + mOnPrimary = "#fbf1c7"; + mOnSecondary = "#fbf1c7"; + mOnSurface = "#3c3836"; + mOnSurfaceVariant = "#7c6f64"; + mOnTertiary = "#fbf1c7"; + mOutline = "#bdae93"; + mPrimary = "#b16286"; + mSecondary = "#458588"; + mShadow = "#d5c4a1"; + mSurface = "#fbf1c7"; + mSurfaceVariant = "#f2e5bc"; + mTertiary = "#427b58"; + terminal = { + background = "#fbf1c7"; + bright = { + black = "#928374"; + blue = "#076678"; + cyan = "#427b58"; + green = "#79740e"; + magenta = "#8f3f71"; + red = "#9d0006"; + white = "#3c3836"; + yellow = "#b57614"; + }; + cursor = "#3c3836"; + cursorText = "#fbf1c7"; + foreground = "#3c3836"; + normal = { + black = "#fbf1c7"; + blue = "#458588"; + cyan = "#689d6a"; + green = "#98971a"; + magenta = "#b16286"; + red = "#cc241d"; + white = "#7c6f64"; + yellow = "#d79921"; + }; + selectionBg = "#3c3836"; + selectionFg = "#fbf1c7"; + }; + }; + }; + }; + + nmt.script = '' + local customPalette="home-files/.config/noctalia/palettes/example_gruvbox.json" + assertFileExists $customPalette + assertFileContent $customPalette "${./expected-custom-palette.json}" + ''; +} diff --git a/tests/modules/programs/noctalia/default.nix b/tests/modules/programs/noctalia/default.nix new file mode 100644 index 000000000..51b1851ba --- /dev/null +++ b/tests/modules/programs/noctalia/default.nix @@ -0,0 +1,7 @@ +{ lib, pkgs, ... }: +lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { + noctalia-empty-config = ./empty-config.nix; + noctalia-example-config = ./example-config.nix; + noctalia-custom-palette = ./custom-palette.nix; + noctalia-systemd-enabled = ./systemd-enabled.nix; +} diff --git a/tests/modules/programs/noctalia/empty-config.nix b/tests/modules/programs/noctalia/empty-config.nix new file mode 100644 index 000000000..87072f088 --- /dev/null +++ b/tests/modules/programs/noctalia/empty-config.nix @@ -0,0 +1,7 @@ +{ + programs.noctalia.enable = true; + + nmt.script = '' + assertPathNotExists "home-files/.config/noctalia"; + ''; +} diff --git a/tests/modules/programs/noctalia/example-config.nix b/tests/modules/programs/noctalia/example-config.nix new file mode 100644 index 000000000..0796c9778 --- /dev/null +++ b/tests/modules/programs/noctalia/example-config.nix @@ -0,0 +1,36 @@ +{ + programs.noctalia = { + enable = true; + checkConfig = false; + settings = { + audio = { + enable_sounds = true; + }; + bar = { + default = { + background_opacity = 0.8; + capsule = true; + capsule_border = "outline"; + font_family = "JetBrainsMono Nerd Font Mono"; + position = "top"; + }; + }; + desktop_widgets = { + grid = { + cell_size = 16; + major_interval = 4; + visible = true; + }; + schema_version = 2; + widget = { }; + widget_order = [ ]; + }; + }; + }; + + nmt.script = '' + local configFile="home-files/.config/noctalia/config.toml" + assertFileExists $configFile + assertFileContent $configFile ${./expected-example-config.toml} + ''; +} diff --git a/tests/modules/programs/noctalia/expected-custom-palette.json b/tests/modules/programs/noctalia/expected-custom-palette.json new file mode 100644 index 000000000..4cf720b52 --- /dev/null +++ b/tests/modules/programs/noctalia/expected-custom-palette.json @@ -0,0 +1,94 @@ +{ + "dark": { + "mError": "#ea6962", + "mHover": "#89b482", + "mOnError": "#141617", + "mOnHover": "#141617", + "mOnPrimary": "#141617", + "mOnSecondary": "#141617", + "mOnSurface": "#ddc7a1", + "mOnSurfaceVariant": "#bdae93", + "mOnTertiary": "#141617", + "mOutline": "#5a524c", + "mPrimary": "#d3869b", + "mSecondary": "#7daea3", + "mShadow": "#141617", + "mSurface": "#141617", + "mSurfaceVariant": "#1d2021", + "mTertiary": "#89b482", + "terminal": { + "background": "#141617", + "bright": { + "black": "#5a524c", + "blue": "#7daea3", + "cyan": "#89b482", + "green": "#a9b665", + "magenta": "#d3869b", + "red": "#ea6962", + "white": "#ebdbb2", + "yellow": "#d8a657" + }, + "cursor": "#ddc7a1", + "cursorText": "#141617", + "foreground": "#ddc7a1", + "normal": { + "black": "#1d2021", + "blue": "#7daea3", + "cyan": "#89b482", + "green": "#a9b665", + "magenta": "#d3869b", + "red": "#ea6962", + "white": "#ddc7a1", + "yellow": "#d8a657" + }, + "selectionBg": "#5a524c", + "selectionFg": "#ddc7a1" + } + }, + "light": { + "mError": "#cc241d", + "mHover": "#427b58", + "mOnError": "#fbf1c7", + "mOnHover": "#fbf1c7", + "mOnPrimary": "#fbf1c7", + "mOnSecondary": "#fbf1c7", + "mOnSurface": "#3c3836", + "mOnSurfaceVariant": "#7c6f64", + "mOnTertiary": "#fbf1c7", + "mOutline": "#bdae93", + "mPrimary": "#b16286", + "mSecondary": "#458588", + "mShadow": "#d5c4a1", + "mSurface": "#fbf1c7", + "mSurfaceVariant": "#f2e5bc", + "mTertiary": "#427b58", + "terminal": { + "background": "#fbf1c7", + "bright": { + "black": "#928374", + "blue": "#076678", + "cyan": "#427b58", + "green": "#79740e", + "magenta": "#8f3f71", + "red": "#9d0006", + "white": "#3c3836", + "yellow": "#b57614" + }, + "cursor": "#3c3836", + "cursorText": "#fbf1c7", + "foreground": "#3c3836", + "normal": { + "black": "#fbf1c7", + "blue": "#458588", + "cyan": "#689d6a", + "green": "#98971a", + "magenta": "#b16286", + "red": "#cc241d", + "white": "#7c6f64", + "yellow": "#d79921" + }, + "selectionBg": "#3c3836", + "selectionFg": "#fbf1c7" + } + } +} diff --git a/tests/modules/programs/noctalia/expected-example-config.toml b/tests/modules/programs/noctalia/expected-example-config.toml new file mode 100644 index 000000000..cd3e05df7 --- /dev/null +++ b/tests/modules/programs/noctalia/expected-example-config.toml @@ -0,0 +1,20 @@ +[audio] +enable_sounds = true + +[bar.default] +background_opacity = 0.8 +capsule = true +capsule_border = "outline" +font_family = "JetBrainsMono Nerd Font Mono" +position = "top" + +[desktop_widgets] +schema_version = 2 +widget_order = [] + +[desktop_widgets.grid] +cell_size = 16 +major_interval = 4 +visible = true + +[desktop_widgets.widget] diff --git a/tests/modules/programs/noctalia/systemd-enabled.nix b/tests/modules/programs/noctalia/systemd-enabled.nix new file mode 100644 index 000000000..7fb015164 --- /dev/null +++ b/tests/modules/programs/noctalia/systemd-enabled.nix @@ -0,0 +1,26 @@ +{ config, ... }: { + programs.noctalia = { + enable = true; + package = config.lib.test.mkStubPackage { outPath = "@noctalia@"; }; + systemd.enable = true; + }; + + nmt.script = '' + assertFileContent \ + home-files/.config/systemd/user/noctalia.service \ + ${builtins.toFile "noctalia.service" '' + [Install] + WantedBy=graphical-session.target + + [Service] + ExecStart=@noctalia@/bin/dummy + Restart=on-failure + + [Unit] + After=graphical-session.target + Description=Noctalia - A lightweight Wayland shell and bar + Documentation=https://docs.noctalia.dev/v5/ + PartOf=graphical-session.target + ''} + ''; +}