dotfiles/modules/hyprland/hyprland.nix
2025-06-26 17:03:29 +02:00

254 lines
7.1 KiB
Nix

{
inputs,
pkgs,
lib,
config,
...
}:
{
options =
let
inherit (lib) mkOption types;
in
{
hyprland = {
mod = mkOption {
type = types.str;
default = "SUPER";
};
autoStart = mkOption {
type = types.listOf types.str;
default = [ ];
};
sensitivity = mkOption {
type = types.float;
default = 0.0;
};
layerRules = mkOption {
type = types.attrsOf (types.listOf types.str);
default = { };
};
windowRules = mkOption {
type = types.attrsOf (types.listOf types.str);
default = { };
};
binds = mkOption {
type = types.attrsOf types.str;
default = { };
};
};
};
config =
let
uwsm = "${pkgs.uwsm}/bin/uwsm";
mkUwsmApp = bin: "${uwsm} app -- ${bin}";
mkUwsmDesktopApp = pkg: name: "${uwsm} app -- ${pkg}/share/applications/${name}.desktop";
cfg = config.hyprland;
monitors = config.monitors;
# There is always exactly one primary monitor as asserted in the monitor module
primaryMonitor = lib.findSingle (m: m.isPrimary) "" "" monitors;
transformToOption =
transform:
if transform == "normal" then
""
else if transform == "clockwise" then
"transform,1"
else if transform == "flip" then
"transform,2"
else if transform == "counterclockwise" then
"transform,3"
else
abort "Invalid transform ${transform}";
in
{
nix.settings = {
substituters = [
"https://hyprland.cachix.org"
];
trusted-public-keys = [
"hyprland.cachix.org-1:a7pgxzMz7+chwVL3/pzj6jIBMioiJM7ypFP8PwtkuGc="
];
};
programs.hyprland = {
enable = true;
withUWSM = true;
};
xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
environment.systemPackages = with pkgs; [
grim # For xdg-desktop-portal-hyprland which does not declare this dependency
slurp # Some for this one
];
home-manager.users.kalle = {
home.packages = with pkgs; [
wl-clipboard
cliphist
ulauncher
];
wayland.windowManager.hyprland = {
enable = true;
# Disable systemd integration since we are using UWSM
systemd.enable = false;
settings = {
monitor =
map (
m:
let
resolution = "${toString m.width}x${toString m.height}@${toString m.refreshRate}";
position = "${toString m.x}x${toString m.y}";
vrr = if m.vrr != 0 then "vrr,1," else "";
transform = transformToOption m.transform;
in
"${m.name},${resolution},${position},1,${vrr}${transform}"
) (monitors)
# Automatically detect newly connected monitors
++ [ ",preferred,auto,auto" ];
exec-once = [
(mkUwsmApp "${pkgs.wl-clipboard}/bin/wl-paste --watch ${pkgs.cliphist}/bin/cliphist store")
(mkUwsmApp "${pkgs.ulauncher}/bin/ulauncher --no-window-shadow --hide-window")
(mkUwsmApp "${pkgs.hyprpolkitagent}/libexec/hyprpolkitagent")
] ++ cfg.autoStart;
env = [
"XCURSOR_SIZE,24"
];
input = {
kb_layout = "us";
kb_variant = "";
kb_options = "";
follow_mouse = 1;
touchpad = {
natural_scroll = "yes";
};
sensitivity = cfg.sensitivity;
accel_profile = "flat";
};
general = {
gaps_in = 5;
gaps_out = 10;
border_size = 1;
# TODO: Make this use system color scheme
"col.active_border" = "rgba(33ccffee) rgba(00ff99ee) 45deg";
"col.inactive_border" = "rgba(595959aa)";
layout = "dwindle";
};
misc.force_default_wallpaper = 1;
decoration = {
rounding = 5;
blur = {
enabled = "yes";
size = 3;
passes = 1;
new_optimizations = "on";
};
};
animations = {
enabled = "yes";
bezier = [
"myBezier, 0.05, 0.9, 0.1, 1.05"
];
animation = [
"windows, 1, 3, myBezier"
"windowsOut, 1, 3, default, popin 80%"
"border, 1, 5, default"
"borderangle, 1, 4, default"
"fade, 1, 3, default"
"workspaces, 1, 2, default"
];
};
layerrule =
let
y = ident: value: "${value},${ident}";
x = ident: values: map (y ident) values;
in
lib.flatten (lib.mapAttrsToList x cfg.layerRules);
workspace =
let
# Create one work space for each non primary monitor
perMonitorWorkspaces = map (m: "name:${m.name}, monitor:${m.name}") (
lib.filter (m: m.name != primaryMonitor.name) monitors
);
# Create 10 work spaces on the primary monitor
primaryMonitorWorkspaces = map (i: "${toString i}, monitor:${primaryMonitor.name}") (
lib.range 1 10
);
in
primaryMonitorWorkspaces ++ perMonitorWorkspaces;
bind =
let
# Create binds to switch workspaces and move windows between them
workspaceBinds = lib.flatten (
map (
i:
let
k = if i == 10 then 0 else i;
in
[
"${cfg.mod}, ${toString k}, workspace, ${toString i}"
"${cfg.mod} SHIFT, ${toString k}, movetoworkspace, ${toString i}"
]
) (lib.range 1 10)
);
# Create binds to manage windows
windowManagementBinds = [
"${cfg.mod} SHIFT, Q, killactive"
"${cfg.mod}, F, togglefloating"
"${cfg.mod} SHIFT, F, fullscreen"
];
configBinds = lib.mapAttrsToList (k: v: "${k}, ${v}") cfg.binds;
in
configBinds ++ workspaceBinds ++ windowManagementBinds;
bindm = [
"${cfg.mod}, mouse:272, movewindow"
"${cfg.mod}, mouse:273, resizewindow"
];
windowrule =
let
y = ident: value: "${value},${ident}";
x = ident: values: map (y ident) values;
in
lib.flatten (lib.mapAttrsToList x cfg.windowRules);
# Enable color management protocol
debug.full_cm_proto = true;
};
};
};
};
}