Repository: nix-community/home-manager
Pull request #6948: mako: refactor
Author: login: aguirre-matteo
Labels: name: wayland
name: notification
Created: 2025-05-01T14:08:56Z | Merged: 2025-05-04T00:09:56Z

### Description

<!--

Please provide a brief description of your change.

-->

This PR refactors the `services.mako` module to replace all its configuration options with `settings` and `criterias`. I also added a test for the configuration. . Indirectly closes #6251.

### Checklist

<!--

Please go through the following checklist before opening a non-WIP
pull-request.

Also make sure to read the guidelines found at

  https://nix-community.github.io/home-manager/#sec-guidelines

-->

- [x] Change is backwards compatible.

- [x] Code formatted with `nix fmt` or `./format`.

- [x] Code tested through `nix-shell --pure tests -A run.all`
    or `nix build --reference-lock-file flake.lock ./tests#test-all` using Flakes.

- [x] Test cases updated/added. See [example](https://github.com/nix-community/home-manager/commit/f3fbb50b68df20da47f9b0def5607857fcc0d021#diff-b61a6d542f9036550ba9c401c80f00ef).

- [x] Commit messages are formatted like

    ```
    {component}: {description}

    {long description}
    ```

    See [CONTRIBUTING](https://nix-community.github.io/home-manager/#sec-commit-style) for more information and [recent commit messages](https://github.com/nix-community/home-manager/commits/master) for examples.

- If this PR adds a new module

  - [ ] Added myself as module maintainer. See [example](https://github.com/nix-community/home-manager/blob/068ff76a10e95820f886ac46957edcff4e44621d/modules/programs/lesspipe.nix#L6).

#### Maintainer CC

<!--
If you are updating a module, please @ people who are in its `meta.maintainers` list.
If in doubt, check `git blame` for whoever last touched something.
-->


Linked issue #6251: mako: add outer-margin
outer-margin is like margin but different. it was added in 2022 as a setting to mako but is unavailable in the module.

https://github.com/emersion/mako/pull/395/files

```
*outer-margin*=_directional_
	Set outer-margin of each edge to the size specified by _directional_. See
	*DIRECTIONAL VALUES* for more information.
	This margin applies to the outside of the whole notification list.
```

```
control over each edge is desired:
- A single value will apply to all four edges.
- Two values will set vertical and horizontal edges separately.
- Three will set top, horizontal, and bottom edges separately.
- Four will give each edge a separate value.
- Four will set top, right, bottom, and left edges separately.
```
Linked issue comments: user: login: khaneliman
created_at: 2025-03-21T14:58:03Z
body: Looks like a module that could use a refactoring to use a feeform settings option and deprecate the top level options. We should be able to use a generator since it's all key=value pairs. 
author_association: COLLABORATOR

Diff:
diff --git a/modules/services/mako.nix b/modules/services/mako.nix
index 40edcce4fd48..88d74aba8d39 100644
--- a/modules/services/mako.nix
+++ b/modules/services/mako.nix
@@ -5,371 +5,139 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toINIWithGlobalSection { };
+  settingsType = with types; attrsOf str;
+  criteriasType = types.attrsOf settingsType;
 in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
-  imports = [ (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ]) ];
-
-  options = {
-    services.mako = {
-      enable = lib.mkEnableOption ''
-        Mako, lightweight notification daemon for Wayland
-      '';
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mako;
-        defaultText = lib.literalExpression "pkgs.mako";
-        description = "The mako package to use.";
-      };
-
-      maxVisible = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of visible notifications. Set -1 to show all.
-        '';
-      };
-
-      maxHistory = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of expired notifications to keep in the history
-          buffer. Set 0 to disable history.
-        '';
-      };
-
-      sort = mkOption {
-        default = "-time";
-        type = types.nullOr (
-          types.enum [
-            "+time"
-            "-time"
-            "+priority"
-            "-priority"
-          ]
-        );
-        description = ''
-          Sorts incoming notifications by time and/or priority in ascending(+)
-          or descending(-) order.
-        '';
-      };
-
-      output = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Show notifications on the specified output. If empty, notifications
-          will appear on the focused output. Requires the compositor to support
-          the Wayland protocol xdg-output-unstable-v1 version 2.
-        '';
-      };
-
-      layer = mkOption {
-        default = "top";
-        type = types.nullOr (
-          types.enum [
-            "background"
-            "bottom"
-            "top"
-            "overlay"
-          ]
-        );
-        description = ''
-          Arrange mako at the specified layer, relative to normal windows.
-          Supported values are background, bottom, top, and overlay. Using
-          overlay will cause notifications to be displayed above fullscreen
-          windows, though this may also occur at top depending on your
-          compositor.
-        '';
-      };
-
-      anchor = mkOption {
-        default = "top-right";
-        type = types.nullOr (
-          types.enum [
-            "top-right"
-            "top-center"
-            "top-left"
-            "bottom-right"
-            "bottom-center"
-            "bottom-left"
-            "center-right"
-            "center-left"
-            "center"
-          ]
-        );
-        description = ''
-          Show notifications at the specified position on the output.
-          Supported values are top-right, top-center, top-left, bottom-right,
-          bottom-center, bottom-left, center-right, center-left and center.
-        '';
-      };
-
-      font = mkOption {
-        default = "monospace 10";
-        type = types.nullOr types.str;
-        description = ''
-          Font to use, in Pango format.
-        '';
-      };
-
-      backgroundColor = mkOption {
-        default = "#285577FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup background color to a specific color, represented in hex
-          color code.
-        '';
-      };
-
-      textColor = mkOption {
-        default = "#FFFFFFFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup text color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      width = mkOption {
-        default = 300;
-        type = types.nullOr types.int;
-        description = ''
-          Set width of notification popups in specified number of pixels.
-        '';
-      };
-
-      height = mkOption {
-        default = 100;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum height of notification popups. Notifications whose text
-          takes up less space are shrunk to fit.
-        '';
-      };
-
-      margin = mkOption {
-        default = "10";
-        type = types.nullOr types.str;
-        description = ''
-          Set margin of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      padding = mkOption {
-        default = "5";
-        type = types.nullOr types.str;
-        description = ''
-          Set padding of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      borderSize = mkOption {
-        default = 1;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup border size to the specified number of pixels.
-        '';
-      };
-
-      borderColor = mkOption {
-        default = "#4C7899FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup border color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      borderRadius = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup corner radius to the specified number of pixels.
-        '';
-      };
-
-      progressColor = mkOption {
-        default = "over #5588AAFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup progress indicator color to a specific color,
-          represented in hex color code. To draw the progress
-          indicator on top of the background color, use the
-          `over` attribute. To replace the background
-          color, use the `source` attribute (this can
-          be useful when the notification is semi-transparent).
-        '';
-      };
-
-      icons = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Whether or not to show icons in notifications.
-        '';
-      };
-
-      maxIconSize = mkOption {
-        default = 64;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum icon size to the specified number of pixels.
-        '';
-      };
-
-      iconPath = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Paths to search for icons when a notification specifies a name
-          instead of a full path. Colon-delimited. This approximates the search
-          algorithm used by the XDG Icon Theme Specification, but does not
-          support any of the theme metadata. Therefore, if you want to search
-          parent themes, you'll need to add them to the path manually.
-
-          The {file}`/usr/share/icons/hicolor` and
-          {file}`/usr/share/pixmaps` directories are
-          always searched.
-        '';
-      };
-
-      markup = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          If 1, enable Pango markup. If 0, disable Pango markup. If enabled,
-          Pango markup will be interpreted in your format specifier and in the
-          body of notifications.
-        '';
-      };
-
-      actions = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Applications may request an action to be associated with activating a
-          notification. Disabling this will cause mako to ignore these requests.
-        '';
-      };
-
-      format = mkOption {
-        default = "<b>%s</b>\\n%b";
-        type = types.nullOr types.str;
-        description = ''
-          Set notification format string to format. See FORMAT SPECIFIERS for
-          more information. To change this for grouped notifications, set it
-          within a grouped criteria.
-        '';
-      };
-
-      defaultTimeout = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set the default timeout to timeout in milliseconds. To disable the
-          timeout, set it to zero.
-        '';
-      };
+  imports =
+    let
+      basePath = [
+        "services"
+        "mako"
+      ];
 
