docs: add book
This commit is contained in:
parent
c9d6fb9e40
commit
d14105eb1a
43 changed files with 7254 additions and 1204 deletions
529
book/src/window-rules.md
Normal file
529
book/src/window-rules.md
Normal file
|
|
@ -0,0 +1,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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue