{ pkgs, scale ? null, ... }: let nautilus = "${pkgs.nautilus}/bin/nautilus"; playerctl = "${pkgs.playerctl}/bin/playerctl"; wpctl = "${pkgs.wireplumber}/bin/wpctl"; brightnessctl = "${pkgs.brightnessctl}/bin/brightnessctl"; # Spawn a foot terminal that the "duplicate window" keybind can later trace back to # its shell. foot's --server mode makes every window report the *server's* PID to the # compositor, so niri can't distinguish windows by PID. Instead we give each window a # unique app-id ("footclient-") and export the same value as FOOT_APPID into its # shell; -E hands footclient's environment (incl. FOOT_APPID) to the server-spawned # shell. launch_terminal then finds the focused window's shell by matching FOOT_APPID. spawn_terminal = pkgs.writeShellScriptBin "spawn_terminal" '' ID="footclient-$$" export FOOT_APPID="$ID" exec ${pkgs.foot}/bin/footclient -E --app-id="$ID" "$@" ''; launch_terminal = pkgs.writeShellScriptBin "launch_terminal" '' function find_leaf_pid(){ local PID local RET local NAME PID=$1 for CHILD_PID in $(pgrep -P "$PID"); do RET=$(find_leaf_pid "$CHILD_PID") if [ "$RET" != "" ]; then PID="$RET" fi done NAME=$(ps -p "$PID" -o comm=) if [ "$NAME" == "zsh" ]; then echo "$PID" elif [ "$NAME" == "ssh" ]; then echo "$PID" fi } # foot runs in --server mode, so niri reports the shared server PID for every foot # window and cannot tell them apart. Resolve the focused window to its own shell by # matching the FOOT_APPID marker that spawn_terminal exports into each window: scan # the server's direct children for the one whose environment carries our app-id. FOCUSED=$(niri msg --json focused-window) SERVER_PID=$(echo "$FOCUSED" | ${pkgs.jq}/bin/jq -r '.pid // empty') APP_ID=$(echo "$FOCUSED" | ${pkgs.jq}/bin/jq -r '.app_id // empty') ROOT_PID="" if [ -n "$APP_ID" ] && [ -n "$SERVER_PID" ]; then for CHILD in $(pgrep -P "$SERVER_PID"); do if grep -qzF "FOOT_APPID=$APP_ID" "/proc/$CHILD/environ" 2>/dev/null; then ROOT_PID="$CHILD" break fi done fi # Focused window isn't one of our tagged foot terminals: just open a plain terminal. if [ -z "$ROOT_PID" ]; then exec ${spawn_terminal}/bin/spawn_terminal fi LEAF_PID=$(find_leaf_pid "$ROOT_PID") if [ "$(ps -p "$LEAF_PID" -o comm=)" == "ssh" ]; then SSH_COMMAND=$(ps -p "$LEAF_PID" -o args --no-headers) if [[ "$SSH_COMMAND" == *"waypipe"* ]]; then CLIENT=$(echo "$SSH_COMMAND" | awk '{ print $5 }') PREVIOUS_SESSION_ID=$(grep -z "SSH_SESSION_ID" "/proc/$LEAF_PID/environ" | xargs -0 -n 1 | grep -oP "[0-9]*") SSH_SESSION_ID=$RANDOM ${spawn_terminal}/bin/spawn_terminal zsh -c "SSH_SESSION_ID=$SSH_SESSION_ID waypipe ssh -t \"$CLIENT\" env SSH_SESSION_ID=\"$SSH_SESSION_ID\" PREVIOUS_SESSION_ID=\"$PREVIOUS_SESSION_ID\" \"zsh --login\"" else ${spawn_terminal}/bin/spawn_terminal zsh -c "ssh -t \"$(echo "$SSH_COMMAND" | awk '{ print $2 }')\"" fi else # Not an ssh session: open a local terminal in the same directory. CWD=$(readlink -e /proc/"$LEAF_PID"/cwd) if [ "$CWD" != "" ]; then ${spawn_terminal}/bin/spawn_terminal --working-directory "$CWD" else ${spawn_terminal}/bin/spawn_terminal fi fi ''; in { # Since native Home Manager doesn't have a programs.niri module yet, # we generate the KDL configuration file directly. home.packages = with pkgs; [ wl-clipboard ]; programs.hyprlock = { enable = true; settings = { input-field = [ { size = "200, 50"; position = "0, -80"; monitor = ""; dots_center = true; fade_on_empty = true; font_color = "rgb(202, 211, 245)"; inner_color = "rgb(91, 96, 120)"; outer_color = "rgb(24, 25, 38)"; outline_thickness = 3; placeholder_text = "Password..."; shadow_passes = 2; } ]; general = { no_fade_in = false; }; background = [ { path = "screenshot"; blur_passes = 3; blur_size = 8; } ]; }; }; xdg.configFile."niri/config.kdl".text = '' spawn-at-startup "${pkgs.swaybg}/bin/swaybg" "-i" "${../hyprland/files/wallpaper.png}" "-m" "fill" spawn-at-startup "foot" "--server" spawn-at-startup "ags" "run" spawn-at-startup "${pkgs.mate-polkit}/bin/polkit-mate" prefer-no-csd hotkey-overlay { skip-at-startup } gestures { hot-corners { off } } layout { gaps 5 border { off } focus-ring { width 4 active-color "rgba(0, 47, 95, 238)" inactive-color "rgba(255, 255, 255, 0)" } } input { mouse { accel-speed 0.5 } touchpad { tap } keyboard { xkb { options "compose:rctrl" } } focus-follows-mouse max-scroll-amount="10%" warp-mouse-to-focus } output "HDMI-A-1" { scale 1.0 position x=0 y=-2160 } ${ if scale != null then '' output "eDP-1" { scale ${toString scale} position x=0 y=0 } '' else "" } window-rule { match app-id="vicinae" open-floating true } binds { // General Binds Mod+Return { spawn "${launch_terminal}/bin/launch_terminal"; } Mod+Shift+Return { spawn "${spawn_terminal}/bin/spawn_terminal"; } Mod+Q { close-window; } Mod+A { spawn "${nautilus}"; } Mod+B { spawn "Helium"; } Mod+M { maximize-column; } Mod+F {maximize-window-to-edges;} Mod+Tab { toggle-overview; } // Vicinae Binds Mod+C { spawn "vicinae" "vicinae://launch/clipboard/history"; } Mod+N { spawn "vicinae" "vicinae://launch/wm/switch-windows"; } Mod+Escape { spawn "vicinae" "open" "-q" "Power Management "; } Mod+Space { spawn "vicinae" "toggle"; } Mod+Comma { consume-or-expel-window-left; } Mod+Period { consume-or-expel-window-right; } Mod+Alt+H { set-column-width "-5%"; } Mod+Alt+L { set-column-width "+5%"; } Mod+Alt+K { set-window-height "-5%"; } Mod+Alt+J { set-window-height "+5%"; } // Movement Mod+H { focus-column-or-monitor-left; } Mod+J { focus-window-or-monitor-down; } Mod+K { focus-window-or-monitor-up; } Mod+L { focus-column-or-monitor-right; } Mod+Shift+H { move-column-left-or-to-monitor-left; } Mod+Shift+L { move-column-right-or-to-monitor-right; } Mod+Shift+J { move-window-down-or-to-workspace-down; } Mod+Shift+K { move-window-up-or-to-workspace-up; } // Move window to the other monitor Mod+Ctrl+K { move-window-to-monitor-up; } Mod+Ctrl+J { move-window-to-monitor-down; } // Workspaces Mod+Shift+U { move-column-to-workspace-down; } Mod+Shift+I { move-column-to-workspace-up; } Mod+I { focus-workspace-up; } Mod+U { focus-workspace-down; } Mod+1 { focus-workspace 1; } Mod+2 { focus-workspace 2; } Mod+3 { focus-workspace 3; } Mod+4 { focus-workspace 4; } Mod+5 { focus-workspace 5; } Mod+6 { focus-workspace 6; } Mod+7 { focus-workspace 7; } Mod+8 { focus-workspace 8; } Mod+9 { focus-workspace 9; } // Move to workspace Mod+Shift+1 { move-column-to-workspace 1; } Mod+Shift+2 { move-column-to-workspace 2; } Mod+Shift+3 { move-column-to-workspace 3; } Mod+Shift+4 { move-column-to-workspace 4; } Mod+Shift+5 { move-column-to-workspace 5; } Mod+Shift+6 { move-column-to-workspace 6; } Mod+Shift+7 { move-column-to-workspace 7; } Mod+Shift+8 { move-column-to-workspace 8; } Mod+Shift+9 { move-column-to-workspace 9; } Print { screenshot; } Ctrl+Alt+L {spawn "${pkgs.hyprlock}/bin/hyprlock"; } XF86MonBrightnessUp { spawn "${brightnessctl}" "set" "+10%"; } XF86MonBrightnessDown { spawn "${brightnessctl}" "set" "10%-"; } // Volume XF86AudioRaiseVolume { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "10%+" "--limit" "1.0"; } XF86AudioLowerVolume { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "10%-"; } XF86AudioMute { spawn "${wpctl}" "set-mute" "@DEFAULT_AUDIO_SINK@" "toggle"; } XF86AudioMicMute { spawn "${wpctl}" "set-mute" "@DEFAULT_AUDIO_SOURCE@" "toggle"; } // Ctrl + horizontal mouse scroll controls volume // (unmodified WheelScroll binds capture the whole wheel in niri and break normal scrolling) Ctrl+Alt+WheelScrollRight cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%+" "--limit" "1.0"; } Ctrl+Alt+WheelScrollLeft cooldown-ms=10 { spawn "${wpctl}" "set-volume" "@DEFAULT_AUDIO_SINK@" "1%-"; } // Mod + horizontal scroll moves between windows (columns) left/right Mod+WheelScrollRight cooldown-ms=200 { focus-column-right; } Mod+WheelScrollLeft cooldown-ms=200 { focus-column-left; } // Mod + vertical scroll moves between workspaces up/down Mod+WheelScrollDown cooldown-ms=100 { focus-workspace-down; } Mod+WheelScrollUp cooldown-ms=100 { focus-workspace-up; } // Media XF86AudioNext { spawn "${playerctl}" "next"; } XF86AudioPrev { spawn "${playerctl}" "previous"; } Mod+XF86AudioMute { spawn "${playerctl}" "play-pause"; } Mod+XF86AudioRaiseVolume { spawn "${playerctl}" "next"; } Mod+XF86AudioLowerVolume { spawn "${playerctl}" "previous"; } } ''; }