-      ignoreTimeout = mkOption {
-        default = false;
-        type = types.nullOr types.bool;
-        description = ''
-          If set, mako will ignore the expire timeout sent by notifications
-          and use the one provided by default-timeout instead.
-        '';
-      };
+      removedOptions = [
+        (lib.mkRemovedOptionModule [
+          "services"
+          "mako"
+          "extraConfig"
+        ] "Use services.mako.settings instead.")
+      ];
 
-      groupBy = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          A comma-separated list of criteria fields that will be compared to
-          other visible notifications to determine if this one should form a
-          group with them. All listed criteria must be exactly equal for two
-          notifications to group.
-        '';
-      };
+      renamedOptions = [
+        "maxVisible"
+        "maxHistory"
+        "sort"
+        "output"
+        "layer"
+        "anchor"
+        "font"
+        "backgroundColor"
+        "textColor"
+        "width"
+        "height"
+        "margin"
+        "padding"
+        "borderSize"
+        "borderColor"
+        "borderRadius"
+        "progressColor"
+        "icons"
+        "maxIconSize"
+        "iconPath"
+        "markup"
+        "actions"
+        "format"
+        "defaultTimeout"
+        "ignoreTimeout"
+        "groupBy"
+      ];
 
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        example = lib.literalExpression ''
-          [urgency=low]
-          border-color=#b8bb26
-        '';
-        description = "Additional configuration.";
-      };
+      mkSettingsRenamedOptionModules =
+        oldPrefix: newPrefix:
+        map (option: lib.mkRenamedOptionModule (oldPrefix ++ [ option ]) (newPrefix ++ [ option ]));
+    in
+    removedOptions
+    ++ mkSettingsRenamedOptionModules basePath (basePath ++ [ "settings" ]) renamedOptions;
+
+  options.services.mako = {
+    enable = mkEnableOption "mako";
+    package = mkPackageOption pkgs "mako" { };
+    settings = mkOption {
+      type = settingsType;
+      default = { };
+      example = ''
+        {
+          actions = "true";
+          anchor = "top-right";
+          backgroundColor = "#000000";
+          borderColor = "#FFFFFF";
+          borderRadius = "0";
+          defaultTimeout = "0";
+          font = "monospace 10";
+          height = "100";
+          width = "300";
+          icons = "true";
+          ignoreTimeout = "false";
+          layer = "top";
+          margin = "10";
+          markup = "true";
+        }
+      '';
+      description = ''
+        Configuration settings for mako. All available options can be found
+        here: <https://github.com/emersion/mako/blob/master/doc/mako.5.scd>.
+      '';
+    };
+    criterias = mkOption {
+      type = criteriasType;
+      default = { };
+      example = {
+        "actionable=true" = {
+          anchor = "top-left";
+        };
+
+        "app-name=Google\\ Chrome" = {
+          max-visible = "5";
+        };
+
+        "field1=value field2=value" = {
+          text-alignment = "left";
+        };
+      };
+      description = ''
+        Criterias for mako's config. All the details can be found in the
+        CRITERIA section in the official documentation.
+      '';
     };
   };
 
