1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
|
### Overview
Window rules let you adjust behavior for individual windows.
They have `match` and `exclude` directives that control which windows the rule should apply to, and a number of properties that you can set.
Window rules are processed in order of appearance in the config file.
This means that you can put more generic rules first, then override them for specific windows later.
For example:
```
// Set open-maximized to true for all windows.
window-rule {
open-maximized true
}
// Then, for Alacritty, set open-maximized back to false.
window-rule {
match app-id="Alacritty"
open-maximized false
}
```
> [!TIP]
> In general, you cannot "unset" a property in a later rule, only set it to a different value.
> Use the `exclude` directives to avoid applying a rule for specific windows.
Here are all matchers and properties that a window rule could have:
```
window-rule {
match title="Firefox"
match app-id="Alacritty"
match is-active=true
match is-focused=false
// Properties that apply once upon window opening.
default-column-width { proportion 0.75; }
open-on-output "eDP-1"
open-maximized true
open-fullscreen true
// Properties that apply continuously.
draw-border-with-background false
opacity 0.5
block-out-from "screencast"
// block-out-from "screen-capture"
min-width 100
max-width 200
min-height 300
max-height 300
}
```
### Window Matching
Each window rule can have several `match` and `exclude` directives.
In order for the rule to apply, a window needs to match *any* of the `match` directives, and *none* of the `exclude` directives.
```
window-rule {
// Match all Telegram windows...
match app-id=r#"^org\.telegram\.desktop$"#
// ...except the media viewer window.
exclude title="^Media viewer$"
// Properties to apply.
open-on-output "HDMI-A-1"
}
```
Match and exclude directives have the same syntax.
There can be multiple *matchers* in one directive, then the window should match all of them for the directive to apply.
```
window-rule {
// Match Firefox windows with Gmail in title.
match app-id="org.mozilla.firefox" title="Gmail"
}
window-rule {
// Match Firefox, but only when it is active...
match app-id=r#"^org\.mozilla\.firefox$"# is-active=true
// ...or match Telegram...
match app-id=r#"^org\.telegram\.desktop$"#
// ...but don't match the Telegram media viewer.
// If you open a tab in Firefox titled "Media viewer",
// it will not be excluded because it doesn't match the app-id
// of this exclude directive.
exclude app-id=r#"^org\.telegram\.desktop$"# title="Media viewer"
}
```
Let's look at the matchers in more detail.
#### `title` and `app-id`
These are regular expressions that should match anywhere in the window title and app ID respectively.
```
// Match windows with title containing "Mozilla Firefox",
// or windows with app ID containing "Alacritty".
window-rule {
match title="Mozilla Firefox"
match app-id="Alacritty"
}
```
Raw KDL strings can be helpful for writing out regular expressions:
```
window-rule {
exclude app-id=r#"^org\.keepassxc\.KeePassXC$"#
}
```
You can find the title and the app ID of the currently focused window by running `niri msg focused-window`.
> [!TIP]
> Another way to find the window title and app ID is to configure the `wlr/taskbar` module in [Waybar](https://github.com/Alexays/Waybar) to include them in the tooltip:
>
> ```json
> "wlr/taskbar": {
> "tooltip-format": "{title} | {app_id}",
> }
> ```
#### `is-active`
Can be `true` or `false`.
Matches active windows (same windows that have the active border / focus ring color).
Every workspace on the focused monitor will have one active window.
This means that you will usually have multiple active windows (one per workspace), and when you switch between workspaces, you can see two active windows at once.
```
window-rule {
match is-active=true
}
```
#### `is-focused`
Can be `true` or `false`.
Matches the window that has the keyboard focus.
Contrary to `is-active`, there can only be a single focused window.
Also, when opening a layer-shell application launcher or pop-up menu, the keyboard focus goes to layer-shell.
While layer-shell has the keyboard focus, windows will not match this rule.
```
window-rule {
match is-focused=true
}
```
### Window Opening Properties
These properties apply once, when a window first opens.
To be precise, they apply at the point when niri sends the initial configure request to the window.
#### `default-column-width`
Set the default width for the new window.
```
// Give Blender and GIMP some guaranteed width on opening.
window-rule {
match app-id="^blender$"
// GIMP app ID contains the version like "gimp-2.99",
// so we only match the beginning (with ^) and not the end.
match app-id="^gimp"
default-column-width { fixed 1200; }
}
```
#### `open-on-output`
Make the window open on a specific output.
If such an output does not exist, the window will open on the currently focused output as usual.
If the window opens on an output that is not currently focused, the window will not be automatically focused.
```
// Open Firefox and Telegram (but not its Media Viewer)
// on a specific monitor.
window-rule {
match app-id=r#"^org\.mozilla\.firefox$"#
match app-id=r#"^org\.telegram\.desktop$"#
exclude app-id=r#"^org\.telegram\.desktop$"# title="^Media viewer$"
open-on-output "HDMI-A-1"
}
```
#### `open-maximized`
Make the window open as a maximized column.
```
// Maximize Firefox by default.
window-rule {
match app-id=r#"^org\.mozilla\.firefox$"#
open-maximized true
}
```
#### `open-fullscreen`
Make the window open fullscreen.
```
window-rule {
open-fullscreen true
}
```
You can also set this to `false` to *prevent* a window from opening fullscreen.
```
// Make the Telegram media viewer open in windowed mode.
window-rule {
match app-id=r#"^org\.telegram\.desktop$"# title="^Media viewer$"
open-fullscreen false
}
```
### Dynamic Properties
These properties apply continuously to open windows.
#### `block-out-from`
You can block out windows from xdg-desktop-portal screencasts.
They will be replaced with solid black rectangles.
This can be useful for password managers or messenger windows, etc.

To preview and set up this rule, check the `preview-render` option in the debug section of the config.
> [!CAUTION]
> The window is **not** blocked out from third-party screenshot tools.
> If you open some screenshot tool with preview while screencasting, blocked out windows **will be visible** on the screencast.
The built-in screenshot UI is not affected by this problem though.
If you open the screenshot UI while screencasting, you will be able to select the area to screenshot while seeing all windows normally, but on a screencast the selection UI will display with windows blocked out.
```
// Block out password managers from screencasts.
window-rule {
match app-id=r#"^org\.keepassxc\.KeePassXC$"#
match app-id=r#"^org\.gnome\.World\.Secrets$"#
block-out-from "screencast"
}
```
Alternatively, you can block out the window out of *all* screen captures, including third-party screenshot tools.
This way you avoid accidentally showing the window on a screencast when opening a third-party screenshot preview.
This setting will still let you use the interactive built-in screenshot UI, but it will block out the window from the fully automatic screenshot actions, such as `screenshot-screen` and `screenshot-window`.
The reasoning is that with an interactive selection, you can make sure that you avoid screenshotting sensitive content.
```
window-rule {
block-out-from "screen-capture"
}
```
> [!WARNING]
> Be careful when blocking out windows based on a dynamically changing window title.
>
> For example, you might try to block out specific Firefox tabs like this:
>
> ```
> window-rule {
> // Doesn't quite work! Try to block out the Gmail tab.
> match app-id=r#"^org\.mozilla\.firefox$"# title="- Gmail "
>
> block-out-from "screencast"
> }
> ```
>
> It will work, but when switching from a sensitive tab to a regular tab, the contents of the sensitive tab **will show up on a screencast** for an instant.
>
> This is because window title (and app ID) are not double-buffered in the Wayland protocol, so they are not tied to specific window contents.
> There's no robust way for Firefox to synchronize visibly showing a different tab and changing the window title.
#### `opacity`
Set the opacity of the window.
`0.0` is fully transparent, `1.0` is fully opaque.
This is applied on top of the window's own opacity, so semitransparent windows will become even more transparent.
Opacity is applied to every surface of the window individually, so subsurfaces and pop-up menus will show window content behind them.

Also, focus ring and border with background will show through semitransparent windows (see `prefer-no-csd` and the `draw-border-with-background` window rule below).
```
// Make inactive windows semitransparent.
window-rule {
match is-active=false
opacity 0.95
}
```
#### `draw-border-with-background`
Override whether the border and the focus ring draw with a background.
Set this to `true` to draw them as solid colored rectangles even for windows which agreed to omit their client-side decorations.
Set this to `false` to draw them as borders around the window even for windows which use client-side decorations.
This property can be useful for rectangular windows that do not support the xdg-decoration protocol.
| With Background | Without Background |
| --------------- | ------------------ |
|  |  |
```
window-rule {
draw-border-with-background false
}
```
#### Size Overrides
You can amend the window's minimum and maximum size in logical pixels.
Keep in mind that the window itself always has a final say in its size.
These values instruct niri to never ask the window to be smaller than the minimum you set, or to be bigger than the maximum you set.
> [!NOTE]
> `max-height` will only apply to automatically-sized windows if it is equal to `min-height`.
> Either set it equal to `min-height`, or change the window height manually after opening it with `set-window-height`.
>
> This is a limitation of niri's window height distribution algorithm.
```
window-rule {
min-width 100
max-width 200
min-height 300
max-height 300
}
```
```
// Fix OBS with server-side decorations missing a minimum width.
window-rule {
match app-id=r#"^com\.obsproject\.Studio$"#
min-width 876
}
```
|