diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 3f37aec0..bf1be1a2 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -137,6 +137,9 @@
/modules/services/imapnotify.nix @nickhu
+/modules/services/kanshi.nix @nurelin
+/tests/modules/services/kanshi @nurelin
+
/modules/services/kdeconnect.nix @adisbladis
/modules/services/keepassx.nix @rycee
diff --git a/modules/misc/news.nix b/modules/misc/news.nix
index 4ec6fc2d..94ce35bd 100644
--- a/modules/misc/news.nix
+++ b/modules/misc/news.nix
@@ -1627,6 +1627,14 @@ in
A new module is available: 'programs.waybar'
'';
}
+
+ {
+ time = "2020-08-14T22:44:20+00:00";
+ condition = hostPlatform.isLinux;
+ message = ''
+ A new module is available: 'services.kanshi'
+ '';
+ }
];
};
}
diff --git a/modules/modules.nix b/modules/modules.nix
index 41877c5e..1744b4fd 100644
--- a/modules/modules.nix
+++ b/modules/modules.nix
@@ -140,6 +140,7 @@ let
(loadModule ./services/grobi.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/hound.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/imapnotify.nix { condition = hostPlatform.isLinux; })
+ (loadModule ./services/kanshi.nix { condition = hostPlatform.isLinux; })
(loadModule ./services/kbfs.nix { })
(loadModule ./services/kdeconnect.nix { })
(loadModule ./services/keepassx.nix { })
diff --git a/modules/services/kanshi.nix b/modules/services/kanshi.nix
new file mode 100644
index 00000000..e54b83aa
--- /dev/null
+++ b/modules/services/kanshi.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+ cfg = config.services.kanshi;
+
+ outputModule = types.submodule {
+ options = {
+
+ criteria = mkOption {
+ type = types.str;
+ description = ''
+ The criteria can either be an output name, an output description or "*".
+ The latter can be used to match any output.
+
+ On
+
+ sway
+ 1
+ ,
+ output names and descriptions can be obtained via
+ swaymsg -t get_outputs.
+ '';
+ };
+
+ status = mkOption {
+ type = types.nullOr (types.enum [ "enable" "disable" ]);
+ default = null;
+ description = ''
+ Enables or disables the specified output.
+ '';
+ };
+
+ mode = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1920x1080@60Hz";
+ description = ''
+ <width>x<height>[@<rate>[Hz]]
+
+ Configures the specified output to use the specified mode.
+ Modes are a combination of width and height (in pixels) and
+ a refresh rate (in Hz) that your display can be configured to use.
+ '';
+ };
+
+ position = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example = "1600,0";
+ description = ''
+ <x>,<y>
+
+ Places the output at the specified position in the global coordinates
+ space.
+ '';
+ };
+
+ scale = mkOption {
+ type = types.nullOr types.float;
+ default = null;
+ example = 2;
+ description = ''
+ Scales the output by the specified scale factor.
+ '';
+ };
+
+ transform = mkOption {
+ type = types.nullOr (types.enum [
+ "normal"
+ "90"
+ "180"
+ "270"
+ "flipped"
+ "flipped-90"
+ "flipped-180"
+ "flipped-270"
+ ]);
+ default = null;
+ description = ''
+ Sets the output transform.
+ '';
+ };
+ };
+ };
+
+ outputStr = { criteria, status, mode, position, scale, transform, ... }:
+ ''output "${criteria}"'' + optionalString (status != null) " ${status}"
+ + optionalString (mode != null) " mode ${mode}"
+ + optionalString (position != null) " position ${position}"
+ + optionalString (scale != null) " scale ${toString scale}"
+ + optionalString (transform != null) " transform ${transform}";
+
+ profileModule = types.submodule {
+ options = {
+ outputs = mkOption {
+ type = types.listOf outputModule;
+ default = [ ];
+ description = ''
+ Outputs configuration.
+ '';
+ };
+
+ exec = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ example =
+ "\${pkg.sway}/bin/swaymsg workspace 1, move workspace to eDP-1";
+ description = ''
+ Command executed after the profile is succesfully applied.
+ '';
+ };
+ };
+ };
+
+ profileStr = name:
+ { outputs, exec, ... }:
+ ''
+ profile ${name} {
+ ${concatStringsSep "\n " (map outputStr outputs)}
+ '' + optionalString (exec != null) " ${exec}" + ''
+ }
+ '';
+in {
+
+ meta.maintainers = [ maintainers.nurelin ];
+
+ options.services.kanshi = {
+ enable = mkEnableOption
+ "kanshi, a Wayland daemon that automatically configures outputs";
+
+ package = mkOption {
+ type = types.package;
+ default = pkgs.kanshi;
+ defaultText = literalExample "pkgs.kanshi";
+ description = ''
+ kanshi derivation to use.
+ '';
+ };
+
+ profiles = mkOption {
+ type = types.attrsOf profileModule;
+ default = { };
+ description = ''
+ List of profiles.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = types.lines;
+ default = "";
+ description = ''
+ Extra configuration lines to append to the kanshi
+ configuration file.
+ '';
+ };
+
+ systemdTarget = mkOption {
+ type = types.str;
+ default = "sway-session.target";
+ description = ''
+ Systemd target to bind to.
+ '';
+ };
+ };
+
+ config = mkIf cfg.enable {
+
+ xdg.configFile."kanshi/config".text = ''
+ ${concatStringsSep "\n" (mapAttrsToList profileStr cfg.profiles)}
+ ${cfg.extraConfig}
+ '';
+
+ 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 ]; };
+ };
+ };
+}
diff --git a/tests/default.nix b/tests/default.nix
index 939a50e2..3aa0c544 100644
--- a/tests/default.nix
+++ b/tests/default.nix
@@ -83,6 +83,7 @@ import nmt {
./modules/services/lieer
./modules/programs/rofi
./modules/programs/waybar
+ ./modules/services/kanshi
./modules/services/polybar
./modules/services/sxhkd
./modules/services/fluidsynth
diff --git a/tests/modules/services/kanshi/basic-configuration.conf b/tests/modules/services/kanshi/basic-configuration.conf
new file mode 100644
index 00000000..905a539d
--- /dev/null
+++ b/tests/modules/services/kanshi/basic-configuration.conf
@@ -0,0 +1,14 @@
+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
+}
+
+profile nomad {
+ output "eDP-1" enable
+}
+
+profile test {
+ output "*" enable
+}
+
diff --git a/tests/modules/services/kanshi/basic-configuration.nix b/tests/modules/services/kanshi/basic-configuration.nix
new file mode 100644
index 00000000..08a2c167
--- /dev/null
+++ b/tests/modules/services/kanshi/basic-configuration.nix
@@ -0,0 +1,51 @@
+{ config, pkgs, ... }: {
+ config = {
+ services.kanshi = {
+ enable = true;
+ package = pkgs.writeScriptBin "dummy-kanshi" "";
+ profiles = {
+ nomad = {
+ outputs = [{
+ criteria = "eDP-1";
+ status = "enable";
+ }];
+ };
+ desktop = {
+ 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";
+ }
+ ];
+ };
+ };
+ extraConfig = ''
+ profile test {
+ output "*" enable
+ }
+ '';
+ };
+
+ nmt.script = ''
+ serviceFile=home-files/.config/systemd/user/kanshi.service
+ assertFileExists $serviceFile
+
+ assertFileExists home-files/.config/kanshi/config
+ assertFileContent home-files/.config/kanshi/config \
+ ${./basic-configuration.conf}
+ '';
+ };
+}
diff --git a/tests/modules/services/kanshi/default.nix b/tests/modules/services/kanshi/default.nix
new file mode 100644
index 00000000..cb6b2a6b
--- /dev/null
+++ b/tests/modules/services/kanshi/default.nix
@@ -0,0 +1 @@
+{ kanshi-basic-configuration = ./basic-configuration.nix; }