-  config =
-    let
-      boolToString = v: if v then "true" else "false";
-      optionalBoolean = name: val: lib.optionalString (val != null) "${name}=${boolToString val}";
-      optionalInteger = name: val: lib.optionalString (val != null) "${name}=${toString val}";
-      optionalString = name: val: lib.optionalString (val != null) "${name}=${val}";
-    in
-    lib.mkIf cfg.enable {
-      assertions = [
-        (lib.hm.assertions.assertPlatform "services.mako" pkgs lib.platforms.linux)
-      ];
-
-      home.packages = [ cfg.package ];
+  config = lib.mkIf cfg.enable {
+    assertions = [
+      (lib.hm.assertions.assertPlatform "services.mako" pkgs lib.platforms.linux)
+    ];
 
-      xdg.configFile."mako/config" = {
-        onChange = ''
-          ${cfg.package}/bin/makoctl reload || true
-        '';
-        text = ''
-          ${optionalInteger "max-visible" cfg.maxVisible}
-          ${optionalInteger "max-history" cfg.maxHistory}
-          ${optionalString "sort" cfg.sort}
-          ${optionalString "output" cfg.output}
-          ${optionalString "layer" cfg.layer}
-          ${optionalString "anchor" cfg.anchor}
+    home.packages = [ cfg.package ];
 
-          ${optionalString "font" cfg.font}
-          ${optionalString "background-color" cfg.backgroundColor}
-          ${optionalString "text-color" cfg.textColor}
-          ${optionalInteger "width" cfg.width}
-          ${optionalInteger "height" cfg.height}
-          ${optionalString "margin" cfg.margin}
-          ${optionalString "padding" cfg.padding}
-          ${optionalInteger "border-size" cfg.borderSize}
-          ${optionalString "border-color" cfg.borderColor}
-          ${optionalInteger "border-radius" cfg.borderRadius}
-          ${optionalString "progress-color" cfg.progressColor}
-          ${optionalBoolean "icons" cfg.icons}
-          ${optionalInteger "max-icon-size" cfg.maxIconSize}
-          ${optionalString "icon-path" cfg.iconPath}
-          ${optionalBoolean "markup" cfg.markup}
-          ${optionalBoolean "actions" cfg.actions}
-          ${optionalString "format" cfg.format}
-          ${optionalInteger "default-timeout" cfg.defaultTimeout}
-          ${optionalBoolean "ignore-timeout" cfg.ignoreTimeout}
-          ${optionalString "group-by" cfg.groupBy}
-
-          ${cfg.extraConfig}
-        '';
+    xdg.configFile."mako/config" = mkIf (cfg.settings != { } || cfg.criterias != { }) {
+      onChange = "${cfg.package}/bin/makoctl reload || true";
+      text = generateConfig {
+        globalSection = cfg.settings;
+        sections = cfg.criterias;
       };
     };
+  };
 }
diff --git a/tests/default.nix b/tests/default.nix
index f94d7eccfc5c..a359f7597814 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -437,6 +437,7 @@ import nmtSrc {
       ./modules/services/lieer
       ./modules/services/linux-wallpaperengine
       ./modules/services/lxqt-policykit-agent
+      ./modules/services/mako
       ./modules/services/mopidy
       ./modules/services/mpd
       ./modules/services/mpd-mpris
diff --git a/tests/modules/services/mako/config b/tests/modules/services/mako/config
new file mode 100644
index 000000000000..8cfda5eb9e69
--- /dev/null
+++ b/tests/modules/services/mako/config
@@ -0,0 +1,23 @@
+actions=true
+anchor=top-right
+backgroundColor=#000000
+borderColor=#FFFFFF
+borderRadius=0
+defaultTimeout=0
+font=monospace 10
+height=100
+icons=true
+ignoreTimeout=false
+layer=top
+margin=10
+markup=true
+width=300
+
+[actionable=true]
+anchor=top-left
+
+[app-name=Google\ Chrome]
+max-visible=5
+
+[field1=value field2=value]
+text-alignment=left
diff --git a/tests/modules/services/mako/default.nix b/tests/modules/services/mako/default.nix
new file mode 100644
index 000000000000..b839429b4f78
--- /dev/null
+++ b/tests/modules/services/mako/default.nix
@@ -0,0 +1 @@
+{ mako-example-config = ./example-config.nix; }
diff --git a/tests/modules/services/mako/example-config.nix b/tests/modules/services/mako/example-config.nix
new file mode 100644
index 000000000000..137a3b7bfda6
--- /dev/null
+++ b/tests/modules/services/mako/example-config.nix
@@ -0,0 +1,41 @@
+{
+  services.mako = {
+    enable = true;
+    settings = {
+      actions = "true";
+      anchor = "top-right";
+      backgroundColor = "#000000";
+      borderColor = "#FFFFFF";
+      borderRadius = "0";
+      defaultTimeout = "0";
+      font = "monospace 10";
+      height = "100";
+      width = "300";
+      icons = "true";
+      ignoreTimeout = "false";
+      layer = "top";
+      margin = "10";
+      markup = "true";
+    };
+
+    criterias = {
+      "actionable=true" = {
+        anchor = "top-left";
+      };
+
+      "app-name=Google\\ Chrome" = {
+        max-visible = "5";
+      };
+
+      "field1=value field2=value" = {
+        text-alignment = "left";
+      };
+    };
+  };
+
+  nmt.script = ''
+    assertFileExists home-files/.config/mako/config
+    assertFileContent home-files/.config/mako/config \
+    ${./config}
+  '';
+}


Reviews:
user: login: khaneliman
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T13:27:33Z
user: login: khaneliman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T16:12:24Z
user: login: khaneliman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-02T18:56:03Z
user: login: aguirre-matteo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T19:15:58Z
user: login: khaneliman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T04:07:34Z
user: login: khaneliman
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-03T04:08:24Z
user: login: aguirre-matteo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T17:58:48Z

Review comments:
diff_hunk: @@ -5,371 +5,79 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
 in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
   imports = [ (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ]) ];
 
-  options = {
-    services.mako = {
-      enable = lib.mkEnableOption ''
-        Mako, lightweight notification daemon for Wayland
+  options.services.mako = {
+    enable = mkEnableOption "mako";
+    package = mkPackageOption pkgs "mako" { };
+    settings = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      example = ''
+        {
+          actions = "true";
+          anchor = "top-right";
+          backgroundColor = "#000000";
+          borderColor = "#FFFFFF";
+          borderRadius = "0";
+          defaultTimeout = "0";
+          font = "monospace 10";
+          height = "100";
+          width = "300";
+          icons = "true";
+          ignoreTimeout = "false";
+          layer = "top";
+          margin = "10";
+          markup = "true";
+        }
+      '';
+      description = ''
+        Configuration settings for mako. All available options can be found
+        here: <https://github.com/emersion/mako/blob/master/doc/mako.5.scd>.
+      '';
+    };
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        [mode=do-not-disturb]
+        invisible=1
+
+        [app-name="Google Chrome"]
+        max-visible=1
+        history=0
+      '';
+      description = ''
+        Extra configuration lines to be appended at the end of the file.
       '';
path: modules/services/mako.nix
user: login: khaneliman
body: ```suggestion
```

If we have a freeform option, we shouldn't need this. 
created_at: 2025-05-02T13:25:56Z
author_association: COLLABORATOR
diff_hunk: @@ -5,371 +5,79 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
 in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
   imports = [ (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ]) ];
 
-  options = {
-    services.mako = {
-      enable = lib.mkEnableOption ''
-        Mako, lightweight notification daemon for Wayland
+  options.services.mako = {
+    enable = mkEnableOption "mako";
+    package = mkPackageOption pkgs "mako" { };
+    settings = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      example = ''
+        {
+          actions = "true";
+          anchor = "top-right";
+          backgroundColor = "#000000";
+          borderColor = "#FFFFFF";
+          borderRadius = "0";
+          defaultTimeout = "0";
+          font = "monospace 10";
+          height = "100";
+          width = "300";
+          icons = "true";
+          ignoreTimeout = "false";
+          layer = "top";
+          margin = "10";
+          markup = "true";
+        }
+      '';
+      description = ''
+        Configuration settings for mako. All available options can be found
+        here: <https://github.com/emersion/mako/blob/master/doc/mako.5.scd>.
+      '';
+    };
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        [mode=do-not-disturb]
+        invisible=1
+
+        [app-name="Google Chrome"]
+        max-visible=1
+        history=0
+      '';
+      description = ''
+        Extra configuration lines to be appended at the end of the file.
       '';
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mako;
-        defaultText = lib.literalExpression "pkgs.mako";
-        description = "The mako package to use.";
-      };
-
-      maxVisible = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of visible notifications. Set -1 to show all.
-        '';
-      };
-
-      maxHistory = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of expired notifications to keep in the history
-          buffer. Set 0 to disable history.
-        '';
-      };
-
-      sort = mkOption {
-        default = "-time";
-        type = types.nullOr (
-          types.enum [
-            "+time"
-            "-time"
-            "+priority"
-            "-priority"
-          ]
-        );
-        description = ''
-          Sorts incoming notifications by time and/or priority in ascending(+)
-          or descending(-) order.
-        '';
-      };
-
-      output = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Show notifications on the specified output. If empty, notifications
-          will appear on the focused output. Requires the compositor to support
-          the Wayland protocol xdg-output-unstable-v1 version 2.
-        '';
-      };
-
-      layer = mkOption {
-        default = "top";
-        type = types.nullOr (
-          types.enum [
-            "background"
-            "bottom"
-            "top"
-            "overlay"
-          ]
-        );
-        description = ''
-          Arrange mako at the specified layer, relative to normal windows.
-          Supported values are background, bottom, top, and overlay. Using
-          overlay will cause notifications to be displayed above fullscreen
-          windows, though this may also occur at top depending on your
-          compositor.
-        '';
-      };
-
-      anchor = mkOption {
-        default = "top-right";
-        type = types.nullOr (
-          types.enum [
-            "top-right"
-            "top-center"
-            "top-left"
-            "bottom-right"
-            "bottom-center"
-            "bottom-left"
-            "center-right"
-            "center-left"
-            "center"
-          ]
-        );
-        description = ''
-          Show notifications at the specified position on the output.
-          Supported values are top-right, top-center, top-left, bottom-right,
-          bottom-center, bottom-left, center-right, center-left and center.
-        '';
-      };
-
-      font = mkOption {
-        default = "monospace 10";
-        type = types.nullOr types.str;
-        description = ''
-          Font to use, in Pango format.
-        '';
-      };
-
-      backgroundColor = mkOption {
-        default = "#285577FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup background color to a specific color, represented in hex
-          color code.
-        '';
-      };
-
-      textColor = mkOption {
-        default = "#FFFFFFFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup text color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      width = mkOption {
-        default = 300;
-        type = types.nullOr types.int;
-        description = ''
-          Set width of notification popups in specified number of pixels.
-        '';
-      };
-
-      height = mkOption {
-        default = 100;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum height of notification popups. Notifications whose text
-          takes up less space are shrunk to fit.
-        '';
-      };
-
-      margin = mkOption {
-        default = "10";
-        type = types.nullOr types.str;
-        description = ''
-          Set margin of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      padding = mkOption {
-        default = "5";
-        type = types.nullOr types.str;
-        description = ''
-          Set padding of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      borderSize = mkOption {
-        default = 1;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup border size to the specified number of pixels.
-        '';
-      };
-
-      borderColor = mkOption {
-        default = "#4C7899FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup border color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      borderRadius = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup corner radius to the specified number of pixels.
-        '';
-      };
-
-      progressColor = mkOption {
-        default = "over #5588AAFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup progress indicator color to a specific color,
-          represented in hex color code. To draw the progress
-          indicator on top of the background color, use the
-          `over` attribute. To replace the background
-          color, use the `source` attribute (this can
-          be useful when the notification is semi-transparent).
-        '';
-      };
-
-      icons = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Whether or not to show icons in notifications.
-        '';
-      };
-
-      maxIconSize = mkOption {
-        default = 64;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum icon size to the specified number of pixels.
-        '';
-      };
-
-      iconPath = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Paths to search for icons when a notification specifies a name
-          instead of a full path. Colon-delimited. This approximates the search
-          algorithm used by the XDG Icon Theme Specification, but does not
-          support any of the theme metadata. Therefore, if you want to search
-          parent themes, you'll need to add them to the path manually.
-
-          The {file}`/usr/share/icons/hicolor` and
-          {file}`/usr/share/pixmaps` directories are
-          always searched.
-        '';
-      };
-
-      markup = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          If 1, enable Pango markup. If 0, disable Pango markup. If enabled,
-          Pango markup will be interpreted in your format specifier and in the
-          body of notifications.
-        '';
-      };
-
-      actions = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Applications may request an action to be associated with activating a
-          notification. Disabling this will cause mako to ignore these requests.
-        '';
-      };
-
-      format = mkOption {
-        default = "<b>%s</b>\\n%b";
-        type = types.nullOr types.str;
-        description = ''
-          Set notification format string to format. See FORMAT SPECIFIERS for
-          more information. To change this for grouped notifications, set it
-          within a grouped criteria.
-        '';
-      };
-
-      defaultTimeout = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set the default timeout to timeout in milliseconds. To disable the
-          timeout, set it to zero.
-        '';
-      };
-
-      ignoreTimeout = mkOption {
-        default = false;
-        type = types.nullOr types.bool;
-        description = ''
-          If set, mako will ignore the expire timeout sent by notifications
-          and use the one provided by default-timeout instead.
-        '';
-      };
-
-      groupBy = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          A comma-separated list of criteria fields that will be compared to
-          other visible notifications to determine if this one should form a
-          group with them. All listed criteria must be exactly equal for two
-          notifications to group.
-        '';
-      };
path: modules/services/mako.nix
user: login: khaneliman
body: Use `mkRenamedOptionModule` to redirect the old options to the new settings option path. 
created_at: 2025-05-02T13:26:55Z
author_association: COLLABORATOR
diff_hunk: @@ -5,371 +5,79 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
 in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
   imports = [ (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ]) ];
 
