diff --git a/README.md b/README.md index 219d2bd..84a885e 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ At the moment `plasma-manager` supports configuring the following: - Panels (via the `panels` module) - Screen locker (via the `kscreenlocker` module) - Fonts (via the `fonts` module) +- Window Rules (via the `window-rules` module) - KDE apps (via the `apps` module). In particular the following kde apps have modules in `plasma-manager`: - kate diff --git a/examples/home.nix b/examples/home.nix index 555768d..c0d67db 100644 --- a/examples/home.nix +++ b/examples/home.nix @@ -104,6 +104,28 @@ } ]; + window-rules = [ + { + description = "Dolphin"; + match = { + window-class = { + value = "dolphin"; + type = "substring"; + }; + window-types = [ "normal" ]; + }; + apply = { + noborder = { + value = true; + apply = "force"; + }; + # `apply` defaults to "apply-initially" + maximizehoriz = true; + maximizevert = true; + }; + } + ]; + # # Some mid-level settings: diff --git a/modules/default.nix b/modules/default.nix index 4f3394b..c24ed46 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -13,6 +13,7 @@ ./spectacle.nix ./startup.nix ./windows.nix + ./window-rules.nix ./workspace.nix ]; diff --git a/modules/files.nix b/modules/files.nix index e6db4ce..6c1d98d 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -58,7 +58,7 @@ let "plasmarc" "plasmashellrc" "systemsettingsrc" - ] else [ ]); + ] else lib.optional (builtins.length plasmaCfg.window-rules > 0) "kwinrulesrc"); in { options.programs.plasma = { diff --git a/modules/window-rules.nix b/modules/window-rules.nix new file mode 100644 index 0000000..aeef308 --- /dev/null +++ b/modules/window-rules.nix @@ -0,0 +1,164 @@ +{ lib +, pkgs +, config +, ... +}: +with lib.types; let + inherit (builtins) length listToAttrs foldl' toString attrNames getAttr hasAttr concatStringsSep add isAttrs; + inherit (lib) mkOption mkIf; + inherit (lib.trivial) mergeAttrs; + inherit (lib.lists) imap0; + inherit (lib.attrsets) optionalAttrs filterAttrs mapAttrsToList; + cfg = config.programs.plasma; + applyRules = { + "do-not-affect" = 1; + "force" = 2; + "initially" = 3; + "remember" = 4; + }; + matchRules = { + "exact" = 1; + "substring" = 2; + "regex" = 3; + }; + windowTypes = { + normal = 1; + desktop = 2; + dock = 4; + toolbar = 8; + torn-of-menu = 16; + dialog = 32; + menubar = 128; + utility = 256; + spash = 512; + osd = 65536; + }; + matchNameMap = { + "window-class" = "wmclass"; + "window-types" = "types"; + }; + matchOptionType = hasMatchWhole: + submodule { + options = + { + value = mkOption { + type = str; + description = "${name} to match"; + }; + type = mkOption { + type = enum (attrNames matchRules); + default = "exact"; + description = "${name} match type"; + }; + } + // optionalAttrs hasMatchWhole { + match-whole = mkOption { + type = bool; + default = true; + description = "Match whole ${name}"; + }; + }; + }; + basicValueType = oneOf [ bool float int str ]; + applyOptionType = submodule { + options = { + value = mkOption { + type = basicValueType; + description = "value to set"; + }; + apply = mkOption { + type = enum (attrNames applyRules); + default = "initially"; + description = "how to apply the value"; + }; + }; + }; + mkMatchOption = name: hasMatchWhole: + mkOption { + type = nullOr (coercedTo str (value: { inherit value; }) (matchOptionType hasMatchWhole)); + default = null; + description = "${name} matching"; + }; + fixMatchName = name: matchNameMap.${name} or name; + buildMatchRule = name: rule: ({ + "${fixMatchName name}" = rule.value; + "${fixMatchName name}match" = getAttr rule.type matchRules; + } + // optionalAttrs (rule ? match-whole) { + "${fixMatchName name}complete" = rule.match-whole; + }); + buildApplyRule = name: rule: { + "${name}" = rule.value; + "${name}rule" = getAttr rule.apply applyRules; + }; + buildWindowRule = rule: + let + matchOptions = filterAttrs (_name: isAttrs) rule.match; + matchRules = mapAttrsToList buildMatchRule matchOptions; + applyRules = mapAttrsToList buildApplyRule rule.apply; + combinedRules = foldl' mergeAttrs { } (matchRules ++ applyRules); + in + { + Description = rule.description; + } + // optionalAttrs (rule.match.window-types != 0) { + types = rule.match.window-types; + } + // combinedRules; + windowRules = listToAttrs (imap0 + (i: rule: { + name = toString (i + 1); + value = buildWindowRule rule; + }) + cfg.window-rules); +in +{ + options.programs.plasma = { + window-rules = mkOption { + type = listOf (submodule { + options = { + match = mkOption { + type = submodule { + options = { + window-class = mkMatchOption "Window class" true; + window-role = mkMatchOption "Window role" false; + title = mkMatchOption "Title" false; + machine = mkMatchOption "clientmachine" false; + window-types = mkOption { + type = listOf (enum (attrNames windowTypes)); + default = [ ]; + description = "Window types to match"; + apply = values: foldl' add 0 (map (val: getAttr val windowTypes) values); + }; + }; + }; + }; + apply = mkOption { + type = attrsOf (coercedTo basicValueType (value: { inherit value; }) applyOptionType); + default = { }; + description = "Values to apply"; + }; + description = mkOption { + type = str; + description = "value to set"; + }; + }; + }); + description = "Kwin window rules"; + default = [ ]; + }; + }; + + config = mkIf (length cfg.window-rules > 0) { + programs.plasma.configFile = { + kwinrulesrc = + { + General = { + count = length cfg.window-rules; + rules = concatStringsSep "," (attrNames windowRules); + }; + } + // windowRules; + }; + }; +}