--- name: niri os: lnx description: "Use for niri Wayland compositor configuration and IPC." license: MIT --- # niri — Wayland compositor config editor niri is a scrollable tiling Wayland compositor configured via KDL at `~/.config/niri/config.kdl`. ## Config file basics - **Location**: `~/.config/niri/config.kdl` - **Syntax**: [KDL](https://kdl.dev/) — similar to TOML but with slashes - **Live-reload**: Save and changes apply immediately - **Validation**: Run `niri validate` to check for errors before restarting - **Default config**: If no config exists, niri creates one from the embedded default at build time - **Include files**: Use `include "file.kdl"` to split config across files (`since 25.11`) ## Config sections quick reference | Section | Controls | Docs | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `input {}` | Keyboard layout, repeat, numlock; touchpad/mouse/trackpoint/trackball/tablet/touch settings; focus-follows-mouse, mod-key | [Input](https://niri-wm.github.io/niri/Configuration%3A-Input.html) | | `output "name" {}` | Per-monitor: mode, scale, transform, position, VRR, background, hot-corners, layout overrides | [Outputs](https://niri-wm.github.io/niri/Configuration%3A-Outputs.html) | | `binds {}` | Key bindings, scroll bindings, mouse click bindings | [Key Bindings](https://niri-wm.github.io/niri/Configuration%3A-Key-Bindings.html) | | `switch-events {}` | Lid open/close, tablet mode switch bindings | [Switch Events](https://niri-wm.github.io/niri/Configuration%3A-Switch-Events.html) | | `layout {}` | Gaps, column widths, focus ring, border, shadow, tab-indicator, struts, background | [Layout](https://niri-wm.github.io/niri/Configuration%3A-Layout.html) | | `workspace "name" {}` | Named workspaces (always exist, can bind to outputs) | [Named Workspaces](https://niri-wm.github.io/niri/Configuration%3A-Named-Workspaces.html) | | `window-rule {}` | Per-window matching and properties (opacity, floating, size, VRR, etc.) | [Window Rules](https://niri-wm.github.io/niri/Configuration%3A-Window-Rules.html) | | `layer-rule {}` | Per-layer-shell-surface matching and properties (opacity, shadow, radius) | [Layer Rules](https://niri-wm.github.io/niri/Configuration%3A-Layer-Rules.html) | | `animations {}` | Animation curves/duration for workspace-switch, window-open/close, resize, overview | [Animations](https://niri-wm.github.io/niri/Configuration%3A-Animations.html) | | `gestures {}` | DnD edge scroll, workspace switch, hot-corners | [Gestures](https://niri-wm.github.io/niri/Configuration%3A-Gestures.html) | | `recent-windows {}` | Alt-Tab switcher: debounce, highlights, previews, binds | [Recent Windows](https://niri-wm.github.io/niri/Configuration%3A-Recent-Windows.html) | | `debug {}` | Debug flags, DRM device override, tracing | [Debug Options](https://niri-wm.github.io/niri/Configuration%3A-Debug-Options.html) | | Top-level flags | `spawn-at-startup`, `prefer-no-csd`, `screenshot-path`, `cursor`, `overview`, `xwayland-satellite`, `clipboard`, `hotkey-overlay`, `config-notification` | [Miscellaneous](https://niri-wm.github.io/niri/Configuration%3A-Miscellaneous.html) | ## KDL syntax basics ```kdl // Single-line comment /- multi-line comment (comments out entire section) -/ // Flags (enable by writing, disable by commenting out) input { focus-follows-mouse // warp-mouse-to-focus // disabled } // Sections (most can only appear once) input { keyboard { xkb { layout "us" variant "colemak_dh_ortho" options "compose:ralt,ctrl:nocaps" } repeat-delay 600 repeat-rate 25 numlock } touchpad { tap natural-scroll accel-speed 0.2 } } // Repeating sections (by device/output name) output "eDP-1" { scale 2.0 mode "1920x1080@120.030" } output "HDMI-A-1" { mode "2560x1440@143.912" } // Colors focus-ring { active-color "#7fc8ff" inactive-color "#505050" urgent-color "#9b0000" active-gradient from="#80c8ff" to="#bbddff" angle=45 } // Proportions vs fixed preset-column-widths { proportion 0.33333 proportion 0.5 fixed 1280 } default-column-width { proportion 0.5; } // Regex strings window-rule { match app-id=r#"^org\.telegram\.desktop$"# match title="^Picture-in-Picture$" } ``` ## Important rules Do not execute `niri-session` for inspection or testing; inspect its source instead. It ignores `--help` and can terminate the active desktop session. Execute it only when the user explicitly requests session startup. 1. **`binds {}` has NO defaults** — if you include it, you must provide all bindings you need. Copy from the default config first. 2. **Most sections cannot repeat** — only `output "name" {}`, `workspace "name" {}`, `window-rule {}`, and `layer-rule {}` can repeat. 3. **Window rules are ordered** — earlier rules match first and override later ones. 4. **Most settings merge** — when included files set partial sections, only those properties change; other properties keep their values. 5. **`struts` is non-merging** — writing `struts {}` in an included file completely replaces (doesn't merge with) a prior `struts {}`. ## Common patterns ### Change keybindings ```kdl binds { // Mod is Super on TTY, Alt in nested/winit mode Mod+T { spawn "alacritty"; } Mod+D { spawn "fuzzel"; } Mod+Shift+E { quit; } Mod+Q { close-window; } Mod+Left { focus-column-left; } Mod+Right { focus-column-right; } Mod+J { focus-window-down; } Mod+K { focus-window-up; } Mod+Shift+Left { move-column-left; } Mod+Shift+Right { move-column-right; } Mod+U { focus-workspace-down; } Mod+I { focus-workspace-up; } Mod+Shift+U { move-workspace-down; } Mod+Shift+I { move-workspace-up; } Mod+Shift+F { toggle-fullscreen; } Mod+V { move-window-between-floating-and-tiling; } Mod+F { maximize-column; } Mod+R { switch-preset-column-width; } Mod+Shift+R { switch-preset-window-height; } Mod+- { resize-column-width -10; } Mod+= { resize-column-width +10; } Mod+Tab { recent-windows-cycle; } Mod+Shift+Tab { recent-windows-cycle reverse; } } ``` ### Configure outputs ```kdl // Built-in laptop screen output "eDP-1" { scale 2.0 } // External monitor with high refresh output "HDMI-A-1" { mode "2560x1440@143.912" position x=1920 y=0 variable-refresh-rate } // Rotate a monitor output "DP-1" { transform "90" } // Disable a monitor output "HDMI-A-2" { off } ``` ### Window rules ```kdl // Open Firefox maximized window-rule { match app-id="firefox$" open-maximized true } // Firefox PiP floating at bottom-left window-rule { match app-id="firefox$" title="^Picture-in-Picture$" open-floating true default-floating-position x=32 y=32 relative-to="bottom-left" default-column-width { fixed 480; } default-window-height { fixed 270; } } // Make Telegram floating except media viewer window-rule { match app-id=r#"^org\.telegram\.desktop$"# exclude title="^Media viewer$" open-floating true } // Block sensitive windows from screencasts window-rule { match app-id=r#"^org\.keepassxc\.KeePassXC$"# block-out-from "screencast" } // App-specific column width window-rule { match app-id="^blender$" default-column-width { fixed 1200; } } // Screencasted windows get red border window-rule { match is-window-cast-target=true focus-ring { active-color "#f38ba8"; inactive-color "#7d0d2d"; } border { inactive-color "#7d0d2d"; } } // Default floating position for a "dropdown terminal" window-rule { match app-id="^dropdown$" open-floating true default-floating-position x=0 y=0 relative-to="top" default-window-height { proportion 0.5; } default-column-width { proportion 0.8; } } ``` ### Layout: gaps, border, focus ring, shadow ```kdl layout { gaps 16 default-column-width { proportion 0.5; } preset-column-widths { proportion 0.2 proportion 0.33333 proportion 0.5 proportion 0.66667 proportion 0.8 } center-focused-column "on-overflow" always-center-single-column empty-workspace-above-first focus-ring { on width 4 active-color "#7fc8ff" inactive-color "#505050" urgent-color "#9b0000" } border { off // width 4 // active-color "#ffc87f" // inactive-color "#505050" } shadow { on softness 30 spread 5 offset x=0 y=5 draw-behind-window true color "#00000070" } tab-indicator { on hide-when-single-tab place-within-column gap 5 width 4 length total-proportion=0.5 position "right" } insert-hint { on color "#ffc87f80" } struts { left 64 right 64 top 64 bottom 64 } } ``` ### Input: keyboard, touchpad ```kdl input { keyboard { xkb { layout "us" variant "colemak_dh_ortho" options "compose:ralt,ctrl:nocaps" } repeat-delay 600 repeat-rate 25 track-layout "global" numlock } touchpad { tap dwt natural-scroll accel-speed 0.2 accel-profile "flat" click-method "clickfinger" scroll-method "two-finger" } mouse { natural-scroll accel-speed 0.2 } focus-follows-mouse warp-mouse-to-focus workspace-auto-back-and-forth } mod-key "Super" mod-key-nested "Alt" ``` ### Animations ```kdl animations { workspace-switch { spring damping-ratio=1.0 stiffness=1000 epsilon=0.0001 } window-open { duration-ms 150 curve "ease-out-expo" } window-close { duration-ms 150 curve "ease-out-quad" } horizontal-view-movement { spring damping-ratio=1.0 stiffness=800 epsilon=0.0001 } window-movement { spring damping-ratio=1.0 stiffness=800 epsilon=0.0001 } window-resize { spring damping-ratio=1.0 stiffness=800 epsilon=0.0001 } overview-open-close { spring damping-ratio=1.0 stiffness=800 epsilon=0.0001 } } ``` ### Named workspaces with layout overrides ```kdl workspace "browser" workspace "chat" { open-on-output "HDMI-A-1" } workspace "code" { layout { gaps 32 border { on width 4 } struts { left 64 right 64 bottom 64 top 64 } } } ``` ### Spawn at startup ```kdl spawn-at-startup "waybar" spawn-at-startup "alacritty" spawn-sh-at-startup "mate-polkit &" ``` ### Layer rules (for waybar, fuzzel, etc.) ```kdl layer-rule { match namespace="waybar" // waybar opacity } layer-rule { match namespace="^launcher$" opacity 0.95 shadow { on; } geometry-corner-radius 10 } layer-rule { match namespace="^notifications$" block-out-from "screencast" } ``` ### Gestures ```kdl gestures { dnd-edge-view-scroll { trigger-width 30 delay-ms 100 max-speed 1500 } dnd-edge-workspace-switch { trigger-height 50 delay-ms 100 max-speed 1500 } hot-corners { top-left // top-right // bottom-left // bottom-right } } ``` ### Recent windows (Alt-Tab switcher) ```kdl recent-windows { debounce-ms 750 open-delay-ms 150 highlight { active-color "#999999ff" urgent-color "#ff9999ff" padding 30 corner-radius 0 } previews { max-height 480 max-scale 0.5 } binds { Alt+Tab { next-window; } Alt+Shift+Tab { previous-window; } Alt+grave { next-window filter="app-id"; } Alt+Shift+grave { previous-window filter="app-id"; } } } ``` ### Miscellaneous settings ```kdl prefer-no-csd screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" cursor { xcursor-theme "breeze_cursors" xcursor-size 48 hide-when-typing hide-after-inactive-ms 1000 } overview { zoom 0.5 backdrop-color "#262626" workspace-shadow { softness 40 spread 10 offset x=0 y=10 color "#00000050" } } xwayland-satellite { // off path "xwayland-satellite" } clipboard { disable-primary } hotkey-overlay { skip-at-startup hide-not-bound } config-notification { disable-failed } ``` ## niri msg IPC commands Communicate with a running niri instance via `niri msg`. Socket at `$NIRI_SOCKET`. ### Query state ```bash niri msg outputs # List outputs with modes, VRR support niri msg windows # List all windows niri msg workspaces # List workspaces niri msg focused-window # Focused window info niri msg focused-output # Focused output name niri msg layers # List layer-shell surfaces niri msg pick-window # Click window → print app-id and title niri msg --json outputs # JSON output for scripting ``` ### Actions ```bash niri msg action focus-workspace niri msg action focus-workspace-up niri msg action focus-workspace-down niri msg action move-workspace-to-top niri msg action move-column-to-workspace niri msg action move-window-to-workspace niri msg action focus-column-left niri msg action focus-column-right niri msg action focus-column-first niri msg action focus-column-last niri msg action focus-window-down niri msg action focus-window-up niri msg action move-column-left niri msg action move-column-right niri msg action move-window-down niri msg action move-window-up niri msg action switch-preset-column-width niri msg action switch-preset-window-height niri msg action set-column-width <+/-pixels> niri msg action set-window-height <+/-pixels> niri msg action maximize-column niri msg action center-column niri msg action resize-column-width <+/-percentage> niri msg action resize-window-height <+/-percentage> niri msg action toggle-fullscreen niri msg action maximize niri msg action unmaximize niri msg action move-window-between-floating-and-tiling niri msg action spawn niri msg action spawn-sh niri msg action close-window niri msg action quit skip-confirmation=true niri msg action screenshot niri msg action screenshot-screen niri msg action screenshot-window niri msg action toggle-window-rule-opacity niri msg action toggle-keyboard-shortcuts-inhibit niri msg action do-screen-transition niri msg action overview-open niri msg action overview-close niri msg action recent-windows-cycle niri msg action recent-windows-cancel niri msg action recent-windows-final niri msg action power-off-monitors niri msg action power-on-monitors niri msg action set-workspace-name niri msg action unset-workspace-name # Debug actions niri msg action toggle-debug-tint niri msg action debug-toggle-opaque-regions niri msg action debug-toggle-damage ``` ### Event stream (for bars/scripts) ```bash # Watch all events as JSON (complete state, then deltas) niri msg --json event-stream # Use socat to test IPC directly socat STDIO "$NIRI_SOCKET" # Then type a request like: {"FocusWindow":null} ``` ### Tips - Run `niri msg action` with no arguments to list ALL available actions - Use `--json` for machine-readable output (stable API) - Output names change dynamically; use `niri msg outputs` to find current names - After upgrading niri, restart it before running `niri msg` to avoid version mismatch errors - The event stream gives you full state upfront, then incremental updates — no desync ## Finding window app-id and title ```bash niri msg pick-window # Click any window — prints its app-id and title # Example output: # app-id: "firefox" # title: "Mozilla Firefox" # Or list all windows: niri msg windows ``` ## Debugging - **Config errors**: `niri validate` parses the config and prints errors - **Restart niri**: If config reload fails, niri keeps running with old config and shows a notification - **IPC errors after upgrade**: Restart niri — old `niri msg` can't talk to new niri - **Journal logs**: `journalctl -ef /usr/bin/niri` for runtime errors and custom shader warnings - **Find XKB key names**: Use `wev` — press any key and it prints the XKB symbol name