-  options = {
-    services.mako = {
-      enable = lib.mkEnableOption ''
-        Mako, lightweight notification daemon for Wayland
+  options.services.mako = {
+    enable = mkEnableOption "mako";
+    package = mkPackageOption pkgs "mako" { };
+    settings = mkOption {
+      type = with types; attrsOf str;
+      default = { };
+      example = ''
+        {
+          actions = "true";
+          anchor = "top-right";
+          backgroundColor = "#000000";
+          borderColor = "#FFFFFF";
+          borderRadius = "0";
+          defaultTimeout = "0";
+          font = "monospace 10";
+          height = "100";
+          width = "300";
+          icons = "true";
+          ignoreTimeout = "false";
+          layer = "top";
+          margin = "10";
+          markup = "true";
+        }
+      '';
+      description = ''
+        Configuration settings for mako. All available options can be found
+        here: <https://github.com/emersion/mako/blob/master/doc/mako.5.scd>.
+      '';
+    };
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        [mode=do-not-disturb]
+        invisible=1
+
+        [app-name="Google Chrome"]
+        max-visible=1
+        history=0
+      '';
+      description = ''
+        Extra configuration lines to be appended at the end of the file.
       '';
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mako;
-        defaultText = lib.literalExpression "pkgs.mako";
-        description = "The mako package to use.";
-      };
-
-      maxVisible = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of visible notifications. Set -1 to show all.
-        '';
-      };
-
-      maxHistory = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of expired notifications to keep in the history
-          buffer. Set 0 to disable history.
-        '';
-      };
-
-      sort = mkOption {
-        default = "-time";
-        type = types.nullOr (
-          types.enum [
-            "+time"
-            "-time"
-            "+priority"
-            "-priority"
-          ]
-        );
-        description = ''
-          Sorts incoming notifications by time and/or priority in ascending(+)
-          or descending(-) order.
-        '';
-      };
-
-      output = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Show notifications on the specified output. If empty, notifications
-          will appear on the focused output. Requires the compositor to support
-          the Wayland protocol xdg-output-unstable-v1 version 2.
-        '';
-      };
-
-      layer = mkOption {
-        default = "top";
-        type = types.nullOr (
-          types.enum [
-            "background"
-            "bottom"
-            "top"
-            "overlay"
-          ]
-        );
-        description = ''
-          Arrange mako at the specified layer, relative to normal windows.
-          Supported values are background, bottom, top, and overlay. Using
-          overlay will cause notifications to be displayed above fullscreen
-          windows, though this may also occur at top depending on your
-          compositor.
-        '';
-      };
-
-      anchor = mkOption {
-        default = "top-right";
-        type = types.nullOr (
-          types.enum [
-            "top-right"
-            "top-center"
-            "top-left"
-            "bottom-right"
-            "bottom-center"
-            "bottom-left"
-            "center-right"
-            "center-left"
-            "center"
-          ]
-        );
-        description = ''
-          Show notifications at the specified position on the output.
-          Supported values are top-right, top-center, top-left, bottom-right,
-          bottom-center, bottom-left, center-right, center-left and center.
-        '';
-      };
-
-      font = mkOption {
-        default = "monospace 10";
-        type = types.nullOr types.str;
-        description = ''
-          Font to use, in Pango format.
-        '';
-      };
-
-      backgroundColor = mkOption {
-        default = "#285577FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup background color to a specific color, represented in hex
-          color code.
-        '';
-      };
-
-      textColor = mkOption {
-        default = "#FFFFFFFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup text color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      width = mkOption {
-        default = 300;
-        type = types.nullOr types.int;
-        description = ''
-          Set width of notification popups in specified number of pixels.
-        '';
-      };
-
-      height = mkOption {
-        default = 100;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum height of notification popups. Notifications whose text
-          takes up less space are shrunk to fit.
-        '';
-      };
-
-      margin = mkOption {
-        default = "10";
-        type = types.nullOr types.str;
-        description = ''
-          Set margin of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      padding = mkOption {
-        default = "5";
-        type = types.nullOr types.str;
-        description = ''
-          Set padding of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      borderSize = mkOption {
-        default = 1;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup border size to the specified number of pixels.
-        '';
-      };
-
-      borderColor = mkOption {
-        default = "#4C7899FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup border color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      borderRadius = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup corner radius to the specified number of pixels.
-        '';
-      };
-
-      progressColor = mkOption {
-        default = "over #5588AAFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup progress indicator color to a specific color,
-          represented in hex color code. To draw the progress
-          indicator on top of the background color, use the
-          `over` attribute. To replace the background
-          color, use the `source` attribute (this can
-          be useful when the notification is semi-transparent).
-        '';
-      };
-
-      icons = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Whether or not to show icons in notifications.
-        '';
-      };
-
-      maxIconSize = mkOption {
-        default = 64;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum icon size to the specified number of pixels.
-        '';
-      };
-
-      iconPath = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Paths to search for icons when a notification specifies a name
-          instead of a full path. Colon-delimited. This approximates the search
-          algorithm used by the XDG Icon Theme Specification, but does not
-          support any of the theme metadata. Therefore, if you want to search
-          parent themes, you'll need to add them to the path manually.
-
-          The {file}`/usr/share/icons/hicolor` and
-          {file}`/usr/share/pixmaps` directories are
-          always searched.
-        '';
-      };
-
-      markup = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          If 1, enable Pango markup. If 0, disable Pango markup. If enabled,
-          Pango markup will be interpreted in your format specifier and in the
-          body of notifications.
-        '';
-      };
-
-      actions = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Applications may request an action to be associated with activating a
-          notification. Disabling this will cause mako to ignore these requests.
-        '';
-      };
-
-      format = mkOption {
-        default = "<b>%s</b>\\n%b";
-        type = types.nullOr types.str;
-        description = ''
-          Set notification format string to format. See FORMAT SPECIFIERS for
-          more information. To change this for grouped notifications, set it
-          within a grouped criteria.
-        '';
-      };
-
-      defaultTimeout = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set the default timeout to timeout in milliseconds. To disable the
-          timeout, set it to zero.
-        '';
-      };
-
-      ignoreTimeout = mkOption {
-        default = false;
-        type = types.nullOr types.bool;
-        description = ''
-          If set, mako will ignore the expire timeout sent by notifications
-          and use the one provided by default-timeout instead.
-        '';
-      };
-
-      groupBy = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          A comma-separated list of criteria fields that will be compared to
-          other visible notifications to determine if this one should form a
-          group with them. All listed criteria must be exactly equal for two
-          notifications to group.
-        '';
-      };
-
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        example = lib.literalExpression ''
-          [urgency=low]
-          border-color=#b8bb26
-        '';
-        description = "Additional configuration.";
-      };
path: modules/services/mako.nix
user: login: khaneliman
body: If we have a freeform settings option, we can use `mkRemovedOptionModule` instead and redirect users to the settings option. 
created_at: 2025-05-02T13:27:23Z
author_association: COLLABORATOR
diff_hunk: @@ -5,371 +5,144 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
 in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
