- Rust 97.7%
- Shell 1.6%
- Dockerfile 0.7%
|
All checks were successful
CI / arch-image (push) Successful in 5s
CI / ci-image (push) Successful in 6s
CI / build (push) Successful in 21s
CI / test (push) Successful in 17s
CI / dist-artifacts (push) Successful in 13s
CI / arch-package (push) Successful in 29s
CI / release (push) Successful in 9s
|
||
|---|---|---|
| .forgejo/workflows | ||
| ci | ||
| log | ||
| misc/niri-rules-git | ||
| src | ||
| tests | ||
| .gitignore | ||
| AGENTS.md | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.kdl | ||
| INSTRUCTIONS.md | ||
| README.md | ||
niri-rules
niri-rules is a small rule engine for the niri Wayland compositor.
It listens to the niri IPC event stream and executes user-defined window rules on specific events.
This project is focused on “window-rules, but with actions”: move/size windows and run arbitrary commands when a rule matches.
What It Can Do
- Subscribe to niri events and evaluate rules.
- Match windows by
app-id,title, and a set of boolean flags. - Execute external commands (
command). - Place a window before/after another matched window (
place-before,place-after). - Toggle
urgentandfloating. - Move a window to an output (
open-on-output). - Set window width/height (fixed pixels or proportion of output size).
- Close a window (
close-window).
Requirements
- niri compositor running and IPC available.
nirion$PATH(this tool shells out toniri msg ...).- Rust toolchain (to build).
Build And Run
cargo build --release
./target/release/niri-rules
Notes:
- Default config path is
config.kdl(override with--config). - Run
niri-rules --helpto see available CLI options.
Logging
Logging uses tracing.
- By default logs are printed to stdout.
- Override log file:
--log-file /path/to/file.logNIRI_RULES_LOG_FILE=/path/to/file.log
- Control verbosity with
RUST_LOG(viatracing-subscriberEnvFilter).
Example:
RUST_LOG=niri_rules=debug ./target/release/niri-rules
Configuration (config.kdl)
The configuration format is KDL (parsed with knus).
At the top level you declare one or more window-rule blocks:
window-rule {
match app-id="^Alacritty$" event="window-created"
place-before app-id="^zen$"
window-width { proportion 0.20; }
}
Rule Evaluation Semantics
- A
window-rulemust contain at least one action. match ...entries are OR’ed together:- If there are no
matchentries, the rule matches everything. - If there are
matchentries, at least one must match.
- If there are no
exclude ...entries are OR’ed together:- If any
excludematches, the whole rule is rejected.
- If any
Events
You can match by event string using the event="..." matcher property.
Currently supported events:
window-createdwindow-closedwindow-focusedwindow-blurred
Matchers
match and exclude nodes accept the same set of properties.
match app-id="^Alacritty$" title="fish" event="window-created" is-floating=false
exclude title="scratchpad"
Supported matcher properties:
app-id="REGEX": Rustregexpattern matched against the windowapp_idtitle="REGEX": Rustregexpattern matched against the window titleevent="window-created|window-closed|window-focused|window-blurred"is-active=true|false: true if the window is the active window in its workspaceis-focused=true|falseis-floating=true|falseis-urgent=true|falseat-startup=true|false: true during the first ~60s afterniri-rulesstartsis-active-in-column=true|false: currently parsed, but not implemented yet (will never match)is-window-cast-target=true|false: currently parsed, but not implemented yet (will never match)
Actions
Actions are the other statements inside a window-rule block. A rule may contain multiple actions; they run in order.
command
Executes an external command. The first argument is the program; the rest are arguments.
command "notify-send" "niri-rules" "matched!"
Environment variables passed to the command (when available):
Focused Window
NIRI_RULES_FOCUSED_WINDOW_IDNIRI_RULES_FOCUSED_WINDOW_APP_IDNIRI_RULES_FOCUSED_WINDOW_TITLENIRI_RULES_FOCUSED_WINDOW_WORKSPACE_IDNIRI_RULES_FOCUSED_WINDOW_WORKSPACE_NAMENIRI_RULES_FOCUSED_WINDOW_WORKSPACE_OUTPUT
Previous Focused Window
NIRI_RULES_PREV_FOCUSED_WINDOW_IDNIRI_RULES_PREV_FOCUSED_WINDOW_APP_IDNIRI_RULES_PREV_FOCUSED_WINDOW_TITLENIRI_RULES_PREV_FOCUSED_WINDOW_WORKSPACE_IDNIRI_RULES_PREV_FOCUSED_WINDOW_WORKSPACE_NAMENIRI_RULES_PREV_FOCUSED_WINDOW_WORKSPACE_OUTPUT
place-before / place-after
Moves the current window so it ends up immediately before/after a target window.
The target is described using matcher properties (same shape as match).
place-before app-id="^zen$"
place-after app-id="^zen$"
Notes:
- This action relies on the window’s scrolling layout position (
pos_in_scrolling_layout). - Under the hood it runs
niri msg action move-column-left/righton the focused window.
urgent
Sets or unsets urgent state:
urgent true
urgent false
floating
Moves the window to floating or back to tiling:
floating true
floating false
open-on-output
Moves the window to a specific output:
open-on-output "DP-1"
window-width / window-height
Set window size either as fixed pixels or as a proportion of the window’s output size.
window-width { fixed 480; }
window-height { fixed 320; }
window-width { proportion 0.333; }
Notes:
- Proportional sizes require output dimensions from
niri msg -j outputs. - If the output name can be resolved but the output size isn’t known, the action will log an error and no-op.
close-window
Closes the matched window via niri msg action close-window.
close-window
More KDL Examples
Multiple match And exclude
match entries are OR'ed together, exclude entries are OR'ed together.
window-rule {
match event="window-created" app-id="^Alacritty$"
match event="window-created" app-id="^kitty$"
exclude title="scratchpad"
window-width { fixed 420; }
}
Chain Multiple Actions
window-rule {
match event="window-created" app-id="^Alacritty$"
place-before app-id="^zen$"
floating true
urgent true
window-width { proportion 0.20; }
command "notify-send" "niri-rules" "placed + sized terminal"
}
Use at-startup
window-rule {
match event="window-created" at-startup=true app-id="^Alacritty$"
place-before app-id="^zen$"
}
Move To An Output
window-rule {
match event="window-created" app-id="^Alacritty$"
open-on-output "DP-1"
}
Fixed Size With Width + Height
window-rule {
match event="window-created" app-id="^Alacritty$"
window-width { fixed 480; }
window-height { fixed 320; }
}
Command Using Focus Env Vars
window-rule {
match event="window-focused"
command "bash" "-lc" "echo focused=$NIRI_RULES_FOCUSED_WINDOW_APP_ID prev=$NIRI_RULES_PREV_FOCUSED_WINDOW_APP_ID"
}
Example: Multiple Rules
window-rule {
match event="window-focused" app-id="^Alacritty$"
command "notify-send" "focused alacritty"
}
window-rule {
match event="window-created" app-id="^Alacritty$"
place-before app-id="^zen$"
window-width { proportion 0.20; }
}