diff --git a/modules/services/window-managers/hyprland.nix b/modules/services/window-managers/hyprland.nix index 89e8e677b..7aaddcfa4 100644 --- a/modules/services/window-managers/hyprland.nix +++ b/modules/services/window-managers/hyprland.nix @@ -9,6 +9,21 @@ let toLua = lib.generators.toLua { }; + settingValueType = + with lib.types; + nullOr (oneOf [ + bool + int + float + str + path + (attrsOf settingValueType) + (listOf settingValueType) + ]) + // { + description = "Hyprland configuration value"; + }; + variables = builtins.concatStringsSep " " cfg.systemd.variables; extraCommands = builtins.concatStringsSep " " (map (f: "&& ${f}") cfg.systemd.extraCommands); systemdActivationCommand = "${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${variables} ${extraCommands}"; @@ -219,24 +234,7 @@ in }; settings = lib.mkOption { - type = - with lib.types; - let - valueType = - nullOr (oneOf [ - bool - int - float - str - path - (attrsOf valueType) - (listOf valueType) - ]) - // { - description = "Hyprland configuration value"; - }; - in - valueType; + type = settingValueType; default = { }; description = '' Hyprland configuration written in Nix. Entries with the same key @@ -334,12 +332,22 @@ in example = "reset"; }; settings = lib.mkOption { - type = (with lib.types; attrsOf (listOf str)) // { + type = (with lib.types; attrsOf (listOf settingValueType)) // { description = "Hyprland binds"; }; default = { }; description = '' Hyprland binds to be put in the submap. + + String entries render only when + {option}`wayland.windowManager.hyprland.configType` is + `"hyprlang"`. + + Attribute set entries render only when + {option}`wayland.windowManager.hyprland.configType` is `"lua"`. + Attribute values with an `_args` list generate multi-argument + calls. Values created with `lib.generators.mkLuaInline` are + rendered as raw Lua expressions. ''; example = lib.literalExpression '' { @@ -352,6 +360,12 @@ in bind = [ ", escape, submap, reset" + { + _args = [ + "escape" + (lib.generators.mkLuaInline "hl.dsp.submap(\"reset\")") + ]; + } ]; } ''; @@ -469,9 +483,6 @@ in { "hypr/hyprland.conf" = lib.mkIf (cfg.configType == "hyprlang") ( let - shouldGenerate = - cfg.systemd.enable || cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ]; - importantPrefixes = cfg.importantPrefixes ++ lib.optional cfg.sourceFirst "source"; pluginsToHyprconf = @@ -483,17 +494,34 @@ in inherit importantPrefixes; }; + hyprlangSubmapSettings = + settings: + lib.filterAttrs (_: values: values != [ ]) ( + lib.mapAttrs (_: builtins.filter lib.isString) settings + ); + + hyprlangSubmaps = lib.filterAttrs ( + _: submap: hyprlangSubmapSettings submap.settings != { } + ) cfg.submaps; + mkSubMap = name: attrs: '' submap = ${name}${lib.optionalString (attrs.onDispatch != "") ", ${attrs.onDispatch}"} ${ lib.hm.generators.toHyprconf { - attrs = attrs.settings; + attrs = hyprlangSubmapSettings attrs.settings; indentLevel = 0; } }submap = reset ''; - submapsToHyprConf = lib.concatMapAttrsStringSep "\n" mkSubMap; + submapsToHyprConf = lib.concatMapAttrsStringSep "\n" mkSubMap hyprlangSubmaps; + + shouldGenerate = + cfg.systemd.enable + || cfg.extraConfig != "" + || cfg.settings != { } + || cfg.plugins != [ ] + || hyprlangSubmaps != { }; in lib.mkIf shouldGenerate { text = @@ -507,7 +535,7 @@ in inherit importantPrefixes; } ) - + lib.optionalString (cfg.submaps != { }) (submapsToHyprConf cfg.submaps) + + lib.optionalString (hyprlangSubmaps != { }) submapsToHyprConf + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; onChange = lib.mkIf (cfg.package != null) reloadConfig; @@ -521,15 +549,23 @@ in startupCommands = lib.optionals cfg.systemd.enable [ systemdActivationCommand ] ++ pluginLoadCommands; + renderArgs = + value: + if lib.isAttrs value && value ? _args then + lib.concatMapStringsSep ", " toLua value._args + else + toLua value; + + renderSection = + name: text: + lib.optionalString (text != "") '' + -- ${name} + ${text} + ''; + renderSettings = let names = lib.sort lib.lessThan (lib.attrNames cfg.settings); - renderArgs = - value: - if lib.isAttrs value && value ? _args then - lib.concatMapStringsSep ", " toLua value._args - else - toLua value; importantNames = lib.unique ( lib.concatMap ( prefix: builtins.filter (name: lib.hasPrefix prefix name) names @@ -541,19 +577,56 @@ in name: value: lib.concatMapStrings (renderCall name) (if builtins.isList value then value else [ value ]); in - lib.concatMapStrings (name: renderCalls name cfg.settings.${name}) orderedNames; + lib.concatMapStrings ( + name: renderSection "settings.${name}" (renderCalls name cfg.settings.${name}) + ) orderedNames; renderStartHook = if startupCommands == [ ] then "" else - '' + renderSection "startup" '' hl.on("hyprland.start", function() ${lib.concatMapStrings (command: " hl.exec_cmd(${toLua command})\n") startupCommands}end) ''; + renderSubmaps = + let + renderLuaArg = value: lib.replaceStrings [ "\n" ] [ "\n " ] (renderArgs value); + renderCall = name: value: " hl.${name}(${renderLuaArg value})\n"; + renderCalls = + name: values: + lib.concatMapStrings (renderCall name) (builtins.filter (value: !lib.isString value) values); + renderSubmap = + name: submap: + renderSection "submaps.${name}" ( + "hl.define_submap(${toLua name}" + + lib.optionalString (submap.onDispatch != "") ", ${toLua submap.onDispatch}" + + ", function()\n" + + lib.concatMapStrings (settingName: renderCalls settingName submap.settings.${settingName}) ( + lib.sort lib.lessThan (lib.attrNames submap.settings) + ) + + "end)\n" + ); + hasLuaSettings = + submap: + lib.any (values: builtins.any (value: !lib.isString value) values) (lib.attrValues submap.settings); + luaSubmaps = lib.filterAttrs (_: hasLuaSettings) cfg.submaps; + names = lib.sort lib.lessThan (lib.attrNames luaSubmaps); + in + lib.concatMapStrings (name: renderSubmap name luaSubmaps.${name}) names; + + hasLuaSubmaps = lib.any ( + submap: + lib.any (values: builtins.any (value: !lib.isString value) values) (lib.attrValues submap.settings) + ) (lib.attrValues cfg.submaps); + shouldGenerate = - cfg.systemd.enable || cfg.extraConfig != "" || cfg.settings != { } || cfg.plugins != [ ]; + cfg.systemd.enable + || cfg.extraConfig != "" + || cfg.settings != { } + || cfg.plugins != [ ] + || hasLuaSubmaps; in lib.mkIf shouldGenerate { text = '' @@ -562,8 +635,9 @@ in '' + renderSettings + + renderSubmaps + renderStartHook - + lib.optionalString (cfg.extraConfig != "") cfg.extraConfig; + + renderSection "extraConfig" cfg.extraConfig; onChange = lib.mkIf (cfg.package != null) reloadConfig; } diff --git a/tests/modules/services/hyprland/lua-config.lua b/tests/modules/services/hyprland/lua-config.lua index 3c5e955c5..74b80ebd6 100644 --- a/tests/modules/services/hyprland/lua-config.lua +++ b/tests/modules/services/hyprland/lua-config.lua @@ -1,6 +1,7 @@ -- Generated by Home Manager. -- See https://wiki.hypr.land/Configuring/Start/ +-- settings.curve hl.curve("smoothIn", { ["points"] = { { @@ -14,6 +15,8 @@ hl.curve("smoothIn", { }, ["type"] = "bezier" }) + +-- settings.animation hl.animation({ ["bezier"] = "smoothIn", ["enabled"] = true, @@ -27,10 +30,15 @@ hl.animation({ ["speed"] = 3, ["style"] = "popin 80%" }) + +-- settings.bind hl.bind("SUPER + Q", (hl.dsp.window.close()), { ["locked"] = true }) hl.bind("SUPER + RETURN", (hl.dsp.exec_cmd("kitty"))) +hl.bind("SUPER + SHIFT + 1", (hl.dsp.window.move({ workspace = "1", follow = false }))) + +-- settings.config hl.config({ ["ecosystem"] = { ["enforce_permissions"] = true @@ -42,24 +50,36 @@ hl.config({ } } }) + +-- settings.device hl.device({ ["enabled"] = true, ["name"] = "some:device" }) + +-- settings.env hl.env("QT_QPA_PLATFORMTHEME", "qt5ct") hl.env("XCURSOR_SIZE", "24") + +-- settings.exec_cmd hl.exec_cmd("hyprctl setcursor Bibata 24") + +-- settings.gesture hl.gesture({ ["action"] = "workspace", ["direction"] = "left", ["fingers"] = 3 }) + +-- settings.layer_rule hl.layer_rule({ ["blur"] = false, ["match"] = { ["namespace"] = "waybar" } }) + +-- settings.monitor hl.monitor({ ["mode"] = "highres", ["output"] = "desc:Monitor", @@ -67,30 +87,53 @@ hl.monitor({ ["scale"] = 1, ["vrr"] = 1 }) + +-- settings.on hl.on("hyprland.start", (function() hl.exec_cmd("waybar") end )) + +-- settings.permission hl.permission({ ["binary"] = "^org\\.example\\..*", ["mode"] = "deny", ["type"] = "screencopy" }) + +-- settings.window_rule hl.window_rule({ ["border_size"] = 2, ["match"] = { ["class"] = "kitty" } }) + +-- settings.workspace_rule hl.workspace_rule({ ["monitor"] = "DP-1", ["workspace"] = "1" }) + +-- submaps.resize +hl.define_submap("resize", "reset", function() + hl.bind("right", (hl.dsp.window.resize({ x = 10, y = 0, relative = true })), { + ["repeating"] = true + }) + hl.bind("left", (hl.dsp.window.resize({ x = -10, y = 0, relative = true })), { + ["repeating"] = true + }) + hl.bind("escape", (hl.dsp.submap("reset"))) +end) + +-- startup hl.on("hyprland.start", function() hl.exec_cmd("@dbus@/bin/dbus-update-activation-environment --systemd DISPLAY HYPRLAND_INSTANCE_SIGNATURE WAYLAND_DISPLAY XDG_CURRENT_DESKTOP XDG_SESSION_TYPE && systemctl --user stop hyprland-session.target && systemctl --user start hyprland-session.target") hl.exec_cmd("hyprctl plugin load /path/to/plugin1") hl.exec_cmd("hyprctl plugin load /nix/store/00000000000000000000000000000000-foo/lib/libfoo.so") end) + +-- extraConfig local mainMod = "SUPER" local terminal = "kitty" hl.define_submap("resize", function() @@ -104,3 +147,4 @@ hl.config({ rounding = 4, }, }) + diff --git a/tests/modules/services/hyprland/lua-config.nix b/tests/modules/services/hyprland/lua-config.nix index d9b5e79c6..1b6a1403e 100644 --- a/tests/modules/services/hyprland/lua-config.nix +++ b/tests/modules/services/hyprland/lua-config.nix @@ -130,6 +130,12 @@ (lib.generators.mkLuaInline ''hl.dsp.exec_cmd("kitty")'') ]; } + { + _args = [ + "SUPER + SHIFT + 1" + (lib.generators.mkLuaInline ''hl.dsp.window.move({ workspace = "1", follow = false })'') + ]; + } ]; on = { @@ -144,6 +150,37 @@ }; }; + submaps = { + resize = { + onDispatch = "reset"; + settings = { + bind = [ + ", q, exec, ignored-hyprlang-bind" + { + _args = [ + "right" + (lib.generators.mkLuaInline "hl.dsp.window.resize({ x = 10, y = 0, relative = true })") + { repeating = true; } + ]; + } + { + _args = [ + "left" + (lib.generators.mkLuaInline "hl.dsp.window.resize({ x = -10, y = 0, relative = true })") + { repeating = true; } + ]; + } + { + _args = [ + "escape" + (lib.generators.mkLuaInline ''hl.dsp.submap("reset")'') + ]; + } + ]; + }; + }; + }; + extraConfig = '' local mainMod = "SUPER" local terminal = "kitty" @@ -165,6 +202,7 @@ config=home-files/.config/hypr/hyprland.lua assertFileExists "$config" assertPathNotExists home-files/.config/hypr/hyprland.conf + assertFileNotRegex "$config" "ignored-hyprlang-bind" normalizedConfig=$(normalizeStorePaths "$config") assertFileContent "$normalizedConfig" ${./lua-config.lua} ''; diff --git a/tests/modules/services/hyprland/submaps-config.nix b/tests/modules/services/hyprland/submaps-config.nix index dacf85ce1..78132af4c 100644 --- a/tests/modules/services/hyprland/submaps-config.nix +++ b/tests/modules/services/hyprland/submaps-config.nix @@ -26,6 +26,12 @@ bind = [ ", escape, submap, reset" ", return, submap, reset" + { + _args = [ + "q" + "ignored-lua-dispatcher" + ]; + } ]; }; }; @@ -51,6 +57,7 @@ nmt.script = '' config=home-files/.config/hypr/hyprland.conf assertFileExists "$config" + assertFileNotRegex "$config" "ignored-lua-dispatcher" normalizedConfig=$(normalizeStorePaths "$config") assertFileContent "$normalizedConfig" ${./submaps-config.conf}