-  imports = [ (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ]) ];
-
-  options = {
-    services.mako = {
-      enable = lib.mkEnableOption ''
-        Mako, lightweight notification daemon for Wayland
+  imports = [
+    (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ])
path: modules/services/mako.nix
user: login: khaneliman
body: ```suggestion
```
Looks like it was already in services? 
created_at: 2025-05-02T16:02:21Z
author_association: COLLABORATOR
diff_hunk: @@ -5,371 +5,144 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
 in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
-  imports = [ (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ]) ];
-
-  options = {
-    services.mako = {
-      enable = lib.mkEnableOption ''
-        Mako, lightweight notification daemon for Wayland
+  imports = [
+    (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ])
+
+    (lib.mkRemovedOptionModule [
+      "services"
+      "mako"
+      "extraConfig"
+    ] "Use services.mako.settings instead.")
+
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "maxVisible" ]
+      [ "services" "mako" "settings" "maxVisible" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "maxHistory" ]
+      [ "services" "mako" "settings" "maxHistory" ]
+    )
+    (lib.mkRenamedOptionModule [ "services" "mako" "sort" ] [ "services" "mako" "settings" "sort" ])
+    (lib.mkRenamedOptionModule [ "services" "mako" "output" ] [ "services" "mako" "settings" "output" ])
+    (lib.mkRenamedOptionModule [ "services" "mako" "layer" ] [ "services" "mako" "settings" "layer" ])
+    (lib.mkRenamedOptionModule [ "services" "mako" "anchor" ] [ "services" "mako" "settings" "anchor" ])
+    (lib.mkRenamedOptionModule [ "services" "mako" "font" ] [ "services" "mako" "settings" "font" ])
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "backgroundColor" ]
+      [ "services" "mako" "settings" "backgroundColor" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "textColor" ]
+      [ "services" "mako" "settings" "textColor" ]
+    )
+    (lib.mkRenamedOptionModule [ "services" "mako" "width" ] [ "services" "mako" "settings" "width" ])
+    (lib.mkRenamedOptionModule [ "services" "mako" "height" ] [ "services" "mako" "settings" "height" ])
+    (lib.mkRenamedOptionModule [ "services" "mako" "margin" ] [ "services" "mako" "settings" "margin" ])
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "padding" ]
+      [ "services" "mako" "settings" "padding" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "borderSize" ]
+      [ "services" "mako" "settings" "borderSize" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "borderColor" ]
+      [ "services" "mako" "settings" "borderColor" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "borderRadius" ]
+      [ "services" "mako" "settings" "borderRadius" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "progressColor" ]
+      [ "services" "mako" "settings" "progressColor" ]
+    )
+    (lib.mkRenamedOptionModule [ "services" "mako" "icons" ] [ "services" "mako" "settings" "icons" ])
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "maxIconSize" ]
+      [ "services" "mako" "settings" "maxIconSize" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "iconPath" ]
+      [ "services" "mako" "settings" "iconPath" ]
+    )
+    (lib.mkRenamedOptionModule [ "services" "mako" "markup" ] [ "services" "mako" "settings" "markup" ])
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "actions" ]
+      [ "services" "mako" "settings" "actions" ]
+    )
+    (lib.mkRenamedOptionModule [ "services" "mako" "format" ] [ "services" "mako" "settings" "format" ])
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "defaultTimeout" ]
+      [ "services" "mako" "settings" "defaultTimeout" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "ignoreTimeout" ]
+      [ "services" "mako" "settings" "ignoreTimeout" ]
+    )
+    (lib.mkRenamedOptionModule
+      [ "services" "mako" "groupBy" ]
+      [ "services" "mako" "settings" "groupBy" ]
+    )
path: modules/services/mako.nix
user: login: khaneliman
body: This could be simplified to reduce size a bit. I implemented something similar in Nixvim to reduce verbosity. 

