jay-compositor 1.13.0

The Jay compositor
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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
# Window & Client Rules

Jay supports powerful, reactive rules for controlling how clients connect and
how windows behave. Rules are defined in the `[[clients]]` and `[[windows]]`
arrays in your configuration file.

## Client Rules

Client rules operate on Wayland clients (processes). They are defined with
`[[clients]]` entries:

```toml
[[clients]]
match.exe = "/usr/bin/firefox"
action = { type = "exec", exec = ["notify-send", "Firefox connected"] }
```

### Structure

Each client rule can have the following fields:

`name`
: A name for cross-referencing this rule from other rules.

`match`
: A `ClientMatch` specifying which clients this rule applies to.

`action`
: An action to run when a client starts matching.

`latch`
: An action to run when a client stops matching.

`capabilities`
: Wayland protocol access granted to matching clients.

`sandbox-bounding-capabilities`
: Upper bounds for protocols available to child sandboxes.

### Client Match Criteria

All client match criteria are constant over the lifetime of a client. If no
fields are set, all clients are matched. If multiple fields are set, they are
implicitly AND-combined.

`sandboxed`
: Whether the client is sandboxed (`true`/`false`).

`sandbox-engine` / `sandbox-engine-regex`
: The sandbox engine (e.g. `org.flatpak`).

`sandbox-app-id` / `sandbox-app-id-regex`
: The app ID provided by the sandbox.

`sandbox-instance-id` / `sandbox-instance-id-regex`
: The instance ID from the sandbox.

`uid`
: The user ID of the client.

`pid`
: The process ID of the client.

`is-xwayland`
: Whether the client is Xwayland (`true`/`false`).

`comm` / `comm-regex`
: The client's `/proc/pid/comm` value.

`exe` / `exe-regex`
: The client's `/proc/pid/exe` path.

`tag` / `tag-regex`
: The connection tag of the client.

### Granting Privileges

Jay splits Wayland protocols into unprivileged and privileged. By default,
applications only have access to unprivileged protocols. This means that tools
like screen lockers, status bars, screen-capture utilities, and clipboard
managers will not work unless you explicitly grant them the necessary
privileges.

