Hyprland Helpers — Typed Nix API for the Home-Manager Hyprland Module
Purpose
The core.hyprland modules extend the upstream Home-Manager wayland.windowManager.hyprland module with a typed Nix API for window rules, permissions, slide-in popups, input defaults, and Lua config generation.
They target the HM-native Lua configuration format (configType = "lua"), which is the repository default.
Entry Point
- Main file:
modules/home-manager/core/hyprland/default.nix - Supporting files:
input.nix,permission.nix,slideIn.nix,workspaces.nix,lua.nix,types.nix,noctalia.nix, and all files underlua/*, in the same directory.
The module structure is:
default.nix # Top-level importer
├── permission.nix # custom-settings.permission
├── slideIn.nix # custom-settings.slideIn
├── input.nix # settings.config defaults (cursor, binds, input, misc)
├── workspaces.nix # custom-settings.workspaces
├── lua.nix # custom-settings.lua (Lua config generation)
│ └── lua/ # Lua source files, with @placeholder@ substitution
│ └── opt/ # Conditionally loaded Lua modules
└── types.nix # Shared type definitions
Options
wayland.windowManager.hyprland.custom-settings.lua.applicationBinds
| Type | attribute set of string |
| Default | { } |
Application binds to generate in Lua config.
wayland.windowManager.hyprland.custom-settings.lua.enable
| Type | boolean |
| Default | false |
| Example | true |
Whether to enable Pure Lua configuration files for Hyprland, with a hint of nix substitution magic..
wayland.windowManager.hyprland.custom-settings.lua.luaExtras
| Type | list of absolute path |
| Default | [ ] |
Extra Lua files to copy to the config directory. These files will be copied to the config directory but not required in init.lua, so you can use them as libraries or for other purposes.
Files defined here will respect parent directories and will be copied to the same relative path in the config directory.
The absolute root of the directory tree will be calculated by finding the closest lua ancestor directory, and copying the entire tree from that root to the config directory.
If a directory is specified, it will be recursively copied to the config directory, preserving the directory structure.
wayland.windowManager.hyprland.custom-settings.lua.luaModules
| Type | list of absolute path |
| Default | [ ] |
Lua modules to load in the main init.lua file. Each module is a path to a Lua file, which will be copied to the config directory and required in init.lua. Each module will have variables substituted according to the “variables” option, so you can use that to inject paths to nix packages or other dynamic values.
wayland.windowManager.hyprland.custom-settings.lua.variables
| Type | attribute set of (null or string) |
| Default | { } |
Variables to substitute in Lua files. Each key “foo” replaces @foo@ in source files with the value.
wayland.windowManager.hyprland.custom-settings.permission.plugin
| Type | list of (package or string) |
| Default | [ ] |
List of plugins that are allowed to run.
wayland.windowManager.hyprland.custom-settings.permission.screenCopy
| Type | list of (package or string) |
| Default | [ ] |
List of applications allowed to copy the screen.
wayland.windowManager.hyprland.custom-settings.slideIn
| Type | list of (submodule) |
| Default | [ ] |
List of slide-in popups that slide in from the edge of the screen.
wayland.windowManager.hyprland.custom-settings.slideIn.*.bind
| Type | string |
Key binding to trigger the slide-in popup.
This is passed through to the lua config without checking for validity, the lua config will throw an error if the binding is invalid.
wayland.windowManager.hyprland.custom-settings.slideIn.*.class
| Type | string |
Window class for the slide-in popup.
wayland.windowManager.hyprland.custom-settings.slideIn.*.exec
| Type | string |
Command to execute for the slide-in popup.
wayland.windowManager.hyprland.custom-settings.slideIn.*.extProp
| Type | attribute set of anything |
| Default | { } |
Extra properties to pass to the scratchpad configuration in pyprland.
wayland.windowManager.hyprland.custom-settings.slideIn.*.position
| Type | one of "left", "right", "top", "bottom" |
| Default | "top" |
Direction from which the popup slides in.
wayland.windowManager.hyprland.custom-settings.slideIn.*.size
| Type | submodule |
| Default | { } |
Size of the slide-in popup. Can specify ‘width’ and/or ‘height’.
wayland.windowManager.hyprland.custom-settings.slideIn.*.size.height
| Type | null or string |
| Default | null |
Height of the slide-in popup. Can be specified as a percentage (e.g., ‘33%’) or in pixels (e.g., ‘300px’).
wayland.windowManager.hyprland.custom-settings.slideIn.*.size.width
| Type | null or string |
| Default | null |
Width of the slide-in popup. Can be specified as a percentage (e.g., ‘20%’) or in pixels (e.g., ‘400px’).
wayland.windowManager.hyprland.custom-settings.workspaces.definitions
| Type | attribute set of (submodule) |
| Default | { } |
Workspace definitions by ID
wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.extraRules
| Type | attribute set of anything |
| Default | { } |
Additional workspace rule properties to pass to workspace_rule().
wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.monitor
| Type | null or string |
| Default | null |
Monitor to assign workspace on startup.
wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.name
| Type | null or string |
| Default | null |
Workspace default name.
wayland.windowManager.hyprland.custom-settings.workspaces.definitions.<name>.startup
| Type | list of (string or attribute set of string) |
| Default | [ ] |
Commands to run when workspace is first created empty.
wayland.windowManager.hyprland.custom-settings.workspaces.enable
| Type | boolean |
| Default | false |
Enable workspace configuration module.
Architecture / Services / Scope
input.nix
Sets sensible default values under settings.config for cursor behavior, input device settings, keyboard binds, and misc Hyprland options.
Default config covers:
cursor: warp behavior, hardware cursors, inactivity timeout, hide-on-key-pressbinds: workspace back-and-forth, allow workspace cycles, focus methodinput: keyboard layout, follow-mouse, touchpad, sensitivity, accel profilemisc: DPMS on key/mouse events
permission.nix
Defines custom-settings.permission for screen copy and plugin permission grants:
custom-settings.permission = {
screenCopy = [ pkgs.firefox pkgs.obs ];
plugin = [ pkgs.hyprlandPlugins.hy3 ];
};
slideIn.nix
Add the option custom-settings.slideIn to define a list of edge-sliding popup windows.
Each entry configures a keybind, executable, window class, position, and optional window rules.
Uses Pyprland scratchpads for dropdown-style window management.
Architecture:
- Nix module: generates
~/.config/pypr/config.tomlwith scratchpad definitions, registerssystemd.user.services.pyprland. - Lua module: registers
hl.bind(...)calls that toggle the scratchpads.
lua.nix
Defines custom-settings.lua — the Lua config generation subsystem:
enable(boolean) — Enable pure Lua configuration files with Nix substitution support.variables(attrs ofnullOr str) — Key-value pairs for@placeholder@substitution in Lua source files. Each keyfooreplaces@foo@only in Lua modules that reference that placeholder. Modules that don’t reference a given placeholder are unaffected, so bundled modules can use disjoint placeholder sets. A placeholder referenced by a module but absent fromvariablesis an error. Some variables are pre-populated automatically (seeapplicationBindsbelow). Common injected values include paths toplayerctl,wpctl,zenity,hyprshutdown, anduwsm-app.luaModules(list of paths) — Lua source files to copy into the Hyprland config directory andrequirefrominit.lua. Each file undergoes@placeholder@substitution using thevariablesattrset. Defaults to the bundledlua/binds.lua.applicationBinds(attrs ofstr) — Application keybinds passed into Lua generation. Each attr key is a bind string and each attr value is a command string. Rendered into@applicationBinds@as Lua table entries consumed bybinds.lua. Generated Lua iterates over those table entries and createshl.bind(..., hl.dsp.exec_cmd(...))calls for each bind/command pair.
Lua bind pattern
In lua/binds.lua, binds use the inline Lua expression pattern via settings.bind with attrsToLuaInlineArgs. The generated Lua calls hl.bind(...) with first-class dispatcher functions:
hl.bind("SUPER + Q", hl.dsp.window.kill())
hl.bind("SUPER + SHIFT + SPACE", hl.dsp.window.float({ action = "toggle" }))
hl.bind("ALT + R", hl.dsp.submap("resize"))
hl.define_submap("resize", function()
hl.bind("ESCAPE", hl.dsp.submap("reset"))
-- ...
end)
This pattern keeps bind and submap definitions inline in Lua. Submaps are defined via hl.define_submap(name, fn) alongside related hl.bind(...) calls.
workspaces.nix
Workspace configuration module at modules/home-manager/core/hyprland/workspaces.nix.
Handles workspace naming, monitor assignments, and startup applications with startup-only monitor assignment behavior:
- Startup-only assignment: Monitor assignments run once via
hl.on("hyprland.start")Lua event hook. Users can move workspaces freely after startup without interference. - Persistent naming: Workspace names and startup commands persist via
hl.workspace_rule(), ensuring defaults are restored if a workspace is recreated. - Conditional Lua module: The
lua/opt/workspaces.luamodule is automatically added to the Lua config only whencustom-settings.workspaces.enable = true. If disabled, no workspace Lua code is loaded. - Configuration source: Workspace data (name, monitor, startup commands) is defined in user configs via the typed
custom-settings.workspaces.definitionsoption and passed to Lua as@workspaceConfig@variable.
Enable and configure workspaces in user home config:
wayland.windowManager.hyprland.custom-settings.workspaces = {
enable = true;
definitions = {
"1" = {
name = "Terminal";
monitor = "DP-6"; # startup-only; workspace can be moved after launch
startup = [ (lib.getExe pkgs.alacritty) ]; # runs when workspace first created
};
"2" = {
name = "Browser";
monitor = "DP-1";
startup = [ (lib.getExe config.programs.firefox.package) ];
};
# ...
};
};
The module generates:
- Lua substitution variable
@workspaceConfig@with workspace definitions as JSON - Conditionally loads
lua/opt/workspaces.luato register startup hooks and workspace rules
lua/opt/workspaces.lua
Lua module at modules/home-manager/core/hyprland/lua/opt/workspaces.lua that registers workspace configuration. Only loaded when custom-settings.workspaces.enable = true. Receives workspace data via @workspaceConfig@ placeholder substitution and:
- Registers
hl.on("hyprland.start")hook to assign workspaces to monitors on startup only - Calls
hl.workspace_rule()for each workspace to set persistent names and startup commands
| Placeholder | Source | Description |
|---|---|---|
@workspaceConfig@ | custom-settings.workspaces.definitions | Workspace config table as JSON |
lua/binds.lua
The default Lua bind template at modules/home-manager/core/hyprland/lua/binds.lua. Uses @placeholder@ substitution for dynamic injection. Substitution is per-file — only placeholders actually present in this template are replaced; other Lua modules are unaffected by binds.lua’s placeholder set.
| Placeholder | Source | Description |
|---|---|---|
@applicationBinds@ | custom-settings.lua.applicationBinds | Auto-generated Lua table of app keybinds |
@playerctl@ | Auto-injected | Path to playerctl binary |
@wpctl@ | Auto-injected | Path to wpctl binary |
@zenity@ | Auto-injected | Path to zenity binary |
@hyprshutdown@ | Auto-injected | Path to hyprshutdown binary |
@uwsmApp@ | Auto-injected | Path to uwsm-app helper |
@DEFAULT_AUDIO_SINK@ | custom-settings.lua.variables | Audio sink name |
@DEFAULT_AUDIO_SOURCE@ | custom-settings.lua.variables | Audio source name |
Add custom placeholders by extending custom-settings.lua.variables.
noctalia.nix
Integrates the Noctalia desktop shell as a Hyprland companion. Requires the noctalia flake input.
The module:
- Enables
programs.noctaliaandsystemd, pinspackagefrom thenoctaliaflake input’s packages, and applies Hyprland support for Noctalia windows. - Mirrors a full exported Noctalia config as a typed Nix attrset (
noctaliaSettings), covering bar layouts with monitor overrides, shell panel/screen corners/screenshot/session actions, theme, wallpaper, calendar, control-center shortcuts, desktop/lockscreen widgets, notification layer, plugin settings, widget config, brightness, and more. - Does not declare top-level
colorsorpluginsHM options, and does not manage raw JSON files directly. - Persists
~/.local/share/noctaliaviauser.persistence.directories. - Reads
core.profile.avatar.path→shell.avatar_pathandcore.profile.wallpaper.directory→wallpaper.directory. Wallpaper fill mode is hardcoded (not a profile option). - Location is driven by
core.profile.location.secret(a SOPS secret name). Two modes:- Normal (
secret == null): setsprograms.noctalia.settings. No location block. - Secret (
secret != null): base TOML generated at build time; activation copies it to~/.config/noctalia/config.tomland appends[location] addressfrom the decrypted SOPS secret.
- Normal (
The user-side Hyprland config pairs with this module via Noctalia IPC keybinds (fullscreen, special workspace toggles, settings, audio/brightness dispatchers). Workspace rules in the user config set persistent = true for defined workspaces so they are always available regardless of Noctalia lifecycle.
types.nix
Shared type definitions used across the modules:
monitorSelector— typed Nix attrs for monitor matching (bynameorindex)workspaceSelector— typed Nix attrs for workspace matching (byid,relativeId,name, orspecial)rule— all typed window rule properties (float, fullscreen, opacity, size, move, center, monitor, workspace, and dozens more)windowMatch— match condition types (class, title, initialClass, initialTitle, tag, xwayland, float, fullscreen, pin, focus, group, modal, fullscreenstate, workspace, content, xdg_tag)
Usage Example
{
wayland.windowManager.hyprland = {
enable = true;
configType = "lua";
custom-settings = {
permission = {
screenCopy = [ pkgs.firefox ];
};
lua = {
enable = true;
luaModules = [ ./lua/window_rules.lua ];
applicationBinds = {
"SUPER + Return" = "${pkgs.kitty}/bin/kitty";
"SUPER + E" = "${pkgs.nautilus}/bin/nautilus";
};
};
};
};
}
Operational Notes / Assumptions
- All options live under
custom-settingsto avoid collision with upstream HM Hyprland options. lua.nixauto-injectsapplicationBinds,playerctl,wpctl,zenity,hyprshutdown, anduwsmAppas substitution variables — no need to set those manually.- Variable substitution is per-file: each Lua module only receives replacements for
@placeholder@tokens it actually contains. A variable defined but unused by a given module is silently ignored for that module. A placeholder referenced by a module but missing fromvariablesis an error. - Unknown dispatchers in Lua raise a runtime error from Hyprland’s Lua parser, not a build-time error.
- CamelCase naming in Nix (e.g.
fullscreenState,idleInhibit,keepAspectRatio,noCloseFor,forceRgbx,syncFullscreen) is translated to snake_case in the Lua output.