```nix
basePath = [
  "services"
  "mako"
];

renamedOptions = [
"maxVisible"
...
...
];

mkSettingsRenamedOptionModules =
    oldPrefix: newPrefix:
    map (
      spec:
      let
        finalSpec =
          if lib.isAttrs spec then
            lib.mapAttrs (_: lib.toList) spec
          else
            {
              old = lib.toList spec;
              new = map finalSpec.old;
            };
      in
      lib.mkRenamedOptionModule (oldPrefix ++ finalSpec.old) (newPrefix ++ finalSpec.new)
  );

imports = [] ++ mkSettingsRenamedOptionModules basePath (basePath ++ [ "settings" ])  renamedOptions;
```
created_at: 2025-05-02T16:12:20Z
author_association: COLLABORATOR
diff_hunk: @@ -20,87 +20,47 @@ in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
-  imports = [
-    (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ])
+  imports =
+    let
+      basePath = [
+        "services"
+        "mako"
+      ];
 
-    (lib.mkRemovedOptionModule [
-      "services"
-      "mako"
-      "extraConfig"
-    ] "Use services.mako.settings instead.")
path: modules/services/mako.nix
user: login: khaneliman
body: Looks like this got removed, by accident. 
created_at: 2025-05-02T18:56:03Z
author_association: COLLABORATOR
diff_hunk: @@ -20,87 +20,47 @@ in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
-  imports = [
-    (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ])
+  imports =
+    let
+      basePath = [
+        "services"
+        "mako"
+      ];
 
