Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions modules/misc/news.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,14 @@ in
A new module is available: 'services.swayosd'
'';
}

{
time = "2023-07-20T21:56:49+00:00";
condition = hostPlatform.isLinux;
message = ''
Comment thread
fufexan marked this conversation as resolved.
A new module is available: 'wayland.windowManager.hyprland'
'';
}
];
};
}
1 change: 1 addition & 0 deletions modules/modules.nix
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ let
./services/window-managers/bspwm/default.nix
./services/window-managers/fluxbox.nix
./services/window-managers/herbstluftwm.nix
./services/window-managers/hyprland.nix
./services/window-managers/i3-sway/i3.nix
./services/window-managers/i3-sway/sway.nix
./services/window-managers/i3-sway/swaynag.nix
Expand Down
216 changes: 216 additions & 0 deletions modules/services/window-managers/hyprland.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
{ config, lib, pkgs, ... }:
let

cfg = config.wayland.windowManager.hyprland;

in {
meta.maintainers = [ lib.maintainers.fufexan ];

# A few option removals and renames to aid those migrating from the upstream
# module.
imports = [
(lib.mkRemovedOptionModule # \
[ "wayland" "windowManager" "hyprland" "disableAutoreload" ]
"Autoreloading now always happen")

(lib.mkRemovedOptionModule # \
[ "wayland" "windowManager" "hyprland" "recommendedEnvironment" ]
"Recommended environment variables are now always set")

(lib.mkRenamedOptionModule # \
[ "wayland" "windowManager" "hyprland" "nvidiaPatches" ] # \
[ "wayland" "windowManager" "hyprland" "enableNvidiaPatches" ])
];

options.wayland.windowManager.hyprland = {
enable = lib.mkEnableOption "Hyprland wayland compositor";

package = lib.mkPackageOption pkgs "hyprland" { };

finalPackage = lib.mkOption {
type = lib.types.package;
readOnly = true;
default = cfg.package.override {
enableXWayland = cfg.xwayland.enable;
hidpiXWayland = cfg.xwayland.hidpi;
nvidiaPatches = cfg.enableNvidiaPatches;
};
defaultText = lib.literalMD
"`wayland.windowManager.hyprland.package` with applied configuration";
description = ''
The Hyprland package after applying configuration.
'';
};

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

systemdIntegration = lib.mkOption {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would you not want to use systemd integration? Btw, when enabling the systemd integration I don't see any attempt at shutting down the graphical session when Hyprland exits. Will that happen automatically?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps it makes sense for users that are on a distro without systemd. But they'd have a hard time using Nix by itself, let alone HM.

No, there's no attempt to shut down the graphical session. I don't know how to do this, but I'm willing to include it if you have clues. Also, it does not automatically shut down.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without something like exec-exit, which would run a command on shutdown it would be tricky to solve the shutdown entirely within Hyprland. I think it would require wrapping the Hyprland executable and in the wrapper do something like

systemctl --user stop graphical-session.target
systemctl --user stop graphical-session-pre.target
# Wait until the units actually stop.
while [ -n "$(systemctl --user --no-legend --state=deactivating list-units)" ]; do
sleep 0.5
done
${optionalString (cfg.importedVariables != [ ])
("systemctl --user unset-environment "
+ escapeShellArgs cfg.importedVariables)}
on shutdown.

type = lib.types.bool;
default = pkgs.stdenv.isLinux;
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 dbus user environment before reaching the target, including
- `DISPLAY`
- `HYPRLAND_INSTANCE_SIGNATURE`
- `WAYLAND_DISPLAY`
- `XDG_CURRENT_DESKTOP`
'';
};

xwayland = {
enable = lib.mkEnableOption "XWayland" // { default = true; };
hidpi = lib.mkEnableOption null // {
description = ''
Enable HiDPI XWayland, based on [XWayland MR 733](https://gitlab.freedesktop.org/xorg/xserver/-/merge_requests/733).
See <https://wiki.hyprland.org/Nix/Options-Overrides/#xwayland-hidpi> for more info.
'';
};
};

enableNvidiaPatches =
lib.mkEnableOption "patching wlroots for better Nvidia support";

settings = lib.mkOption {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note, name changed to settings in line with RFC 42.

type = with lib.types;
let
valueType = nullOr (oneOf [
bool
int
float
str
path
(attrsOf valueType)
(listOf valueType)
]) // {
description = "Hyprland configuration value";
};
in valueType;
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.hyprland.org> for more examples.

::: {.note}
Use the [](#opt-wayland.windowManager.hyprland.plugins) option to
declare plugins.
:::

'';
example = lib.literalExpression ''
{
decoration = {
shadow_offset = "0 5";
"col.shadow" = "rgba(00000099)";
};

"$mod" = "SUPER";

bindm = [
# mouse movements
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
"$mod ALT, mouse:272, resizewindow"
];
}
'';
};

extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
example = ''
# window resize
bind = $mod, S, submap, resize

submap = resize
binde = , right, resizeactive, 10 0
binde = , left, resizeactive, -10 0
binde = , up, resizeactive, 0 -10
binde = , down, resizeactive, 0 10
bind = , escape, submap, reset
submap = reset
'';
description = ''
Extra configuration lines to add to `~/.config/hypr/hyprland.conf`.
'';
};
};