See the [Protocol Support](features.md#protocol-support) table in the Features
chapter for the full list of protocols and whether they are privileged.

There are three ways to grant privileges, from simplest to most fine-grained.

#### 1. Grant all privileges via `privileged = true` (exec) or `jay run-privileged`

The simplest approach gives a program access to **all** privileged protocols.
This is appropriate for trusted tools like screen lockers where you don't want
to think about which specific protocols they need.

In the config, set `privileged = true` in the exec table:

```toml
on-idle = {
    type = "exec",
    exec = {
        prog = "swaylock",
        privileged = true,
    },
}
```

From the command line, use `jay run-privileged`:

```shell
~$ jay run-privileged waybar
```

Both methods connect the program to a privileged Wayland socket that grants
access to all privileged protocols.

#### 2. Grant capabilities via connection tags

Connection tags let you combine the CLI with client rules for precise control.
You tag a program at launch time, then write a client rule that matches
the tag and grants specific capabilities.

First, launch the program with a tag -- either from the command line:

```shell
~$ jay run-tagged bar waybar
```

Or from the config using the `tag` field in an exec action:

```toml
[shortcuts]
alt-w = {
    type = "exec",
    exec = {
        prog = "waybar",
        tag = "bar",
    },
}
```

Then write a client rule that matches the tag and grants capabilities:

```toml
[[clients]]
match.tag = "bar"
capabilities = ["layer-shell", "foreign-toplevel-list"]
```

This way, only the specific instance you launched with the tag receives the
privileges -- other programs with the same binary name do not.

Available capability values: `none`, `all`, `data-control`,
`virtual-keyboard`, `foreign-toplevel-list`, `idle-notifier`, `session-lock`,
`layer-shell`, `screencopy`, `seat-manager`, `drm-lease`, `input-method`,
`workspace-manager`, `foreign-toplevel-manager`, `head-manager`,
`gamma-control-manager`, `virtual-pointer`.

**Default capabilities:** unsandboxed clients receive `layer-shell` and
`drm-lease`. Sandboxed clients receive only `drm-lease`. If any client rule
matches, its capabilities **replace** the defaults entirely. If multiple rules
match, their capabilities are unioned together, but the defaults are not
included unless a matching rule also grants them.

#### 3. Grant capabilities via client match rules

Client rules can also match programs by properties like their executable name
instead of a tag. This is convenient when you always want a given program to
have certain capabilities, regardless of how it was launched:

```toml
[[clients]]
match.comm = "waybar"
capabilities = ["layer-shell", "foreign-toplevel-list"]

# Vim 9.2 uses the data-control protocol for seamless wayland integration.
[[clients]]
match.comm = "vim"
match.sandboxed = false
capabilities = "data-control"

# Older versions use wl-copy and wl-paste.
[[clients]]
match.any = [
    { comm = "wl-copy" },
    { comm = "wl-paste" },
]
match.sandboxed = false
capabilities = "data-control"
```

> [!NOTE]
> Client match criteria like `comm`, `exe`, and `pid` are checked when a
> client connects. Any process with a matching name receives the specified
> capabilities. If you need to restrict privileges to programs you launch
> yourself, use connection tags (method 2) instead.

#### Bounding capabilities (sandboxes)

Capabilities can never exceed the client's **bounding capabilities**. Use
`sandbox-bounding-capabilities` on a client rule to set the upper bound for
protocols available to sandboxes created by that client:

```toml
[[clients]]
match.comm = "flatpak-portal"
sandbox-bounding-capabilities = ["drm-lease", "layer-shell"]
```

## Window Rules

Window rules operate on individual windows. They are defined with `[[windows]]`
entries:

```toml
[[windows]]
match.app-id = "org.gnome.Nautilus"
initial-tile-state = "floating"
```

### Structure

Each window rule can have the following fields:

`name`
: A name for cross-referencing this rule from other rules.

`match`
: A `WindowMatch` specifying which windows this rule applies to.

`action`
: An action to run when a window starts matching.

`latch`
: An action to run when a window stops matching.

`initial-tile-state`
: `"floating"` or `"tiled"` -- force the initial tile state.

`auto-focus`
: `true`/`false` -- whether the window gets focus on map. Without a matching
  rule, newly mapped windows always receive focus (except for Xwayland
  override-redirect windows such as menus and tooltips, which bypass the
  normal mapping path).

The `initial-tile-state` and `auto-focus` fields are **ad-hoc properties**.
They are evaluated synchronously during the mapping process (before the window
is first displayed), unlike `action` which runs asynchronously after mapping.
If multiple rules match and any sets `auto-focus` to `false`, the window will
not be focused.

### Window Match Criteria

If no fields are set, all windows are matched. If multiple fields are set, they
are implicitly AND-combined. Without a `types` criterion, rules only match
client-created windows (XDG toplevels and X windows).

`types`
: Window type mask: `none`, `any`, `container`, `xdg-toplevel`, `x-window`, `client-window`.

`client`
: A nested `ClientMatch` -- matches the window's owning client.

`title` / `title-regex`
: The window title.

`app-id` / `app-id-regex`
: The XDG app ID.

`floating`
: Whether the window is floating.

`visible`
: Whether the window is visible.

`urgent`
: Whether the window has the urgency flag.

`focused`
: Whether the window has keyboard focus.

`fullscreen`
: Whether the window is fullscreen.

`just-mapped`
: `true` for one compositor iteration after the window maps.

`tag` / `tag-regex`
: The XDG toplevel tag.

`x-class` / `x-class-regex`
: The X11 class (X windows only).

`x-instance` / `x-instance-regex`
: The X11 instance (X windows only).

`x-role` / `x-role-regex`
: The X11 role (X windows only).

`workspace` / `workspace-regex`
: The workspace the window is on.

`content-types`
: Content type mask: `none`, `any`, `photo`, `video`, `game`.

## Combining Criteria

All match objects (both `ClientMatch` and `WindowMatch`) support the same
logical combinators.

### AND (Multiple Fields)

Multiple fields in one match table are implicitly AND-combined:

```toml
[[windows]]
match.title = "VIM"
match.app-id = "Alacritty"
```

This matches only windows whose title is `VIM` **and** whose app ID is
`Alacritty`.

### OR (Array of Matchers)

An array of match objects matches if **any** element matches:

```toml
[[windows]]
match.any = [
    { title = "chromium" },
    { title = "spotify" },
]
```

### NOT (Negation)

```toml
[[windows]]
match.not.title = "firefox"
```

### ALL (Explicit AND)

```toml
[[windows]]
match.all = [
    { title-regex = "chro" },
    { title-regex = "mium" },
]
```

### EXACTLY (N of M)

Match if exactly N of the listed criteria match:

```toml
[[windows]]
match.exactly.num = 1
match.exactly.list = [
    { title = "VIM" },
    { client.sandboxed = true },
]
```

### Cross-referencing Rules by Name

Rules can reference other rules by name:

```toml
[[windows]]
name = "spotify-windows"
match.client.sandbox-app-id = "com.spotify.Client"

[[windows]]
match.name = "spotify-windows"
action = "enter-fullscreen"
```

## Reactive Behavior

Rules are **re-evaluated dynamically** whenever any referenced criterion
changes. For example, if a rule matches on `title`, it is re-checked every time
the window title changes.

- **`action`** fires each time a window transitions from not-matching to
  matching.
- **`latch`** fires each time a window transitions from matching to
  not-matching.

### just-mapped

If you only want a rule to fire once when a window first appears, add
`just-mapped = true` to the match:

```toml
[[windows]]
match.title = "VIM"
match.just-mapped = true
action = "enter-fullscreen"
```

This is similar to the `initial-title` criterion found in some other
compositors.

### Loop Protection

Rules can trigger each other. For example, one rule could fullscreen a window
while another exits fullscreen on the same condition, creating a loop. Jay
prevents such loops from locking up the compositor by capping action callbacks
at 1000 iterations before yielding to other work. However, such loops will
still cause 100% CPU usage and will likely cause affected clients to be killed,
since they won't be able to receive Wayland messages fast enough.

## Practical Examples

### Force an App to Start Floating

```toml
[[windows]]
match.app-id = "pavucontrol"
initial-tile-state = "floating"
```

### Move a Specific App to a Workspace

```toml
[[windows]]
match.client.sandbox-app-id = "com.spotify.Client"
action = { type = "move-to-workspace", name = "3" }
```

### Float Splash Screens Without Stealing Focus

```toml
[[windows]]
match.any = [
    { title = "GIMP Startup", app-id = "gimp" },
    {
        title = "splash",
        x-class-regex = "^jetbrains-(clion|rustrover)$",
    },
]
initial-tile-state = "floating"
auto-focus = false
```

### Run a Command When a Window Appears

```toml
[[windows]]
match.app-id = "firefox"
match.just-mapped = true
action = {
    type = "exec",
    exec = ["notify-send", "Firefox window opened"],
}
```

### Grant Protocol Access to a Trusted App

```toml
[[clients]]
match.comm = "swaylock"
capabilities = ["session-lock", "layer-shell"]

[[clients]]
match.comm = "waybar"
capabilities = ["layer-shell", "foreign-toplevel-list"]
```

### Suppress Focus Stealing for Chromium Screen-Share Windows

```toml
[[windows]]
match.title-regex = 'is sharing (your screen|a window)\.$'
match.client.comm = "chromium"
initial-tile-state = "floating"
auto-focus = false
```

## Introspection

Jay provides several ways to discover the property values you need for writing
rules.

### jay tree

Interactively select a window and print its properties:

```shell
~$ jay tree query select-window
```

Example output:

```text
- xdg-toplevel:
    id: 258ae697663a1b8abc7e4da9570ad36f
    pos: 1920x36 + 1920x1044
    client:
      id: 15
      uid: 1000
      pid: 2159136
      comm: chromium
      exe: /usr/lib/chromium/chromium
    title: YouTube - Chromium
    app-id: chromium
    workspace: 2
    visible
```

### jay clients

Inspect the client owning a window:

```shell
~$ jay clients show select-window
```

### Control Center Window Search

The control center (opened with `alt-c` by default, or `jay control-center`)
includes a **Window Search** pane where you can search and filter windows using
composable criteria -- helpful for experimenting with match expressions before
putting them in your config.

See [spec.generated.md](https://github.com/mahkoh/jay/blob/master/toml-spec/spec/spec.generated.md) for the full
specification of `WindowRule`, `WindowMatch`, `ClientRule`, `ClientMatch`, and
all available actions.