From ce3bb0072c9d9e69c8a4f4ac5f5ec886c1851cf9 Mon Sep 17 00:00:00 2001 From: magnouvean Date: Mon, 25 Mar 2024 13:48:41 +0100 Subject: [PATCH 1/3] Initial try to add support for immutable settings --- modules/files.nix | 57 ++++++++++++++++++++++-------------------- modules/kwin.nix | 4 +-- modules/shortcuts.nix | 16 ++++++------ modules/windows.nix | 2 +- modules/workspace.nix | 4 +-- script/rc2nix.rb | 8 +++--- script/write_config.py | 5 ++++ 7 files changed, 53 insertions(+), 43 deletions(-) diff --git a/modules/files.nix b/modules/files.nix index 335cc808..437c9460 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -16,38 +16,40 @@ let (prependPath config.xdg.configHome plasmaCfg.configFile) // (prependPath config.xdg.dataHome plasmaCfg.dataFile); - # Flatten nested sections - flatten = config: builtins.listToAttrs (lib.flatten (lib.mapAttrsToList - (n: v: - let - keySet = lib.filterAttrs (n: v: !builtins.isAttrs v) v; - sectionSet = flatten (lib.filterAttrs (n: v: builtins.isAttrs v) v); - in - lib.optionals (keySet != { }) [{ - name = n; - value = keySet; - }] ++ lib.mapAttrsToList - (group: v: { - name = "${n}][${group}"; - value = v; - }) - sectionSet) - config)); - - flatCfg = builtins.mapAttrs (file: flatten) cfg; + ############################################################################## + # Modify the settings to respect the different options in settingType. + isFinalValue = value: (builtins.hasAttr "value" value) && (!builtins.isAttrs value.value); + settingsModify = + (name: value: (if (!isFinalValue value) then + (lib.attrsets.nameValuePair name (lib.attrsets.mapAttrs' settingsModify value)) else + # If the value if set to be immutable, we need to add [$i] at the end of the + # key. + (lib.attrsets.nameValuePair "${name}${if value.immutable then ''[$i]'' else ''''}" value.value))); ############################################################################## - # A type for storing settings. - settingsFileType = with lib.types; let - valueType = attrsOf (nullOr (oneOf [ bool float int str valueType ])) // - { description = "Plasma settings"; }; - in - attrsOf (attrsOf (valueType)); + # Types for storing settings. + settingsValueType = (with lib.types; + nullOr (oneOf [ bool float int str ])); + settingType = (with lib.types; submodule { + options = { + value = lib.mkOption { + type = settingsValueType; + default = null; + description = "The value for some key."; + }; + immutable = lib.mkOption { + type = bool; + default = false; + description = "Whether to make the key immutable."; + }; + }; + }); + settingsFileType = with lib.types; attrsOf (attrsOf (attrsOf settingType)); ############################################################################## # Generate a script that will use write_config.py to update all # settings. - script = pkgs.writeScript "plasma-config" (writeConfig flatCfg); + script = pkgs.writeScript "plasma-config" (writeConfig (lib.attrsets.mapAttrs' settingsModify cfg)); ############################################################################## # Generate a script that will remove all the current config files. @@ -166,7 +168,8 @@ in home.activation.configure-plasma = (lib.hm.dag.entryAfter [ "writeBoundary" ] '' $DRY_RUN_CMD ${if plasmaCfg.overrideConfig then (createResetScript plasmaCfg) else ""} - $DRY_RUN_CMD ${script} + $DRY_RUN_CMD ${script} ''); }; } + diff --git a/modules/kwin.nix b/modules/kwin.nix index 9ebe58d9..5abdc2cb 100644 --- a/modules/kwin.nix +++ b/modules/kwin.nix @@ -77,12 +77,12 @@ in programs.plasma.configFile."kwinrc"."org.kde.kdecoration2" = mkMerge [ ( mkIf (cfg.kwin.titlebarButtons.left != null) { - "ButtonsOnLeft" = strings.concatStrings (getShortNames cfg.kwin.titlebarButtons.left); + "ButtonsOnLeft".value = strings.concatStrings (getShortNames cfg.kwin.titlebarButtons.left); } ) ( mkIf (cfg.kwin.titlebarButtons.right != null) { - "ButtonsOnRight" = strings.concatStrings (getShortNames cfg.kwin.titlebarButtons.right); + "ButtonsOnRight".value = strings.concatStrings (getShortNames cfg.kwin.titlebarButtons.right); } ) ]; diff --git a/modules/shortcuts.nix b/modules/shortcuts.nix index 0ad622b1..f7cf117b 100644 --- a/modules/shortcuts.nix +++ b/modules/shortcuts.nix @@ -5,7 +5,7 @@ let cfg = config.programs.plasma; # Convert one shortcut into a settings attribute set. - shortcutToNameValuePair = _action: skey: + shortcutToConfigValue = _action: skey: let # Keys are expected to be a list: keys = @@ -17,15 +17,17 @@ let # Don't allow un-escaped commas: escape = lib.escape [ "," ]; in - lib.concatStringsSep "," [ - (if ((builtins.length keys) == 1) then (escape (builtins.head keys)) else "\t" + (lib.concatStringsSep "\t" (map escape keys))) - "" # List of default keys, not needed. - "" # Display string, not needed. - ]; + { + value = lib.concatStringsSep "," [ + (if ((builtins.length keys) == 1) then (escape (builtins.head keys)) else "\t" + (lib.concatStringsSep "\t" (map escape keys))) + "" # List of default keys, not needed. + "" # Display string, not needed. + ]; + }; shortcutsToSettings = groups: lib.mapAttrs - (group: attrs: (lib.mapAttrs shortcutToNameValuePair attrs)) + (group: attrs: (lib.mapAttrs shortcutToConfigValue attrs)) groups; in { diff --git a/modules/windows.nix b/modules/windows.nix index e4a53462..02cdcc82 100644 --- a/modules/windows.nix +++ b/modules/windows.nix @@ -19,7 +19,7 @@ in config = lib.mkIf cfg.enable { programs.plasma.configFile = { kdeglobals = { - General.AllowKDEAppsToRememberWindowPositions = + General.AllowKDEAppsToRememberWindowPositions.value = lib.mkDefault cfg.windows.allowWindowsToRememberPositions; }; }; diff --git a/modules/workspace.nix b/modules/workspace.nix index d0bba199..f355f601 100644 --- a/modules/workspace.nix +++ b/modules/workspace.nix @@ -81,12 +81,12 @@ in config = lib.mkMerge [ (lib.mkIf cfg.enable { programs.plasma.configFile.kdeglobals = { - KDE.SingleClick = lib.mkDefault (cfg.workspace.clickItemTo == "open"); + KDE.SingleClick.value = lib.mkDefault (cfg.workspace.clickItemTo == "open"); }; }) (lib.mkIf (cfg.enable && cfg.workspace.tooltipDelay > 0) { programs.plasma.configFile.plasmarc = { - PlasmaToolTips.Delay = cfg.workspace.tooltipDelay; + PlasmaToolTips.Delay.value = cfg.workspace.tooltipDelay; }; }) (lib.mkIf diff --git a/script/rc2nix.rb b/script/rc2nix.rb index d97de637..76cff060 100755 --- a/script/rc2nix.rb +++ b/script/rc2nix.rb @@ -143,9 +143,9 @@ def parse ############################################################################ def parse_group(line) - line.gsub(/\s*\[([^\]]+)\]\s*/) do |match| - $1 + "\".\"" - end.sub(/\"."$/, '') + line.gsub("/", "\\\\\\/").gsub(/\s*\[([^\]]+)\]\s*/) do |match| + $1 + "/" + end.sub(/\/$/, '') end end @@ -206,7 +206,7 @@ def pp_settings(settings, indent) print(" " * indent) print("\"#{file}\".") print("\"#{group}\".") - print("\"#{key}\" = ") + print("\"#{key}\".value = ") print(nix_val(settings[file][group][key])) print(";\n") end diff --git a/script/write_config.py b/script/write_config.py index eab6b935..0970e9e9 100644 --- a/script/write_config.py +++ b/script/write_config.py @@ -106,6 +106,11 @@ def write_config_single(filepath: str, items: Dict): config = KConfParser(filepath) for group, entry in items.items(): + # The nix expressions will have / to separate groups. We replace this by + # ][ which is needed for the kde config files. If the / is escaped, it + # will simply be replaced by a normal /. + group = re.sub(r"(? Date: Tue, 26 Mar 2024 14:19:46 +0100 Subject: [PATCH 2/3] Add shellExpand option and update documentation --- README.md | 4 ++-- example/home.nix | 9 +++++++-- modules/files.nix | 30 ++++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 29b8ac57..d125d126 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Configuration is broken down into three layers: ```nix { programs.plasma = { - configFile."baloofilerc"."Basic Settings"."Indexing-Enabled" = false; + configFile."baloofilerc"."Basic Settings"."Indexing-Enabled".value = false; }; } ``` @@ -76,7 +76,7 @@ Configuration is broken down into three layers: configuration-writing script (which is very similar to `kwriteconfig5`). -An example is available in the `example` directory. +For some examples see the [example](./example/) directory. ## Capturing Your Current Configuration diff --git a/example/home.nix b/example/home.nix index 9f8768d8..7f05992e 100644 --- a/example/home.nix +++ b/example/home.nix @@ -64,8 +64,13 @@ # Some low-level settings: # configFile = { - "baloofilerc"."Basic Settings"."Indexing-Enabled" = false; - "kwinrc"."org.kde.kdecoration2"."ButtonsOnLeft" = "SF"; + "baloofilerc"."Basic Settings"."Indexing-Enabled".value = false; + "kwinrc"."org.kde.kdecoration2"."ButtonsOnLeft".value = "SF"; + "kwinrc"."Desktops"."Number" = { + value = 8; + # Forces kde to not change this value (even through the settings app). + immutable = true; + }; }; }; } diff --git a/modules/files.nix b/modules/files.nix index 437c9460..06d81a43 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -18,13 +18,24 @@ let ############################################################################## # Modify the settings to respect the different options in settingType. + # Checks if "value" represents the final value. isFinalValue = value: (builtins.hasAttr "value" value) && (!builtins.isAttrs value.value); + # Calculates the "marking" we should add to the keys, which for example may be + # [$i] if we want immutability, or [$e] if we want to expand variables. See + # https://api.kde.org/frameworks/kconfig/html/options.html for some options. + calculateKeyMarking = value: + if !(value.immutable || value.shellExpand) then "" else + ( + if (value.immutable && value.shellExpand) then "[$ei]" else + ( + # Here we know that exactly one of immutable or shellExpand is enabled. + if value.immutable then "[$i]" else "[$e]" + ) + ); settingsModify = (name: value: (if (!isFinalValue value) then (lib.attrsets.nameValuePair name (lib.attrsets.mapAttrs' settingsModify value)) else - # If the value if set to be immutable, we need to add [$i] at the end of the - # key. - (lib.attrsets.nameValuePair "${name}${if value.immutable then ''[$i]'' else ''''}" value.value))); + (lib.attrsets.nameValuePair "${name}${calculateKeyMarking value}" value.value))); ############################################################################## # Types for storing settings. @@ -40,7 +51,18 @@ let immutable = lib.mkOption { type = bool; default = false; - description = "Whether to make the key immutable."; + description = '' + Whether to make the key immutable. This corresponds to adding [$i] to + the end of the key. + ''; + }; + shellExpand = lib.mkOption { + type = bool; + default = false; + description = '' + Whether to mark the key for shell expansion. This corresponds to + adding [$e] to the end of the key. + ''; }; }; }); From 28d4aef95bfbec86518e6cce158bac3a2f2428e5 Mon Sep 17 00:00:00 2001 From: magnouvean Date: Wed, 27 Mar 2024 13:37:14 +0100 Subject: [PATCH 3/3] Fix konsole module and some other minor cleanups --- flake.lock | 6 +- lib/types.nix | 36 ++++++++++ modules/apps/konsole.nix | 151 ++++++++++++++++++--------------------- modules/files.nix | 50 +++---------- 4 files changed, 120 insertions(+), 123 deletions(-) create mode 100644 lib/types.nix diff --git a/flake.lock b/flake.lock index b9f6d4b6..98f63d30 100644 --- a/flake.lock +++ b/flake.lock @@ -23,11 +23,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1710951922, - "narHash": "sha256-FOOBJ3DQenLpTNdxMHR2CpGZmYuctb92gF0lpiirZ30=", + "lastModified": 1711124224, + "narHash": "sha256-l0zlN/3CiodvWDtfBOVxeTwYSRz93muVbXWSpaMjXxM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f091af045dff8347d66d186a62d42aceff159456", + "rev": "56528ee42526794d413d6f244648aaee4a7b56c0", "type": "github" }, "original": { diff --git a/lib/types.nix b/lib/types.nix new file mode 100644 index 00000000..0aaa5c4a --- /dev/null +++ b/lib/types.nix @@ -0,0 +1,36 @@ +{ lib, ... }: +let + ############################################################################## + # Types for storing settings. + basicSettingsType = (with lib.types; + nullOr (oneOf [ bool float int str ])); + advancedSettingsType = (with lib.types; submodule { + options = { + value = lib.mkOption { + type = basicSettingsType; + default = null; + description = "The value for some key."; + }; + immutable = lib.mkOption { + type = bool; + default = false; + description = '' + Whether to make the key immutable. This corresponds to adding [$i] to + the end of the key. + ''; + }; + shellExpand = lib.mkOption { + type = bool; + default = false; + description = '' + Whether to mark the key for shell expansion. This corresponds to + adding [$e] to the end of the key. + ''; + }; + }; + }); +in +{ + inherit basicSettingsType; + inherit advancedSettingsType; +} diff --git a/modules/apps/konsole.nix b/modules/apps/konsole.nix index d61e5160..43f7ffca 100644 --- a/modules/apps/konsole.nix +++ b/modules/apps/konsole.nix @@ -1,38 +1,37 @@ -{ config, lib, ... }: - -with lib; - +{ config, lib, pkgs, ... }: let + inherit (import ../../lib/types.nix { inherit lib; }) basicSettingsType; + cfg = config.programs.konsole; profilesSubmodule = { options = { - name = mkOption { - type = with types; nullOr str; + name = lib.mkOption { + type = with lib.types; nullOr str; default = null; description = '' Name of the profile. Defaults to the attribute name. ''; }; - colorScheme = mkOption { - type = with types; nullOr str; - default = "Breeze"; + colorScheme = lib.mkOption { + type = with lib.types; nullOr str; + default = null; example = "Catppuccin-Mocha"; description = '' Color scheme the profile will use. You can check the files you can use in ~/.local/share/konsole or /run/current-system/share/konsole ''; }; - command = mkOption { - type = with types; nullOr str; - default = "/run/current-system/sw/bin/bash"; + command = lib.mkOption { + type = with lib.types; nullOr str; + default = null; example = "''${pkgs.zsh}/bin/zsh"; description = '' The command to run on new sessions. ''; }; font = { - name = mkOption { - type = with types; nullOr str; + name = lib.mkOption { + type = lib.types.str; /* TODO: Set default to null after adding an assertion Konsole needs to have a font set to be able to change font size @@ -46,9 +45,9 @@ let Name of the font the profile should use. ''; }; - size = mkOption { + size = lib.mkOption { # The konsole ui gives you a limited range - type = with types; nullOr (ints.between 4 128); + type = (lib.types.ints.between 4 128); default = 10; example = 12; description = '' @@ -59,36 +58,16 @@ let }; }; }; - - # A module for storing settings. - settingType = { name, ... }: { - freeformType = with lib.types; - attrsOf (nullOr (oneOf [ bool float int str ])); - - options = { - configGroupNesting = lib.mkOption { - type = lib.types.nonEmptyListOf lib.types.str; - # We allow escaping periods using \\. - default = (map - (e: builtins.replaceStrings [ "\\u002E" ] [ "." ] e) - (lib.splitString "." - (builtins.replaceStrings [ "\\." ] [ "\\u002E" ] name) - ) - ); - description = "Group name, and sub-group names."; - }; - }; - }; in { options.programs.konsole = { - enable = mkEnableOption '' + enable = lib.mkEnableOption '' Enable configuration management for Konsole. ''; - - defaultProfile = mkOption { - type = with types; nullOr str; + + defaultProfile = lib.mkOption { + type = with lib.types; nullOr str; default = null; example = "Catppuccin"; description = '' @@ -97,16 +76,16 @@ in ''; }; - profiles = mkOption { - type = with types; nullOr (attrsOf (submodule profilesSubmodule)); - default = {}; + profiles = lib.mkOption { + type = with lib.types; nullOr (attrsOf (submodule profilesSubmodule)); + default = { }; description = '' Plasma profiles to generate. ''; }; - extraConfig = mkOption { - type = with types; nullOr (attrsOf (submodule settingType)); + extraConfig = lib.mkOption { + type = with lib.types; nullOr (attrsOf (attrsOf (basicSettingsType))); default = null; description = '' Extra config to add to konsolerc. @@ -114,49 +93,59 @@ in }; }; - config = mkIf (cfg.enable) { - programs.plasma.configFile."konsolerc" = mkMerge [ + config = lib.mkIf (cfg.enable) { + programs.plasma.configFile."konsolerc" = lib.mkMerge [ ( - mkIf (cfg.defaultProfile != null ) { - "Desktop Entry"."DefaultProfile" = cfg.defaultProfile; + lib.mkIf (cfg.defaultProfile != null) { + "Desktop Entry"."DefaultProfile".value = cfg.defaultProfile; } ) ( - mkIf (cfg.extraConfig != null) cfg.extraConfig + lib.mkIf (cfg.extraConfig != null) (lib.mapAttrs + (groupName: groupAttrs: + (lib.mapAttrs (keyName: keyAttrs: { value = keyAttrs; }) groupAttrs)) + cfg.extraConfig) ) ]; - xdg.dataFile = mkIf (cfg.profiles != {}) ( - mkMerge ([ - ( - mkMerge ( - mapAttrsToList ( - attrName: profile: - let - # Use the name from the name option if it's set - profileName = if builtins.isString profile.name then profile.name else attrName; - fontString = mkIf (profile.font.name != null) "${profile.font.name},${builtins.toString profile.font.size}"; - in - { - "konsole/${profileName}.profile".text = lib.generators.toINI {} { - "General" = { - "Command" = (mkIf (profile.command != null) profile.command).content; - "Name" = profileName; - # Konsole generated profiles seem to allways have this - "Parent" = "FALLBACK/"; - }; - "Appearance" = { - "ColorScheme" = (mkIf (profile.colorScheme != null) profile.colorScheme).content; - # If the font size is not set we leave a comma a the end after the name - # We should fix this probs but konsole doesn't seem to care ¯\_(ツ)_/¯ - "Font" = fontString.content; - }; - }; - } - ) cfg.profiles + xdg.dataFile = lib.mkIf (cfg.profiles != { }) + ( + lib.mkMerge ([ + ( + lib.mkMerge ( + lib.mapAttrsToList + ( + attrName: profile: + let + # Use the name from the name option if it's set + profileName = if builtins.isString profile.name then profile.name else attrName; + fontString = lib.mkIf (profile.font.name != null) "${profile.font.name},${builtins.toString profile.font.size}"; + in + { + "konsole/${profileName}.profile".text = lib.generators.toINI { } { + "General" = ( + { + "Name" = profileName; + # Konsole generated profiles seem to always have this + "Parent" = "FALLBACK/"; + } // + (lib.optionalAttrs (profile.command != null) { "Command" = profile.command; }) + ); + "Appearance" = ( + { + # If the font size is not set we leave a comma a the end after the name + # We should fix this probs but konsole doesn't seem to care ¯\_(ツ)_/¯ + "Font" = fontString.content; + } // + (lib.optionalAttrs (profile.colorScheme != null) { "ColorScheme" = profile.colorScheme; }) + ); + }; + } + ) + cfg.profiles + ) ) - ) - ]) - ); + ]) + ); }; } diff --git a/modules/files.nix b/modules/files.nix index 3b2048bd..2e1bdbd3 100644 --- a/modules/files.nix +++ b/modules/files.nix @@ -2,8 +2,8 @@ { config, lib, pkgs, ... }: let - inherit (import ../lib/writeconfig.nix { inherit lib pkgs; }) - writeConfig; + inherit (import ../lib/writeconfig.nix { inherit lib pkgs; }) writeConfig; + inherit (import ../lib/types.nix { inherit lib; }) advancedSettingsType; # Helper function to prepend the appropriate path prefix (e.g. XDG_CONFIG_HOME) to file prependPath = prefix: attrset: @@ -17,7 +17,7 @@ let (prependPath config.xdg.dataHome plasmaCfg.dataFile); ############################################################################## - # Modify the settings to respect the different options in settingType. + # Modify the settings to respect the different options in advancedSettingsType. # Checks if "value" represents the final value. isFinalValue = value: (builtins.hasAttr "value" value) && (!builtins.isAttrs value.value); # Calculates the "marking" we should add to the keys, which for example may be @@ -32,46 +32,18 @@ let if value.immutable then "[$i]" else "[$e]" ) ); - settingsModify = + fileSettingsModify = (name: value: (if (!isFinalValue value) then - (lib.attrsets.nameValuePair name (lib.attrsets.mapAttrs' settingsModify value)) else + (lib.attrsets.nameValuePair name (lib.attrsets.mapAttrs' fileSettingsModify value)) else (lib.attrsets.nameValuePair "${name}${calculateKeyMarking value}" value.value))); - ############################################################################## - # Types for storing settings. - settingsValueType = (with lib.types; - nullOr (oneOf [ bool float int str ])); - settingType = (with lib.types; submodule { - options = { - value = lib.mkOption { - type = settingsValueType; - default = null; - description = "The value for some key."; - }; - immutable = lib.mkOption { - type = bool; - default = false; - description = '' - Whether to make the key immutable. This corresponds to adding [$i] to - the end of the key. - ''; - }; - shellExpand = lib.mkOption { - type = bool; - default = false; - description = '' - Whether to mark the key for shell expansion. This corresponds to - adding [$e] to the end of the key. - ''; - }; - }; - }); - settingsFileType = with lib.types; attrsOf (attrsOf (attrsOf settingType)); + + fileSettingsType = with lib.types; attrsOf (attrsOf (attrsOf advancedSettingsType)); ############################################################################## # Generate a script that will use write_config.py to update all # settings. - script = pkgs.writeScript "plasma-config" (writeConfig (lib.attrsets.mapAttrs' settingsModify cfg)); + script = pkgs.writeScript "plasma-config" (writeConfig (lib.attrsets.mapAttrs' fileSettingsModify cfg)); ############################################################################## # Generate a script that will remove all the current config files. @@ -130,7 +102,7 @@ in { options.programs.plasma = { file = lib.mkOption { - type = settingsFileType; + type = fileSettingsType; default = { }; description = '' An attribute set where the keys are file names (relative to @@ -139,7 +111,7 @@ in ''; }; configFile = lib.mkOption { - type = settingsFileType; + type = fileSettingsType; default = { }; description = '' An attribute set where the keys are file names (relative to @@ -148,7 +120,7 @@ in ''; }; dataFile = lib.mkOption { - type = settingsFileType; + type = fileSettingsType; default = { }; description = '' An attribute set where the keys are file names (relative to