From 4855bfb6ce20225a1b0e2aae2379da909ab38350 Mon Sep 17 00:00:00 2001
From: Vincent Gatine <nurelin@nurelin.eu>
Date: Thu, 9 May 2024 18:04:48 +0200
Subject: [PATCH] kanshi: update configuration to better match upstream

---
 modules/services/kanshi.nix                   | 175 ++++++++++++++----
 .../services/kanshi/basic-configuration.nix   |   5 +
 tests/modules/services/kanshi/default.nix     |   5 +-
 .../services/kanshi/new-configuration.conf    |  19 ++
 .../services/kanshi/new-configuration.nix     |  63 +++++++
 5 files changed, 231 insertions(+), 36 deletions(-)
 create mode 100644 tests/modules/services/kanshi/new-configuration.conf
 create mode 100644 tests/modules/services/kanshi/new-configuration.nix

diff --git a/modules/services/kanshi.nix b/modules/services/kanshi.nix
index 02fe8e3cf..1e6950304 100644
--- a/modules/services/kanshi.nix
+++ b/modules/services/kanshi.nix
@@ -6,6 +6,48 @@ let
 
   cfg = config.services.kanshi;
 
+  directivesTag = types.attrTag {
+    profile = mkOption {
+      type = profileModule;
+      description = ''
+        profile attribute set.
+      '';
+    };
+    output = mkOption {
+      type = outputModule;
+      description = ''
+        output attribute set.
+      '';
+    };
+    include = mkOption {
+      type = types.str;
+      description = ''
+        Include as another file from _path_.
+        Expands shell syntax (see *wordexp*(3) for details).
+      '';
+    };
+  };
+
+  tagToStr = x:
+    if x ? profile then
+      profileStr x.profile
+    else if x ? output then
+      outputStr x.output
+    else if x ? include then
+      ''include "${x.include}"''
+    else
+      throw "Unknown tags ${attrNames x}";
+
+  directivesStr = ''
+    ${concatStringsSep "\n" (map tagToStr cfg.settings)}
+  '';
+
+  oldDirectivesStr = ''
+    ${concatStringsSep "\n"
+    (mapAttrsToList (n: v: profileStr (v // { name = n; })) cfg.profiles)}
+    ${cfg.extraConfig}
+  '';
+
   outputModule = types.submodule {
     options = {
 
@@ -113,6 +155,14 @@ let
         '';
       };
 
+      name = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Profile name
+        '';
+      };
+
       exec = mkOption {
         type = with types; coercedTo str singleton (listOf str);
         default = [ ];
@@ -127,15 +177,14 @@ let
     };
   };
 
-  profileStr = name:
-    { outputs, exec, ... }: ''
-      profile ${name} {
-        ${
-          concatStringsSep "\n  "
-          (map outputStr outputs ++ map (cmd: "exec ${cmd}") exec)
-        }
+  profileStr = { outputs, exec, ... }@args: ''
+    profile ${args.name or ""} {
+      ${
+        concatStringsSep "\n  "
+        (map outputStr outputs ++ map (cmd: "exec ${cmd}") exec)
       }
-    '';
+    }
+  '';
 in {
 
   meta.maintainers = [ hm.maintainers.nurelin ];
@@ -157,7 +206,7 @@ in {
       type = types.attrsOf profileModule;
       default = { };
       description = ''
-        List of profiles.
+        Attribute set of profiles.
       '';
       example = literalExpression ''
         undocked = {
@@ -190,6 +239,39 @@ in {
       '';
     };
 
+    settings = mkOption {
+      type = types.listOf directivesTag;
+      default = [ ];
+      description = ''
+        Ordered list of directives.
+        See kanshi(5) for informations.
+      '';
+      example = literalExpression ''
+        { include = "path/to/included/files"; }
+        { output.criteria = "eDP-1";
+          output.scale = 2;
+        }
+        { profile.name = "undocked";
+          profile.outputs = [
+            {
+              criteria = "eDP-1";
+            }
+          ];
+        }
+        { profile.name = "docked";
+          profile.outputs = [
+            {
+              criteria = "eDP-1";
+            }
+            {
+              criteria = "Some Company ASDF 4242";
+              transform = "90";
+            }
+          ];
+        }
+      '';
+    };
+
     systemdTarget = mkOption {
       type = types.str;
       default = "sway-session.target";
@@ -199,33 +281,56 @@ in {
     };
   };
 
-  config = mkIf cfg.enable {
-    assertions = [
-      (lib.hm.assertions.assertPlatform "services.kanshi" pkgs
-        lib.platforms.linux)
-    ];
+  config = mkIf cfg.enable (mkMerge [
+    {
+      assertions = [
+        (lib.hm.assertions.assertPlatform "services.kanshi" pkgs
+          lib.platforms.linux)
+        {
+          assertion = (cfg.profiles == { } && cfg.extraConfig == "")
+            || (length cfg.settings) == 0;
+          message =
+            "Cannot mix kanshi.settings with kanshi.profiles or kanshi.extraConfig";
+        }
+      ];
+    }
 
-    xdg.configFile."kanshi/config".text = ''
-      ${concatStringsSep "\n" (mapAttrsToList profileStr cfg.profiles)}
-      ${cfg.extraConfig}
-    '';
+    (mkIf (cfg.profiles != { }) {
+      warnings = [
+        "kanshi.profiles option is deprecated. Use kanshi.settings instead."
+      ];
+    })
 
-    systemd.user.services.kanshi = {
-      Unit = {
-        Description = "Dynamic output configuration";
-        Documentation = "man:kanshi(1)";
-        PartOf = cfg.systemdTarget;
-        Requires = cfg.systemdTarget;
-        After = cfg.systemdTarget;
+    (mkIf (cfg.extraConfig != "") {
+      warnings = [
+        "kanshi.extraConfig option is deprecated. Use kanshi.settings instead."
+      ];
+    })
+
+    {
+      xdg.configFile."kanshi/config".text =
+        if cfg.profiles == { } && cfg.extraConfig == "" then
+          directivesStr
+        else
+          oldDirectivesStr;
+
+      systemd.user.services.kanshi = {
+        Unit = {
+          Description = "Dynamic output configuration";
+          Documentation = "man:kanshi(1)";
+          PartOf = cfg.systemdTarget;
+          Requires = cfg.systemdTarget;
+          After = cfg.systemdTarget;
+        };
+
+        Service = {
+          Type = "simple";
+          ExecStart = "${cfg.package}/bin/kanshi";
+          Restart = "always";
+        };
+
+        Install = { WantedBy = [ cfg.systemdTarget ]; };
       };
-
-      Service = {
-        Type = "simple";
-        ExecStart = "${cfg.package}/bin/kanshi";
-        Restart = "always";
-      };
-
-      Install = { WantedBy = [ cfg.systemdTarget ]; };
-    };
-  };
+    }
+  ]);
 }
diff --git a/tests/modules/services/kanshi/basic-configuration.nix b/tests/modules/services/kanshi/basic-configuration.nix
index c5706e635..ffb6db7b1 100644
--- a/tests/modules/services/kanshi/basic-configuration.nix
+++ b/tests/modules/services/kanshi/basic-configuration.nix
@@ -47,6 +47,11 @@
       '';
     };
 
+    test.asserts.warnings.expected = [
+      "kanshi.profiles option is deprecated. Use kanshi.settings instead."
+      "kanshi.extraConfig option is deprecated. Use kanshi.settings instead."
+    ];
+
     nmt.script = ''
       serviceFile=home-files/.config/systemd/user/kanshi.service
       assertFileExists $serviceFile
diff --git a/tests/modules/services/kanshi/default.nix b/tests/modules/services/kanshi/default.nix
index cb6b2a6b7..b7704b112 100644
--- a/tests/modules/services/kanshi/default.nix
+++ b/tests/modules/services/kanshi/default.nix
@@ -1 +1,4 @@
-{ kanshi-basic-configuration = ./basic-configuration.nix; }
+{
+  kanshi-basic-configuration = ./basic-configuration.nix;
+  kanshi-new-configuration = ./new-configuration.nix;
+}
diff --git a/tests/modules/services/kanshi/new-configuration.conf b/tests/modules/services/kanshi/new-configuration.conf
new file mode 100644
index 000000000..c0858a048
--- /dev/null
+++ b/tests/modules/services/kanshi/new-configuration.conf
@@ -0,0 +1,19 @@
+include "path/to/included/file"
+output "*" enable
+profile nomad {
+  output "eDP-1" enable
+}
+
+profile desktop {
+  output "eDP-1" disable
+  output "Iiyama North America PLE2483H-DP" enable position 0,0
+  output "Iiyama North America PLE2483H-DP 1158765348486" enable mode 1920x1080 position 1920,0 scale 2.100000 transform flipped-270
+  exec echo "1 two 3"
+  exec echo "4 five 6"
+}
+
+profile  {
+  output "LVDS-1" enable
+  exec echo "7 eight 9"
+}
+
diff --git a/tests/modules/services/kanshi/new-configuration.nix b/tests/modules/services/kanshi/new-configuration.nix
new file mode 100644
index 000000000..9b3a36478
--- /dev/null
+++ b/tests/modules/services/kanshi/new-configuration.nix
@@ -0,0 +1,63 @@
+{ config, pkgs, ... }: {
+  config = {
+    services.kanshi = {
+      enable = true;
+      package = config.lib.test.mkStubPackage { };
+      settings = [
+        { include = "path/to/included/file"; }
+        {
+          output = {
+            criteria = "*";
+            status = "enable";
+          };
+        }
+        {
+          profile.name = "nomad";
+          profile.outputs = [{
+            criteria = "eDP-1";
+            status = "enable";
+          }];
+        }
+        {
+          profile.name = "desktop";
+          profile.exec = [ ''echo "1 two 3"'' ''echo "4 five 6"'' ];
+          profile.outputs = [
+            {
+              criteria = "eDP-1";
+              status = "disable";
+            }
+            {
+              criteria = "Iiyama North America PLE2483H-DP";
+              status = "enable";
+              position = "0,0";
+            }
+            {
+              criteria = "Iiyama North America PLE2483H-DP 1158765348486";
+              status = "enable";
+              position = "1920,0";
+              scale = 2.1;
+              mode = "1920x1080";
+              transform = "flipped-270";
+            }
+          ];
+        }
+        {
+          profile.outputs = [{
+            criteria = "LVDS-1";
+            status = "enable";
+          }];
+          profile.exec = ''echo "7 eight 9"'';
+        }
+      ];
+    };
+
+    nmt.script = ''
+      serviceFile=home-files/.config/systemd/user/kanshi.service
+      assertFileExists $serviceFile
+
+      assertFileExists home-files/.config/kanshi/config
+      assertFileContent home-files/.config/kanshi/config \
+                ${./new-configuration.conf}
+    '';
+  };
+}