hyprland: submaps option lua support

This commit is contained in:
Austin Horstman 2026-05-12 15:27:29 -05:00
parent acd9d8af2f
commit 03d2aa6317
4 changed files with 198 additions and 35 deletions

View file

@ -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;
}

View file

@ -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,
},
})

View file

@ -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}
'';

View file

@ -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}