# 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.

### 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.

## 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"],
}
```

### 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.
