From ea2682f31d86340858c57eca410cc2f0221240f3 Mon Sep 17 00:00:00 2001 From: aaron Date: Thu, 1 Oct 2026 18:43:48 +0200 Subject: [PATCH] feature(niri): use home-managers new niri-module to configure niri on my machines with host-specific additions --- hosts/argon/configuration.nix | 3 + hosts/argon/niri.nix | 36 ++++ hosts/neon/configuration.nix | 3 + hosts/neon/niri.nix | 34 ++++ modules/home-manager/niri.nix | 298 ++++++++++++++++++++++++++++++++++ users/aaron/home.nix | 1 + 6 files changed, 375 insertions(+) create mode 100644 hosts/argon/niri.nix create mode 100644 hosts/neon/niri.nix create mode 100644 modules/home-manager/niri.nix diff --git a/hosts/argon/configuration.nix b/hosts/argon/configuration.nix index 76a37d3..5a44438 100644 --- a/hosts/argon/configuration.nix +++ b/hosts/argon/configuration.nix @@ -26,6 +26,9 @@ ../../modules/nixos/vial.nix ]; + # host-specific niri config (outputs, input devices) + home-manager.users.aaron.imports = [ ./niri.nix ]; + # set hostname networking.hostName = "argon"; diff --git a/hosts/argon/niri.nix b/hosts/argon/niri.nix new file mode 100644 index 0000000..03ba3ab --- /dev/null +++ b/hosts/argon/niri.nix @@ -0,0 +1,36 @@ +# argon: desktop with two 1440p monitors side by side +{ + wayland.windowManager.niri.settings = { + input = { + touchpad.off = { }; + trackpoint.off = { }; + }; + + _children = [ + { + output = { + _args = [ "DP-1" ]; + mode = "2560x1440@165.000"; + scale = 1; + transform = "normal"; + position._props = { + x = 0; + y = 0; + }; + }; + } + { + output = { + _args = [ "DP-2" ]; + mode = "2560x1440@143.912"; + scale = 1; + transform = "normal"; + position._props = { + x = 2560; + y = 0; + }; + }; + } + ]; + }; +} diff --git a/hosts/neon/configuration.nix b/hosts/neon/configuration.nix index 2e2a06d..2b9a93a 100644 --- a/hosts/neon/configuration.nix +++ b/hosts/neon/configuration.nix @@ -23,6 +23,9 @@ ../../modules/nixos/vial.nix ]; + # host-specific niri config (input devices, laptop binds) + home-manager.users.aaron.imports = [ ./niri.nix ]; + # set hostname networking.hostName = "neon"; diff --git a/hosts/neon/niri.nix b/hosts/neon/niri.nix new file mode 100644 index 0000000..b6c52b0 --- /dev/null +++ b/hosts/neon/niri.nix @@ -0,0 +1,34 @@ +# neon: laptop, built-in display is auto-configured by niri +{ lib, ... }: + +let + brightness = step: { + _props.allow-when-locked = true; + spawn = [ + "brightnessctl" + "--class=backlight" + "set" + step + ]; + }; +in +{ + wayland.windowManager.niri.settings = { + input.touchpad = { + tap = { }; + natural-scroll = { }; + }; + + layout.gaps = lib.mkForce 10; + + binds = { + "Print".screenshot = { }; + "Ctrl+Print".screenshot-screen = { }; + "Mod+Shift+P".power-off-monitors = { }; + + # finer brightness steps; mkForce, since spawn lists would otherwise be concatenated + "XF86MonBrightnessUp" = lib.mkForce (brightness "+1%"); + "XF86MonBrightnessDown" = lib.mkForce (brightness "1%-"); + }; + }; +} diff --git a/modules/home-manager/niri.nix b/modules/home-manager/niri.nix new file mode 100644 index 0000000..0d5a694 --- /dev/null +++ b/modules/home-manager/niri.nix @@ -0,0 +1,298 @@ +{ lib, osConfig, ... }: + +let + # Mod+1..9 focus a workspace, Mod+Ctrl+1..9 move the column there. + workspaceBinds = lib.mergeAttrsList ( + map (n: { + "Mod+${toString n}".focus-workspace = n; + "Mod+Ctrl+${toString n}".move-column-to-workspace = n; + }) (lib.range 1 9) + ); + + # Shorthand for binds that only run spawn-sh while allowed on the lock screen. + lockedSh = cmd: { + _props.allow-when-locked = true; + spawn-sh = cmd; + }; +in +{ + # Niri config, rendered to ~/.config/niri/config.kdl by home-manager's built-in + # wayland.windowManager.niri module. The compositor itself, its session, systemd + # units and portals are provided by the NixOS module (modules/nixos/niri.nix), so + # this module only writes the config. Host-specific outputs and input devices + # live in hosts//niri.nix. + + wayland.windowManager.niri = { + # only on hosts that run niri (see modules/nixos/niri.nix) + enable = osConfig.programs.niri.enable; + + # Use the system's niri so the config is validated against the running version. + package = osConfig.programs.niri.package; + systemd.enable = false; + portalPackage = null; + xwaylandSatellitePackage = null; # installed system-wide + + settings = { + input = { + keyboard = { + xkb.layout = "ch"; + numlock = { }; + }; + mouse = { + natural-scroll = { }; + accel-speed = 0.5; + scroll-method = "no-scroll"; + }; + }; + + environment.GTK_IM_MODULE = "simple"; + + layout = { + gaps = 16; + center-focused-column = "never"; + + preset-column-widths._children = [ + { proportion = 0.33333; } + { proportion = 0.5; } + { proportion = 0.66667; } + { fixed = 2560; } + ]; + + default-column-width.proportion = 0.5; + + focus-ring = { + on = { }; + width = 2; + active-color = "#4c566a"; + inactive-color = "#2e3440"; + urgent-color = "#a3be8c"; + }; + + border.off = { }; + + shadow = { + on = { }; + draw-behind-window = true; + softness = 30; + spread = 10; + offset._props = { + x = 0; + y = 5; + }; + color = "#0007"; + }; + }; + + spawn-at-startup = "noctalia"; + + hotkey-overlay.skip-at-startup = { }; + + screenshot-path = "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png"; + + _children = [ + # Open the Firefox picture-in-picture player as floating by default. + { + window-rule._children = [ + { + match._props = { + app-id = "firefox$"; + title = "^Picture-in-Picture$"; + }; + } + { open-floating = true; } + ]; + } + # Block out the password manager from screen capture. + { + window-rule._children = [ + { match._props.app-id = "^org\\.keepassxc\\.KeePassXC$"; } + { block-out-from = "screen-capture"; } + ]; + } + { + window-rule = { + geometry-corner-radius = 12; + clip-to-geometry = true; + }; + } + ]; + + binds = workspaceBinds // { + # programs: terminal, app launcher, screen locker + "Mod+Return" = { + _props.hotkey-overlay-title = "Open a Terminal"; + spawn = "ghostty"; + }; + "Mod+D" = { + _props.hotkey-overlay-title = "Run an Application"; + spawn = [ + "noctalia" + "msg" + "panel-toggle" + "launcher" + ]; + }; + "Super+Alt+L" = { + _props.hotkey-overlay-title = "Lock the Screen"; + spawn = [ + "noctalia" + "msg" + "session" + "lock" + ]; + }; + + # media keys (PipeWire / WirePlumber) + "XF86AudioRaiseVolume" = lockedSh "wpctl set-volume @DEFAULT_SINK@ 0.01+ -l 1.0"; + "XF86AudioLowerVolume" = lockedSh "wpctl set-volume @DEFAULT_SINK@ 0.01-"; + "XF86AudioMute" = lockedSh "wpctl set-mute @DEFAULT_SINK@ toggle"; + "XF86AudioMicMute" = lockedSh "wpctl set-mute @DEFAULT_SOURCE@ toggle"; + "XF86AudioPlay" = lockedSh "playerctl play-pause"; + "XF86AudioStop" = lockedSh "playerctl stop"; + "XF86AudioPrev" = lockedSh "playerctl previous"; + "XF86AudioNext" = lockedSh "playerctl next"; + "XF86MonBrightnessUp" = { + _props.allow-when-locked = true; + spawn = [ + "brightnessctl" + "--class=backlight" + "set" + "+10%" + ]; + }; + "XF86MonBrightnessDown" = { + _props.allow-when-locked = true; + spawn = [ + "brightnessctl" + "--class=backlight" + "set" + "10%-" + ]; + }; + + "Mod+O" = { + _props.repeat = false; + toggle-overview = { }; + }; + "Mod+Q" = { + _props.repeat = false; + close-window = { }; + }; + + # focus / move columns and windows + "Mod+Left".focus-column-left = { }; + "Mod+Down".focus-window-down = { }; + "Mod+Up".focus-window-up = { }; + "Mod+Right".focus-column-right = { }; + "Mod+H".focus-column-left = { }; + "Mod+J".focus-window-down = { }; + "Mod+K".focus-window-up = { }; + "Mod+L".focus-column-right = { }; + + "Mod+Shift+Left".move-column-left = { }; + "Mod+Shift+Down".move-window-down = { }; + "Mod+Shift+Up".move-window-up = { }; + "Mod+Shift+Right".move-column-right = { }; + "Mod+Shift+H".move-column-left = { }; + "Mod+Shift+J".move-window-down = { }; + "Mod+Shift+K".move-window-up = { }; + "Mod+Shift+L".move-column-right = { }; + + "Mod+Home".focus-column-first = { }; + "Mod+End".focus-column-last = { }; + "Mod+Ctrl+Home".move-column-to-first = { }; + "Mod+Ctrl+End".move-column-to-last = { }; + + # move columns between monitors + "Mod+Ctrl+Left".move-column-to-monitor-left = { }; + "Mod+Ctrl+Down".move-column-to-monitor-down = { }; + "Mod+Ctrl+Up".move-column-to-monitor-up = { }; + "Mod+Ctrl+Right".move-column-to-monitor-right = { }; + "Mod+Ctrl+H".move-column-to-monitor-left = { }; + "Mod+Ctrl+J".move-column-to-monitor-down = { }; + "Mod+Ctrl+K".move-column-to-monitor-up = { }; + "Mod+Ctrl+L".move-column-to-monitor-right = { }; + + # workspaces + "Mod+Page_Down".focus-workspace-down = { }; + "Mod+Page_Up".focus-workspace-up = { }; + "Mod+U".focus-workspace-down = { }; + "Mod+I".focus-workspace-up = { }; + "Mod+Ctrl+Page_Down".move-column-to-workspace-down = { }; + "Mod+Ctrl+Page_Up".move-column-to-workspace-up = { }; + "Mod+Ctrl+U".move-column-to-workspace-down = { }; + "Mod+Ctrl+I".move-column-to-workspace-up = { }; + + "Mod+Shift+Page_Down".move-workspace-down = { }; + "Mod+Shift+Page_Up".move-workspace-up = { }; + "Mod+Shift+U".move-workspace-down = { }; + "Mod+Shift+I".move-workspace-up = { }; + + # mouse wheel + "Mod+WheelScrollDown" = { + _props.cooldown-ms = 150; + focus-workspace-down = { }; + }; + "Mod+WheelScrollUp" = { + _props.cooldown-ms = 150; + focus-workspace-up = { }; + }; + "Mod+Ctrl+WheelScrollDown" = { + _props.cooldown-ms = 150; + move-column-to-workspace-down = { }; + }; + "Mod+Ctrl+WheelScrollUp" = { + _props.cooldown-ms = 150; + move-column-to-workspace-up = { }; + }; + + "Mod+WheelScrollRight".focus-column-right = { }; + "Mod+WheelScrollLeft".focus-column-left = { }; + "Mod+Ctrl+WheelScrollRight".move-column-right = { }; + "Mod+Ctrl+WheelScrollLeft".move-column-left = { }; + + "Mod+Shift+WheelScrollDown".focus-column-right = { }; + "Mod+Shift+WheelScrollUp".focus-column-left = { }; + "Mod+Ctrl+Shift+WheelScrollDown".move-column-right = { }; + "Mod+Ctrl+Shift+WheelScrollUp".move-column-left = { }; + + # columns + "Mod+BracketLeft".consume-or-expel-window-left = { }; + "Mod+BracketRight".consume-or-expel-window-right = { }; + "Mod+Comma".consume-window-into-column = { }; + "Mod+Period".expel-window-from-column = { }; + + "Mod+R".switch-preset-column-width = { }; + "Mod+Shift+R".switch-preset-window-height = { }; + "Mod+Ctrl+R".reset-window-height = { }; + "Mod+F".maximize-column = { }; + "Mod+Shift+F".fullscreen-window = { }; + "Mod+Ctrl+F".expand-column-to-available-width = { }; + + "Mod+C".center-column = { }; + "Mod+Ctrl+C".center-visible-columns = { }; + + "Mod+Minus".set-column-width = "-10%"; + "Mod+Equal".set-column-width = "+10%"; + "Mod+Shift+Minus".set-window-height = "-10%"; + "Mod+Shift+Equal".set-window-height = "+10%"; + + "Mod+Space".toggle-window-floating = { }; + "Mod+Shift+Space".switch-focus-between-floating-and-tiling = { }; + "Mod+W".toggle-column-tabbed-display = { }; + + # screenshots + "Mod+P".screenshot = { }; + "Alt+Print".screenshot-window = { }; + + # escape hatch for apps that inhibit keyboard shortcuts + "Mod+Escape" = { + _props.allow-inhibiting = false; + toggle-keyboard-shortcuts-inhibit = { }; + }; + + "Mod+Shift+E".quit = { }; + }; + }; + }; +} diff --git a/users/aaron/home.nix b/users/aaron/home.nix index 22caf6c..ab5fc23 100644 --- a/users/aaron/home.nix +++ b/users/aaron/home.nix @@ -4,6 +4,7 @@ ../../modules/home-manager/git.nix ../../modules/home-manager/ghostty.nix ../../modules/home-manager/nixvim.nix + ../../modules/home-manager/niri.nix ../../modules/home-manager/noctalia.nix ../../modules/home-manager/programs.nix ../../modules/home-manager/shell.nix