config = lib.mkIf cfg.enable {
Comment thread
fufexan marked this conversation as resolved.
assertions = [
(lib.hm.assertions.assertPlatform "wayland.windowManager.hyprland" pkgs
lib.platforms.linux)
];

warnings = let
inconsistent = (cfg.systemdIntegration || cfg.plugins != [ ])
&& cfg.extraConfig == "" && cfg.settings == { };
warning =
"You have enabled hyprland.systemdIntegration or listed plugins in hyprland.plugins but do not have any configuration in hyprland.settings or hyprland.extraConfig. This is almost certainly a mistake.";
in lib.optional inconsistent warning;

home.packages = lib.optional (cfg.package != null) cfg.finalPackage;

xdg.configFile."hypr/hyprland.conf" = let
combinedSettings = cfg.settings // {
plugin = let
mkEntry = entry:
if lib.types.package.check entry then
"${entry}/lib/lib${entry.pname}.so"
else
entry;
in map mkEntry cfg.plugins;
};

shouldGenerate = cfg.systemdIntegration || cfg.extraConfig != ""
|| combinedSettings != { };

toHyprconf = attrs:
let
mkSection = n: attrs: ''
${n} {
${toHyprconf attrs}}
'';
mkFields = lib.generators.toKeyValue { listsAsDuplicateKeys = true; };
sections = lib.filterAttrs (n: v: lib.isAttrs v) attrs;
fields = lib.filterAttrs (n: v: !(lib.isAttrs v)) attrs;
in lib.concatStringsSep "\n" (lib.mapAttrsToList mkSection sections)
+ mkFields fields;
in lib.mkIf shouldGenerate {
text = lib.optionalString cfg.systemdIntegration ''
exec-once = ${pkgs.dbus}/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP && systemctl --user start hyprland-session.target
'' + lib.optionalString (combinedSettings != { })
(toHyprconf combinedSettings)
+ lib.optionalString (cfg.extraConfig != "") cfg.extraConfig;
onChange = lib.mkIf (cfg.package != null) ''
( # execute in subshell so that `shopt` won't affect other scripts
shopt -s nullglob # so that nothing is done if /tmp/hypr/ does not exist or is empty
for instance in /tmp/hypr/*; do
HYPRLAND_INSTANCE_SIGNATURE=''${instance##*/} ${cfg.finalPackage}/bin/hyprctl reload config-only \
|| true # ignore dead instance(s)
done
)
'';
};

