Files
dotfiles/niri/config.kdl
T
funman300 05cc026d75 fix(window-restore): re-read workspace/window state after the wofi pick
The picker blocks on user input for however long they take. In that
window niri can destroy a dynamic workspace (its last window closed)
and renumber every later one down, so the pre-pick current["idx"] can
point at the wrong workspace by the time move-window-to-workspace
runs — restoring to the wrong place. The stashed window itself can
also have closed in the meantime, in which case the move/focus calls
would silently no-op. main() now re-reads workspaces/windows after
pick() returns, recomputes the current workspace, and confirms the
chosen window still exists before acting; each bails with a notify
and the documented exit code otherwise. Adds window_exists() as a
pure, tested helper alongside the existing pure functions.

Also: clarify the focus=false comment in niri/config.kdl (it read as
if focus=false caused the problem it actually prevents), and note in
the design doc's Out of scope section that the stash's index-1
pinning is per-output, so a second monitor would need its own
analysis of the Mod+1..9 offset.
2026-07-16 10:10:40 -07:00

205 lines
5.8 KiB
KDL

layout {
gaps 0
border {
off
}
focus-ring {
off
}
}
input {
keyboard {
xkb {
layout "us"
}
}
touchpad {
tap
natural-scroll
accel-speed 0.2
accel-profile "adaptive"
scroll-method "two-finger"
disabled-on-external-mouse
}
mouse {
accel-profile "flat"
}
}
environment {
XDG_CURRENT_DESKTOP "niri:GNOME"
}
spawn-at-startup "sh" "-c" "systemctl --user import-environment XDG_CURRENT_DESKTOP && dbus-update-activation-environment XDG_CURRENT_DESKTOP"
spawn-at-startup "waybar"
spawn-at-startup "mako"
spawn-at-startup "awww-daemon"
spawn-at-startup "nm-applet" "--indicator"
spawn-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
spawn-at-startup "hypridle"
spawn-at-startup "wl-paste" "--watch" "cliphist" "store"
spawn-at-startup "wlsunset" "-l" "49.2" "-L" "-123.1"
spawn-at-startup "xwayland-satellite"
spawn-at-startup "gamemode-watch"
// Stash for minimized windows (Mod+M). niri pins declared named workspaces to
// index 1 and refuses to move them, so real workspaces start at index 2 — hence
// the +1 offset on the Mod+1..9 binds below. Do not remove this declaration:
// without it the "minimized" binds silently do nothing (niri validate still
// passes, because the name is just a string to the parser).
workspace "minimized"
binds {
Mod+Q repeat=false { close-window; }
Mod+F { fullscreen-window; }
Mod+Z { maximize-column; }
Mod+V { toggle-window-floating; }
Mod+Return { spawn "alacritty"; }
Mod+D { spawn "wofi" "--show" "drun"; }
Mod+E { spawn "thunar"; }
Mod+Shift+B { spawn "waybar-restart"; }
Mod+G { spawn "gamemode-session" "toggle"; }
Mod+H { focus-column-left; }
Mod+L { focus-column-right; }
Mod+J { focus-window-down; }
Mod+K { focus-window-up; }
Mod+Left { focus-column-left; }
Mod+Right { focus-column-right; }
Mod+Down { focus-window-down; }
Mod+Up { focus-window-up; }
Mod+Shift+H { move-column-left; }
Mod+Shift+L { move-column-right; }
Mod+Shift+J { move-window-down; }
Mod+Shift+K { move-window-up; }
Mod+Shift+Left { move-column-left; }
Mod+Shift+Right { move-column-right; }
Mod+Shift+Down { move-window-down; }
Mod+Shift+Up { move-window-up; }
// +1 offset: the "minimized" workspace is always index 1 (see the
// declaration above), so the first real workspace is index 2.
Mod+1 { focus-workspace 2; }
Mod+2 { focus-workspace 3; }
Mod+3 { focus-workspace 4; }
Mod+4 { focus-workspace 5; }
Mod+5 { focus-workspace 6; }
Mod+6 { focus-workspace 7; }
Mod+7 { focus-workspace 8; }
Mod+8 { focus-workspace 9; }
Mod+9 { focus-workspace 10; }
Mod+Shift+1 { move-window-to-workspace 2; }
Mod+Shift+2 { move-window-to-workspace 3; }
Mod+Shift+3 { move-window-to-workspace 4; }
Mod+Shift+4 { move-window-to-workspace 5; }
Mod+Shift+5 { move-window-to-workspace 6; }
Mod+Shift+6 { move-window-to-workspace 7; }
Mod+Shift+7 { move-window-to-workspace 8; }
Mod+Shift+8 { move-window-to-workspace 9; }
Mod+Shift+9 { move-window-to-workspace 10; }
Mod+Print { spawn "screenshot"; }
// focus=false is required here — otherwise focus follows the window into
// the stash, the opposite of minimizing.
Mod+M { move-window-to-workspace "minimized" focus=false; }
Mod+Shift+M { spawn "window-restore"; }
Mod+Shift+E { spawn "hyprlock"; }
Mod+Shift+P { spawn "powermenu"; }
Mod+Shift+C { spawn "clipboard-picker"; }
Mod+Shift+X { quit; }
XF86AudioRaiseVolume allow-when-locked=true { spawn "volume" "up"; }
XF86AudioLowerVolume allow-when-locked=true { spawn "volume" "down"; }
XF86AudioMute allow-when-locked=true { spawn "volume" "mute"; }
XF86MonBrightnessUp allow-when-locked=true { spawn "brightnessctl" "set" "+10%"; }
XF86MonBrightnessDown allow-when-locked=true { spawn "brightnessctl" "set" "10%-"; }
}
// ── Window Rules ─────────────────────────────────────────────────────────────
// Fullscreen games: no border, open fullscreen.
// (Re-add `variable-refresh-rate true` here if a VRR-capable external
// monitor is ever attached — the internal eDP-1 panel has no VRR.)
window-rule {
match app-id="steam_app_"
open-fullscreen true
border {
off
}
focus-ring {
off
}
}
// Wine System Tray: hide the empty host window off-screen
window-rule {
match app-id="^explorer.exe$"
match title="^Wine System Tray$"
open-floating true
default-floating-position x=-9999 y=-9999
}
// Bolt Launcher
window-rule {
match app-id="^bolt-launcher$"
open-floating true
}
// RuneLite
window-rule {
match app-id="^(net-runelite-client-RuneLite|runelite|RuneLite)$"
open-floating true
border {
off
}
focus-ring {
off
}
}
// Thunar file-operation progress dialog
window-rule {
match app-id="^thunar$" title="^File Operation Progress$"
open-floating true
}
// Steam client + child windows: float and center (friends list, settings,
// game properties, dialogs). Games (steam_app_) are handled above, unaffected.
window-rule {
match app-id="^steam$"
open-floating true
}
// Steam main library window: float large (centered by niri)
window-rule {
match app-id="^steam$" title="^Steam$"
default-column-width { proportion 0.75; }
default-window-height { proportion 0.85; }
}
// Steam notification toasts: unobtrusive, bottom-right, no border/focus-ring
window-rule {
match app-id="^steam$" title="^notificationtoasts"
open-floating true
default-floating-position x=16 y=16 relative-to="bottom-right"
border {
off
}
focus-ring {
off
}
}