From ef59977f97da1688ffc183ea0234f7c712d0daa4 Mon Sep 17 00:00:00 2001 From: Ormoyo Date: Sat, 23 May 2026 20:51:27 +0300 Subject: [PATCH 1/2] feat(wrapperModules.quickshell): add option to set component's module --- wrapperModules/q/quickshell/check.nix | 28 ++++++++++-- wrapperModules/q/quickshell/module.nix | 60 ++++++++++++++++++-------- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/wrapperModules/q/quickshell/check.nix b/wrapperModules/q/quickshell/check.nix index db07bb3b..d5acf15f 100644 --- a/wrapperModules/q/quickshell/check.nix +++ b/wrapperModules/q/quickshell/check.nix @@ -69,7 +69,10 @@ test { wrapper = "quickshell"; } { isShellPresent = wrapper: isFile "${wrapper}/${wrapper.passthru.configuration.binName}-config/shell.qml"; isBarPresent = - wrapper: isFile "${wrapper}/${wrapper.passthru.configuration.binName}-config/Bar.qml"; + wrapper: mod: + isFile "${wrapper}/${wrapper.passthru.configuration.binName}-config/${ + if mod != null then "${mod}/" else "" + }Bar.qml"; isCorrectConfig = wrapper: '' logs=$("${wrapper}/bin/quickshell" 2>&1) echo "$logs" | grep -q "Launching config: \"${wrapper}/${wrapper.passthru.configuration.binName}-config/shell.qml\"" @@ -85,7 +88,7 @@ test { wrapper = "quickshell"; } { in [ (isShellPresent wrapper) - (isBarPresent wrapper) + (isBarPresent wrapper null) (isCorrectConfig wrapper) ]; @@ -93,12 +96,29 @@ test { wrapper = "quickshell"; } { let wrapper = baseWrapper.wrap { configFile = pkgs.writeText "quickshell-test-shell.qml" shellContent; - components.bar = pkgs.writeText "quickshell-test-bar.qml" barContent; + components.bar.data = pkgs.writeText "quickshell-test-bar.qml" barContent; + components.bar.name = "Bar.qml"; }; in [ (isShellPresent wrapper) - (isBarPresent wrapper) + (isBarPresent wrapper null) + (isCorrectConfig wrapper) + ]; + + "wrapper should keep correct hierachy with component modules" = + let + wrapper = baseWrapper.wrap { + configFile = "import qs.foo.boo\n" + shellContent; + components.bar = { + data = barContent; + module = "foo.boo"; + }; + }; + in + [ + (isShellPresent wrapper) + (isBarPresent wrapper "foo/boo") (isCorrectConfig wrapper) ]; }; diff --git a/wrapperModules/q/quickshell/module.nix b/wrapperModules/q/quickshell/module.nix index d3c6abd0..503e3970 100644 --- a/wrapperModules/q/quickshell/module.nix +++ b/wrapperModules/q/quickshell/module.nix @@ -16,6 +16,35 @@ let isLinkable = wlib.types.linkable.check; makeForce = lib.mkOverride 0; + + componentModule = + { name, config, ... }: + { + options = { + name = mkOption { + type = types.str; + default = + let + firstChar = builtins.substring 0 1 name; + rest = builtins.substring 1 (-1) name; + in + if (isLinkable config.data) then + (builtins.baseNameOf config.data) + else + (lib.toUpper firstChar) + rest + ".qml"; + description = "The name of this component (either filename or directory name)"; + }; + data = mkOption { + type = types.either wlib.types.linkable types.lines; + description = "The component's inlined text or path"; + }; + module = mkOption { + type = types.nullOr types.str; + default = null; + description = "The component's module, to be imported by `import qs.`"; + }; + }; + }; in { imports = [ wlib.modules.default ]; @@ -31,7 +60,7 @@ in ''; }; components = mkOption { - type = types.attrsOf (types.either wlib.types.linkable types.lines); + type = types.attrsOf (wlib.types.spec componentModule); default = { }; description = "Quickshell components to include in the configuration"; }; @@ -56,24 +85,17 @@ in }/${config.binName}-config"; config.constructFiles = - mapAttrs' ( - name: val: - let - firstChar = builtins.substring 0 1 name; - rest = builtins.substring 1 (-1) name; - capitalizedName = (lib.toUpper firstChar) + rest; - linkable = isLinkable val; - in - { - name = "${name}Component"; - value = { - content = mkIf (!linkable) val; - builder = mkIf linkable ''ln -s ${val} "$2"''; - output = makeForce config.generated.output; - relPath = makeForce "${config.binName}-config/${capitalizedName}.qml"; - }; - } - ) config.components + mapAttrs' (name: val: { + name = "${name}Component"; + value = { + content = mkIf (!isLinkable val.data) val.data; + builder = mkIf (isLinkable val.data) ''ln -s ${val.data} "$2"''; + output = makeForce config.generated.output; + relPath = makeForce "${config.binName}-config/${ + if val.module != null then "${lib.replaceString "." "/" val.module}/" else "" + }${val.name}"; + }; + }) config.components // { generatedConfig = { content = mkIf (!isLinkable config.configFile) config.configFile; From 774a7c88b8d3214b61ee7114a9558e78a5503476 Mon Sep 17 00:00:00 2001 From: Ormoyo Date: Mon, 25 May 2026 15:51:48 +0300 Subject: [PATCH 2/2] feat(wrapperModules.quickshell): allow setting component's module with recursive attrset --- wrapperModules/q/quickshell/check.nix | 8 +- wrapperModules/q/quickshell/module.nix | 102 +++++++++++++++---------- 2 files changed, 63 insertions(+), 47 deletions(-) diff --git a/wrapperModules/q/quickshell/check.nix b/wrapperModules/q/quickshell/check.nix index d5acf15f..19badd51 100644 --- a/wrapperModules/q/quickshell/check.nix +++ b/wrapperModules/q/quickshell/check.nix @@ -96,8 +96,7 @@ test { wrapper = "quickshell"; } { let wrapper = baseWrapper.wrap { configFile = pkgs.writeText "quickshell-test-shell.qml" shellContent; - components.bar.data = pkgs.writeText "quickshell-test-bar.qml" barContent; - components.bar.name = "Bar.qml"; + components.bar = pkgs.writeText "quickshell-test-bar.qml" barContent; }; in [ @@ -110,10 +109,7 @@ test { wrapper = "quickshell"; } { let wrapper = baseWrapper.wrap { configFile = "import qs.foo.boo\n" + shellContent; - components.bar = { - data = barContent; - module = "foo.boo"; - }; + components.foo.boo.bar = barContent; }; in [ diff --git a/wrapperModules/q/quickshell/module.nix b/wrapperModules/q/quickshell/module.nix index 503e3970..7fec7455 100644 --- a/wrapperModules/q/quickshell/module.nix +++ b/wrapperModules/q/quickshell/module.nix @@ -7,7 +7,8 @@ }: let inherit (lib) - mapAttrs' + isStringLike + mapAttrs mkDefault mkIf mkOption @@ -17,34 +18,15 @@ let isLinkable = wlib.types.linkable.check; makeForce = lib.mkOverride 0; - componentModule = - { name, config, ... }: - { - options = { - name = mkOption { - type = types.str; - default = - let - firstChar = builtins.substring 0 1 name; - rest = builtins.substring 1 (-1) name; - in - if (isLinkable config.data) then - (builtins.baseNameOf config.data) - else - (lib.toUpper firstChar) + rest + ".qml"; - description = "The name of this component (either filename or directory name)"; - }; - data = mkOption { - type = types.either wlib.types.linkable types.lines; - description = "The component's inlined text or path"; - }; - module = mkOption { - type = types.nullOr types.str; - default = null; - description = "The component's module, to be imported by `import qs.`"; - }; - }; - }; + componentType = types.submodule { + freeformType = types.lazyAttrsOf ( + types.oneOf [ + wlib.types.linkable + types.lines + (componentType // { description = "nested components"; }) + ] + ); + }; in { imports = [ wlib.modules.default ]; @@ -60,9 +42,30 @@ in ''; }; components = mkOption { - type = types.attrsOf (wlib.types.spec componentModule); + type = componentType; default = { }; description = "Quickshell components to include in the configuration"; + example = lib.literalExpression '' + { + some.path."Bar.qml" = ./some-widget.qml; + light.clock = ''' + Text { + text: "hello world" + } + '''; + dark.clock = "/etc/quickshell/Clock.qml"; + lockscreensDir = + let + repo = pkgs.fetchFromGitHub { + owner = "Darkkal44"; + repo = "qylock"; + rev = "cde4d11e9e3d385620becdc877a0521e40a55e47"; + hash = "sha256-17kRwrkdfe+hJdChMxove73zNCKcSi0nmSrO8Fh8hz0="; + }; + in + "''${repo}/quickshell-lockscreen"; + } + ''; }; generated.output = mkOption { type = types.str; @@ -85,17 +88,34 @@ in }/${config.binName}-config"; config.constructFiles = - mapAttrs' (name: val: { - name = "${name}Component"; - value = { - content = mkIf (!isLinkable val.data) val.data; - builder = mkIf (isLinkable val.data) ''ln -s ${val.data} "$2"''; - output = makeForce config.generated.output; - relPath = makeForce "${config.binName}-config/${ - if val.module != null then "${lib.replaceString "." "/" val.module}/" else "" - }${val.name}"; - }; - }) config.components + let + getComponents' = + prefix: val: + if builtins.isAttrs val then + lib.foldlAttrs ( + acc: name: v: + let + value = if isStringLike v then builtins.toString v else v; + + # Auto capitalization for qml files or inlined text + attrIsFile = builtins.isString v || (isStringLike v && lib.hasSuffix ".qml" value); + + firstChar = builtins.substring 0 1 name; + restChars = builtins.substring 1 (-1) name; + finalName = if !attrIsFile then name else (lib.toUpper firstChar) + restChars + ".qml"; + in + acc // getComponents' (prefix + "/" + finalName) value + ) { } val + else + { ${prefix} = val; }; + getComponents = getComponents' ""; + in + (mapAttrs (name: value: { + content = mkIf (!isLinkable value) value; + builder = mkIf (isLinkable value) ''ln -s ${value} "$2"''; + output = makeForce config.generated.output; + relPath = makeForce "${config.binName}-config${name}"; + }) (getComponents config.components)) // { generatedConfig = { content = mkIf (!isLinkable config.configFile) config.configFile;