diff --git a/modules/misc/news/2025/09/2025-09-19_16-24-17.nix b/modules/misc/news/2025/09/2025-09-19_16-24-17.nix new file mode 100644 index 000000000..087d2af8a --- /dev/null +++ b/modules/misc/news/2025/09/2025-09-19_16-24-17.nix @@ -0,0 +1,11 @@ +{ pkgs, ... }: + +{ + time = "2025-09-19T14:24:17+00:00"; + condition = pkgs.stdenv.hostPlatform.isLinux; + message = '' + A new module is available: `programs.wleave` + + A Rust/GTK4 port of `programs.wlogout` with improvements. + ''; +} diff --git a/modules/programs/wleave.nix b/modules/programs/wleave.nix new file mode 100644 index 000000000..748e6fd0a --- /dev/null +++ b/modules/programs/wleave.nix @@ -0,0 +1,113 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + cfg = config.programs.wleave; + + jsonFormat = pkgs.formats.json { }; +in +{ + meta.maintainers = [ lib.maintainers.jaredmontoya ]; + + options.programs.wleave = with lib.types; { + enable = lib.mkEnableOption "wleave"; + + package = lib.mkPackageOption pkgs "wleave" { nullable = true; }; + + settings = lib.mkOption { + inherit (jsonFormat) type; + default = { }; + description = '' + Configuration for wleave. + See for supported values. + ''; + example = lib.literalExpression '' + { + margin = 200; + buttons-per-row = "1/1"; + delay-command-ms = 100; + close-on-lost-focus = true; + show-keybinds = true; + buttons = [ + { + label = "lock"; + action = "swaylock"; + text = "Lock"; + keybind = "l"; + icon = "''${pkgs.wleave}/share/wleave/icons/lock.svg"; + } + { + label = "logout"; + action = "loginctl terminate-user $USER"; + text = "Logout"; + keybind = "e"; + icon = "''${pkgs.wleave}/share/wleave/icons/logout.svg"; + } + { + label = "shutdown"; + action = "systemctl poweroff"; + text = "Shutdown"; + keybind = "s"; + icon = "''${pkgs.wleave}/share/wleave/icons/shutdown.svg"; + } + ]; + } + ''; + }; + + style = lib.mkOption { + type = nullOr (either path lines); + default = null; + description = '' + CSS style of wleave. + + See + for the documentation. + + If the value is set to a path literal, then the path will be used as the css file. + ''; + example = '' + window { + background-color: rgba(12, 12, 12, 0.8); + } + + button { + color: var(--view-fg-color); + background-color: var(--view-bg-color); + border: none; + padding: 10px; + } + + button:hover, + button:focus { + color: var(--accent-color); + background-color: var(--window-bg-color); + } + ''; + }; + }; + + config = lib.mkIf cfg.enable { + assertions = [ + (lib.hm.assertions.assertPlatform "programs.wleave" pkgs lib.platforms.linux) + ]; + + home.packages = lib.mkIf (cfg.package != null) [ cfg.package ]; + + xdg.configFile."wleave/layout.json" = lib.mkIf (cfg.settings != { }) { + source = pkgs.writeText "wleave-layout.json" ((builtins.toJSON cfg.settings) + "\n"); + }; + + xdg.configFile."wleave/style.css" = lib.mkIf (cfg.style != null) { + source = + if builtins.isPath cfg.style || lib.isStorePath cfg.style then + cfg.style + else + pkgs.writeText "wleave-style.css" cfg.style; + }; + }; +} diff --git a/tests/modules/programs/wleave/default.nix b/tests/modules/programs/wleave/default.nix new file mode 100644 index 000000000..8d89dc797 --- /dev/null +++ b/tests/modules/programs/wleave/default.nix @@ -0,0 +1,6 @@ +{ lib, pkgs, ... }: +lib.optionalAttrs pkgs.stdenv.hostPlatform.isLinux { + wleave-styling = ./styling.nix; + wleave-layout-single = ./layout-single.nix; + wleave-layout-multiple = ./layout-multiple.nix; +} diff --git a/tests/modules/programs/wleave/layout-multiple-expected.json b/tests/modules/programs/wleave/layout-multiple-expected.json new file mode 100644 index 000000000..1698fd201 --- /dev/null +++ b/tests/modules/programs/wleave/layout-multiple-expected.json @@ -0,0 +1 @@ +{"buttons":[{"action":"systemctl poweroff","keybind":"s","label":"shutdown","text":"Shutdown"},{"action":"systemctl hibernate","height":0.5,"keybind":"h","label":"hibernate","text":"Hibernate","width":0.5},{"action":"systemctl suspend","circular":true,"keybind":"u","label":"suspend","text":"Suspend"},{"action":"swaymsg exit","keybind":"e","label":"exit","text":"Exit"},{"action":"systemctl reboot","keybind":"r","label":"reboot","text":"Reboot"},{"action":"gtklock","keybind":"l","label":"lock","text":"Lock"}],"buttons-per-row":"3","no-version-info":true} diff --git a/tests/modules/programs/wleave/layout-multiple.nix b/tests/modules/programs/wleave/layout-multiple.nix new file mode 100644 index 000000000..6c218d21b --- /dev/null +++ b/tests/modules/programs/wleave/layout-multiple.nix @@ -0,0 +1,64 @@ +{ config, ... }: +{ + config = { + home.stateVersion = "22.11"; + + programs.wleave = { + package = config.lib.test.mkStubPackage { outPath = "@wleave@"; }; + enable = true; + settings = { + no-version-info = true; + buttons-per-row = "3"; + + buttons = [ + { + label = "shutdown"; + action = "systemctl poweroff"; + text = "Shutdown"; + keybind = "s"; + } + { + label = "hibernate"; + action = "systemctl hibernate"; + text = "Hibernate"; + keybind = "h"; + height = 0.5; + width = 0.5; + } + { + label = "suspend"; + action = "systemctl suspend"; + text = "Suspend"; + keybind = "u"; + circular = true; + } + { + label = "exit"; + action = "swaymsg exit"; + text = "Exit"; + keybind = "e"; + } + { + label = "reboot"; + action = "systemctl reboot"; + text = "Reboot"; + keybind = "r"; + } + { + label = "lock"; + action = "gtklock"; + text = "Lock"; + keybind = "l"; + } + ]; + }; + }; + + nmt.script = '' + assertPathNotExists home-files/.config/wleave/style.css + assertFileContent \ + home-files/.config/wleave/layout.json \ + ${./layout-multiple-expected.json} + ''; + }; +} diff --git a/tests/modules/programs/wleave/layout-single-expected.json b/tests/modules/programs/wleave/layout-single-expected.json new file mode 100644 index 000000000..9985e7be7 --- /dev/null +++ b/tests/modules/programs/wleave/layout-single-expected.json @@ -0,0 +1 @@ +{"buttons":[{"action":"systemctl poweroff","keybind":"s","label":"shutdown","text":"Shutdown"}]} diff --git a/tests/modules/programs/wleave/layout-single.nix b/tests/modules/programs/wleave/layout-single.nix new file mode 100644 index 000000000..031ca5cc5 --- /dev/null +++ b/tests/modules/programs/wleave/layout-single.nix @@ -0,0 +1,26 @@ +{ config, ... }: +{ + config = { + home.stateVersion = "22.11"; + + programs.wleave = { + package = config.lib.test.mkStubPackage { outPath = "@wleave@"; }; + enable = true; + settings.buttons = [ + { + label = "shutdown"; + action = "systemctl poweroff"; + text = "Shutdown"; + keybind = "s"; + } + ]; + }; + + nmt.script = '' + assertPathNotExists home-files/.config/wleave/style.css + assertFileContent \ + home-files/.config/wleave/layout.json \ + ${./layout-single-expected.json} + ''; + }; +} diff --git a/tests/modules/programs/wleave/styling-expected.css b/tests/modules/programs/wleave/styling-expected.css new file mode 100644 index 000000000..49050beb4 --- /dev/null +++ b/tests/modules/programs/wleave/styling-expected.css @@ -0,0 +1,16 @@ +* { + border: none; + border-radius: 0; + font-family: Source Code Pro; + font-weight: bold; + color: #abb2bf; + font-size: 18px; + min-height: 0px; +} +window { + background: #16191C; + color: #aab2bf; +} +#window { + padding: 0 0px; +} diff --git a/tests/modules/programs/wleave/styling.nix b/tests/modules/programs/wleave/styling.nix new file mode 100644 index 000000000..f4b6c536a --- /dev/null +++ b/tests/modules/programs/wleave/styling.nix @@ -0,0 +1,36 @@ +{ config, ... }: +{ + config = { + home.stateVersion = "22.11"; + + programs.wleave = { + package = config.lib.test.mkStubPackage { outPath = "@wleave@"; }; + enable = true; + style = '' + * { + border: none; + border-radius: 0; + font-family: Source Code Pro; + font-weight: bold; + color: #abb2bf; + font-size: 18px; + min-height: 0px; + } + window { + background: #16191C; + color: #aab2bf; + } + #window { + padding: 0 0px; + } + ''; + }; + + nmt.script = '' + assertPathNotExists home-files/.config/wleave/layout.json + assertFileContent \ + home-files/.config/wleave/style.css \ + ${./styling-expected.css} + ''; + }; +}