Files
home-manager/modules/services/window-managers/hyprland/lib.nix
T
Austin Horstman 26e34a99e4 hyprland: reorganize config generation
Move local render helpers closer to their generated outputs and split xdg.configFile construction into named builders.

Keep luaFiles behavior in the preceding feature commit; this only reduces duplicated predicates and makes the module layout easier to scan.
2026-06-01 21:32:10 -05:00

244 lines
7.8 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
attrNames
concatMapStrings
filter
filterAttrs
isString
optionalString
replaceStrings
sort
;
toLua = lib.generators.toLua { };
systemdVariables = lib.concatStringsSep " " config.systemd.variables;
systemdExtraCommands = lib.concatStringsSep " " (map (f: "&& ${f}") config.systemd.extraCommands);
systemdActivationCommand = "${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${systemdVariables} ${systemdExtraCommands}";
pluginPath =
entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry;
renderSection =
name: text:
optionalString (text != "") ''
-- ${name}
${text}
'';
luaModuleName = name: replaceStrings [ "/" ] [ "." ] (lib.removeSuffix ".lua" name);
luaModulePath = name: replaceStrings [ "." ] [ "/" ] (luaModuleName name);
luaFileName = name: "${luaModulePath name}.lua";
# `_args` renders attrsets as Lua multi-argument calls instead of tables.
renderLuaArgs =
value:
if lib.isAttrs value && value ? _args then
lib.concatMapStringsSep ", " toLua value._args
else
toLua value;
isLuaLocal = value: lib.isAttrs value && value ? _var;
luaLocalName = name: value: value.name or name;
hasNonStringValue = values: lib.any (value: !isString value) values;
isPathLike = value: lib.isPath value || lib.isStorePath value;
in
{
inherit
luaFileName
luaModuleName
luaModulePath
;
hyprlangConfig =
{
reloadConfig,
}:
let
importantPrefixes = config.importantPrefixes ++ lib.optional config.sourceFirst "source";
pluginsToHyprconf =
plugins:
lib.hm.generators.toHyprconf {
attrs = {
"exec-once" = map (entry: "hyprctl plugin load ${pluginPath entry}") plugins;
};
inherit importantPrefixes;
};
# Hyprlang submaps render only string entries.
hyprlangSubmapSettings =
settings: filterAttrs (_: values: values != [ ]) (lib.mapAttrs (_: filter isString) settings);
hyprlangSubmaps = filterAttrs (
_: submap: hyprlangSubmapSettings submap.settings != { }
) config.submaps;
mkSubMap = name: attrs: ''
submap = ${name}${optionalString (attrs.onDispatch != "") ", ${attrs.onDispatch}"}
${
lib.hm.generators.toHyprconf {
attrs = hyprlangSubmapSettings attrs.settings;
indentLevel = 0;
}
}submap = reset
'';
submapsToHyprConf = lib.concatMapAttrsStringSep "\n" mkSubMap hyprlangSubmaps;
shouldGenerate =
config.systemd.enable
|| config.extraConfig != ""
|| config.settings != { }
|| config.plugins != [ ]
|| hyprlangSubmaps != { };
in
lib.mkIf shouldGenerate {
text =
optionalString config.systemd.enable ''
exec-once = ${systemdActivationCommand}
''
+ optionalString (config.plugins != [ ]) (pluginsToHyprconf config.plugins)
+ optionalString (config.settings != { }) (
lib.hm.generators.toHyprconf {
attrs = config.settings;
inherit importantPrefixes;
}
)
+ optionalString (hyprlangSubmaps != { }) submapsToHyprConf
+ optionalString (config.extraConfig != "") config.extraConfig;
onChange = lib.mkIf (config.package != null) reloadConfig;
};
luaConfig =
{
reloadConfig,
xdgConfigHome,
}:
let
pluginLoadCommands = map (entry: "hyprctl plugin load ${pluginPath entry}") config.plugins;
startupCommands =
lib.optionals config.systemd.enable [ systemdActivationCommand ] ++ pluginLoadCommands;
renderSettings =
let
names = sort lib.lessThan (attrNames config.settings);
luaLocalNames = filter (name: isLuaLocal config.settings.${name}) names;
settingNames = filter (name: !(builtins.elem name luaLocalNames)) names;
importantNames = lib.unique (
lib.concatMap (
prefix: filter (name: lib.hasPrefix prefix name) settingNames
) config.importantPrefixes
);
orderedNames = importantNames ++ filter (name: !(builtins.elem name importantNames)) settingNames;
renderLocal =
name:
let
value = config.settings.${name};
in
"local ${luaLocalName name value} = ${renderLuaArgs value._var}\n";
renderCall = name: value: "hl.${name}(${renderLuaArgs value})\n";
renderCalls =
name: value: concatMapStrings (renderCall name) (if lib.isList value then value else [ value ]);
in
optionalString (luaLocalNames != [ ]) (
renderSection "settings.locals" (concatMapStrings renderLocal luaLocalNames)
)
+ concatMapStrings (
name: renderSection "settings.${name}" (renderCalls name config.settings.${name})
) orderedNames;
renderStartHook =
if startupCommands == [ ] then
""
else
renderSection "startup" ''
hl.on("hyprland.start", function()
${concatMapStrings (command: " hl.exec_cmd(${toLua command})\n") startupCommands}end)
'';
renderLuaFiles =
let
autoloadFiles = filterAttrs (_: file: file.autoLoad) config.extraLuaFiles;
names = sort lib.lessThan (attrNames autoloadFiles);
in
if names == [ ] then
""
else
renderSection "extraLuaFiles" (
''
local hm_xdg_config_home = os.getenv("XDG_CONFIG_HOME") or ${toLua xdgConfigHome}
package.path = hm_xdg_config_home .. "/hypr/?.lua;" .. hm_xdg_config_home .. "/hypr/?/init.lua;" .. package.path
''
+ lib.concatMapStringsSep "\n" (name: "require(${toLua (luaModuleName name)})") names
);
# Lua submaps render structured entries and skip Hyprlang strings.
luaSubmaps = filterAttrs (
_: submap: lib.any hasNonStringValue (lib.attrValues submap.settings)
) config.submaps;
renderSubmaps =
let
renderLuaArg = value: replaceStrings [ "\n" ] [ "\n " ] (renderLuaArgs value);
renderCall = name: value: " hl.${name}(${renderLuaArg value})\n";
renderCalls =
name: values: concatMapStrings (renderCall name) (filter (value: !isString value) values);
renderSubmap =
name: submap:
renderSection "submaps.${name}" (
"hl.define_submap(${toLua name}"
+ optionalString (submap.onDispatch != "") ", ${toLua submap.onDispatch}"
+ ", function()\n"
+ concatMapStrings (settingName: renderCalls settingName submap.settings.${settingName}) (
sort lib.lessThan (attrNames submap.settings)
)
+ "end)\n"
);
names = sort lib.lessThan (attrNames luaSubmaps);
in
concatMapStrings (name: renderSubmap name luaSubmaps.${name}) names;
shouldGenerate =
config.systemd.enable
|| config.extraConfig != ""
|| config.extraLuaFiles != { }
|| config.settings != { }
|| config.plugins != [ ]
|| luaSubmaps != { };
in
lib.mkIf shouldGenerate {
text = ''
-- Generated by Home Manager.
-- See https://wiki.hypr.land/Configuring/Start/
''
+ renderLuaFiles
+ renderSettings
+ renderSubmaps
+ renderStartHook
+ renderSection "extraConfig" config.extraConfig;
onChange = lib.mkIf (config.package != null) reloadConfig;
};
extraLuaFiles = lib.mapAttrs' (
name: file:
lib.nameValuePair "hypr/${luaFileName name}" (
if isPathLike file.content then { source = file.content; } else { text = file.content; }
)
) config.extraLuaFiles;
}