--- title: niri description: Scrollable-tiling Wayland compositor config — input, layout, window rules, per-display profiles, keybindings, and launcher scripts. --- [niri](https://github.com/YaLTeR/niri) is a scrollable-tiling Wayland compositor. Here it runs with the Noctalia shell (bar, panels, OSD, notifications), vicinae as the launcher, and `niri-dynamic-workspaces` for named workspaces. :::note Linux `desktop` and `laptop` roles only. `.chezmoiignore.tmpl` skips `~/.config/niri/**`, `~/.local/bin/niri-launch`, and `~/.local/bin/cp-appid` on other roles. The `niri-launch-*` wrappers are not in the ignore list and are applied everywhere. `dot_config/niri` is a git submodule ([prdlk/niri](https://github.com/prdlk/niri)). Edit inside the submodule, commit there, then commit the updated pointer in the chezmoi repo. ::: ## Files | Source (repo) | Target | | --- | --- | | `dot_config/niri/config.kdl` | `~/.config/niri/config.kdl` | | `dot_config/niri/noctalia.kdl` | `~/.config/niri/noctalia.kdl` | | `dot_config/niri/exact_config.d/*.kdl` | `~/.config/niri/config.d/*.kdl` | | `dot_config/niri/scripts/executable_*` | `~/.config/niri/scripts/*` (executable) | | `dot_local/bin/executable_niri-launch`, `executable_niri-launch-{ai,browser,terminal,webapp}` | `~/.local/bin/niri-launch*` | | `dot_local/bin/executable_cp-appid` | `~/.local/bin/cp-appid` | `exact_config.d` is a chezmoi **exact** directory. On apply, chezmoi deletes any file in `~/.config/niri/config.d/` that the repo does not manage, so stale fragments cannot linger. None of these files are templates. - ~/.config/niri/ - config.kdl - noctalia.kdl - config.d/ - 10-input-and-cursor.kdl - 20-layout-and-overview.kdl - 30-window-rules.kdl - 40-environment.kdl - 50-startup.kdl - 60-animations.kdl - 70-binds.kdl - 80-layer-rules.kdl - 90-outputs.kdl - 91-internal-display.kdl - 92-ultrawide.kdl - 93-apple-display.kdl - 94-portable-hdmi.kdl - 95-portable-usbc.kdl - scripts/ - kill-phantom-display.fish - ndw-route-workspace.sh - niri-floating-sidebar.sh - niri-focus.sh - niri-move.sh ## Installation - `niri` and `xdg-user-dirs` from pacman, `noctalia-shell` and `vicinae-bin` from the AUR. Declared under `packages.arch.desktop` in `.chezmoidata/packages.yaml` and installed by `run_onchange_before_10-packages.sh.tmpl` for `desktop`/`laptop` roles. See [chezmoi scripts](/chezmoi/scripts). - `.chezmoiscripts/run_once_after_30-user-services.sh.tmpl` runs once on Linux. If `~/.config/systemd/user/nirinit.service` exists, it runs `systemctl --user daemon-reload` and `systemctl --user enable nirinit.service`. The script's comment says other personal units (brain-lens, omp-loop, rclone, hermes, voxtype) ship as files only and are enabled by hand. The repo does not currently contain `dot_config/systemd`, so the unit must come from elsewhere for the enable step to run. - Tools the config calls but does not install: `cliphist`/`wl-paste`, `polkit-gnome`, `kbuildsycoca6`, `voxtype`, `niri-dynamic-workspaces`, `dot-screenshot`, `niri-smart-close`, `niri-smart-move`, and the `capitaine-cursors-light` cursor theme. `niri-smart-close`, `niri-smart-move`, and `dot-screenshot` are called by path or name but are not in this repo. [vicinae](/desktop/vicinae) is installed and configured here. ## Load order `config.kdl` sets two top-level options and then includes the fragments in numeric order: ```kdl ~/.config/niri/config.kdl hotkey-overlay { skip-at-startup } screenshot-path "~/Pictures/Screenshots/Screenshot from %Y-%m-%d %H-%M-%S.png" include "config.d/10-input-and-cursor.kdl" // … 20 through 90 … include "config.d/90-outputs.kdl" ``` After the includes, `config.kdl` repeats inline `input`, `layout`, `gestures`, `cursor`, `overview`, `window-rule`, `animations`, and `environment` blocks, a leftover of the iNiR (illogical-impulse) base it started from. It then sets `debug { honor-xdg-activation-with-invalid-serial }`, which lets Noctalia v5 activate windows from notification actions. It includes `./noctalia.kdl` and ends with `prefer-no-csd`. niri merges repeated sections and later values win, so the inline blocks override the fragments where they differ. Those differences are listed in each section below. ## Input and cursor `config.d/10-input-and-cursor.kdl`: | Setting | Value | | --- | --- | | Keyboard layout | `us` | | Repeat delay / rate | 400 ms / 50 Hz | | Touchpad | `natural-scroll`, `tap`, `tap-button-map "left-right-middle"`, `accel-speed 1.0` | | Tablet and touch | `map-to-output "eDP-1"` (pen and touchscreen stay on the laptop panel) | | Mod key | `Super`; `Alt` when nested inside another compositor | | Cursor | `capitaine-cursors-light`, size 24, `hide-when-typing` | The inline `input` block in `config.kdl` repeats the same keyboard and touchpad values without `accel-speed` or the tablet/touch mappings, so those two settings from the fragment still apply. ## Layout and overview `config.d/20-layout-and-overview.kdl`: | Setting | Value | | --- | --- | | `center-focused-column` | `never` | | `background-color` | `transparent` (the Noctalia wallpaper shows through as backdrop) | | `default-column-width` | proportion 0.5 | | `preset-column-widths` | 0.33333, 0.5, 0.66667 | | Focus ring | width 1.5 | | Border | `off` (width 1.5 if enabled) | | Shadow | `off` (softness 30, spread 5, offset y=5 if enabled) | | Tab indicator | `place-within-column`, `hide-when-single-tab`, position `left` | | Overview | `zoom 0.4`, workspace shadow off | The `gestures { hot-corners { } }` block is empty in the fragment. The inline block in `config.kdl` sets `hot-corners { off }`, so hot corners are disabled. ### Colors (Noctalia) `noctalia.kdl` is included last, so its colors override the fragment's placeholder colors (`#7fc8ff` ring, `#ffc87f` border): | Element | Active | Inactive | Urgent | | --- | --- | --- | --- | | Focus ring | `#a398b3` | `#1e1c21` | `#fd4663` | | Border | `#a398b3` | `#1e1c21` | `#fd4663` | | Tab indicator | `#a398b3` | `#4a4158` | `#fd4663` | | Recent-windows highlight | `#a398b3` | — | `#fd4663` | Shadow color is `#1e1c2170` and the insert hint is `#a398b380`. The palette follows the Noctalia shell's color scheme, not cyberdream. ## Window rules `config.d/30-window-rules.kdl`: | Match | Rule | | --- | --- | | All windows | `geometry-corner-radius 10`, `clip-to-geometry true`; background blur on, xray off | | `dev.noctalia.Noctalia` (Noctalia v5 settings) | Floating, 1080 × 920 | | `Folo`, `Spotify`, `zen`, `brave-origin`, `gemini-desktop`, `claude-desktop` | Opacity 0.9, tiled, blur, no border background, open focused | | `kitty`, `zed`, `obsidian`, `Beeper`, `chromium`, `com.mitchellh.ghostty` | Opacity 0.9, tiled, blur, no border background, open focused | | `org.gnome.Nautilus`, `com.gabm.satty` | Floating, focused, width proportion 0.5 | | Title `Picture-in-Picture` (app `zen`) or `Picture in picture` | Floating, not focused, opacity 1.0, 24 px from the top-right corner, on `eDP-1` | | `zed`, `obsidian`, `sticky-notest-canvas`, title `Sticky Notes`, `vacuumtube`, `Fladder`, `kitty`, `kitty-herdr`, `kitty-tmux` | Open on the Apple Studio Display | Global blur settings: `passes 4`, `offset 5.0`, `noise 0.02`, `saturation 2.0`. The inline `window-rule` in `config.kdl` sets `geometry-corner-radius 8` after the fragment, so the effective radius is 8. ## Environment `config.d/40-environment.kdl` (repeated verbatim inline in `config.kdl`): | Variable | Value | Purpose | | --- | --- | --- | | `XDG_CURRENT_DESKTOP` | `niri` | | | `XDG_MENU_PREFIX` | `plasma-` | Dolphin/KDE file associations | | `QT_QPA_PLATFORM` | `wayland` | | | `ELECTRON_OZONE_PLATFORM_HINT` | `auto` | Native Wayland for Electron | | `QT_LOGGING_RULES` | `quickshell.dbus.properties=false` | Silence quickshell D-Bus logs | | `QT_QPA_PLATFORMTHEME` | `kde` | Qt colors from `kdeglobals` (needs `plasma-integration`) | | `QT_STYLE_OVERRIDE` | `Darkly` | Qt widget style | | `INIR_VENV`, `ILLOGICAL_IMPULSE_VIRTUAL_ENV` | `$HOME/.local/state/quickshell/.venv` | Quickshell Python venv | ## Startup `config.d/50-startup.kdl` spawns at login: | Command | Purpose | | --- | --- | | `systemctl --user import-environment XDG_MENU_PREFIX && kbuildsycoca6` | Export the menu prefix to systemd, rebuild the KDE service cache | | `/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1` | Polkit agent | | `noctalia` | Shell: bar, panels, notifications, OSD, wallpaper | | `niri-dynamic-workspaces daemon` | Named dynamic workspaces | | `vicinae server` | Launcher backend | | `wl-paste --watch cliphist store` | Clipboard history | ## Animations `config.d/60-animations.kdl` makes every animation a critically damped spring (`damping-ratio=1.0`, `epsilon=0.0001`) with high stiffness: workspace-switch 4000, window-open 5000, window-close 6000, horizontal-view-movement 4500, window-movement 5000, window-resize 5500, config-notification-open-close 5000, screenshot-ui-open 5000. The inline `animations` block in `config.kdl` comes later and overrides these with softer springs tuned to match quickshell's Material motion: | Animation | Damping ratio | Stiffness | | --- | --- | --- | | workspace-switch | 0.78 | 1350 | | window-open | 0.82 | 1125 | | window-close | 0.88 | 2025 | | horizontal-view-movement | 0.80 | 1238 | | window-movement | 0.85 | 1463 | | window-resize | 0.88 | 1575 | | config-notification-open-close | 0.90 | 1800 | | screenshot-ui-open | 0.85 | 1688 | ## Keybindings All bindings from `config.d/70-binds.kdl`. `Mod` is `Super` (`Alt` when niri runs nested). `Super+…` and `Mod+…` are the same modifier. The table groups bindings by the file's comment headers. ### System and launcher | Keys | Action | | --- | --- | | `Mod+Tab` | Toggle overview (no repeat) | | `Mod+Shift+E` | Quit niri | | `Mod+Escape` | Toggle keyboard-shortcuts inhibit (cannot itself be inhibited) | | `Mod+Space` | `vicinae toggle` — launcher | | `Mod+Shift+Space` | vicinae emoji search | | `Mod+Shift+Return` | vicinae window switcher | | `Super+Alt+Ctrl+Return` | vicinae window switcher | | `Mod+BackSpace` | Focus previous window | | `Mod+P` | vicinae clipboard history | | `Mod+Ctrl+R` | Toggle the Noctalia `prdlk/ostt:recorder` plugin | | `Mod+Period` | Noctalia control center panel | | `Mod+Comma` | Noctalia settings | | `Mod+Shift+Q` | Noctalia session menu (hotkey-overlay title "Session Menu: noctalia session") | | `Mod+F24` | `voxtype record toggle --profile hermes` — dictation | ### Screenshots | Keys | Action | | --- | --- | | `Mod+S` | `dot-screenshot region` | | `Mod+Shift+S` | `dot-screenshot window` | | `Mod+Ctrl+S` | `dot-screenshot monitor-focused` | | `Mod+Ctrl+Shift+S` | `dot-screenshot monitor-all` | | `Print` | niri screenshot UI | | `Ctrl+Print` | Screenshot the screen | | `Alt+Print` | Screenshot the window | ### Applications | Keys | Action | | --- | --- | | `Mod+Shift+G` | Goliath: `kitty --class=kitty-goliath --title=goliath --config ~/.config/kitty/goliath.conf` (mosh + tmux; see [kitty](/terminal/kitty)) | | `Super+Ctrl+A` | Claude desktop (via vicinae) | | `Super+Ctrl+G` | Gemini desktop (via vicinae) | | `Super+Alt+Ctrl+H` | `niri-launch-ai` — cycle Claude/Hermes windows | | `Super+Alt+Ctrl+J` | `niri-launch-terminal` — cycle terminals | | `Super+Alt+Ctrl+K` | `niri-launch-browser` — cycle Brave Origin | | `Super+Alt+Ctrl+O` | `niri-launch obsidian` | | `Super+Alt+Ctrl+Y` | `niri-launch-webapp` — cycle web apps | | `Super+Alt+Ctrl+W` | tldraw offline (via vicinae) | | `Super+Alt+Ctrl+E` | Fastmail (via vicinae) | | `Super+Alt+Ctrl+B` | Beeper (via vicinae) | | `Super+Alt+Ctrl+M` | Beeper (via vicinae) | | `Super+Alt+Ctrl+G` | Ghostty (via vicinae) | | `Super+Alt+Ctrl+L` | Linear web app (via vicinae) | | `Super+Alt+Ctrl+T` | Todoist (via vicinae) | | `Super+Alt+Ctrl+N` | Open the Noctalia `noctalia/notes` panel | | `Super+Alt+Ctrl+F` | Nautilus (via vicinae) | | `Super+Alt+Ctrl+Space` | Sticky Notes Canvas (via vicinae) | ### Dynamic workspaces | Keys | Action | | --- | --- | | `Super+Shift+Alt+Ctrl+W` | `niri-dynamic-workspaces switch w` | | `Super+Shift+Alt+Ctrl+H` | `niri-dynamic-workspaces switch h` | | `Super+Alt+Ctrl+X` | `niri-dynamic-workspaces switch x` | ### Focus and movement | Keys | Action | | --- | --- | | `Mod+H` | Focus column left (wraps to last) | | `Mod+L` | Focus column right (wraps to first) | | `Mod+J` | Focus window below, or workspace below | | `Mod+K` | Focus window above, or workspace above | | `Mod+Shift+H` | `niri-smart-move left` | | `Mod+Shift+L` | `niri-smart-move right` | | `Mod+Shift+J` | Move column to workspace below | | `Mod+Shift+K` | Move column to workspace above | | `Super+Ctrl+H` | Focus first column | | `Super+Ctrl+L` | Focus last column | | `Super+Ctrl+Shift+H` | Move column to first | | `Super+Ctrl+Shift+L` | Move column to last | | `Mod+Q` | `niri-smart-close` (no repeat) | ### Monitors | Keys | Action | | --- | --- | | `Mod+Left` | Focus monitor left | | `Mod+Up` | Focus monitor up | | `Mod+Right` | Focus monitor right | | `Mod+Shift+Down` | Move column to monitor right | | `Mod+Shift+Up` | Move column to monitor left | | `Super+O` | Focus next monitor | | `Super+Ctrl+J` | Focus next monitor | | `Super+Ctrl+K` | Focus previous monitor | | `Super+Shift+O` | Move column to next monitor | | `Mod+Ctrl+Shift+J` | Move column to next monitor | | `Mod+Ctrl+Shift+K` | Move column to previous monitor | ### Layout and sizing | Keys | Action | | --- | --- | | `Mod+R` | Cycle preset column widths | | `Mod+Shift+R` | Cycle preset window heights | | `Mod+F` | Expand column to available width | | `Mod+Shift+F` | Maximize column | | `Mod+Ctrl+F` | Fullscreen window | | `Super+C` | Center column | | `Mod+Ctrl+C` | Center visible columns | | `Mod+Ctrl+O` | Toggle window floating | | `Mod+W` | Toggle tabbed column display | | `Alt+Shift+H` | Column width −10% | | `Alt+Shift+L` | Column width +10% | | `Alt+Shift+J` | Window height −10% | | `Alt+Shift+K` | Window height +10% | ### Hardware keys All of these work while the screen is locked (`allow-when-locked=true`) and go through Noctalia IPC, so the Noctalia OSD shows feedback. The commented `v4:` lines in the file are the old `qs -c noctalia-shell ipc call …` forms. | Keys | Action | | --- | --- | | `XF86AudioRaiseVolume` | `noctalia msg volume-up` | | `XF86AudioLowerVolume` | `noctalia msg volume-down` | | `XF86AudioMute` | `noctalia msg volume-mute` | | `XF86AudioMicMute` | `noctalia msg mic-mute` | | `XF86AudioNext` | `noctalia msg media next` | | `XF86AudioPrev` | `noctalia msg media previous` | | `XF86AudioPlay` / `XF86AudioPause` | `noctalia msg media toggle` | | `XF86MonBrightnessUp` | `noctalia msg brightness-up` | | `XF86MonBrightnessDown` | `noctalia msg brightness-down` | ## Layer rules `config.d/80-layer-rules.kdl`: - Noctalia surfaces matching `^noctalia-(bar-…|notification|dock|panel|attached-panel|osd)$` get `background-effect { xray false }`, so their blur samples what is actually behind them. Noctalia publishes blur regions itself when `ext-background-effects` is available. - `^noctalia-wallpaper` is placed within the backdrop (`place-within-backdrop true`). The wallpaper stays still while workspaces move and shows in the overview. - `layout { background-color "transparent" }` and overview `workspace-shadow { off }` are restated here so the backdrop is always visible. ## Outputs and display profiles `config.d/90-outputs.kdl` turns `DP-5` off and includes the per-display profiles. An older Apple Display block and a `DP-4 off` block remain as comments. ```kdl ~/.config/niri/config.d/90-outputs.kdl include "91-internal-display.kdl" // include "92-ultrawide.kdl" include "93-apple-display.kdl" include "94-portable-hdmi.kdl" // include "95-portable-usbc.kdl" ``` The Apple Studio Display also exposes a phantom connector (DP-4/5/6, varies per boot) whose only mode is 640×480. A commented `spawn-at-startup` for `scripts/kill-phantom-display.fish` handles it by mode signature, since connector name and EDID both shift. `Tianma Microelectronics Ltd. TL134ADXP03 Unknown`, the laptop panel. **Active.** | Setting | Value | | --- | --- | | Position / scale | `0,0` / 1.5 | | VRR | `on-demand=true` | | Gaps | 8 | | Default column width | 1.0 | | Preset widths | 0.5, 1.0 | | Centering | `always-center-single-column`, `center-focused-column "never"` | Named workspaces `1`, `2`, `3` open on this panel. Three window rules open these apps maximized and focused on workspace `1`, without border backgrounds (all three target `"1"` as written): - `chrome-youtube.com__-Default`, `chromium` - `org.gnome.Calendar`, `discord`, `slack`, `com-fastmail-fastmail`, `gemini-desktop`, `Beeper`, `com.anthropic.Claude`, `Hermes`, `todoist` - `WebApp-CloudflarePersonal7594`, `WebApp-CloudflareWalletbase8881` `Samsung Electric Company S34CG50 HNTY501103`. **Included in the repo but commented out.** | Setting | Value | | --- | --- | | Mode | `3440x1440@100.00` | | Position / scale | `0,0` / 1 | | VRR | `on-demand=true` | | Gaps | 16 | | Default column width | 0.3333 | | Preset widths | 0.3333, 0.5, 0.6667 | | Centering | `always-center-single-column`, `center-focused-column "never"` | `Apple Computer Inc StudioDisplay 0x984262A7`. **Active.** | Setting | Value | | --- | --- | | Scale | 1.75 | | Gaps | 8 | | Default column width | 0.5 | | Preset widths | 0.25, 0.3333, 0.5, 0.6667 | | Centering | `always-center-single-column`, `center-focused-column "never"` | `PNP(AOP) 16PM1QB 14490001D3X00`. **Active.** | Setting | Value | | --- | --- | | Transform | `90` (portrait) | | Gaps | 2 | | Default column width | 1.0 | | Preset widths | 0.5, 1.0 | | Centering | `always-center-single-column`, `center-focused-column "never"` | `ASUSTek COMPUTER INC ASUS MB16AC M8LMTF195749`. **Included in the repo but commented out.** | Setting | Value | | --- | --- | | Position | `0,0` | | Gaps | 2 | | Default column width | 1.0 | | Preset widths | 0.5, 1.0 | | Centering | `always-center-single-column`, `center-focused-column "never"` | To switch a profile, uncomment or comment its `include` line in `90-outputs.kdl`. niri reloads config on save. ## Scripts Deployed to `~/.config/niri/scripts/`. `70-binds.kdl` binds none of them, so they run by hand or from other tools. | Script | Usage | Behavior | | --- | --- | --- | | `kill-phantom-display.fish` | no args | Polls `niri msg -j outputs` up to 10 times, 1 s apart. Any output whose only mode is 640×480 gets `niri msg output off`. Does not handle mid-session hotplug. | | `ndw-route-workspace.sh` | `$NDW_WORKSPACE_KEY` or `$1` | niri-dynamic-workspaces `on_create` hook. Keys `y g m x z p l a` move the workspace to `HDMI-A-1`. Keys `b f s w c d o` move it to the Apple Studio Display (resolved by make/model through `niri msg --json outputs`), or to `HDMI-A-1` if that display is absent. Other keys exit silently. | | `niri-floating-sidebar.sh` | `toggle` \| `hide` \| `flip` \| `reorder` \| `help` | Per-workspace floating sidebar on the right edge. Windows are 550 px wide and stack into 4 slots (more as needed) with 15 px gaps and 30/50/20 px top/bottom/side margins. `toggle` floats the focused window into the sidebar or restores it to tiling at its original size. `hide` slides the sidebar off-screen, leaving a 70 px sliver. `flip` switches bottom-up and top-down stacking. State is kept in `/tmp/niri.toggle..*`. | | `niri-focus.sh` | `up` \| `down` | Cascading focus: next window in the column, then the next *occupied* workspace (skipping the trailing empty one), then the adjacent monitor. Wraps to the far-end monitor at the edge. | | `niri-move.sh` | `up` \| `down` | Cascading move: reorder within the column, then move to the next workspace (including the empty trailing one), then the adjacent monitor. Wraps at the edge. | ## Launchers ### niri-launch `~/.local/bin/niri-launch ` focuses an app's window, cycles through its windows on repeated presses (sorted by window id), or launches it when none is open. | Argument | Windows matched (app-id) | Launched when none | | --- | --- | --- | | `webapp` | `^(webapp-\|chrome-)` (Firefox and Chromium/Brave site-specific apps) | Re-runs as `webapp-youtube` | | `webapp-NAME` | `WebApp-Name` (case-insensitive) | `gtk-launch` on `~/.local/share/applications/WebApp-Name*.desktop` | | `kitty` | any app-id starting with `kitty` | `kitty` | | `kitty-CLASS` | exactly that class | `kitty --class kitty-CLASS` (optional command from the empty `KITTY_CMDS` map) | | `ghostty` | `ghostty` | nothing, prints a message | | `browser` | `^(zen\|brave\|chromium\|firefox)` | `zen` | | `terminal` | `kitty\|alacritty\|ghostty` | `xdg-terminal-exec` (the OS default terminal from `~/.config/xdg-terminals.list`) | | `ai` | `claude\|hermes` | `hermes desktop` | | anything else | exact app-id | the argument as a command | ### Wrappers Each wrapper resolves its own symlink and execs `niri-launch` from the same directory: | Script | Runs | Bound to | | --- | --- | --- | | `niri-launch-ai` | `niri-launch ai` | `Super+Alt+Ctrl+H` | | `niri-launch-terminal` | `niri-launch terminal` | `Super+Alt+Ctrl+J` | | `niri-launch-browser` | `niri-launch brave-origin` (exact app-id, not the `browser` mode) | `Super+Alt+Ctrl+K` | | `niri-launch-webapp` | `niri-launch webapp` | `Super+Alt+Ctrl+Y` | ### cp-appid `cp-appid` lists every `.desktop` file (via `fd`) in `/usr/share/applications`, `/usr/local/share/applications`, `~/.local/share/applications`, the system and user Flatpak export dirs, and `/var/lib/snapd/desktop/applications`. It shows each entry's `Name=` and ID in `fzf`, then copies the chosen ID to the clipboard with `wl-copy`. Use it to find app-ids for window rules and `niri-launch`. ## Related - [Hyprland](/desktop/hyprland): the Omarchy setup with the same key layout. - [kitty](/terminal/kitty): `kitty-goliath`, `kitty-herdr`, and `kitty-tmux` classes referenced by the rules.