-    (lib.mkRemovedOptionModule [
-      "services"
-      "mako"
-      "extraConfig"
-    ] "Use services.mako.settings instead.")
path: modules/services/mako.nix
user: login: aguirre-matteo
body: Oops.
created_at: 2025-05-02T19:15:58Z
author_association: MEMBER
diff_hunk: @@ -5,371 +5,113 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
path: modules/services/mako.nix
user: login: khaneliman
body: Looking upstream, I think we need a different generator. They have a more INI style configuration that accepts top level non attrsets. 
created_at: 2025-05-03T04:07:34Z
author_association: COLLABORATOR
diff_hunk: @@ -5,371 +5,113 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
 in
 {
   meta.maintainers = [ lib.maintainers.onny ];
 
-  imports = [ (lib.mkRenamedOptionModule [ "programs" "mako" ] [ "services" "mako" ]) ];
-
-  options = {
-    services.mako = {
-      enable = lib.mkEnableOption ''
-        Mako, lightweight notification daemon for Wayland
-      '';
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.mako;
-        defaultText = lib.literalExpression "pkgs.mako";
-        description = "The mako package to use.";
-      };
-
-      maxVisible = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of visible notifications. Set -1 to show all.
-        '';
-      };
-
-      maxHistory = mkOption {
-        default = 5;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum number of expired notifications to keep in the history
-          buffer. Set 0 to disable history.
-        '';
-      };
-
-      sort = mkOption {
-        default = "-time";
-        type = types.nullOr (
-          types.enum [
-            "+time"
-            "-time"
-            "+priority"
-            "-priority"
-          ]
-        );
-        description = ''
-          Sorts incoming notifications by time and/or priority in ascending(+)
-          or descending(-) order.
-        '';
-      };
-
-      output = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Show notifications on the specified output. If empty, notifications
-          will appear on the focused output. Requires the compositor to support
-          the Wayland protocol xdg-output-unstable-v1 version 2.
-        '';
-      };
-
-      layer = mkOption {
-        default = "top";
-        type = types.nullOr (
-          types.enum [
-            "background"
-            "bottom"
-            "top"
-            "overlay"
-          ]
-        );
-        description = ''
-          Arrange mako at the specified layer, relative to normal windows.
-          Supported values are background, bottom, top, and overlay. Using
-          overlay will cause notifications to be displayed above fullscreen
-          windows, though this may also occur at top depending on your
-          compositor.
-        '';
-      };
-
-      anchor = mkOption {
-        default = "top-right";
-        type = types.nullOr (
-          types.enum [
-            "top-right"
-            "top-center"
-            "top-left"
-            "bottom-right"
-            "bottom-center"
-            "bottom-left"
-            "center-right"
-            "center-left"
-            "center"
-          ]
-        );
-        description = ''
-          Show notifications at the specified position on the output.
-          Supported values are top-right, top-center, top-left, bottom-right,
-          bottom-center, bottom-left, center-right, center-left and center.
-        '';
-      };
-
-      font = mkOption {
-        default = "monospace 10";
-        type = types.nullOr types.str;
-        description = ''
-          Font to use, in Pango format.
-        '';
-      };
-
-      backgroundColor = mkOption {
-        default = "#285577FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup background color to a specific color, represented in hex
-          color code.
-        '';
-      };
-
-      textColor = mkOption {
-        default = "#FFFFFFFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup text color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      width = mkOption {
-        default = 300;
-        type = types.nullOr types.int;
-        description = ''
-          Set width of notification popups in specified number of pixels.
-        '';
-      };
-
-      height = mkOption {
-        default = 100;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum height of notification popups. Notifications whose text
-          takes up less space are shrunk to fit.
-        '';
-      };
-
-      margin = mkOption {
-        default = "10";
-        type = types.nullOr types.str;
-        description = ''
-          Set margin of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      padding = mkOption {
-        default = "5";
-        type = types.nullOr types.str;
-        description = ''
-          Set padding of each edge specified in pixels. Specify single value to
-          apply margin on all sides. Two comma-separated values will set
-          vertical and horizontal edges separately. Four comma-separated will
-          give each edge a separate value.
-          For example: 10,20,5 will set top margin to 10, left and right to 20
-          and bottom to five.
-        '';
-      };
-
-      borderSize = mkOption {
-        default = 1;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup border size to the specified number of pixels.
-        '';
-      };
-
-      borderColor = mkOption {
-        default = "#4C7899FF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup border color to a specific color, represented in hex color
-          code.
-        '';
-      };
-
-      borderRadius = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set popup corner radius to the specified number of pixels.
-        '';
-      };
-
-      progressColor = mkOption {
-        default = "over #5588AAFF";
-        type = types.nullOr types.str;
-        description = ''
-          Set popup progress indicator color to a specific color,
-          represented in hex color code. To draw the progress
-          indicator on top of the background color, use the
-          `over` attribute. To replace the background
-          color, use the `source` attribute (this can
-          be useful when the notification is semi-transparent).
-        '';
-      };
-
-      icons = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Whether or not to show icons in notifications.
-        '';
-      };
-
-      maxIconSize = mkOption {
-        default = 64;
-        type = types.nullOr types.int;
-        description = ''
-          Set maximum icon size to the specified number of pixels.
-        '';
-      };
-
-      iconPath = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          Paths to search for icons when a notification specifies a name
-          instead of a full path. Colon-delimited. This approximates the search
-          algorithm used by the XDG Icon Theme Specification, but does not
-          support any of the theme metadata. Therefore, if you want to search
-          parent themes, you'll need to add them to the path manually.
-
-          The {file}`/usr/share/icons/hicolor` and
-          {file}`/usr/share/pixmaps` directories are
-          always searched.
-        '';
-      };
-
-      markup = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          If 1, enable Pango markup. If 0, disable Pango markup. If enabled,
-          Pango markup will be interpreted in your format specifier and in the
-          body of notifications.
-        '';
-      };
-
-      actions = mkOption {
-        default = true;
-        type = types.nullOr types.bool;
-        description = ''
-          Applications may request an action to be associated with activating a
-          notification. Disabling this will cause mako to ignore these requests.
-        '';
-      };
-
-      format = mkOption {
-        default = "<b>%s</b>\\n%b";
-        type = types.nullOr types.str;
-        description = ''
-          Set notification format string to format. See FORMAT SPECIFIERS for
-          more information. To change this for grouped notifications, set it
-          within a grouped criteria.
-        '';
-      };
-
-      defaultTimeout = mkOption {
-        default = 0;
-        type = types.nullOr types.int;
-        description = ''
-          Set the default timeout to timeout in milliseconds. To disable the
-          timeout, set it to zero.
-        '';
-      };
+  imports =
+    let
+      basePath = [
+        "services"
+        "mako"
+      ];
 
