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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Window rules per output OR window matching by output

1 participant