Skip to content

add on-output window rule match property#3474

Open
adeci wants to merge 4 commits intoniri-wm:mainfrom
adeci:window-rule-on-output
Open

add on-output window rule match property#3474
adeci wants to merge 4 commits intoniri-wm:mainfrom
adeci:window-rule-on-output

Conversation

@adeci
Copy link

@adeci adeci commented Feb 17, 2026

Closes #2600

This adds an on-output match property to window rules, so you can apply different rules to windows depending on which monitor they're on. It matches by connector name, like "DP-1", or make/model/serial, same style as output config blocks.

Motivation

I use a Framework 13 laptop with a 32" external monitor and want different window sizing per display.

A specific example:

  • I want to open a Firefox window at 1/3 width on the big 32" monitor, but maximized on other screens.
  • I want messaging clients (discord, signal, etc) to open at 1/2 width on the 32" monitor, but maximized on other screens.

Per-output layout blocks get you part of the way there but they apply to all windows on that output, not specific ones.

Here's a snippet of what my config.kdl looks like now with these things applied:

// Firefox: 1/3 width on the MSI 32"
window-rule {
    match app-id="firefox" on-output="Microstep MSI MAG321CQR KA3H071804955"
    default-column-width { proportion 0.33333; }
}

// Firefox: open maximized on all other displays
window-rule {
    match app-id="firefox"
    exclude on-output="Microstep MSI MAG321CQR KA3H071804955"
    open-maximized true
}

// Messaging clients: 1/2 width on the MSI 32"
window-rule {
    match app-id=r#"^vesktop$"# on-output="Microstep MSI MAG321CQR KA3H071804955"
    match app-id=r#"^Element"# on-output="Microstep MSI MAG321CQR KA3H071804955"
    match app-id=r#"^signal$"# on-output="Microstep MSI MAG321CQR KA3H071804955"
    default-column-width { proportion 0.5; }
}

// Messaging clients: open maximized on all other displays
window-rule {
    match app-id=r#"^vesktop$"#
    match app-id=r#"^Element"#
    match app-id=r#"^signal$"#
    exclude on-output="Microstep MSI MAG321CQR KA3H071804955"
    open-maximized true
}

Notes

Circular dependency guard

Like discussed in #2600, on-output and open-on-output in the same rule would create a circular dependency so we'd need to know the target output to evaluate the rule, but the rule changes the target output. So this is handled with:

  • A config-level validation that rejects any window rule combining on-output matchers with open-on-output or open-on-workspace
  • A two-pass evaluation at window open time. The first pass resolves open-on-output/open-on-workspace redirects without output context, second pass re-evaluates with the resolved target output for on-output matching

I think this implementation is sensible, I would appreciate any feedback / help with this here!

Changes

  • niri-config: add on_output field to Match, reject open-on-output/open-on-workspace in rules with on-output
  • src/window: thread Option<&OutputName> through ResolvedWindowRules::compute and window_matches
  • src/handlers/xdg_shell.rs: two-pass rule evaluation in send_initial_configure, preliminary pass for output/workspace redirects, then full pass with resolved output
  • src/niri.rs: pass output context when recomputing rules for mapped/unmapped windows
  • Wiki: document on-output with examples and must-fail block for the circular case

@adeci adeci force-pushed the window-rule-on-output branch from a8f3848 to 6c409e4 Compare March 4, 2026 18:39
@Sempyos Sempyos added area:config Config parsing, default config, new settings pr kind:feature New features and functionality labels Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:config Config parsing, default config, new settings pr kind:feature New features and functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Window rules per output OR window matching by output

2 participants