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:
[[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
ClientMatchspecifying 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/commvalue. exe/exe-regex- The client’s
/proc/pid/exepath. 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 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:
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
From the command line, use jay run-privileged:
~$ 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:
~$ jay run-tagged bar waybar
Or from the config using the tag field in an exec action:
[shortcuts]
alt-w = {
type = "exec",
exec = {
prog = "waybar",
tag = "bar",
},
}
Then write a client rule that matches the tag and grants capabilities:
[[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:
[[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, andpidare 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:
[[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:
[[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
WindowMatchspecifying 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-focustrue/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-mappedtruefor 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:
[[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:
[[windows]]
match.any = [
{ title = "chromium" },
{ title = "spotify" },
]
NOT (Negation)
[[windows]]
match.not.title = "firefox"
ALL (Explicit AND)
[[windows]]
match.all = [
{ title-regex = "chro" },
{ title-regex = "mium" },
]
EXACTLY (N of M)
Match if exactly N of the listed criteria match:
[[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:
[[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.
actionfires each time a window transitions from not-matching to matching.latchfires 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:
[[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
[[windows]]
match.app-id = "pavucontrol"
initial-tile-state = "floating"
Move a Specific App to a Workspace
[[windows]]
match.client.sandbox-app-id = "com.spotify.Client"
action = { type = "move-to-workspace", name = "3" }
Float Splash Screens Without Stealing Focus
[[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
[[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
[[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
[[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:
~$ jay tree query select-window
Example output:
- 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:
~$ 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 for the full
specification of WindowRule, WindowMatch, ClientRule, ClientMatch, and
all available actions.