Luigit
repositories / dotfiles

dotfiles

bugabingas dorkfiles

owned by admin

pi/agent/skillz/niri/SKILL.md

Raw
Rendered preview

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 — 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
output "name" {} Per-monitor: mode, scale, transform, position, VRR, background, hot-corners, layout overrides Outputs
binds {} Key bindings, scroll bindings, mouse click bindings Key Bindings
switch-events {} Lid open/close, tablet mode switch bindings Switch Events
layout {} Gaps, column widths, focus ring, border, shadow, tab-indicator, struts, background Layout
workspace "name" {} Named workspaces (always exist, can bind to outputs) Named Workspaces
window-rule {} Per-window matching and properties (opacity, floating, size, VRR, etc.) Window Rules
layer-rule {} Per-layer-shell-surface matching and properties (opacity, shadow, radius) Layer Rules
animations {} Animation curves/duration for workspace-switch, window-open/close, resize, overview Animations
gestures {} DnD edge scroll, workspace switch, hot-corners Gestures
recent-windows {} Alt-Tab switcher: debounce, highlights, previews, binds Recent Windows
debug {} Debug flags, DRM device override, tracing Debug Options
Top-level flags spawn-at-startup, prefer-no-csd, screenshot-path, cursor, overview, xwayland-satellite, clipboard, hotkey-overlay, config-notification Miscellaneous

KDL syntax basics

// 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

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

// 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

// 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

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

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

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

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

spawn-at-startup "waybar"
spawn-at-startup "alacritty"
spawn-sh-at-startup "mate-polkit &"

Layer rules (for waybar, fuzzel, etc.)

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

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)

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

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

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

niri msg action focus-workspace <index-or-name>
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 <workspace-name>
niri msg action move-window-to-workspace <workspace-name>

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 <program> <args...>
niri msg action spawn-sh <shell-command>
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 <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)

# 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

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
---
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 <index-or-name>
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 <workspace-name>
niri msg action move-window-to-workspace <workspace-name>

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 <program> <args...>
niri msg action spawn-sh <shell-command>
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 <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