systemd.user.targets.hyprland-session = lib.mkIf cfg.systemdIntegration {
Unit = {
Description = "Hyprland compositor session";
Documentation = [ "man:systemd.special(7)" ];
BindsTo = [ "graphical-session.target" ];
Wants = [ "graphical-session-pre.target" ];
After = [ "graphical-session-pre.target" ];
};
};
};
}
1 change: 1 addition & 0 deletions tests/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ import nmt {
./modules/services/udiskie
./modules/services/window-managers/bspwm
./modules/services/window-managers/herbstluftwm
./modules/services/window-managers/hyprland
./modules/services/window-managers/i3
./modules/services/window-managers/spectrwm
./modules/services/window-managers/sway
Expand Down
4 changes: 4 additions & 0 deletions tests/modules/services/window-managers/hyprland/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
hyprland-simple-config = ./simple-config.nix;
hyprland-inconsistent-config = ./inconsistent-config.nix;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{ config, lib, ... }:

{
wayland.windowManager.hyprland = {
enable = true;
package = lib.makeOverridable
(attrs: config.lib.test.mkStubPackage { name = "hyprland"; }) { };
plugins =
[ "/path/to/plugin1" (config.lib.test.mkStubPackage { name = "foo"; }) ];
};

test.asserts.warnings.expected = [
"You have enabled hyprland.systemdIntegration or listed plugins in hyprland.plugins but do not have any configuration in hyprland.settings or hyprland.extraConfig. This is almost certainly a mistake."
];
test.asserts.warnings.enable = true;

nmt.script = ''
config=home-files/.config/hypr/hyprland.conf
assertFileExists "$config"
'';
}
30 changes: 30 additions & 0 deletions tests/modules/services/window-managers/hyprland/simple-config.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
exec-once = /nix/store/00000000000000000000000000000000-dbus/bin/dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY HYPRLAND_INSTANCE_SIGNATURE XDG_CURRENT_DESKTOP && systemctl --user start hyprland-session.target
decoration {
col.shadow=rgba(00000099)
shadow_offset=0 5
}

input {
touchpad {
scroll_factor=0.300000
}
accel_profile=flat
follow_mouse=1
kb_layout=ro
}
$mod=SUPER
bindm=$mod, mouse:272, movewindow
bindm=$mod, mouse:273, resizewindow
bindm=$mod ALT, mouse:272, resizewindow
plugin=/path/to/plugin1
plugin=/nix/store/00000000000000000000000000000000-foo/lib/libfoo.so
# window resize
bind = $mod, S, submap, resize

submap = resize
binde = , right, resizeactive, 10 0
binde = , left, resizeactive, -10 0
binde = , up, resizeactive, 0 -10
binde = , down, resizeactive, 0 10
bind = , escape, submap, reset
submap = reset
53 changes: 53 additions & 0 deletions tests/modules/services/window-managers/hyprland/simple-config.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{ config, lib, ... }:

{
wayland.windowManager.hyprland = {
enable = true;
package = lib.makeOverridable
(attrs: config.lib.test.mkStubPackage { name = "hyprland"; }) { };
plugins =
[ "/path/to/plugin1" (config.lib.test.mkStubPackage { name = "foo"; }) ];
settings = {
decoration = {
shadow_offset = "0 5";
"col.shadow" = "rgba(00000099)";
};

"$mod" = "SUPER";

input = {
kb_layout = "ro";
follow_mouse = 1;
accel_profile = "flat";
touchpad = { scroll_factor = 0.3; };
};

bindm = [
# mouse movements
"$mod, mouse:272, movewindow"
"$mod, mouse:273, resizewindow"
"$mod ALT, mouse:272, resizewindow"
];
};
extraConfig = ''
# window resize
bind = $mod, S, submap, resize

submap = resize
binde = , right, resizeactive, 10 0
binde = , left, resizeactive, -10 0
binde = , up, resizeactive, 0 -10
binde = , down, resizeactive, 0 10
bind = , escape, submap, reset
submap = reset
'';
};

nmt.script = ''
config=home-files/.config/hypr/hyprland.conf
assertFileExists "$config"

normalizedConfig=$(normalizeStorePaths "$config")
assertFileContent "$normalizedConfig" ${./simple-config.conf}
'';
}