{ 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 = with types; coercedTo str singleton (listOf str); default = [ ]; example = "[ \${pkg.sway}/bin/swaymsg workspace 1, move workspace to eDP-1 ]"; description = '' Commands executed after the profile is successfully applied. Note that if you provide multiple commands, they will be executed asynchronously with no guaranteed ordering. ''; }; }; }; profileStr = name: { outputs, exec, ... }: '' profile ${name} { ${ concatStringsSep "\n " (map outputStr outputs ++ map (cmd: "exec ${cmd}") exec) } } ''; in { meta.maintainers = [ hm.maintainers.nurelin ]; options.services.kanshi = { enable = mkEnableOption "kanshi, a Wayland daemon that automatically configures outputs"; package = mkOption { type = types.package; default = pkgs.kanshi; defaultText = literalExpression "pkgs.kanshi"; description = '' kanshi derivation to use. ''; }; profiles = mkOption { type = types.attrsOf profileModule; default = { }; description = '' List of profiles. ''; example = literalExpression '' undocked = { outputs = [ { criteria = "eDP-1"; } ]; }; docked = { outputs = [ { criteria = "eDP-1"; } { criteria = "Some Company ASDF 4242"; transform = "90"; } ]; }; ''; }; 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 { assertions = [ (lib.hm.assertions.assertPlatform "services.kanshi" pkgs lib.platforms.linux) ]; 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 ]; }; }; }; }