From 61382f4d32a56e00db34d1cefa2c0947be31be6c Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 16 Jan 2025 04:23:55 +0100 Subject: [PATCH] Add niri module - An overridden `niri.service` is provided, so that systemd does not take over autostart and try to start things *before* we have an XWayland server running. - Somehow `wlsunset` is still not working, might need some investigation into why. --- home/common.nix | 1 + home/fish/fish.nix | 5 + home/kagami-pc-home.nix | 1 + home/modules/programs/my-kitty/default.nix | 2 +- home/modules/programs/my-niri.nix | 328 +++++++++++++++++++++ home/modules/programs/my-waybar.nix | 16 + modules/common/linux/default.nix | 28 +- 7 files changed, 379 insertions(+), 2 deletions(-) create mode 100644 home/modules/programs/my-niri.nix diff --git a/home/common.nix b/home/common.nix index 0768e21..a8d6035 100644 --- a/home/common.nix +++ b/home/common.nix @@ -7,6 +7,7 @@ ./modules/programs/my-broot.nix ./modules/programs/my-waybar.nix ./modules/programs/my-sway + ./modules/programs/my-niri.nix ./modules/programs/my-kitty ./modules/programs/openconnect-epfl.nix ./common-linux.nix diff --git a/home/fish/fish.nix b/home/fish/fish.nix index 23a49cd..58babb5 100644 --- a/home/fish/fish.nix +++ b/home/fish/fish.nix @@ -13,6 +13,9 @@ let if which sway &>/dev/null set -a CHOICES "sway" end + if which niri-session &>/dev/null + set -a CHOICES "Niri" + end if which startplasma-wayland &>/dev/null set -a CHOICES "KDE Plasma" end @@ -22,6 +25,8 @@ let case "sway" systemctl --user unset-environment NIXOS_OZONE_WL exec sway + case "Niri" + exec niri-session case "KDE Plasma" exec ${pkgs.kdePackages.plasma-workspace}/libexec/plasma-dbus-run-session-if-needed startplasma-wayland case '*' diff --git a/home/kagami-pc-home.nix b/home/kagami-pc-home.nix index 7eac50d..659c13d 100644 --- a/home/kagami-pc-home.nix +++ b/home/kagami-pc-home.nix @@ -39,6 +39,7 @@ linux.graphical.wallpaper = ./images/pixiv_18776904.png; linux.graphical.defaults.webBrowser.package = pkgs.zen-browser-bin; linux.graphical.defaults.webBrowser.desktopFile = "zen.desktop"; + programs.my-niri.enable = true; programs.my-sway.enable = true; programs.my-sway.fontSize = 15.0; programs.my-sway.enableLaptop = true; diff --git a/home/modules/programs/my-kitty/default.nix b/home/modules/programs/my-kitty/default.nix index fe82ff0..45801dd 100644 --- a/home/modules/programs/my-kitty/default.nix +++ b/home/modules/programs/my-kitty/default.nix @@ -74,7 +74,7 @@ with lib; # Background color and transparency background = if isNull cfg.background then { - background_opacity = "0.85"; + background_opacity = "0.93"; dynamic_background_opacity = true; } else { background_image = "${cfg.background}"; diff --git a/home/modules/programs/my-niri.nix b/home/modules/programs/my-niri.nix new file mode 100644 index 0000000..d6311b2 --- /dev/null +++ b/home/modules/programs/my-niri.nix @@ -0,0 +1,328 @@ +{ config, osConfig, lib, pkgs, ... }: +let + cfg = config.programs.my-niri; + + sh = config.lib.niri.actions.spawn "sh" "-c"; + playerctl = lib.getExe pkgs.playerctl; + amixer = lib.getExe' pkgs.alsa-utils "amixer"; + brightnessctl = lib.getExe pkgs.brightnessctl; + app-menu = "${pkgs.dmenu}/bin/dmenu_path | ${pkgs.bemenu}/bin/bemenu | ${pkgs.findutils}/bin/xargs swaymsg exec --"; + + wallpaper = config.linux.graphical.wallpaper; + + workspaces = { + "01" = { name = "🌏 web"; }; + "02" = { name = "💬 chat"; }; + "03" = { name = "⚙️ code"; }; + "04" = { name = "🎶 music"; }; + "05" = { name = "🔧 extra"; }; + "06" = { name = "🧰 6"; }; + "07" = { name = "🔩 7"; }; + "08" = { name = "🛠️ 8"; }; + "09" = { name = "🔨 9"; }; + "10" = { name = "🎲 misc"; }; + "99" = { name = "📧 Email"; }; + }; + + xwayland-display = ":12"; + +in +{ + options.programs.my-niri = { + enable = lib.mkEnableOption "My own niri configuration"; + + enableLaptop = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable laptop options"; + }; + + lock-command = lib.mkOption { + type = lib.types.listOf lib.types.str; + description = "The command to lock the screen"; + default = [ "${pkgs.swaylock}/bin/swaylock" ] + ++ (if wallpaper == "" then [ "" ] else [ "-i" "${wallpaper}" "-s" "fill" ]) + ++ [ "-l" "-k" ]; + }; + }; + + config = lib.mkIf cfg.enable { + programs.niri.settings = { + environment = { + QT_QPA_PLATFORM = "wayland"; + QT_WAYLAND_DISABLE_WINDOWDECORATION = "1"; + QT_IM_MODULE = "fcitx"; + GTK_IM_MODULE = "fcitx"; # Til text-input is merged + # export NIXOS_OZONE_WL=1 # Until text-input is merged + DISPLAY = xwayland-display; + } // lib.optionalAttrs osConfig.services.desktopManager.plasma6.enable { + XDG_MENU_PREFIX = "plasma-"; + }; + input.keyboard.xkb = { layout = "jp"; }; + input.touchpad = lib.mkIf cfg.enableLaptop { + tap = true; + dwt = true; + natural-scroll = true; + middle-emulation = true; + }; + input.mouse = { + accel-profile = "flat"; + }; + input.warp-mouse-to-focus = true; + input.focus-follows-mouse = { + enable = true; + max-scroll-amount = "0%"; + }; + + outputs = + let + eachMonitor = _: monitor: { + name = monitor.meta.niriName or monitor.name; # Niri might not find the monitor by name + value = { + mode = monitor.meta.mode; + position = monitor.meta.fixedPosition or null; + scale = monitor.scale or 1; + variable-refresh-rate = (monitor.adaptive_sync or "off") == "on"; + }; + }; + in + lib.mapAttrs' eachMonitor config.common.monitors; + + spawn-at-startup = [ + # Wallpaper + { command = [ (lib.getExe pkgs.swaybg) "-i" "${wallpaper}" "-m" "fill" ]; } + # IME + { command = [ "fcitx5" ]; } + # XWayland + { command = [ (lib.getExe pkgs.xwayland-satellite) xwayland-display ]; } + # Waybar + { command = [ "systemctl" "--user" "start" "swaync.service" ]; } + { command = [ "systemctl" "--user" "start" "xdg-desktop-portal-gtk.service" "xdg-desktop-portal.service" ]; } + { command = [ "systemctl" "--user" "reset-failed" "waybar.service" "wlsunset.service" ]; } + # Startup + { command = [ (lib.getExe pkgs.dex) "-ae" "niri" ]; } + ]; + + layout = { + gaps = 16; + preset-column-widths = [ + { proportion = 1. / 3.; } + { proportion = 1. / 2.; } + { proportion = 2. / 3.; } + ]; + default-column-width.proportion = 1. / 2.; + + focus-ring = { + width = 4; + active.gradient = { from = "#00447AFF"; to = "#71C4FFAA"; angle = 45; }; + inactive.color = "#505050"; + }; + border.enable = false; + struts = let v = 8; in { left = v; right = v; bottom = v; top = v; }; + }; + + prefer-no-csd = true; + + inherit workspaces; + + window-rules = [ + # Rounded Corners + { + geometry-corner-radius = let v = 8.0; in { bottom-left = v; bottom-right = v; top-left = v; top-right = v; }; + clip-to-geometry = true; + } + # Workspace assignments + { + open-on-workspace = workspaces."01".name; + open-maximized = true; + matches = [ + { at-startup = true; app-id = "^firefox$"; } + { at-startup = true; app-id = "^librewolf$"; } + { at-startup = true; app-id = "^zen$"; } + ]; + } + { + open-on-workspace = workspaces."02".name; + open-maximized = true; + matches = [ + { title = "^((d|D)iscord|((A|a)rm(c|C)ord))$"; } + { title = "VencordDesktop"; } + { app-id = "VencordDesktop"; } + { title = "vesktop"; } + { app-id = "vesktop"; } + + { title = "Slack"; } + ]; + } + { + open-on-workspace = workspaces."99".name; + open-maximized = true; + matches = [ + { app-id = "thunderbird"; } + { app-id = "evolution"; } + ]; + } + # Floating + { + open-floating = true; + matches = [ + { app-id = ".*float.*"; } + { app-id = "org\\.freedesktop\\.impl\\.portal\\.desktop\\..*"; } + { title = ".*float.*"; } + { title = "Extension: .*Bitwarden.*"; } + { app-id = "Rofi"; } + ]; + } + + # Kitty dimming + { + matches = [{ app-id = "kitty"; }]; + excludes = [{ is-focused = true; }]; + opacity = 0.95; + } + ]; + + layer-rules = [ + { + matches = [{ namespace = "^swaync-.*"; }]; + block-out-from = "screen-capture"; + } + ]; + + binds = with config.lib.niri.actions; { + # Mod-Shift-/, which is usually the same as Mod-?, + # shows a list of important hotkeys. + "Mod+Shift+Slash".action = show-hotkey-overlay; + + # Some basic spawns + "Mod+Return".action = spawn (lib.getExe config.linux.graphical.defaults.terminal.package); + "Mod+Space".action = spawn (lib.getExe pkgs.rofi) "-show" "drun"; + "Mod+R".action = sh app-menu; + "Mod+Semicolon".action = spawn cfg.lock-command; + + # Audio and Volume + "XF86AudioPrev" = { action = spawn playerctl "previous"; allow-when-locked = true; }; + "XF86AudioPlay" = { action = spawn playerctl "play-pause"; allow-when-locked = true; }; + "Shift+XF86AudioPlay" = { action = spawn playerctl "stop"; allow-when-locked = true; }; + "XF86AudioNext" = { action = spawn playerctl "next"; allow-when-locked = true; }; + "XF86AudioRecord" = { action = spawn amixer "-q" "set" "Capture" "toggle"; allow-when-locked = true; }; + "XF86AudioMute" = { action = spawn amixer "-q" "set" "Master" "toggle"; allow-when-locked = true; }; + "XF86AudioLowerVolume" = { action = spawn amixer "-q" "set" "Master" "3%-"; allow-when-locked = true; }; + "XF86AudioRaiseVolume" = { action = spawn amixer "-q" "set" "Master" "3%+"; allow-when-locked = true; }; + + # Backlight + "XF86MonBrightnessDown".action = spawn brightnessctl "s" "10%-"; + "XF86MonBrightnessUp".action = spawn brightnessctl "s" "10%+"; + "Shift+XF86MonBrightnessDown".action = spawn brightnessctl "-d" "kbd_backlight" "s" "25%-"; + "Shift+XF86MonBrightnessUp".action = spawn brightnessctl "-d" "kbd_backlight" "s" "25%+"; + + "Mod+Shift+Q".action = close-window; + + "Mod+Left".action = focus-column-or-monitor-left; + "Mod+Right".action = focus-column-or-monitor-right; + "Mod+Up".action = focus-window-or-workspace-up; + "Mod+Down".action = focus-window-or-workspace-down; + "Mod+H".action = focus-column-or-monitor-left; + "Mod+L".action = focus-column-or-monitor-right; + "Mod+K".action = focus-window-or-workspace-up; + "Mod+J".action = focus-window-or-workspace-down; + + "Mod+Shift+Left".action = move-column-left-or-to-monitor-left; + "Mod+Shift+Right".action = move-column-right-or-to-monitor-right; + "Mod+Shift+Up".action = move-window-up-or-to-workspace-up; + "Mod+Shift+Down".action = move-window-down-or-to-workspace-down; + "Mod+Shift+H".action = move-column-left-or-to-monitor-left; + "Mod+Shift+L".action = move-column-right-or-to-monitor-right; + "Mod+Shift+K".action = move-window-up-or-to-workspace-up; + "Mod+Shift+J".action = move-window-down-or-to-workspace-down; + + "Mod+Bracketleft".action = focus-column-first; + "Mod+Bracketright".action = focus-column-last; + "Mod+Shift+Bracketleft".action = move-column-to-first; + "Mod+Shift+Bracketright".action = move-column-to-last; + + # For compat with my current sway + "Mod+Ctrl+H".action = move-workspace-to-monitor-left; + "Mod+Ctrl+L".action = move-workspace-to-monitor-right; + + "Mod+I".action = focus-workspace-down; + "Mod+O".action = focus-workspace-up; + "Mod+Shift+I".action = move-column-to-workspace-down; + "Mod+Shift+O".action = move-column-to-workspace-up; + "Mod+Ctrl+I".action = move-workspace-down; + "Mod+Ctrl+O".action = move-workspace-up; + + # Mouse bindings + "Mod+WheelScrollDown" = { action = focus-workspace-down; cooldown-ms = 150; }; + "Mod+WheelScrollUp" = { action = focus-workspace-up; cooldown-ms = 150; }; + "Mod+Ctrl+WheelScrollDown" = { action = move-column-to-workspace-down; cooldown-ms = 150; }; + "Mod+Ctrl+WheelScrollUp" = { action = move-column-to-workspace-up; cooldown-ms = 150; }; + + "Mod+WheelScrollRight".action = focus-column-right; + "Mod+WheelScrollLeft".action = focus-column-left; + "Mod+Ctrl+WheelScrollRight".action = move-column-right; + "Mod+Ctrl+WheelScrollLeft".action = move-column-left; + + # You can refer to workspaces by index. However, keep in mind that + # niri is a dynamic workspace system, so these commands are kind of + # "best effort". Trying to refer to a workspace index bigger than + # the current workspace count will instead refer to the bottommost + # (empty) workspace. + # + # For example, with 2 workspaces + 1 empty, indices 3, 4, 5 and so on + # will all refer to the 3rd workspace. + "Mod+1".action = focus-workspace (workspaces."01".name); + "Mod+2".action = focus-workspace (workspaces."02".name); + "Mod+3".action = focus-workspace (workspaces."03".name); + "Mod+4".action = focus-workspace (workspaces."04".name); + "Mod+5".action = focus-workspace (workspaces."05".name); + "Mod+6".action = focus-workspace (workspaces."06".name); + "Mod+7".action = focus-workspace (workspaces."07".name); + "Mod+8".action = focus-workspace (workspaces."08".name); + "Mod+9".action = focus-workspace (workspaces."09".name); + "Mod+0".action = focus-workspace (workspaces."10".name); + "Mod+Ctrl+1".action = move-column-to-workspace (workspaces."01".name); + "Mod+Ctrl+2".action = move-column-to-workspace (workspaces."02".name); + "Mod+Ctrl+3".action = move-column-to-workspace (workspaces."03".name); + "Mod+Ctrl+4".action = move-column-to-workspace (workspaces."04".name); + "Mod+Ctrl+5".action = move-column-to-workspace (workspaces."05".name); + "Mod+Ctrl+6".action = move-column-to-workspace (workspaces."06".name); + "Mod+Ctrl+7".action = move-column-to-workspace (workspaces."07".name); + "Mod+Ctrl+8".action = move-column-to-workspace (workspaces."08".name); + "Mod+Ctrl+9".action = move-column-to-workspace (workspaces."09".name); + "Mod+Ctrl+0".action = move-column-to-workspace (workspaces."10".name); + + "Mod+asciicircum".action = focus-workspace (workspaces."99".name); + "Mod+Shift+asciicircum".action = move-column-to-workspace (workspaces."99".name); + + "Mod+Tab".action = focus-workspace-previous; + + "Mod+Comma".action = consume-or-expel-window-left; + "Mod+Period".action = consume-or-expel-window-right; + + "Mod+W".action = switch-preset-column-width; + "Mod+Shift+W".action = switch-preset-window-height; + "Mod+Ctrl+W".action = reset-window-height; + "Mod+F".action = maximize-column; + "Mod+Shift+F".action = fullscreen-window; + "Mod+E".action = center-column; + + "Mod+Minus".action = set-column-width "-10%"; + "Mod+At".action = set-column-width "+10%"; + "Mod+Shift+Minus".action = set-window-height "-10%"; + "Mod+Shift+At".action = set-window-height "+10%"; + + "Mod+V".action = switch-focus-between-floating-and-tiling; + "Mod+Shift+V".action = toggle-window-floating; + "Mod+Shift+Space".action = toggle-window-floating; # Sway compat + + "Print".action = screenshot; + "Ctrl+Print".action = screenshot-screen; + "Shift+Print".action = screenshot-window; + + "Mod+Shift+E".action = quit; + }; + }; + }; +} + diff --git a/home/modules/programs/my-waybar.nix b/home/modules/programs/my-waybar.nix index f9ffe30..0f9bcd7 100644 --- a/home/modules/programs/my-waybar.nix +++ b/home/modules/programs/my-waybar.nix @@ -45,11 +45,14 @@ in config.programs.waybar = let barWith = { showMedia ? true, showConnectivity ? true, extraSettings ? { }, ... }: lib.mkMerge ([{ + layer = "top"; position = "top"; modules-left = [ "sway/workspaces" "sway/mode" "sway/window" + "niri/workspaces" + "niri/window" ]; modules-center = [ ]; @@ -91,6 +94,19 @@ in "\\((\\d+)\\) Discord \\| (.*)" = "[🗨️] {$1} $2"; }; }; + "niri/window" = { + format = "{title}"; + "rewrite" = { + "(.*) — Mozilla Firefox" = "[🌎] $1"; + "(.*) - Mozilla Thunderbird" = "[📧] $1"; + "(.*) - Kakoune" = "[⌨️] $1"; + "(.*) - fish" = "[>_] $1"; + "(.*) - Discord" = "[🗨️] $1"; + # ArmCord thing + "• Discord \\| (.*)" = "[🗨️] $1"; + "\\((\\d+)\\) Discord \\| (.*)" = "[🗨️] {$1} $2"; + }; + }; "tray" = { icon-size = 21; spacing = 10; diff --git a/modules/common/linux/default.nix b/modules/common/linux/default.nix index 3b53a0d..0ccc742 100644 --- a/modules/common/linux/default.nix +++ b/modules/common/linux/default.nix @@ -45,12 +45,31 @@ let # services.gnome.evolution-data-server.plugins = with pkgs; [ evolution-ews ]; }; - wlr = { ... }: mkIf config.common.linux.enable { + wlr = { lib, config, ... }: mkIf config.common.linux.enable { # swaync disable notifications on screencast xdg.portal.wlr.settings.screencast = { exec_before = ''which swaync-client && swaync-client --inhibitor-add "xdg-desktop-portal-wlr" || true''; exec_after = ''which swaync-client && swaync-client --inhibitor-remove "xdg-desktop-portal-wlr" || true''; }; + + # Niri stuff + # https://github.com/sodiboo/niri-flake/blob/main/docs.md + programs.niri.enable = true; + programs.niri.package = pkgs.niri-stable; + systemd.user.services.niri = { + description = "A scrollable-tiling Wayland compositor"; + bindsTo = [ "graphical-session.target" ]; + before = [ "graphical-session.target" ]; + wants = [ "graphical-session-pre.target" ]; + after = [ "graphical-session-pre.target" ]; + + serviceConfig.Slice = "session.slice"; + serviceConfig.Type = "notify"; + serviceConfig.ExecStart = "${lib.getExe config.programs.niri.package} --session"; + path = mkForce [ ]; + }; + # Override gnome-keyring disabling + services.gnome.gnome-keyring.enable = lib.mkForce false; }; logitech = { pkgs, ... }: mkIf cfg.enable { @@ -343,6 +362,13 @@ in extraPortals = [ pkgs.kdePackages.xdg-desktop-portal-kde pkgs.xdg-desktop-portal-gtk ]; config.sway.default = [ "wlr" "kde" "kwallet" ]; + config.niri = { + default = [ "gnome" "gtk" ]; + "org.freedesktop.impl.portal.Access" = "gtk"; + "org.freedesktop.impl.portal.Notification" = "gtk"; + "org.freedesktop.impl.portal.Secret" = "kwallet"; + "org.freedesktop.impl.portal.FileChooser" = "kde"; + }; }; # D-Bus services.dbus.packages = with pkgs; [ gcr ];