diff --git a/doc/src/modules.md b/doc/src/modules.md index 0250dffcd..d2b21d3a2 100644 --- a/doc/src/modules.md +++ b/doc/src/modules.md @@ -106,6 +106,13 @@ the following applies: - There is no reliable way to detect whether the target is installed, *and* enabling it unconditionally would cause problems. +> [!CAUTION] +> The boolean value after `mkEnableTarget` should usually be a static `true` or +> `false` literal. +> +> Using a dynamic value requires you to document the dynamic expression using +> `mkEnableTargetWith`'s `autoEnableExpr` argument. + ### Overlays If your module is provided as an overlay it uses a special format, where config diff --git a/modules/feh/hm.nix b/modules/feh/hm.nix index 5c028991d..d0187ebf0 100644 --- a/modules/feh/hm.nix +++ b/modules/feh/hm.nix @@ -15,6 +15,14 @@ mkTarget { || i3.enable || spectrwm.enable || xmonad.enable; + autoEnableExpr = '' + with config.xsession.windowManager; + bspwm.enable + || herbstluftwm.enable + || i3.enable + || spectrwm.enable + || xmonad.enable + ''; configElements = { imageScalingMode, image }: diff --git a/modules/feh/nixos.nix b/modules/feh/nixos.nix index eefd8190c..cc0ee85ef 100644 --- a/modules/feh/nixos.nix +++ b/modules/feh/nixos.nix @@ -11,6 +11,10 @@ mkTarget { autoEnable = with config.services.xserver.windowManager; xmonad.enable || i3.enable; + autoEnableExpr = '' + with services.xserver.windowManager; + xmonad.enable || i3.enable + ''; configElements = { image, imageScalingMode }: diff --git a/modules/gnome/hm.nix b/modules/gnome/hm.nix index bc64a6cbc..9b8e45615 100644 --- a/modules/gnome/hm.nix +++ b/modules/gnome/hm.nix @@ -44,7 +44,11 @@ let in { options.stylix.targets.gnome = { - enable = config.lib.stylix.mkEnableTarget "GNOME" pkgs.stdenv.hostPlatform.isLinux; + enable = config.lib.stylix.mkEnableTargetWith { + name = "GNOME"; + autoEnable = pkgs.stdenv.hostPlatform.isLinux; + autoEnableExpr = "pkgs.stdenv.hostPlatform.isLinux"; + }; useWallpaper = config.lib.stylix.mkEnableWallpaper "GNOME" true; }; diff --git a/modules/hyprland/hm.nix b/modules/hyprland/hm.nix index cb16ceb9a..70ef1be2c 100644 --- a/modules/hyprland/hm.nix +++ b/modules/hyprland/hm.nix @@ -7,9 +7,11 @@ mkTarget { name = "hyprland"; humanName = "Hyprland"; - extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTarget "Hyprpaper" ( - config.stylix.image != null - ); + extraOptions.hyprpaper.enable = config.lib.stylix.mkEnableTargetWith { + name = "Hyprpaper"; + autoEnable = config.stylix.image != null; + autoEnableExpr = "stylix.image != null"; + }; configElements = [ ( { colors }: diff --git a/modules/qt/hm.nix b/modules/qt/hm.nix index 562a4a4c1..dd876237d 100644 --- a/modules/qt/hm.nix +++ b/modules/qt/hm.nix @@ -13,9 +13,11 @@ # # [1]: https://github.com/nix-community/stylix/issues/933 # [2]: https://github.com/nix-community/home-manager/issues/6565 - enable = config.lib.stylix.mkEnableTarget "QT" ( - pkgs.stdenv.hostPlatform.isLinux && osConfig != null - ); + enable = config.lib.stylix.mkEnableTargetWith { + name = "QT"; + autoEnable = pkgs.stdenv.hostPlatform.isLinux && osConfig != null; + autoEnableExpr = "pkgs.stdenv.hostPlatform.isLinux && osConfig != null"; + }; platform = lib.mkOption { description = '' diff --git a/modules/qt/nixos.nix b/modules/qt/nixos.nix index 55fe5afad..7a1fcf3a6 100644 --- a/modules/qt/nixos.nix +++ b/modules/qt/nixos.nix @@ -1,9 +1,4 @@ -{ - lib, - pkgs, - config, - ... -}: +{ lib, config, ... }: let @@ -16,7 +11,7 @@ let in { options.stylix.targets.qt = { - enable = config.lib.stylix.mkEnableTarget "QT" pkgs.stdenv.hostPlatform.isLinux; + enable = config.lib.stylix.mkEnableTarget "QT" true; platform = lib.mkOption { description = '' Selects the platform theme to use for Qt applications. diff --git a/modules/swaylock/hm.nix b/modules/swaylock/hm.nix index a0440b236..20504c5c9 100644 --- a/modules/swaylock/hm.nix +++ b/modules/swaylock/hm.nix @@ -26,15 +26,19 @@ in }) ]; options.stylix.targets.swaylock = { - enable = - config.lib.stylix.mkEnableTarget "Swaylock" - # When the state version is older than 23.05, Swaylock enables itself - # automatically if `settings != {}` [1]. Therefore, Swaylock theming - # shouldn't be enabled by default for such state versions, to avoid - # inadvertently installing Swaylock when it's not desired. - # - # [1]: https://github.com/nix-community/home-manager/blob/5cfbf5cc37a3bd1da07ae84eea1b828909c4456b/modules/programs/swaylock.nix#L12-L17 - (lib.versionAtLeast config.home.stateVersion "23.05"); + enable = config.lib.stylix.mkEnableTargetWith { + name = "Swaylock"; + # When the state version is older than 23.05, Swaylock enables itself + # automatically if `settings != {}` [1]. Therefore, Swaylock theming + # shouldn't be enabled by default for such state versions, to avoid + # inadvertently installing Swaylock when it's not desired. + # + # [1]: https://github.com/nix-community/home-manager/blob/5cfbf5cc37a3bd1da07ae84eea1b828909c4456b/modules/programs/swaylock.nix#L12-L17 + autoEnable = lib.versionAtLeast config.home.stateVersion "23.05"; + autoEnableExpr = '' + lib.versionAtLeast home.stateVersion "23.05" + ''; + }; useWallpaper = config.lib.stylix.mkEnableWallpaper "Swaylock" true; }; diff --git a/stylix/mk-target.nix b/stylix/mk-target.nix index 96603173f..5fd048f01 100644 --- a/stylix/mk-target.nix +++ b/stylix/mk-target.nix @@ -66,6 +66,27 @@ This should be disabled if manual setup is required or if auto-enabling causes issues. + The default (`true`) is inherited from `mkEnableTargetWith`. + + `autoEnableExpr` (String) + : A string representation of `autoEnable`, for use in documentation. + + Not required if `autoEnable` is a literal `true` or `false`, but **must** + be used when `autoEnable` is a dynamic expression. + + E.g. `"pkgs.stdenv.hostPlatform.isLinux"`. + + `autoWrapEnableExpr` (Boolean) + : Whether to automatically wrap `autoEnableExpr` with parenthesis, when it + contains a potentially problematic infix. + + The default (`true`) is inherited from `mkEnableTargetWith`. + + `enableExample` (Boolean or literal expression) + : An example to include on the enable option. The default is calculated + automatically by `mkEnableTargetWith` and depends on `autoEnable` and + whether an `autoEnableExpr` is used. + `extraOptions` (Attribute set) : Additional options to be added in the `stylix.targets.${name}` namespace along the `stylix.targets.${name}.enable` option. @@ -152,12 +173,15 @@ { name, humanName, - autoEnable ? true, + autoEnable ? null, + autoEnableExpr ? null, + autoWrapEnableExpr ? null, + enableExample ? null, extraOptions ? { }, configElements ? [ ], generalConfig ? null, imports ? [ ], -}: +}@args: let module = { config, lib, ... }: @@ -212,7 +236,19 @@ let inherit imports; options.stylix.targets.${name}.enable = - config.lib.stylix.mkEnableTarget humanName autoEnable; + let + enableArgs = + { + name = humanName; + } + // lib.optionalAttrs (args ? autoEnable) { inherit autoEnable; } + // lib.optionalAttrs (args ? autoEnableExpr) { inherit autoEnableExpr; } + // lib.optionalAttrs (args ? autoWrapEnableExpr) { + autoWrapExpr = autoWrapEnableExpr; + } + // lib.optionalAttrs (args ? enableExample) { example = enableExample; }; + in + config.lib.stylix.mkEnableTargetWith enableArgs; config = lib.mkIf (config.stylix.enable && cfg.enable) ( lib.mkMerge ( diff --git a/stylix/target.nix b/stylix/target.nix index 1074acb3d..4ec787602 100644 --- a/stylix/target.nix +++ b/stylix/target.nix @@ -34,28 +34,100 @@ config.lib.stylix = let cfg = config.stylix; + self = config.lib.stylix; + + # Will wrap with (parentheses) if the expr contains operators with lower precedence than `&&` + wrapExprWith = + { + autoWrapExpr ? true, + trimExpr ? true, + indentMultilineExpr ? true, + }: + expr: + let + trimmed = if trimExpr then lib.trim expr else expr; + isWrapped = builtins.match ''[(].*[)]'' trimmed != null; + hasNewlines = lib.hasInfix "\n" trimmed; + needsWrapping = builtins.any (op: lib.hasInfix op trimmed) [ + # These operators have lower precedence than `&&` + # See https://nix.dev/manual/nix/2.28/language/operators + "||" + "->" + "|>" + "<|" + # These keywords would also need wrapping + "with " + "assert " + ]; + indented = + if indentMultilineExpr then + lib.pipe trimmed [ + (lib.strings.splitString "\n") + (map (line: if line == "" then "" else " " + line)) + (builtins.concatStringsSep "\n") + ] + else + trimmed; + wrapped = if hasNewlines then "(\n${indented}\n)" else "(${trimmed})"; + in + if autoWrapExpr && !isWrapped && needsWrapping then wrapped else trimmed; in { mkEnableTarget = - humanName: autoEnable: - lib.mkEnableOption "theming for ${humanName}" - // { + name: autoEnable: + config.lib.stylix.mkEnableTargetWith { inherit name autoEnable; }; + + mkEnableTargetWith = + { + name, + autoEnable ? true, + autoEnableExpr ? null, + autoWrapExpr ? true, + example ? if args ? autoEnableExpr then true else !autoEnable, + }@args: + let + wrapExpr = wrapExprWith { + inherit autoWrapExpr; + }; + in + self.mkEnableIf { + description = "Whether to enable theming for ${name}"; default = cfg.autoEnable && autoEnable; - example = !autoEnable; - } - // lib.optionalAttrs autoEnable { - defaultText = lib.literalMD "same as `stylix.autoEnable`"; + defaultText = + if args ? autoEnableExpr then + lib.literalExpression "stylix.autoEnable && ${wrapExpr autoEnableExpr}" + else if autoEnable then + lib.literalExpression "stylix.autoEnable" + else + false; + inherit example; }; + mkEnableWallpaper = humanName: autoEnable: - lib.mkOption { + self.mkEnableIf { + description = "Whether to set the wallpaper for ${humanName}."; default = config.stylix.image != null && autoEnable; + defaultText = + if autoEnable then lib.literalExpression "stylix.image != null" else false; example = config.stylix.image == null; - description = "Whether to set the wallpaper for ${humanName}."; + }; + + mkEnableIf = + { + description, + default, + defaultText ? null, + example ? if args ? defaultText then true else !default, + }@args: + lib.mkOption { type = lib.types.bool; - } - // lib.optionalAttrs autoEnable { - defaultText = lib.literalMD "`stylix.image != null`"; + defaultText = if args ? defaultText then defaultText else default; + inherit + default + description + example + ; }; }; }