mirror of
https://github.com/nix-community/home-manager.git
synced 2026-06-05 21:02:51 +00:00
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.
This commit is contained in:
@@ -1,782 +1,7 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.wayland.windowManager.hyprland;
|
||||
|
||||
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}";
|
||||
|
||||
pluginPath =
|
||||
entry: if lib.types.package.check entry then "${entry}/lib/lib${entry.pname}.so" else entry;
|
||||
|
||||
luaModuleName = name: lib.replaceStrings [ "/" ] [ "." ] (lib.removeSuffix ".lua" name);
|
||||
|
||||
luaFileName = name: "${lib.replaceStrings [ "." ] [ "/" ] (luaModuleName name)}.lua";
|
||||
|
||||
reloadConfig = ''
|
||||
(
|
||||
XDG_RUNTIME_DIR=''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
|
||||
if [[ -d "/tmp/hypr" || -d "$XDG_RUNTIME_DIR/hypr" ]]; then
|
||||
for i in $(${cfg.finalPackage}/bin/hyprctl instances -j | jq ".[].instance" -r); do
|
||||
${cfg.finalPackage}/bin/hyprctl -i "$i" reload config-only
|
||||
done
|
||||
fi
|
||||
)
|
||||
'';
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
fufexan
|
||||
khaneliman
|
||||
];
|
||||
|
||||
options.wayland.windowManager.hyprland = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable configuration for Hyprland, a tiling Wayland
|
||||
compositor that doesn't sacrifice on its looks.
|
||||
|
||||
::: {.note}
|
||||
This module configures Hyprland and adds it to your user's {env}`PATH`,
|
||||
but does not make certain system-level changes. NixOS users should
|
||||
enable the NixOS module with {option}`programs.hyprland.enable`, which
|
||||
makes system-level changes such as adding a desktop session entry.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs "hyprland" {
|
||||
nullable = true;
|
||||
extraDescription = "Set this to null if you use the NixOS module to install Hyprland.";
|
||||
};
|
||||
|
||||
portalPackage = lib.mkPackageOption pkgs "xdg-desktop-portal-hyprland" {
|
||||
nullable = true;
|
||||
};
|
||||
|
||||
finalPackage = lib.mkOption {
|
||||
type = with lib.types; nullOr package;
|
||||
readOnly = true;
|
||||
default =
|
||||
if cfg.package != null then
|
||||
cfg.package.override { enableXWayland = cfg.xwayland.enable; }
|
||||
else
|
||||
null;
|
||||
defaultText = lib.literalMD "`wayland.windowManager.hyprland.package` with applied configuration";
|
||||
description = ''
|
||||
The Hyprland package after applying configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
finalPortalPackage = lib.mkOption {
|
||||
type = with lib.types; nullOr package;
|
||||
readOnly = true;
|
||||
default =
|
||||
if (cfg.portalPackage != null) then
|
||||
if cfg.finalPackage != null then
|
||||
cfg.portalPackage.override { hyprland = cfg.finalPackage; }
|
||||
else
|
||||
cfg.portalPackage
|
||||
else
|
||||
null;
|
||||
defaultText = lib.literalMD ''
|
||||
`wayland.windowManager.hyprland.portalPackage` with
|
||||
`wayland.windowManager.hyprland.finalPackage` override'';
|
||||
description = ''
|
||||
The xdg-desktop-portal-hyprland package after overriding its hyprland input.
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = lib.mkOption {
|
||||
type = with lib.types; listOf (either package path);
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of Hyprland plugins to use. Can either be packages or
|
||||
absolute plugin paths.
|
||||
'';
|
||||
};
|
||||
|
||||
configType = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"hyprlang"
|
||||
"lua"
|
||||
];
|
||||
inherit
|
||||
(lib.hm.deprecations.mkStateVersionOptionDefault {
|
||||
inherit (config.home) stateVersion;
|
||||
since = "26.05";
|
||||
optionPath = [
|
||||
"wayland"
|
||||
"windowManager"
|
||||
"hyprland"
|
||||
"configType"
|
||||
];
|
||||
legacy.value = "hyprlang";
|
||||
current.value = "lua";
|
||||
})
|
||||
default
|
||||
defaultText
|
||||
;
|
||||
description = ''
|
||||
The type of Hyprland configuration to generate.
|
||||
|
||||
`hyprlang` writes {file}`$XDG_CONFIG_HOME/hypr/hyprland.conf`.
|
||||
`lua` writes {file}`$XDG_CONFIG_HOME/hypr/hyprland.lua`.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd = {
|
||||
enable = lib.mkEnableOption null // {
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable {file}`hyprland-session.target` on
|
||||
hyprland startup. This links to `graphical-session.target`.
|
||||
Some important environment variables will be imported to systemd
|
||||
and D-Bus user environment before reaching the target, including
|
||||
- `DISPLAY`
|
||||
- `HYPRLAND_INSTANCE_SIGNATURE`
|
||||
- `WAYLAND_DISPLAY`
|
||||
- `XDG_CURRENT_DESKTOP`
|
||||
- `XDG_SESSION_TYPE`
|
||||
'';
|
||||
};
|
||||
|
||||
variables = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [
|
||||
"DISPLAY"
|
||||
"HYPRLAND_INSTANCE_SIGNATURE"
|
||||
"WAYLAND_DISPLAY"
|
||||
"XDG_CURRENT_DESKTOP"
|
||||
"XDG_SESSION_TYPE"
|
||||
];
|
||||
example = [ "--all" ];
|
||||
description = ''
|
||||
Environment variables to be imported in the systemd & D-Bus user
|
||||
environment.
|
||||
'';
|
||||
};
|
||||
|
||||
extraCommands = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [
|
||||
"systemctl --user stop hyprland-session.target"
|
||||
"systemctl --user start hyprland-session.target"
|
||||
];
|
||||
description = "Extra commands to be run after D-Bus activation.";
|
||||
};
|
||||
|
||||
enableXdgAutostart = lib.mkEnableOption ''
|
||||
autostart of applications using
|
||||
{manpage}`systemd-xdg-autostart-generator(8)`'';
|
||||
};
|
||||
|
||||
xwayland.enable = lib.mkEnableOption "XWayland" // {
|
||||
default = true;
|
||||
description = ''
|
||||
Whether or not to enable XWayland.
|
||||
|
||||
Overrides the `enableXWayland` option of the Hyprland package.
|
||||
|
||||
In newer versions of Hyprland, you can use the {option}`wayland.windowManager.hyprland.settings.xwayland`
|
||||
option to avoid recompiling Hyprland.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = settingValueType;
|
||||
default = { };
|
||||
description = ''
|
||||
Hyprland configuration written in Nix. Entries with the same key
|
||||
should be written as lists. Variables' and colors' names should be
|
||||
quoted. See <https://wiki.hypr.land> for more examples.
|
||||
|
||||
::: {.note}
|
||||
Use the [](#opt-wayland.windowManager.hyprland.plugins) option to
|
||||
declare plugins.
|
||||
:::
|
||||
|
||||
When {option}`wayland.windowManager.hyprland.configType` is `"lua"`,
|
||||
each attribute maps to an `hl.<name>(...)` call. List values generate
|
||||
one call per element.
|
||||
|
||||
Attribute values with an `_args` list generate multi-argument calls.
|
||||
Attribute values with `_var` generate a Lua local variable instead of
|
||||
an `hl.<name>(...)` call. If no `name` is set, the attribute name is
|
||||
used as the Lua variable name.
|
||||
Values created with `lib.generators.mkLuaInline` are rendered as raw
|
||||
Lua expressions.
|
||||
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
mod = {
|
||||
_var = "SUPER";
|
||||
};
|
||||
|
||||
config = {
|
||||
general = {
|
||||
gaps_in = 5;
|
||||
gaps_out = 20;
|
||||
border_size = 2;
|
||||
};
|
||||
|
||||
decoration = {
|
||||
rounding = 10;
|
||||
};
|
||||
};
|
||||
|
||||
bind = [
|
||||
{
|
||||
_args = [
|
||||
(lib.generators.mkLuaInline "mod .. \" + Q\"")
|
||||
(lib.generators.mkLuaInline "hl.dsp.window.close()")
|
||||
{ locked = true; }
|
||||
];
|
||||
}
|
||||
{
|
||||
_args = [
|
||||
"SUPER + RETURN"
|
||||
(lib.generators.mkLuaInline "hl.dsp.exec_cmd(\"kitty\")")
|
||||
];
|
||||
}
|
||||
{
|
||||
_args = [
|
||||
"ALT + R"
|
||||
(lib.generators.mkLuaInline "hl.dsp.submap(\"resize\")")
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
define_submap = {
|
||||
_args = [
|
||||
"resize"
|
||||
(lib.generators.mkLuaInline "function()\n hl.bind(\"right\", hl.dsp.window.resize({ x = 10, y = 0, relative = true }), { repeating = true })\n hl.bind(\"left\", hl.dsp.window.resize({ x = -10, y = 0, relative = true }), { repeating = true })\n hl.bind(\"escape\", hl.dsp.submap(\"reset\"))\nend")
|
||||
];
|
||||
};
|
||||
|
||||
window_rule = {
|
||||
match.class = "kitty";
|
||||
border_size = 2;
|
||||
};
|
||||
|
||||
on = {
|
||||
_args = [
|
||||
"hyprland.start"
|
||||
(lib.generators.mkLuaInline "function()\n hl.exec_cmd(\"waybar\")\nend")
|
||||
];
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
submaps = lib.mkOption {
|
||||
description = ''
|
||||
Attribute set of Hyprland submaps.
|
||||
|
||||
See <https://wiki.hypr.land/Configuring/Basics/Binds/#submaps> to learn about submaps.
|
||||
'';
|
||||
default = { };
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
onDispatch = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Submap to use after a dispatch. Can either be a name or `reset` to disable submap after any dispatch.
|
||||
'';
|
||||
example = "reset";
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
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 ''
|
||||
{
|
||||
binde = [
|
||||
", right, resizeactive, 10 0"
|
||||
", left, resizeactive, -10 0"
|
||||
", up, resizeactive, 0 -10"
|
||||
", down, resizeactive, 0 10"
|
||||
];
|
||||
|
||||
bind = [
|
||||
", escape, submap, reset"
|
||||
{
|
||||
_args = [
|
||||
"escape"
|
||||
(lib.generators.mkLuaInline "hl.dsp.submap(\"reset\")")
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
# submap to change window focus with vim keys
|
||||
move_focus = {
|
||||
settings = {
|
||||
bind = [
|
||||
", h, movefocus, l"
|
||||
", j, movefocus, d"
|
||||
", k, movefocus, u"
|
||||
", l, movefocus, r"
|
||||
|
||||
", escape, submap, reset"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
other_submap = {
|
||||
settings = {
|
||||
# ...
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
hl.on("window.open", function()
|
||||
hl.timer(function()
|
||||
hl.dispatch(hl.dsp.exec_cmd("notify-send 'Window opened'"))
|
||||
end, {
|
||||
timeout = 100,
|
||||
type = "oneshot",
|
||||
})
|
||||
end)
|
||||
'';
|
||||
description = ''
|
||||
Extra configuration content appended to the generated Hyprland file.
|
||||
'';
|
||||
};
|
||||
|
||||
extraLuaFiles = lib.mkOption {
|
||||
type =
|
||||
with lib.types;
|
||||
attrsOf (
|
||||
coercedTo (either path lines)
|
||||
(content: {
|
||||
inherit content;
|
||||
autoLoad = true;
|
||||
})
|
||||
(submodule {
|
||||
options = {
|
||||
content = lib.mkOption {
|
||||
type = either path lines;
|
||||
description = ''
|
||||
Lua file content, set either by specifying a path to a Lua
|
||||
file or by providing a multi-line Lua string.
|
||||
'';
|
||||
};
|
||||
|
||||
autoLoad = lib.mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to generate a `require(...)` call for this file in
|
||||
{file}`$XDG_CONFIG_HOME/hypr/hyprland.lua`.
|
||||
'';
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = { };
|
||||
description = ''
|
||||
Extra Lua files written under {file}`$XDG_CONFIG_HOME/hypr`.
|
||||
|
||||
Attribute names are used as Lua module names and converted to file
|
||||
names with a {file}`.lua` suffix added when missing. For example,
|
||||
`bindings` writes
|
||||
{file}`$XDG_CONFIG_HOME/hypr/bindings.lua`, while
|
||||
`lib.helpers` writes {file}`$XDG_CONFIG_HOME/hypr/lib/helpers.lua`.
|
||||
|
||||
Files with {option}`autoLoad` enabled generate `require(...)` calls in
|
||||
{file}`$XDG_CONFIG_HOME/hypr/hyprland.lua` after adding the Hypr config
|
||||
directory to Lua's `package.path`. Use {option}`autoLoad = false` for
|
||||
helper modules that are imported by other Lua files.
|
||||
|
||||
This option only affects generated files when
|
||||
{option}`wayland.windowManager.hyprland.configType` is `"lua"`.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
"00-vars" = '\'
|
||||
local M = {}
|
||||
M.mainMod = "SUPER"
|
||||
return M
|
||||
'\';
|
||||
|
||||
"bindings" = {
|
||||
content = ./bindings.lua;
|
||||
autoLoad = true;
|
||||
};
|
||||
|
||||
"lib.helpers" = {
|
||||
content = ./helpers.lua;
|
||||
autoLoad = false;
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
sourceFirst =
|
||||
lib.mkEnableOption ''
|
||||
putting source entries at the top of the configuration
|
||||
''
|
||||
// {
|
||||
default = true;
|
||||
};
|
||||
|
||||
importantPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [
|
||||
"$"
|
||||
"bezier"
|
||||
"curve"
|
||||
"name"
|
||||
"output"
|
||||
];
|
||||
example = [
|
||||
"$"
|
||||
"bezier"
|
||||
];
|
||||
description = ''
|
||||
List of prefix of attributes to source at the top of the config.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
(lib.hm.assertions.assertPlatform "wayland.windowManager.hyprland" pkgs lib.platforms.linux)
|
||||
{
|
||||
assertion = !builtins.hasAttr "reset" cfg.submaps;
|
||||
message = "Submaps can't be named 'reset'. The name 'reset' is reserved in order to have a way to switch to the default submap; as if 'reset' was its name.";
|
||||
}
|
||||
{
|
||||
assertion = !builtins.elem "hyprland.lua" (map luaFileName (lib.attrNames cfg.extraLuaFiles));
|
||||
message = "wayland.windowManager.hyprland.extraLuaFiles cannot define hyprland.lua because it is generated by the Hyprland module.";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
let
|
||||
targets = map luaFileName (lib.attrNames cfg.extraLuaFiles);
|
||||
in
|
||||
lib.length targets == lib.length (lib.unique targets);
|
||||
message = "wayland.windowManager.hyprland.extraLuaFiles contains entries that resolve to the same Lua file path.";
|
||||
}
|
||||
];
|
||||
|
||||
warnings =
|
||||
let
|
||||
inconsistent =
|
||||
(cfg.systemd.enable || cfg.plugins != [ ])
|
||||
&& cfg.extraConfig == ""
|
||||
&& cfg.extraLuaFiles == { }
|
||||
&& cfg.settings == { }
|
||||
&& cfg.submaps == { };
|
||||
warning = "You have enabled hyprland.systemd.enable or listed plugins in hyprland.plugins but do not have any configuration in hyprland.settings, hyprland.extraConfig, hyprland.extraLuaFiles or hyprland.submaps. This is almost certainly a mistake.";
|
||||
|
||||
filterNonBinds =
|
||||
attrs: builtins.filter (n: builtins.match "bind[[:lower:]]*" n == null) (builtins.attrNames attrs);
|
||||
|
||||
# attrset of { <submap name> = <list of non bind* keys>; } for all submaps
|
||||
submapWarningsAttrset = builtins.mapAttrs (
|
||||
_name: submap: filterNonBinds submap.settings
|
||||
) cfg.submaps;
|
||||
|
||||
submapWarnings = lib.mapAttrsToList (submapName: nonBinds: ''
|
||||
wayland.windowManager.hyprland.submaps."${submapName}".settings: found non-bind entries: [${toString nonBinds}], which will have no effect in a submap
|
||||
'') (lib.filterAttrs (_n: v: v != [ ]) submapWarningsAttrset);
|
||||
in
|
||||
submapWarnings ++ lib.optional inconsistent warning;
|
||||
|
||||
home.packages = lib.mkIf (cfg.package != null) (
|
||||
[ cfg.finalPackage ] ++ lib.optional cfg.xwayland.enable pkgs.xwayland
|
||||
);
|
||||
|
||||
xdg.configFile = lib.mkMerge [
|
||||
{
|
||||
"hypr/.luarc.json" = lib.mkIf (cfg.configType == "lua" && cfg.finalPackage != null) {
|
||||
text = builtins.toJSON {
|
||||
workspace.library = [ "${cfg.finalPackage}/share/hypr/stubs" ];
|
||||
diagnostics.globals = [ "hl" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
"hypr/hyprland.conf" = lib.mkIf (cfg.configType == "hyprlang") (
|
||||
let
|
||||
importantPrefixes = cfg.importantPrefixes ++ lib.optional cfg.sourceFirst "source";
|
||||
|
||||
pluginsToHyprconf =
|
||||
plugins:
|
||||
lib.hm.generators.toHyprconf {
|
||||
attrs = {
|
||||
"exec-once" = map (entry: "hyprctl plugin load ${pluginPath entry}") plugins;
|
||||
};
|
||||
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 = hyprlangSubmapSettings attrs.settings;
|
||||
indentLevel = 0;
|
||||
}
|
||||
}submap = reset
|
||||
'';
|
||||
|
||||
submapsToHyprConf = lib.concatMapAttrsStringSep "\n" mkSubMap hyprlangSubmaps;
|
||||
|
||||
shouldGenerate =
|
||||
cfg.systemd.enable
|
||||
|| cfg.extraConfig != ""
|
||||
|| cfg.settings != { }
|
||||
|| cfg.plugins != [ ]
|
||||
|| hyprlangSubmaps != { };
|
||||
in
|
||||
lib.mkIf shouldGenerate {
|
||||
text =
|
||||
lib.optionalString cfg.systemd.enable ''
|
||||
exec-once = ${systemdActivationCommand}
|
||||
''
|
||||
+ lib.optionalString (cfg.plugins != [ ]) (pluginsToHyprconf cfg.plugins)
|
||||
+ lib.optionalString (cfg.settings != { }) (
|
||||
lib.hm.generators.toHyprconf {
|
||||
attrs = cfg.settings;
|
||||
inherit importantPrefixes;
|
||||
}
|
||||
)
|
||||
+ lib.optionalString (hyprlangSubmaps != { }) submapsToHyprConf
|
||||
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
|
||||
|
||||
onChange = lib.mkIf (cfg.package != null) reloadConfig;
|
||||
}
|
||||
);
|
||||
}
|
||||
{
|
||||
"hypr/hyprland.lua" = lib.mkIf (cfg.configType == "lua") (
|
||||
let
|
||||
pluginLoadCommands = map (entry: "hyprctl plugin load ${pluginPath entry}") cfg.plugins;
|
||||
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);
|
||||
luaLocalNames = builtins.filter (
|
||||
name: lib.isAttrs cfg.settings.${name} && cfg.settings.${name} ? _var
|
||||
) names;
|
||||
settingNames = builtins.filter (name: !(builtins.elem name luaLocalNames)) names;
|
||||
importantNames = lib.unique (
|
||||
lib.concatMap (
|
||||
prefix: builtins.filter (name: lib.hasPrefix prefix name) settingNames
|
||||
) cfg.importantPrefixes
|
||||
);
|
||||
orderedNames =
|
||||
importantNames ++ builtins.filter (name: !(builtins.elem name importantNames)) settingNames;
|
||||
renderLocal =
|
||||
name:
|
||||
let
|
||||
value = cfg.settings.${name};
|
||||
in
|
||||
"local ${value.name or name} = ${renderArgs value._var}\n";
|
||||
renderCall = name: value: "hl.${name}(${renderArgs value})\n";
|
||||
renderCalls =
|
||||
name: value:
|
||||
lib.concatMapStrings (renderCall name) (if builtins.isList value then value else [ value ]);
|
||||
in
|
||||
lib.optionalString (luaLocalNames != [ ]) (
|
||||
renderSection "settings.locals" (lib.concatMapStrings renderLocal luaLocalNames)
|
||||
)
|
||||
+ 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)
|
||||
'';
|
||||
|
||||
renderLuaFiles =
|
||||
let
|
||||
autoloadFiles = lib.filterAttrs (_: file: file.autoLoad) cfg.extraLuaFiles;
|
||||
names = lib.sort lib.lessThan (lib.attrNames autoloadFiles);
|
||||
in
|
||||
if names == [ ] then
|
||||
""
|
||||
else
|
||||
renderSection "extraLuaFiles" (
|
||||
''
|
||||
local hm_xdg_config_home = os.getenv("XDG_CONFIG_HOME") or ${toLua config.xdg.configHome}
|
||||
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
|
||||
);
|
||||
|
||||
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.extraLuaFiles != { }
|
||||
|| cfg.settings != { }
|
||||
|| cfg.plugins != [ ]
|
||||
|| hasLuaSubmaps;
|
||||
in
|
||||
lib.mkIf shouldGenerate {
|
||||
text = ''
|
||||
-- Generated by Home Manager.
|
||||
-- See https://wiki.hypr.land/Configuring/Start/
|
||||
|
||||
''
|
||||
+ renderLuaFiles
|
||||
+ renderSettings
|
||||
+ renderSubmaps
|
||||
+ renderStartHook
|
||||
+ renderSection "extraConfig" cfg.extraConfig;
|
||||
|
||||
onChange = lib.mkIf (cfg.package != null) reloadConfig;
|
||||
}
|
||||
);
|
||||
}
|
||||
(lib.mkIf (cfg.configType == "lua") (
|
||||
lib.mapAttrs' (
|
||||
name: file:
|
||||
lib.nameValuePair "hypr/${luaFileName name}" (
|
||||
if builtins.isPath file.content || lib.isStorePath file.content then
|
||||
{ source = file.content; }
|
||||
else
|
||||
{ text = file.content; }
|
||||
)
|
||||
) cfg.extraLuaFiles
|
||||
))
|
||||
];
|
||||
|
||||
xdg.portal = {
|
||||
enable = cfg.finalPortalPackage != null;
|
||||
extraPortals = lib.mkIf (cfg.finalPortalPackage != null) [ cfg.finalPortalPackage ];
|
||||
configPackages = lib.mkIf (cfg.finalPackage != null) (lib.mkDefault [ cfg.finalPackage ]);
|
||||
};
|
||||
|
||||
systemd.user.targets.hyprland-session = lib.mkIf cfg.systemd.enable {
|
||||
Unit = {
|
||||
Description = "Hyprland compositor session";
|
||||
Documentation = [ "man:systemd.special(7)" ];
|
||||
BindsTo = [ "graphical-session.target" ];
|
||||
Wants = [
|
||||
"graphical-session-pre.target"
|
||||
]
|
||||
++ lib.optional cfg.systemd.enableXdgAutostart "xdg-desktop-autostart.target";
|
||||
After = [ "graphical-session-pre.target" ];
|
||||
Before = lib.mkIf cfg.systemd.enableXdgAutostart [ "xdg-desktop-autostart.target" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
# NOTE: Keep this compatibility shim so external flakes using disabledModules
|
||||
# with the old hyprland.nix path can still disable the module. See
|
||||
# https://github.com/nix-community/home-manager/pull/7304 and
|
||||
# https://github.com/hyprland-community/hyprnix.
|
||||
imports = [ ./hyprland ];
|
||||
}
|
||||
|
||||
598
modules/services/window-managers/hyprland/default.nix
Normal file
598
modules/services/window-managers/hyprland/default.nix
Normal file
@@ -0,0 +1,598 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.wayland.windowManager.hyprland;
|
||||
|
||||
hyprlandLib = import ./lib.nix {
|
||||
inherit
|
||||
lib
|
||||
pkgs
|
||||
;
|
||||
config = cfg;
|
||||
};
|
||||
|
||||
settingValueType =
|
||||
with lib.types;
|
||||
nullOr (oneOf [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
path
|
||||
(attrsOf settingValueType)
|
||||
(listOf settingValueType)
|
||||
])
|
||||
// {
|
||||
description = "Hyprland configuration value";
|
||||
};
|
||||
|
||||
reloadConfig = ''
|
||||
(
|
||||
XDG_RUNTIME_DIR=''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
|
||||
if [[ -d "/tmp/hypr" || -d "$XDG_RUNTIME_DIR/hypr" ]]; then
|
||||
for i in $(${cfg.finalPackage}/bin/hyprctl instances -j | jq ".[].instance" -r); do
|
||||
${cfg.finalPackage}/bin/hyprctl -i "$i" reload config-only
|
||||
done
|
||||
fi
|
||||
)
|
||||
'';
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [
|
||||
fufexan
|
||||
khaneliman
|
||||
];
|
||||
|
||||
options.wayland.windowManager.hyprland = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable configuration for Hyprland, a tiling Wayland
|
||||
compositor that doesn't sacrifice on its looks.
|
||||
|
||||
::: {.note}
|
||||
This module configures Hyprland and adds it to your user's {env}`PATH`,
|
||||
but does not make certain system-level changes. NixOS users should
|
||||
enable the NixOS module with {option}`programs.hyprland.enable`, which
|
||||
makes system-level changes such as adding a desktop session entry.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkPackageOption pkgs "hyprland" {
|
||||
nullable = true;
|
||||
extraDescription = "Set this to null if you use the NixOS module to install Hyprland.";
|
||||
};
|
||||
|
||||
portalPackage = lib.mkPackageOption pkgs "xdg-desktop-portal-hyprland" {
|
||||
nullable = true;
|
||||
};
|
||||
|
||||
finalPackage = lib.mkOption {
|
||||
type = with lib.types; nullOr package;
|
||||
readOnly = true;
|
||||
default =
|
||||
if cfg.package != null then
|
||||
cfg.package.override { enableXWayland = cfg.xwayland.enable; }
|
||||
else
|
||||
null;
|
||||
defaultText = lib.literalMD "`wayland.windowManager.hyprland.package` with applied configuration";
|
||||
description = ''
|
||||
The Hyprland package after applying configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
finalPortalPackage = lib.mkOption {
|
||||
type = with lib.types; nullOr package;
|
||||
readOnly = true;
|
||||
default =
|
||||
if (cfg.portalPackage != null) then
|
||||
if cfg.finalPackage != null then
|
||||
cfg.portalPackage.override { hyprland = cfg.finalPackage; }
|
||||
else
|
||||
cfg.portalPackage
|
||||
else
|
||||
null;
|
||||
defaultText = lib.literalMD ''
|
||||
`wayland.windowManager.hyprland.portalPackage` with
|
||||
`wayland.windowManager.hyprland.finalPackage` override'';
|
||||
description = ''
|
||||
The xdg-desktop-portal-hyprland package after overriding its hyprland input.
|
||||
'';
|
||||
};
|
||||
|
||||
plugins = lib.mkOption {
|
||||
type = with lib.types; listOf (either package path);
|
||||
default = [ ];
|
||||
description = ''
|
||||
List of Hyprland plugins to use. Can either be packages or
|
||||
absolute plugin paths.
|
||||
'';
|
||||
};
|
||||
|
||||
configType = lib.mkOption {
|
||||
type = lib.types.enum [
|
||||
"hyprlang"
|
||||
"lua"
|
||||
];
|
||||
inherit
|
||||
(lib.hm.deprecations.mkStateVersionOptionDefault {
|
||||
inherit (config.home) stateVersion;
|
||||
since = "26.05";
|
||||
optionPath = [
|
||||
"wayland"
|
||||
"windowManager"
|
||||
"hyprland"
|
||||
"configType"
|
||||
];
|
||||
legacy.value = "hyprlang";
|
||||
current.value = "lua";
|
||||
})
|
||||
default
|
||||
defaultText
|
||||
;
|
||||
description = ''
|
||||
The type of Hyprland configuration to generate.
|
||||
|
||||
`hyprlang` writes {file}`$XDG_CONFIG_HOME/hypr/hyprland.conf`.
|
||||
`lua` writes {file}`$XDG_CONFIG_HOME/hypr/hyprland.lua`.
|
||||
'';
|
||||
};
|
||||
|
||||
systemd = {
|
||||
enable = lib.mkEnableOption null // {
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable {file}`hyprland-session.target` on
|
||||
hyprland startup. This links to `graphical-session.target`.
|
||||
Some important environment variables will be imported to systemd
|
||||
and D-Bus user environment before reaching the target, including
|
||||
- `DISPLAY`
|
||||
- `HYPRLAND_INSTANCE_SIGNATURE`
|
||||
- `WAYLAND_DISPLAY`
|
||||
- `XDG_CURRENT_DESKTOP`
|
||||
- `XDG_SESSION_TYPE`
|
||||
'';
|
||||
};
|
||||
|
||||
variables = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [
|
||||
"DISPLAY"
|
||||
"HYPRLAND_INSTANCE_SIGNATURE"
|
||||
"WAYLAND_DISPLAY"
|
||||
"XDG_CURRENT_DESKTOP"
|
||||
"XDG_SESSION_TYPE"
|
||||
];
|
||||
example = [ "--all" ];
|
||||
description = ''
|
||||
Environment variables to be imported in the systemd & D-Bus user
|
||||
environment.
|
||||
'';
|
||||
};
|
||||
|
||||
extraCommands = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [
|
||||
"systemctl --user stop hyprland-session.target"
|
||||
"systemctl --user start hyprland-session.target"
|
||||
];
|
||||
description = "Extra commands to be run after D-Bus activation.";
|
||||
};
|
||||
|
||||
enableXdgAutostart = lib.mkEnableOption ''
|
||||
autostart of applications using
|
||||
{manpage}`systemd-xdg-autostart-generator(8)`'';
|
||||
};
|
||||
|
||||
xwayland.enable = lib.mkEnableOption "XWayland" // {
|
||||
default = true;
|
||||
description = ''
|
||||
Whether or not to enable XWayland.
|
||||
|
||||
Overrides the `enableXWayland` option of the Hyprland package.
|
||||
|
||||
In newer versions of Hyprland, you can use the {option}`wayland.windowManager.hyprland.settings.xwayland`
|
||||
option to avoid recompiling Hyprland.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = settingValueType;
|
||||
default = { };
|
||||
description = ''
|
||||
Hyprland configuration written in Nix. Entries with the same key
|
||||
should be written as lists. Variables' and colors' names should be
|
||||
quoted. See <https://wiki.hypr.land> for more examples.
|
||||
|
||||
::: {.note}
|
||||
Use the [](#opt-wayland.windowManager.hyprland.plugins) option to
|
||||
declare plugins.
|
||||
:::
|
||||
|
||||
When {option}`wayland.windowManager.hyprland.configType` is `"lua"`,
|
||||
each attribute maps to an `hl.<name>(...)` call. List values generate
|
||||
one call per element.
|
||||
|
||||
Attribute values with an `_args` list generate multi-argument calls.
|
||||
Attribute values with `_var` generate a Lua local variable instead of
|
||||
an `hl.<name>(...)` call. If no `name` is set, the attribute name is
|
||||
used as the Lua variable name.
|
||||
Values created with `lib.generators.mkLuaInline` are rendered as raw
|
||||
Lua expressions.
|
||||
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
mod = {
|
||||
_var = "SUPER";
|
||||
};
|
||||
|
||||
config = {
|
||||
general = {
|
||||
gaps_in = 5;
|
||||
gaps_out = 20;
|
||||
border_size = 2;
|
||||
};
|
||||
|
||||
decoration = {
|
||||
rounding = 10;
|
||||
};
|
||||
};
|
||||
|
||||
bind = [
|
||||
{
|
||||
_args = [
|
||||
(lib.generators.mkLuaInline "mod .. \" + Q\"")
|
||||
(lib.generators.mkLuaInline "hl.dsp.window.close()")
|
||||
{ locked = true; }
|
||||
];
|
||||
}
|
||||
{
|
||||
_args = [
|
||||
"SUPER + RETURN"
|
||||
(lib.generators.mkLuaInline "hl.dsp.exec_cmd(\"kitty\")")
|
||||
];
|
||||
}
|
||||
{
|
||||
_args = [
|
||||
"ALT + R"
|
||||
(lib.generators.mkLuaInline "hl.dsp.submap(\"resize\")")
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
define_submap = {
|
||||
_args = [
|
||||
"resize"
|
||||
(lib.generators.mkLuaInline "function()\n hl.bind(\"right\", hl.dsp.window.resize({ x = 10, y = 0, relative = true }), { repeating = true })\n hl.bind(\"left\", hl.dsp.window.resize({ x = -10, y = 0, relative = true }), { repeating = true })\n hl.bind(\"escape\", hl.dsp.submap(\"reset\"))\nend")
|
||||
];
|
||||
};
|
||||
|
||||
window_rule = {
|
||||
match.class = "kitty";
|
||||
border_size = 2;
|
||||
};
|
||||
|
||||
on = {
|
||||
_args = [
|
||||
"hyprland.start"
|
||||
(lib.generators.mkLuaInline "function()\n hl.exec_cmd(\"waybar\")\nend")
|
||||
];
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
submaps = lib.mkOption {
|
||||
description = ''
|
||||
Attribute set of Hyprland submaps.
|
||||
|
||||
See <https://wiki.hypr.land/Configuring/Basics/Binds/#submaps> to learn about submaps.
|
||||
'';
|
||||
default = { };
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
onDispatch = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Submap to use after a dispatch. Can either be a name or `reset` to disable submap after any dispatch.
|
||||
'';
|
||||
example = "reset";
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
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 ''
|
||||
{
|
||||
binde = [
|
||||
", right, resizeactive, 10 0"
|
||||
", left, resizeactive, -10 0"
|
||||
", up, resizeactive, 0 -10"
|
||||
", down, resizeactive, 0 10"
|
||||
];
|
||||
|
||||
bind = [
|
||||
", escape, submap, reset"
|
||||
{
|
||||
_args = [
|
||||
"escape"
|
||||
(lib.generators.mkLuaInline "hl.dsp.submap(\"reset\")")
|
||||
];
|
||||
}
|
||||
];
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
# submap to change window focus with vim keys
|
||||
move_focus = {
|
||||
settings = {
|
||||
bind = [
|
||||
", h, movefocus, l"
|
||||
", j, movefocus, d"
|
||||
", k, movefocus, u"
|
||||
", l, movefocus, r"
|
||||
|
||||
", escape, submap, reset"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
other_submap = {
|
||||
settings = {
|
||||
# ...
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
example = ''
|
||||
hl.on("window.open", function()
|
||||
hl.timer(function()
|
||||
hl.dispatch(hl.dsp.exec_cmd("notify-send 'Window opened'"))
|
||||
end, {
|
||||
timeout = 100,
|
||||
type = "oneshot",
|
||||
})
|
||||
end)
|
||||
'';
|
||||
description = ''
|
||||
Extra configuration content appended to the generated Hyprland file.
|
||||
'';
|
||||
};
|
||||
|
||||
extraLuaFiles = lib.mkOption {
|
||||
type =
|
||||
with lib.types;
|
||||
attrsOf (
|
||||
coercedTo (either path lines)
|
||||
(content: {
|
||||
inherit content;
|
||||
autoLoad = true;
|
||||
})
|
||||
(submodule {
|
||||
options = {
|
||||
content = lib.mkOption {
|
||||
type = either path lines;
|
||||
description = ''
|
||||
Lua file content, set either by specifying a path to a Lua
|
||||
file or by providing a multi-line Lua string.
|
||||
'';
|
||||
};
|
||||
|
||||
autoLoad = lib.mkOption {
|
||||
type = bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to generate a `require(...)` call for this file in
|
||||
{file}`$XDG_CONFIG_HOME/hypr/hyprland.lua`.
|
||||
'';
|
||||
};
|
||||
};
|
||||
})
|
||||
);
|
||||
default = { };
|
||||
description = ''
|
||||
Extra Lua files written under {file}`$XDG_CONFIG_HOME/hypr`.
|
||||
|
||||
Attribute names are used as Lua module names and converted to file
|
||||
names with a {file}`.lua` suffix added when missing. For example,
|
||||
`bindings` writes
|
||||
{file}`$XDG_CONFIG_HOME/hypr/bindings.lua`, while
|
||||
`lib.helpers` writes {file}`$XDG_CONFIG_HOME/hypr/lib/helpers.lua`.
|
||||
|
||||
Files with {option}`autoLoad` enabled generate `require(...)` calls in
|
||||
{file}`$XDG_CONFIG_HOME/hypr/hyprland.lua` after adding the Hypr config
|
||||
directory to Lua's `package.path`. Use {option}`autoLoad = false` for
|
||||
helper modules that are imported by other Lua files.
|
||||
|
||||
This option only affects generated files when
|
||||
{option}`wayland.windowManager.hyprland.configType` is `"lua"`.
|
||||
'';
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
"00-vars" = '\'
|
||||
local M = {}
|
||||
M.mainMod = "SUPER"
|
||||
return M
|
||||
'\';
|
||||
|
||||
"ui.bindings" = {
|
||||
content = ./bindings.lua;
|
||||
autoLoad = true;
|
||||
};
|
||||
|
||||
"lib.helpers" = {
|
||||
content = ./helpers.lua;
|
||||
autoLoad = false;
|
||||
};
|
||||
|
||||
"from-path.lua" = ./startup.lua;
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
sourceFirst =
|
||||
lib.mkEnableOption ''
|
||||
putting source entries at the top of the configuration
|
||||
''
|
||||
// {
|
||||
default = true;
|
||||
};
|
||||
|
||||
importantPrefixes = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = [
|
||||
"$"
|
||||
"bezier"
|
||||
"curve"
|
||||
"name"
|
||||
"output"
|
||||
];
|
||||
example = [
|
||||
"$"
|
||||
"bezier"
|
||||
];
|
||||
description = ''
|
||||
List of prefix of attributes to source at the top of the config.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
luaLanguageServerConfig = {
|
||||
"hypr/.luarc.json" = lib.mkIf (cfg.configType == "lua" && cfg.finalPackage != null) {
|
||||
text = builtins.toJSON {
|
||||
workspace.library = [ "${cfg.finalPackage}/share/hypr/stubs" ];
|
||||
diagnostics.globals = [ "hl" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
hyprlangConfigFile = {
|
||||
"hypr/hyprland.conf" = lib.mkIf (cfg.configType == "hyprlang") (
|
||||
hyprlandLib.hyprlangConfig { inherit reloadConfig; }
|
||||
);
|
||||
};
|
||||
|
||||
luaConfigFile = {
|
||||
"hypr/hyprland.lua" = lib.mkIf (cfg.configType == "lua") (
|
||||
hyprlandLib.luaConfig {
|
||||
inherit reloadConfig;
|
||||
xdgConfigHome = config.xdg.configHome;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
extraLuaFiles = lib.mkIf (cfg.configType == "lua") hyprlandLib.extraLuaFiles;
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
assertions = [
|
||||
(lib.hm.assertions.assertPlatform "wayland.windowManager.hyprland" pkgs lib.platforms.linux)
|
||||
{
|
||||
assertion = !builtins.hasAttr "reset" cfg.submaps;
|
||||
message = "Submaps can't be named 'reset'. The name 'reset' is reserved in order to have a way to switch to the default submap; as if 'reset' was its name.";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
!builtins.elem "hyprland.lua" (map hyprlandLib.luaFileName (lib.attrNames cfg.extraLuaFiles));
|
||||
message = "wayland.windowManager.hyprland.extraLuaFiles cannot define hyprland.lua because it is generated by the Hyprland module.";
|
||||
}
|
||||
{
|
||||
assertion =
|
||||
let
|
||||
targets = map hyprlandLib.luaFileName (lib.attrNames cfg.extraLuaFiles);
|
||||
in
|
||||
lib.length targets == lib.length (lib.unique targets);
|
||||
message = "wayland.windowManager.hyprland.extraLuaFiles contains entries that resolve to the same Lua file path.";
|
||||
}
|
||||
];
|
||||
|
||||
warnings =
|
||||
let
|
||||
inconsistent =
|
||||
(cfg.systemd.enable || cfg.plugins != [ ])
|
||||
&& cfg.extraConfig == ""
|
||||
&& cfg.extraLuaFiles == { }
|
||||
&& cfg.settings == { }
|
||||
&& cfg.submaps == { };
|
||||
warning = "You have enabled hyprland.systemd.enable or listed plugins in hyprland.plugins but do not have any configuration in hyprland.settings, hyprland.extraConfig, hyprland.extraLuaFiles or hyprland.submaps. This is almost certainly a mistake.";
|
||||
|
||||
filterNonBinds =
|
||||
attrs: builtins.filter (n: builtins.match "bind[[:lower:]]*" n == null) (builtins.attrNames attrs);
|
||||
|
||||
# attrset of { <submap name> = <list of non bind* keys>; } for all submaps
|
||||
submapWarningsAttrset = builtins.mapAttrs (
|
||||
_name: submap: filterNonBinds submap.settings
|
||||
) cfg.submaps;
|
||||
|
||||
submapWarnings = lib.mapAttrsToList (submapName: nonBinds: ''
|
||||
wayland.windowManager.hyprland.submaps."${submapName}".settings: found non-bind entries: [${toString nonBinds}], which will have no effect in a submap
|
||||
'') (lib.filterAttrs (_n: v: v != [ ]) submapWarningsAttrset);
|
||||
in
|
||||
submapWarnings ++ lib.optional inconsistent warning;
|
||||
|
||||
home.packages = lib.mkIf (cfg.package != null) (
|
||||
[ cfg.finalPackage ] ++ lib.optional cfg.xwayland.enable pkgs.xwayland
|
||||
);
|
||||
|
||||
xdg.configFile = lib.mkMerge [
|
||||
luaLanguageServerConfig
|
||||
hyprlangConfigFile
|
||||
luaConfigFile
|
||||
extraLuaFiles
|
||||
];
|
||||
|
||||
xdg.portal = {
|
||||
enable = cfg.finalPortalPackage != null;
|
||||
extraPortals = lib.mkIf (cfg.finalPortalPackage != null) [ cfg.finalPortalPackage ];
|
||||
configPackages = lib.mkIf (cfg.finalPackage != null) (lib.mkDefault [ cfg.finalPackage ]);
|
||||
};
|
||||
|
||||
systemd.user.targets.hyprland-session = lib.mkIf cfg.systemd.enable {
|
||||
Unit = {
|
||||
Description = "Hyprland compositor session";
|
||||
Documentation = [ "man:systemd.special(7)" ];
|
||||
BindsTo = [ "graphical-session.target" ];
|
||||
Wants = [
|
||||
"graphical-session-pre.target"
|
||||
]
|
||||
++ lib.optional cfg.systemd.enableXdgAutostart "xdg-desktop-autostart.target";
|
||||
After = [ "graphical-session-pre.target" ];
|
||||
Before = lib.mkIf cfg.systemd.enableXdgAutostart [ "xdg-desktop-autostart.target" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
243
modules/services/window-managers/hyprland/lib.nix
Normal file
243
modules/services/window-managers/hyprland/lib.nix
Normal file
@@ -0,0 +1,243 @@
|
||||
{
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user