-      ignoreTimeout = mkOption {
-        default = false;
-        type = types.nullOr types.bool;
-        description = ''
-          If set, mako will ignore the expire timeout sent by notifications
-          and use the one provided by default-timeout instead.
-        '';
-      };
+      removedOptions = [
+        (lib.mkRemovedOptionModule [
+          "services"
+          "mako"
+          "extraConfig"
+        ] "Use services.mako.settings instead.")
+      ];
 
-      groupBy = mkOption {
-        default = null;
-        type = types.nullOr types.str;
-        description = ''
-          A comma-separated list of criteria fields that will be compared to
-          other visible notifications to determine if this one should form a
-          group with them. All listed criteria must be exactly equal for two
-          notifications to group.
-        '';
-      };
+      renamedOptions = [
+        "maxVisible"
+        "maxHistory"
+        "sort"
+        "output"
+        "layer"
+        "anchor"
+        "font"
+        "backgroundColor"
+        "textColor"
+        "width"
+        "height"
+        "margin"
+        "padding"
+        "borderSize"
+        "borderColor"
+        "borderRadius"
+        "progressColor"
+        "icons"
+        "maxIconSize"
+        "iconPath"
+        "markup"
+        "actions"
+        "format"
+        "defaultTimeout"
+        "ignoreTimeout"
+        "groupBy"
+      ];
 
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        example = lib.literalExpression ''
-          [urgency=low]
-          border-color=#b8bb26
-        '';
-        description = "Additional configuration.";
-      };
+      mkSettingsRenamedOptionModules =
+        oldPrefix: newPrefix:
+        map (option: lib.mkRenamedOptionModule (oldPrefix ++ [ option ]) (newPrefix ++ [ option ]));
+    in
+    removedOptions
+    ++ mkSettingsRenamedOptionModules basePath (basePath ++ [ "settings" ]) renamedOptions;
+
+  options.services.mako = {
+    enable = mkEnableOption "mako";
+    package = mkPackageOption pkgs "mako" { };
+    settings = mkOption {
+      type = with types; attrsOf str;
path: modules/services/mako.nix
user: login: khaneliman
body: This wouldn't support an attrset for sections 

```
   [urgency=low]
   border-color=#b8bb26
```
created_at: 2025-05-03T04:08:24Z
author_association: COLLABORATOR
diff_hunk: @@ -5,371 +5,113 @@
   ...
 }:
 let
-  inherit (lib) mkOption types;
+  inherit (lib)
+    types
+    mkIf
+    mkEnableOption
+    mkPackageOption
+    mkOption
+    ;
 
   cfg = config.services.mako;
 
+  generateConfig = lib.generators.toKeyValue { };
path: modules/services/mako.nix
user: login: aguirre-matteo
body: Ok. I switched to `lib.generators.toINIWithGlobalSection` and added an option dedicated to the criterias (subsections) of Mako's cfg so the end user doesn't have to do:

```nix
services.mako = {
  settings = {
    globalSection = {...};
    sections = {...};
  };
};
```

But instead:

```nix
services.mako = {
  settings = {...};
  criterias = {...};
};
```

Since the second is more intuitive.
created_at: 2025-05-03T17:58:48Z
author_association: MEMBER

PR comments:
user: login: khaneliman
created_at: 2025-05-03T18:32:09Z
body: Looks pretty good, ideally `criterias` could just be attributes inside `settings` option support with a more robust generator. But, that can be done later, if someone wants. 
author_association: COLLABORATOR
user: login: 0xda157
created_at: 2025-05-04T03:03:47Z
body: btw criterias is a typo, criteria is the plural of criterion 
author_association: CONTRIBUTOR
user: login: ghost
created_at: 2025-05-05T00:24:58Z
body: This isn't backwards compatible, since all options are now required to be strings, by the way. Is that intentional?
author_association: NONE
user: login: khaneliman
created_at: 2025-05-05T00:38:51Z
body: > This isn't backwards compatible, since all options are now required to be strings, by the way. Is that intentional?

No, we should accept more types. The generator should be able to convert other types. 
author_association: COLLABORATOR
user: login: RaitoBezarius
created_at: 2025-05-10T16:01:16Z
body: For the next time, can you avoid deprecating escape options like `extraConfig` if you are not certain you will maintain backward compatible with the new set of options? Otherwise, this is a bit difficult for end users to upgrade and then we have to pile up patches.
author_association: MEMBER
user: login: matthiasbeyer
created_at: 2025-06-26T07:40:14Z
body: Why was this config replaced in the first place?

author_association: CONTRIBUTOR
user: login: aguirre-matteo
created_at: 2025-06-28T15:41:45Z
body: > Why was this config replaced in the first place?
> 

Because the original module hardcoded the options (maxVisible, maxHistory, ... ) instead of using a single, more flexible option to configure everything. Before:

```nix
{
  services.mako = {
    maxVisible = ...;
    maxHistory = ...;
    newOption = ...; # This will fail, since such option doesn't exists
  };
}
```

Now:

```nix
{
  services.mako = {
    settings = {
      max-visible = ...;
      max-history = ...;
      new-option = ...; # Will work
    };
  };
}
```

This reduces significantly the work for maintainers, since they don't have to modify the module every time a new config option is added to mako (upstream).
author_association: MEMBER
user: login: matthiasbeyer
created_at: 2025-06-29T10:10:45Z
body: Ah, I thought you have to use `extraConfig` now to specify a string. I didn't get that you can just use everything as a nix attribute set! Awesome indeed, thanks for that change! 
author_association: CONTRIBUTOR