feat(compositors)!: Add niri
According to its github page, niri is "A scrollable-tiling Wayland
compositor". Myself and coded have been looking for alternatives to
hyprland for stability and community reasons, and niri looks reasonable
for us
It's possible we'll remove hyprland altogether. If we do, we plan to
still maintain a more traditional compositor (e.g. swayfx)
BREAKING-CHANGE: This patch renames some hyprland options
Change-Id: I170dde769b55ebb32a3212a54012443fb4d0faf8
Reviewed-on: https://git.clicks.codes/c/Chimera/NixFiles/+/713
Reviewed-by: Samuel Shuert <coded@clicks.codes>
Tested-by: Samuel Shuert <coded@clicks.codes>
diff --git a/modules/home/wayland/niri/default.nix b/modules/home/wayland/niri/default.nix
new file mode 100644
index 0000000..0695273
--- /dev/null
+++ b/modules/home/wayland/niri/default.nix
@@ -0,0 +1,277 @@
+{
+ pkgs,
+ config,
+ inputs,
+ system,
+ lib,
+ ...
+}: {
+
+ options.chimera.niri = {
+ enable = lib.mkEnableOption "Use Niri as your window manager";
+ monitors = lib.mkOption {
+ type = lib.types.attrsOf (lib.types.submodule {
+ options = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ description = "Enable this monitor";
+ };
+ mode = lib.mkOption {
+ type = lib.types.nullOr (lib.types.submodule {
+ options = {
+ width = lib.mkOption {
+ type = lib.types.int;
+ };
+ height = lib.mkOption {
+ type = lib.types.int;
+ };
+ refresh = lib.mkOption {
+ type = lib.types.nullOr lib.types.float;
+ default = null;
+ };
+ };
+ });
+ default = null;
+ };
+ position = lib.mkOption {
+ type = lib.types.nullOr (lib.types.submodule {
+ options = {
+ x = lib.mkOption {
+ type = lib.types.int;
+ };
+ y = lib.mkOption {
+ type = lib.types.int;
+ };
+ };
+ });
+ default = null;
+ };
+ scale = lib.mkOption {
+ type = lib.types.float;
+ default = 1.;
+ };
+ transform = {
+ flipped = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ rotation = lib.mkOption {
+ type = lib.types.enum [ 0 90 180 270 ];
+ default = 0;
+ };
+ };
+ variable-refresh-rate = lib.mkEnableOption "Enable Variable Refresh Rate (AMD FreeSync / Nvidia G-Sync)";
+ };
+ });
+ description = "Atribute set of monitors";
+ default = { };
+ };
+ };
+
+ config = lib.mkIf config.chimera.niri.enable {
+ chimera.wayland.enable = true;
+
+ programs.bash.profileExtra = lib.mkIf config.chimera.shell.bash.enable (lib.mkBefore ''
+ if [ -z $WAYLAND_DISPLAY ] && [ "$(tty)" = "/dev/tty1" ]; then
+ exec ${pkgs.systemd}/bin/systemd-cat -t niri ${pkgs.dbus}/bin/dbus-run-session ${config.programs.niri.package}/bin/niri --session
+ fi
+ '');
+
+ programs.zsh.profileExtra = lib.mkIf config.chimera.shell.zsh.enable (lib.mkBefore ''
+ if [ -z $WAYLAND_DISPLAY ] && [ "$(tty)" = "/dev/tty1" ]; then
+ exec ${pkgs.systemd}/bin/systemd-cat -t niri ${pkgs.dbus}/bin/dbus-run-session ${config.programs.niri.package}/bin/niri --session
+ fi
+ '');
+
+ home.sessionVariables.NIXOS_OZONE_WL = "1";
+
+ programs.niri = let
+ mod = "Super";
+ mod1 = "Alt";
+ terminal = "${pkgs.kitty}/bin/kitty";
+ menu = (if config.chimera.runner.anyrun.enable then "${inputs.anyrun.packages.${system}.anyrun}/bin/anyrun" else "");
+
+ lock = ''${pkgs.swaylock}/bin/swaylock -i ${config.chimera.theme.wallpaper} -s fill'';
+ in {
+ enable = true;
+ package = pkgs.niri-stable;
+ settings = {
+ input.keyboard = {
+ track-layout = "window";
+ repeat-delay = 200;
+ repeat-rate = 25;
+ xkb = {
+ layout = config.chimera.input.keyboard.layout;
+ variant = config.chimera.input.keyboard.variant;
+ };
+ };
+
+ input.mouse.natural-scroll = config.chimera.input.mouse.scrolling.natural;
+ input.touchpad.natural-scroll = config.chimera.input.touchpad.scrolling.natural;
+
+ input.warp-mouse-to-focus = true;
+ input.focus-follows-mouse = true;
+
+ input.power-key-handling.enable = false;
+
+ binds = let
+ inherit (config.lib.niri) actions;
+
+ generateWorkspaceBindings = workspaceNumber: {
+ "${mod}+${builtins.toString (lib.mod workspaceNumber 10)}".action = actions.focus-workspace workspaceNumber;
+ "${mod}+Shift+${builtins.toString (lib.mod workspaceNumber 10)}".action = actions.move-column-to-workspace workspaceNumber;
+ };
+ joinAttrsetList = listOfAttrsets: lib.fold (a: b: a // b) {} listOfAttrsets;
+ in { # General Keybinds
+ "${mod}+Q".action = actions.close-window;
+ "${mod}+Return".action = actions.spawn "${terminal}";
+ "${mod}+L".action = actions.spawn ["sh" "-c" "${config.programs.niri.package}/bin/niri msg action do-screen-transition && ${lock}"];
+ "${mod}+P".action = actions.power-off-monitors;
+
+ "${mod}+R".action = actions.screenshot;
+ "${mod}+Ctrl+R".action = actions.screenshot-screen;
+ "${mod}+Shift+R".action = actions.screenshot-window;
+ "Print".action = actions.screenshot;
+ "Ctrl+Print".action = actions.screenshot-screen;
+ "Shift+Print".action = actions.screenshot-window;
+
+ "${mod}+Space".action = actions."switch-layout" "next";
+ "${mod}+Shift+Space".action = actions."switch-layout" "prev";
+
+ "${mod}+D" = lib.mkIf config.chimera.runner.enable { action = actions.spawn "${menu}"; };
+ "Alt+Space" = lib.mkIf (
+ config.chimera.runner.enable
+ && config.chimera.input.keybinds.alternativeSearch.enable
+ ) { action = actions.spawn "${menu}"; };
+ "${mod}+Shift+Slash".action = actions.show-hotkey-overlay;
+ } // ( # Workspace Keybinds
+ lib.pipe (lib.range 1 10) [
+ (map generateWorkspaceBindings)
+ joinAttrsetList
+ ]
+ ) // ( # Window Manipulation Bindings
+ {
+ "${mod}+BracketLeft".action = actions."consume-or-expel-window-left";
+ "${mod}+BracketRight".action = actions."consume-or-expel-window-right";
+ "${mod}+Shift+BracketLeft".action = actions."consume-window-into-column";
+ "${mod}+Shift+BracketRight".action = actions."expel-window-from-column";
+ "${mod}+Slash".action = actions."switch-preset-column-width";
+ "${mod}+${mod1}+F".action = actions."fullscreen-window";
+
+ # Focus
+ "${mod}+Up".action = actions."focus-window-or-workspace-up";
+ "${mod}+Down".action = actions."focus-window-or-workspace-down";
+
+ # Non Jump Movement
+ "${mod}+Shift+Up".action = actions."move-window-up-or-to-workspace-up";
+ "${mod}+Shift+Down".action = actions."move-window-down-or-to-workspace-down";
+ "${mod}+Shift+Left".action = actions."consume-or-expel-window-left";
+ "${mod}+Shift+Right".action = actions."consume-or-expel-window-right";
+
+ # To Monitor
+ "${mod}+Shift+Ctrl+Up".action = actions."move-window-to-monitor-up";
+ "${mod}+Shift+Ctrl+Down".action = actions."move-window-to-monitor-down";
+ "${mod}+Shift+Ctrl+Left".action = actions."move-window-to-monitor-left";
+ "${mod}+Shift+Ctrl+Right".action = actions."move-window-to-monitor-right";
+
+ # To Workspace
+ "${mod}+Ctrl+Up".action = actions."move-window-to-workspace-up";
+ "${mod}+Ctrl+Down".action = actions."move-window-to-workspace-down";
+
+ # Sizing
+ "${mod}+Equal".action = actions."set-window-height" "+5%";
+ "${mod}+Minus".action = actions."set-window-height" "-5%";
+ }) // ( # Column Manipulation Bindings
+ {
+ # Focus
+ "${mod}+Left".action = actions."focus-column-left";
+ "${mod}+Right".action = actions."focus-column-right";
+ "${mod}+${mod1}+C".action = actions."center-column";
+ "${mod}+F".action = actions."maximize-column";
+
+ # Non Monitor Movement
+ "${mod}+${mod1}+Shift+Up".action = actions."move-column-to-workspace-up";
+ "${mod}+${mod1}+Shift+Down".action = actions."move-column-to-workspace-down";
+ "${mod}+${mod1}+Shift+Left".action = actions."move-column-left";
+ "${mod}+${mod1}+Shift+Right".action = actions."move-column-right";
+
+ # To Monitor
+ "${mod}+${mod1}+Shift+Ctrl+Up".action = actions."move-column-to-monitor-up";
+ "${mod}+${mod1}+Shift+Ctrl+Down".action = actions."move-column-to-monitor-down";
+ "${mod}+${mod1}+Shift+Ctrl+Left".action = actions."move-column-to-monitor-left";
+ "${mod}+${mod1}+Shift+Ctrl+Right".action = actions."move-column-to-monitor-right";
+
+ # Sizing
+ "${mod}+${mod1}+Equal".action = actions."set-column-width" "+5%";
+ "${mod}+${mod1}+Minus".action = actions."set-column-width" "-5%";
+ }) // ( # Workspace Manipulation Bindings
+ {
+ # Focus
+ "${mod}+Page_Up".action = actions."focus-workspace-up";
+ "${mod}+Page_Down".action = actions."focus-workspace-down";
+
+ # Within Itself
+ "${mod}+Shift+Page_Up".action = actions."move-workspace-up";
+ "${mod}+Shift+Page_Down".action = actions."move-workspace-down";
+
+ # To Monitor
+ "${mod}+Shift+Ctrl+Page_Up".action = actions."move-workspace-to-monitor-up";
+ "${mod}+Shift+Ctrl+Page_Down".action = actions."move-workspace-to-monitor-down";
+ "${mod}+Shift+Ctrl+Home".action = actions."move-workspace-to-monitor-left";
+ "${mod}+Shift+Ctrl+End".action = actions."move-workspace-to-monitor-right";
+ }) // { # Audio
+ "XF86AudioRaiseVolume" = {
+ allow-when-locked = true;
+ action = actions.spawn "${pkgs.wireplumber}/bin/wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05+";
+ };
+ "XF86AudioLowerVolume" = {
+ allow-when-locked = true;
+ action = actions.spawn "${pkgs.wireplumber}/bin/wpctl" "set-volume" "@DEFAULT_AUDIO_SINK@" "0.05-";
+ };
+ "XF86AudioMute" = {
+ allow-when-locked = true;
+ action = actions.spawn "${pkgs.wireplumber}/bin/wpctl" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle";
+ };
+ "XF86AudioMicMute" = {
+ allow-when-locked = true;
+ action = actions.spawn "${pkgs.wireplumber}/bin/wpctl" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle";
+ };
+ };
+
+ outputs = config.chimera.niri.monitors;
+
+ cursor = {
+ size = config.chimera.theme.cursor.size;
+ theme = config.chimera.theme.cursor.name;
+ };
+
+ layout = {
+ gaps = 16;
+
+ center-focused-column = "on-overflow";
+
+ preset-column-widths = [
+ { proportion = 1. / 4.; }
+ { proportion = 1. / 3.; }
+ { proportion = 1. / 2.; }
+ { proportion = 2. / 3.; }
+ ]; # TODO: clicks to PR a docs update for niri-flake
+ };
+
+ prefer-no-csd = true; # No "client-side-decorations" (i.e. client-side window open/close buttons)
+ hotkey-overlay.skip-at-startup = true;
+ screenshot-path = null;
+
+ spawn-at-startup = [
+ {
+ command = [ "${pkgs.waybar}/bin/waybar" ];
+ }
+ {
+ command = [ "${pkgs.swaybg}/bin/swaybg" "-i" "${config.chimera.theme.wallpaper}" "-m" "fill" ];
+ }
+ ];
+ };
+ };
+ };
+}