Introduction
Note
This book was written entirely by Claude Opus 4.6 and will likely also be maintained in the future with the help of AI to ensure a consistent tone of voice, style, and quality.
All contents have been reviewed by humans but there might still be some mistakes left. That would put it in good company since the artisanal, hand-crafted spec.yaml, that contains the TOML config specification, contains plenty of mistakes as well.
The writing is of high quality in my estimation, certainly better than what I would have produced in any reasonable amount of time. There are few obvious slop indicators. The AI sometimes uses superlatives (“ideal”, “best”) that I would not use myself, but I’ve seen this style in other pieces of documentation, so maybe it is just a style that aims to be approachable or to guide users towards good solutions.
If you find something objectionable in this book, do not hesitate to open an issue.
Jay is a Wayland compositor for Linux with an i3-inspired tiling layout. It supports Vulkan and OpenGL rendering, multi-GPU setups, fractional scaling, variable refresh rate (VRR), tearing presentation, HDR, and screen sharing via xdg-desktop-portal. X11 applications are supported through Xwayland.
Jay is configured through a declarative TOML file, with an optional advanced
mode that uses a shared library for programmatic control. A built-in
control center (opened with alt-c) provides a full GUI
for inspecting and changing compositor settings at runtime – including output
arrangement, input devices, color management, GPU selection, and more. A
comprehensive command-line interface makes scripting and automation
straightforward.
See the Features chapter for a comprehensive overview of what Jay can do, or jump straight to Installation to get started.
License
Jay is free software licensed under the GNU General Public License v3.0.
Community
Features
This chapter provides a high-level overview of what Jay can do. Each feature links to the chapter where it is covered in detail.
Configuration
Jay can be configured via:
- a declarative TOML file, or
- a shared library that gets injected into the compositor for programmatic control.
Most users will use the TOML file. The configuration supports composable actions, input modes for vim-style modal keybindings, and powerful match rules. See Configuration Overview for details on getting started and how the config file works.
i3 Look and Feel
Jay provides an i3-inspired tiling layout with manual tiling, horizontal/vertical splits, fullscreen, and floating windows. Its appearance is based on the default i3 look and feel.
Colors, sizes, and fonts can all be customized. See Theme & Appearance for details, and Tiling and Floating Windows for how window management works.
Stability
Jay has been stable for a long time. Crashes and incorrect behavior in released versions are rare.
Jay also aims to be forward and backward compatible for existing setups, allowing you to upgrade or downgrade the compositor without having to adjust your configuration.
There is a small but growing integration test suite that is used to ensure this.
Command-Line Interface
Jay has a comprehensive CLI that can be used to inspect and configure the
compositor at runtime. All query commands support --json for machine-readable
output:
~$ jay
A wayland compositor
Usage: jay [OPTIONS] <COMMAND>
Commands:
run Run the compositor
config Create/modify the toml config
generate-completion Generate shell completion scripts for jay
log Open the log file
set-log-level Sets the log level
quit Stop the compositor
unlock Unlocks the compositor
screenshot Take a screenshot
idle Inspect/modify the idle (screensaver) settings
run-privileged Run a privileged program
run-tagged Run a program with a connection tag
seat-test Tests the events produced by a seat
portal Run the desktop portal
randr Inspect/modify graphics card and connector settings
input Inspect/modify input settings
xwayland Inspect/modify xwayland settings
color-management Inspect/modify the color-management settings
clients Inspect/manipulate the connected clients
tree Inspect the surface tree
control-center Opens the control center
version Prints the Jay version and exits
pid Prints the Jay PID and exits
help Print this message or the help of the given subcommand(s)
Options:
--log-level <LOG_LEVEL> The log level [default: info] [possible values: trace, debug, info, warn, error, off]
--json Output data as JSONL
--all-json-fields Print all fields in JSON output
-h, --help Print help (see more with '--help')
See the full Command-Line Interface reference for details.
Control Center
Jay includes a built-in GUI control center (opened with alt-c) for managing
outputs, input devices, GPUs, idle settings, color management, and more –
without editing the config file. See Control Center.
Multi-Monitor Support
Jay can be used with multiple monitors with hot-plug and hot-unplug support. When a monitor is unplugged, all of its workspaces are automatically moved to one of the remaining monitors. When the monitor is plugged in again, these workspaces are restored.
See Outputs (Monitors) for configuration options.
Multi-GPU Support
Jay can be used with multiple GPUs and monitors connected to different GPUs. One GPU is always used for rendering the desktop. You can change this GPU at runtime.
See GPUs for details.
Screen Sharing
Jay supports screen sharing via xdg-desktop-portal. Three capture modes are available:
- Window capture – share a single window.
- Output capture – share an entire monitor.
- Workspace capture – like output capture, but only one workspace is shown.
See Screen Sharing for setup instructions.
Screen Locking
Jay can automatically lock your screen and disable outputs after inactivity. See Idle & Screen Locking for configuration options.
Notifications
Jay supports the zwlr_layer_shell_v1 protocol used by notification daemons
such as mako, which is launched
automatically by the default configuration.
Fractional Scaling
Jay supports per-monitor fractional scaling. Scale factors can be set per output in the config file or at runtime via the control center and CLI.
See Outputs (Monitors) for details.
OpenGL and Vulkan
Jay can use either OpenGL or Vulkan for rendering. Vulkan offers better performance and memory usage but OpenGL is still provided for older hardware.
You can change the rendering API at runtime without restarting the compositor.
See GPUs for details.
Explicit Sync
Jay supports explicit sync for compatibility with Nvidia hardware. This requires Linux 6.7 or later.
Xwayland
Jay supports running X11 applications seamlessly through Xwayland. See Xwayland for configuration options.
Clipboard Managers
Jay supports clipboard managers via the zwlr_data_control_manager_v1 and
ext_data_control_manager_v1 protocols.
Privilege Separation
Jay splits protocols into unprivileged and privileged protocols. By default, applications only have access to unprivileged protocols. This means that tools like screen lockers, status bars, and clipboard managers need to be explicitly granted access.
Jay provides several ways to grant privileges, from giving a program full access to all privileged protocols down to granting individual capabilities to specific tagged processes. See Granting Privileges for a detailed guide and the Protocol Support section below for the full list of protocols and their privilege requirements.
Push to Talk
Jay’s shortcut system allows you to execute an action when a key is pressed and a different action when the key is released, enabling push-to-talk functionality. See Shortcuts for details.
VR
Jay supports leasing VR headsets to applications via the
wp_drm_lease_device_v1 protocol.
Adaptive Sync
Jay supports adaptive sync (VRR) with configurable cursor refresh rates. See Outputs (Monitors) for per-output VRR settings.
Tearing
Jay supports tearing presentation for games. See Outputs (Monitors) for per-output tearing settings.
Low Input Latency
Jay uses frame scheduling to achieve input latency as low as 1.5 ms.
Color Management & HDR
Jay supports the Wayland color management protocol and HDR10 output with per-monitor color space, transfer function, brightness, and blend space controls. See HDR & Color Management for a full walkthrough.
Night Light
Jay supports night-light applications via the
zwlr_gamma_control_manager_v1 protocol.
Window and Client Rules
Jay supports powerful, reactive window and client rules. Rules are re-evaluated whenever matching criteria change (e.g. a window’s title changes).
See Window & Client Rules for details.
Protocol Support
Jay supports a large number of Wayland protocols. Protocols marked as Privileged are only accessible to applications that have been explicitly granted access. See Granting Privileges for how to do this.
| Protocol | Version | Privileged |
|---|---|---|
| ext_data_control_manager_v1 | 1 | Yes |
| ext_foreign_toplevel_image_capture_source_manager_v1 | 1 | |
| ext_foreign_toplevel_list_v1 | 1 | Yes |
| ext_idle_notifier_v1 | 2 | Yes |
| ext_image_copy_capture_manager_v1 | 11 | Yes |
| ext_output_image_capture_source_manager_v1 | 1 | |
| ext_session_lock_manager_v1 | 1 | Yes |
| ext_transient_seat_manager_v1 | 12 | Yes |
| ext_workspace_manager_v1 | 1 | Yes |
| jay_popup_ext_manager_v1 | 1 | |
| jay_tray_v1 | 1 | |
| org_kde_kwin_server_decoration_manager | 1 | |
| wl_compositor | 7 | |
| wl_data_device_manager | 4 | |
| wl_drm | 2 | |
| wl_fixes | 1 | |
| wl_output | 4 | |
| wl_seat | 10 | |
| wl_shm | 2 | |
| wl_subcompositor | 1 | |
| wp_alpha_modifier_v1 | 1 | |
| wp_color_manager_v1 | 2 | |
| wp_color_representation_manager_v1 | 1 | |
| wp_commit_timing_manager_v1 | 1 | |
| wp_content_type_manager_v1 | 1 | |
| wp_cursor_shape_manager_v1 | 2 | |
| wp_drm_lease_device_v1 | 1 | |
| wp_fifo_manager_v1 | 1 | |
| wp_fractional_scale_manager_v1 | 1 | |
| wp_linux_drm_syncobj_manager_v1 | 1 | |
| wp_pointer_warp_v1 | 1 | |
| wp_presentation | 2 | |
| wp_security_context_manager_v1 | 1 | |
| wp_single_pixel_buffer_manager_v1 | 1 | |
| wp_tearing_control_manager_v1 | 1 | |
| wp_viewporter | 1 | |
| xdg_activation_v1 | 1 | |
| xdg_toplevel_drag_manager_v1 | 1 | |
| xdg_toplevel_tag_manager_v1 | 1 | |
| xdg_wm_base | 7 | |
| xdg_wm_dialog_v1 | 1 | |
| zwlr_data_control_manager_v1 | 2 | Yes |
| zwlr_foreign_toplevel_manager_v1 | 3 | Yes |
| zwlr_gamma_control_manager_v1 | 1 | Yes |
| zwlr_layer_shell_v1 | 5 | No3 |
| zwlr_output_manager_v1 | 4 | Yes |
| zwlr_screencopy_manager_v1 | 3 | Yes |
| zwlr_virtual_pointer_manager_v1 | 2 | Yes |
| zwp_idle_inhibit_manager_v1 | 1 | |
| zwp_input_method_manager_v2 | 1 | Yes |
| zwp_linux_dmabuf_v1 | 5 | |
| zwp_pointer_constraints_v1 | 1 | |
| zwp_pointer_gestures_v1 | 3 | |
| zwp_primary_selection_device_manager_v1 | 1 | |
| zwp_relative_pointer_manager_v1 | 1 | |
| zwp_tablet_manager_v2 | 2 | |
| zwp_text_input_manager_v3 | 1 | |
| zwp_virtual_keyboard_manager_v1 | 1 | Yes |
| zxdg_decoration_manager_v1 | 2 | |
| zxdg_output_manager_v1 | 3 |
-
Cursors are always composited. ↩
-
Seat creation is always rejected. ↩
-
Sandboxes can restrict access to this protocol. ↩
Installation
Compile-Time Dependencies
The following libraries must be installed before building Jay. They are linked at build time.
| Library | Arch Linux | Fedora | Debian / Ubuntu |
|---|---|---|---|
| libinput | libinput | libinput-devel | libinput-dev |
| libgbm | mesa | mesa-libgbm-devel | libgbm-dev |
| libudev | systemd-libs | systemd-devel | libudev-dev |
| libpangocairo | pango | pango-devel | libpango1.0-dev |
| libfontconfig | fontconfig | fontconfig-devel | libfontconfig-dev |
One-liner install commands:
Arch Linux:
~$ sudo pacman -S libinput mesa systemd-libs pango fontconfig
Fedora:
~$ sudo dnf install libinput-devel mesa-libgbm-devel systemd-devel pango-devel fontconfig-devel
Debian / Ubuntu:
~$ sudo apt install libinput-dev libgbm-dev libudev-dev libpango1.0-dev libfontconfig-dev
You also need a C compiler (GCC or Clang) and the latest stable version of Rust. Install Rust with rustup:
~$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Runtime Dependencies
These libraries are loaded dynamically at runtime. Jay requires at least one working renderer – without one, no GPU can be initialized and nothing will be displayed.
| Library | Purpose | Arch Linux | Fedora | Debian / Ubuntu |
|---|---|---|---|---|
| libEGL + libGLESv2 | OpenGL renderer (legacy) | mesa or libglvnd | mesa-libEGL + mesa-libGLES | libegl1 + libgles2 |
| libvulkan | Vulkan renderer (recommended) | vulkan-icd-loader | vulkan-loader | libvulkan1 |
For Vulkan, you also need the driver for your GPU:
| GPU | Arch Linux | Fedora | Debian / Ubuntu |
|---|---|---|---|
| AMD | vulkan-radeon | mesa-vulkan-drivers | mesa-vulkan-drivers |
| Intel | vulkan-intel | mesa-vulkan-drivers | mesa-vulkan-drivers |
| Nvidia | nvidia-utils | xorg-x11-drv-nvidia | nvidia-vulkan-icd |
Optional Runtime Dependencies
- Linux 6.7 or later – required for explicit sync (needed for Nvidia GPUs).
- Xwayland – required for running X11 applications.
- PipeWire – required for screen sharing.
- logind (part of systemd) – required when running Jay from a virtual terminal or display manager.
Building
AUR (Arch Linux)
Arch Linux users can install Jay from the AUR. Two packages are available:
- jay – builds the latest released version.
- jay-git – builds from the latest commit on the master branch.
Install with your preferred AUR helper, for example:
~$ yay -S jay
The AUR packages handle all compile-time dependencies automatically.
From crates.io (recommended)
~$ cargo install --locked jay-compositor
This installs the jay binary to ~/.cargo/bin/jay.
From git (latest development version)
~$ cargo install --locked --git https://github.com/mahkoh/jay.git jay-compositor
From a local clone
~$ git clone https://github.com/mahkoh/jay.git
~$ cd jay
~$ cargo build --release
The binary is then available at ./target/release/jay.
CAP_SYS_NICE (Optional)
Granting CAP_SYS_NICE to the Jay binary can improve responsiveness when the
CPU or GPU are under heavy load:
~$ sudo setcap cap_sys_nice=p $(which jay)
When this capability is available, Jay will elevate its scheduler to SCHED_RR
(real-time round-robin) and create Vulkan queues with the highest available
priority. Both of these help Jay maintain smooth frame delivery under
contention.
Jay drops all capabilities almost immediately after startup. A dedicated thread
retains CAP_SYS_NICE solely for creating elevated Vulkan queues later.
Note
You need to re-run the
setcapcommand each time you update the Jay binary.
SCHED_RR and config.so
SCHED_RR and config.so are mutually exclusive: running untrusted code at
real-time priority would be a security risk. Jay enforces this as follows:
- If
config.soexists in the config directory, Jay skips theSCHED_RRelevation (elevated Vulkan queues are still created). - If Jay has already elevated to
SCHED_RR, it refuses to loadconfig.so.
You can also skip SCHED_RR explicitly by setting JAY_NO_REALTIME=1:
~$ JAY_NO_REALTIME=1 jay run
This still allows elevated Vulkan queues and does not affect config.so
loading.
The mutual exclusion can be overridden at compile time by building Jay with
JAY_ALLOW_REALTIME_CONFIG_SO=1.
Recommended Applications
The following applications work well with Jay:
- Alacritty – the default terminal emulator in the built-in configuration.
- bemenu – the default application launcher in the built-in configuration.
- xdg-desktop-portal-gtk4 – a file-picker portal with thumbnail support. Used automatically when installed.
- wl-tray-bridge – shows D-Bus StatusNotifierItem applications as tray icons.
- mako – a notification daemon. Launched automatically by the default configuration.
- window-to-tray – run most Wayland applications as tray applications (e.g.
window-to-tray pavucontrol-qt).
Running Jay
From a Virtual Terminal
Switch to a virtual terminal (e.g. ctrl-alt-F2), log in, and run:
~$ jay run
From a Display Manager
To make Jay appear as a session option in your display manager (GDM, SDDM, etc.), install the session file.
If you have the repository checked out:
~$ sudo cp etc/jay.desktop /usr/share/wayland-sessions/jay.desktop
Otherwise, create it manually:
~$ sudo tee /usr/share/wayland-sessions/jay.desktop > /dev/null << 'EOF'
[Desktop Entry]
Name=Jay
Comment=A Wayland Compositor
Exec=jay run
Type=Application
DesktopNames=jay
EOF
Then log out and select Jay from the session list.
Note
If you installed Jay via
cargo install, thejaybinary lives in~/.cargo/bin/. Your display manager may not include this directory in itsPATH. Either add~/.cargo/binto the systemPATH, or copy/symlink the binary to/usr/local/bin:~$ sudo ln -s ~/.cargo/bin/jay /usr/local/bin/jay
The Control Center
Once Jay is running, press alt-c to open the
control center – a built-in GUI that lets you inspect and
modify most compositor settings without editing config files or running CLI
commands. From the control center you can:
- Rearrange monitors with a visual drag-and-drop editor.
- Configure input devices – acceleration, tap behavior, keymaps, and more.
- Switch GPUs and graphics APIs.
- Adjust theme colors, fonts, borders, and gaps with live color pickers.
- Manage idle timeouts and screen locking.
- Search and filter windows and clients.
- Toggle Xwayland and color management.
You can also open it from the command line with jay control-center. See the
Control Center chapter for a full tour of every pane.
Default Keybindings
Jay ships with a built-in default configuration. The most important default keybindings are listed below.
Note
Alacritty and bemenu must be installed for the default terminal and launcher bindings to work.
Super_L(left Windows key)- Open Alacritty terminal
alt-p- Open bemenu application launcher
alt-q- Quit Jay
alt-h/j/k/l- Move focus (left/down/up/right)
alt-shift-h/j/k/l- Move focused window
alt-d- Split horizontally
alt-v- Split vertically
alt-u- Toggle fullscreen
alt-shift-f- Toggle floating
alt-c- Open the control center
alt-shift-c- Close focused window
alt-t- Toggle split direction
alt-m- Toggle mono (stacking) layout
alt-f- Focus parent container
alt-shift-r- Reload configuration
The defaults also include ctrl-alt-F1 through F12 for switching virtual
terminals, alt-F1 through F12 for switching workspaces, and
alt-shift-F1 through F12 for moving windows to workspaces.
Once you create a configuration file, the built-in defaults are entirely
replaced – even an empty config file means no shortcuts. Run jay config init
to generate a config pre-populated with all the defaults. See the
Configuration Overview chapter for details.
Control Center
The control center is Jay’s built-in graphical interface for inspecting and
modifying compositor settings. It provides a convenient alternative to editing
configuration files or running CLI commands – most settings that can be changed
in config.toml or via the CLI can also be changed here.
Note
Changes made in the control center are not persisted across compositor restarts. To make settings permanent, add them to your
config.toml.
Tip
The control center consumes GPU and CPU resources while open. Close it when not in use to avoid reducing compositor performance.
Opening the Control Center
-
Press
alt-c(the default shortcut for theopen-control-centeraction). -
Run the CLI command:
~$ jay control-center
Interface Overview
The control center window has a sidebar on the left listing all available panes and a central panel area on the right.
- Click a pane name in the sidebar to open it.
- Multiple panes can be open at the same time – they appear as tabs in the panel area.
- Drag pane tabs to rearrange them or to split the panel area into side-by-side or stacked layouts.
- Close a pane by clicking its X button or middle-clicking its tab.
Panes
Compositor
General information and top-level controls for the running compositor.
- Repository
- Link to the Jay GitHub repository
- Version
- The running Jay version
- PID
- The compositor process ID
- WAYLAND_DISPLAY
- The Wayland socket name (shown when available)
- Config DIR
- Path to the active configuration directory (shown when available)
- Libei Socket
- Toggle the libei input emulation socket
- LIBEI_SOCKET
- The socket name (shown when the Libei Socket toggle is enabled)
- Workspace Display Order
- Dropdown to select how workspaces are ordered in the bar
- Log Level
- Dropdown to change the active log level at runtime (shown when the logger is available)
- Log File
- Click to copy the log file path to the clipboard (shown when the logger is available)
Buttons at the bottom:
- Quit – stop the compositor.
- Reload Config – reload the configuration file.
- Switch to VT – switch to another virtual terminal (with a numeric input to select which one).
Outputs
The Outputs pane is the largest and most interactive pane. It has two sub-views: a visual arrangement editor and per-connector settings.
Arrangement editor
A 2D preview of your monitor layout. Monitors are drawn as labeled rectangles at their configured positions and sizes.
- Click a monitor to select it (highlighted with a shadow).
- Drag a selected monitor to reposition it.
- Scroll to zoom in and out.
- Middle-click drag or right-click drag to pan the viewport.
- Arrow keys nudge the selected monitor by 1 pixel.
- Snap to neighbor – when enabled, dragged monitors snap to the edges of neighboring monitors within a 10-pixel threshold. Hold Shift to temporarily invert the snapping behavior.
- Guide lines – optional horizontal and vertical lines at the edges of all monitors, helping you align them precisely.
A Zoom To Fit checkbox in the top bar auto-scales the view to fit all monitors. It is disabled when you manually pan or zoom.
Arrangement settings (accessible via the Settings button):
- Show guide lines
- Draw alignment guide lines
- Snap to neighbor
- Snap edges when dragging (hold Shift to invert)
- Show arrangement area
- Toggle the visual arrangement sub-pane
- Layout
- How the arrangement and settings are split: Auto, Vertical, or Horizontal
- Show disconnected heads
- Include outputs that are no longer connected
- Show disabled heads
- Include outputs that are disabled
Staged changes
Changes made in the Outputs pane are staged – they are not applied immediately. Three buttons in the top bar control the workflow:
- Test – validates the staged changes against the display backend without applying them. Errors are shown in the pane.
- Commit – applies all staged changes. The pane title shows
Outputs (*)when there are uncommitted changes. - Reset – discards all staged changes and reverts to the live state. This is displayed as a checkbox; checking it resets the staged changes.
When a staged value differs from the live value, the current (live) value is
shown alongside with a ^ current annotation.
Per-connector settings
Each connected display appears as a collapsible section with the connector name, manufacturer, and model. Inside:
- Serial Number
- Read-only identifier
- Enabled
- Toggle the connector on or off
- Position
- X and Y coordinates in compositor space
- Scale
- Fractional scaling factor, with +/- buttons for fine adjustment
- Mode
- Resolution and refresh rate (dropdown when multiple modes are available)
- Physical Size (mm)
- Read-only physical dimensions in millimeters
- Size
- Read-only computed pixel dimensions of the output
- Transform
- Rotation and mirroring (none, rotate-90, rotate-180, rotate-270, flip, and flipped rotations)
- Custom Brightness
- Toggle whether to use a custom SDR content brightness
- Brightness
- Brightness value in cd/m^2 (shown when Custom Brightness is enabled)
- Colorimetry
- Color space (depends on monitor capabilities)
- EOTF
- Transfer function (depends on monitor capabilities)
- Format
- Framebuffer pixel format
- Tearing
- Tearing mode. When set to a “Fullscreen” mode, a Limit Windows checkbox appears; inside that, a Requests Tearing checkbox filters by whether the window has requested tearing.
- VRR Active
- Read-only indicator of whether VRR is currently active (only shown when the monitor supports VRR)
- VRR
- Variable refresh rate mode (only shown when the monitor supports VRR). When set to a “Fullscreen” mode, a Limit Windows checkbox appears; inside that, a Limit Content Types checkbox enables filtering by content type (Photos, Videos, Games checkboxes).
- Non-desktop
- Read-only indicator (Yes/No) of whether the connector is inherently non-desktop
- Override
- Force the connector to be treated as desktop or non-desktop
- Blend Space
- How colors are blended during compositing (sRGB or linear)
- Use Native Gamut
- Use the display’s advertised color primaries instead of assuming sRGB
- Native Gamut
- Read-only CIE xy primaries for red, green, blue, and white point
- Limit Cursor HZ
- Toggle to limit cursor-triggered refresh rate when VRR is active
- Cursor HZ
- Cursor refresh rate value (shown when Limit Cursor HZ is enabled)
- Flip Margin (ms)
- Read-only page-flip margin for this connector
Virtual Outputs
Manage headless virtual outputs. These are useful for screen sharing, testing, or running applications on a display without a physical monitor.
- View the list of existing virtual outputs.
- Add a new virtual output by entering a name and clicking Add.
- Remove an existing virtual output by clicking its X button.
GPUs
Inspect and configure graphics cards (DRM devices). Each GPU appears as a collapsible section showing its device path and model name.
- Vendor
- Read-only GPU vendor name
- Model
- Read-only GPU model name
- Devnode
- Read-only device path
- Syspath
- Read-only sysfs path
- PCI ID
- Read-only vendor:model in hex
- Dev
- Read-only major:minor device numbers
- API
- Dropdown to select the graphics API – Vulkan (recommended) or the legacy OpenGL renderer
- Primary Device
- Checkbox to make this GPU the render device
- Direct Scanout
- Toggle direct scanout (bypasses composition for lower latency)
- Flip Margin
- Adjust the page-flip margin in milliseconds, with +/- buttons for 0.1 ms steps
- Connectors
- List of display connectors attached to this GPU
Input
The Input pane is divided into per-seat and per-device sections.
Per-seat settings
Each seat (typically just default) appears as a collapsible section:
- Repeat Rate
- Key repeat speed, with +/- 20 buttons
- Repeat Delay
- Initial delay before key repeat begins, with +/- 20 buttons
- Cursor Size
- Size of the seat cursor in pixels
- Simple IM
- Toggle the built-in XCompose-based input method
- Hardware Cursor
- Toggle hardware cursor rendering
- Pointer Revert Key
- Text field for the keysym name of the cancel key
- Focus Follows Mouse
- Toggle whether moving the pointer over a window gives it focus
- Fallback Output Mode
- Dropdown to choose between cursor-based and focus-based output selection
Below the settings grid:
- Focus History – checkboxes for “Only Visible” and “Same Workspace”.
- Reload Simple IM – button to reload XCompose files without restarting.
Keymap management
Each seat has a full keymap management section:
- Copy Keymap – copies the current keymap text to the clipboard.
- Load Default Keymap – restores the compositor’s default keymap.
- Backup / Restore Keymap – save and restore a keymap backup.
- Load Keymap from Clipboard – paste a keymap from the clipboard.
- Create Keymap from Names – build a keymap from RMLVO (Rules, Model, Layout, Variant, Options) fields. Rules and Model have a text input and a “Default” checkbox; Layouts, Variants, and Options have text inputs only. Click Load to apply.
Per-device settings
Each input device appears as a collapsible section. The available settings depend on the device’s capabilities:
- Seat
- Dropdown to assign the device to a seat, with a Detach button. Shown for all devices.
- Syspath / Devnode
- Read-only device paths. Shown for all devices.
- Capabilities
- Read-only list (e.g. Keyboard, Pointer, Touch). Shown for all devices.
- Natural Scrolling
- Toggle scroll direction. Shown for devices that support it.
- Scroll Distance (px)
- Pixels per legacy scroll event. Shown for pointer devices.
- Accel Profile
- Dropdown: Flat or Adaptive. Shown for devices with acceleration.
- Accel Speed
- Numeric input (0.0 to 1.0). Shown for devices with acceleration.
- Click Method
- Dropdown: none, button-areas, clickfinger. Shown for devices that support it.
- Tap Enabled
- Toggle tap-to-click. Shown for touchpads.
- Tap Drag Enabled
- Toggle tap-and-drag. Shown for touchpads.
- Tap Drag Lock Enabled
- Toggle tap-drag lock. Shown for touchpads.
- Left Handed
- Swap primary and secondary buttons. Shown for devices that support it.
- Simultaneous left+right produces middle click. Shown for devices that support it.
- Output
- Dropdown to map the device to a specific output (only has effect for touch and tablet devices), with a Detach button. Shown for all devices.
- Transform Matrix
- 2x2 matrix applied to relative motion. Shown for pointer devices.
- Calibration Matrix
- 2x3 matrix for absolute input calibration. Shown for devices that support it.
- Device Keymap
- Override the seat keymap for this device, with full keymap management UI and a “Use Seat Keymap” button to revert. Shown for keyboards.
Idle
Configure the screensaver and idle behavior:
- Interval
- Minutes and seconds of inactivity before the on-idle action fires
- Grace period
- Minutes and seconds of the warning phase (screen goes black but is not yet locked)
- Inhibitors
- Collapsible list showing which applications are currently preventing idle (e.g. video players), with a count in the header
Look and Feel
Visual customization with live preview. Changes take effect immediately.
- Show Bar
- Toggle the status bar
- Bar Position
- Dropdown to select the bar position
- Show Titles
- Toggle window title bars
- Primary Selection
- Toggle middle-click paste (requires application restart to take effect)
- UI Drag
- Toggle whether workspaces and tiles can be dragged
- UI Drag Threshold (px)
- Minimum distance in pixels before a drag begins
- Float Pin Icon
- Show the pin icon on floating windows even when not pinned
- Float Above Fullscreen
- Show floating windows above fullscreen windows
- Font
- Text field for the main compositor font family
- Title Font
- Override font for window title bars (empty = use main font)
- Bar Font
- Override font for the status bar (empty = use main font)
Three reset buttons at the bottom: Reset Sizes, Reset Colors, and Reset Fonts.
Sizes
A collapsible section with numeric inputs for every theme size: border widths, title heights, bar height, gaps, and other spacing values.
Colors
A collapsible section with color pickers for every theme color. Click a color swatch to open a full RGBA color picker with sliders and hex input. This includes colors for backgrounds, borders, text, the status bar, focused and unfocused windows, attention indicators, and more.
Clients
Inspect and manage connected Wayland clients.
A Filter toggle at the top enables the composable filter builder (see Filtering below). When filtering is off, all clients are shown.
Each client appears as a collapsible section showing its ID and process name. Expand it to see:
- ID
- Client identifier
- PID
- Process ID
- UID
- User ID
- comm
- Process name
- exe
- Executable path
- Sandboxed
- Whether the client is sandboxed (only shown for sandboxed clients)
- Secure
- Whether the client uses the privileged socket (only shown for secure clients)
- Xwayland
- Shown only for X11 clients
- Sandbox Engine
- Sandbox engine name (shown when sandboxed)
- App ID
- Sandbox application ID (shown when sandboxed)
- Instance ID
- Sandbox instance ID (shown when sandboxed)
- Tag
- The connection tag, if any
- Kill
- Button to forcefully disconnect the client
- Capabilities
- Collapsible list of effective Wayland capabilities
- Windows
- Collapsible list of all windows owned by this client
Click the open in new pane icon on any client to open a dedicated pane for that client, allowing you to keep it visible while browsing other panes.
Window Search
Search and filter windows across the compositor using the composable filter builder (see Filtering below).
Each matching window appears as a collapsible section showing its title. Expand it to see:
- ID
- Window identifier
- Title
- Window title
- Workspace
- Which workspace the window is on
- Type
- Container, xdg_toplevel, X Window, or Placeholder
- Tag
- Toplevel tag (set via window rules); only shown for xdg_toplevel windows
- X11 properties
- Class, Instance, and Role (only shown for Xwayland windows)
- App ID
- Application identifier
- Floating
- Whether the window is floating
- Visible
- Whether the window is visible
- Urgent
- Whether the window has the urgency flag
- Fullscreen
- Whether the window is fullscreen
- Content Type
- The content type hint (photo, video, game), if set
- Client
- Full client details (same as the Clients pane)
Click the open in new pane icon on any window to open a dedicated pane for that window.
Xwayland
Manage the Xwayland compatibility layer for running X11 applications:
- Enabled
- Toggle Xwayland on or off
- Scaling Mode
- Dropdown:
defaultordownscaled(renders at highest integer scale then downscales for sharper text on HiDPI) - DISPLAY
- Read-only X11 display number (only shown when Xwayland is running)
- Running
- Whether Xwayland is currently running
- PID
- Xwayland process ID (only shown when Xwayland is running)
- Kill
- Button to forcefully terminate Xwayland (only shown when Xwayland is running)
- Client
- Collapsible section with full client details for the Xwayland process (only shown when Xwayland is running)
Color Management
Configure the Wayland color management protocol:
- Enabled
- Toggle the color management protocol for clients
- Available
- Read-only indicator of whether color management is available with the current renderer and hardware
Filtering
The Clients and Window Search panes share a composable filter system for narrowing down results. The filter builder works as follows:
At the top level, select a combinator or a leaf criterion from the dropdown:
- Not – inverts a single child criterion.
- All – all child criteria must match (AND).
- Any – at least one child criterion must match (OR).
- Exactly(n) – exactly n child criteria must match (with a numeric input for n).
Compound criteria contain a list of children. Click Add to append a new criterion; click the X button on any child to remove it. Criteria can be nested to arbitrary depth.
Leaf criteria vary by context:
Client criteria: Comm, Exe, Tag, Sandbox Engine, Sandbox App ID, Sandbox Instance ID (all regex-matched text fields with a “Regex” checkbox), Sandboxed, Is Xwayland (boolean), UID, PID (numeric inputs).
Window criteria: Title, App ID, Tag, Workspace, X Class, X Instance, X Role (all regex-matched text fields), Floating, Visible, Urgent, Fullscreen (boolean), Content Types (checkboxes for Photo, Video, Game), and Client (a nested client criterion builder for filtering by the owning client’s properties).
Text-matching criteria have a Regex checkbox. When unchecked, the input is matched as a literal string. When checked, it is treated as a regular expression. Invalid regex patterns show an error message.
Configuration Overview
Jay is configured through a single TOML file located at:
~/.config/jay/config.toml
If this file does not exist, Jay uses built-in defaults that provide a reasonable starting configuration with common shortcuts, a US QWERTY keymap, and other sensible settings.
Warning
Once
config.tomlexists, the entire built-in default configuration is replaced – not merged. Even a completely empty file means no shortcuts, no startup actions, nothing. Always start from a full config rather than writing one from scratch.
Initializing the config
The easiest way to get started is to let Jay write the defaults for you:
~$ jay config init
This creates ~/.config/jay/config.toml pre-populated with the full default
configuration. You can then edit it to suit your needs.
If you already have a config file and want to reset it:
~$ jay config init --overwrite
The old file will be backed up to config.toml.1 (or .2, .3, etc.) before
being replaced.
Other config subcommands
Print the path to the config file:
~$ jay config path
Open the config directory in your file manager:
~$ jay config open-dir
Reloading the configuration
By default, Jay does not automatically reload config.toml when it changes on
disk. To apply changes, trigger a reload manually:
- Press
alt-shift-r(the default shortcut), or - Use the
reload-config-tomlaction in any other action context (e.g. a named action or a window rule).
Most settings take effect immediately on reload. A few exceptions (like
log-level, explicit-sync, and initial drm-devices settings) only apply
at compositor startup.
Automatic reloading
To have Jay watch config.toml for changes and reload automatically, add:
auto-reload = true
When enabled, Jay uses inotify to monitor the config file and its parent directories. Changes are debounced — the config is reloaded 400 ms after the last write, so rapid successive saves don’t cause multiple reloads. If the file contents haven’t actually changed, the reload is skipped.
Setting auto-reload = false will stop the watcher. Removing the key entirely
leaves the watcher state unchanged (if it was running, it keeps running until
the compositor restarts).
Named actions
You can define reusable actions in the [actions] table and reference them
anywhere an action is accepted by prefixing the name with $:
[actions]
launch-terminal = { type = "exec", exec = "alacritty" }
launch-browser = { type = "exec", exec = "firefox" }
[shortcuts]
alt-Return = "$launch-terminal"
alt-b = "$launch-browser"
Named actions can reference other named actions. The max-action-depth setting
controls the maximum recursion depth to prevent infinite loops (default: 16):
max-action-depth = 32
Composable actions
Anywhere an action is accepted, you can use an array of actions instead. This applies to shortcuts, startup hooks, named actions, and any other action field:
[shortcuts]
alt-q = [
{ type = "exec", exec = ["notify-send", "Goodbye!"] },
"quit",
]
Advanced: shared library configuration
For users who need programmatic configuration beyond what TOML offers, Jay also supports configuration via a compiled Rust shared library using the jay-config crate. This is an advanced option – the TOML config is sufficient for the vast majority of use cases.
Full specification
This book covers the most common configuration options with explanations and examples. For an exhaustive listing of every field, type, and action, see the auto-generated specification.
Keymaps & Repeat Rate
Jay uses XKB keymaps for keyboard layout configuration. The default keymap is US QWERTY.
Setting the keymap
There are several ways to define a keymap.
Using RMLVO names (recommended)
The simplest approach is to specify the layout using RMLVO (Rules, Model, Layout, Variants, Options) names:
keymap.rmlvo = { layout = "de" }
You can specify any combination of RMLVO fields:
keymap.rmlvo = {
layout = "us,de",
variants = "dvorak,",
options = "grp:ctrl_space_toggle",
}
All fields are optional. When a field is omitted, Jay checks the corresponding environment variable, then falls back to a default:
| Field | Environment Variable | Default |
|---|---|---|
rules | XKB_DEFAULT_RULES | evdev |
model | XKB_DEFAULT_MODEL | pc105 |
layout | XKB_DEFAULT_LAYOUT | us |
variants | XKB_DEFAULT_VARIANTS | (none) |
options | XKB_DEFAULT_OPTIONS | (none) |
Using a raw XKB string
You can provide a complete XKB keymap as a multi-line string. See the ArchWiki XKB guide for background on the format.
keymap = """
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us+inet(evdev)" };
};
"""
Loading from a file
Point to an XKB file. Relative paths are resolved from the config directory
(~/.config/jay/):
keymap.path = "./my-keymap.xkb"
Named keymaps
You can define multiple named keymaps and switch between them at runtime.
Define them with the [[keymaps]] array, then select the default with
keymap.name:
keymap.name = "laptop"
[[keymaps]]
name = "laptop"
rmlvo = { layout = "us" }
[[keymaps]]
name = "external"
rmlvo = { layout = "de", options = "compose:ralt" }
Each entry in [[keymaps]] must have a name and exactly one of map,
path, or rmlvo.
Switching keymaps at runtime
Use the set-keymap action to switch between named keymaps:
[shortcuts]
alt-F9 = { type = "set-keymap", keymap.name = "laptop" }
alt-F10 = { type = "set-keymap", keymap.name = "external" }
You can also switch keymaps from the command line:
~$ jay input seat default set-keymap-from-names --layout de
Repeat rate
The repeat rate controls how keys behave when held down. It has two parameters:
rate– number of key repeats per seconddelay– milliseconds to wait before repeating begins
repeat-rate = { rate = 25, delay = 250 }
Changing repeat rate at runtime
Use the set-repeat-rate action:
[shortcuts]
alt-F11 = {
type = "set-repeat-rate",
rate = { rate = 40, delay = 200 },
}
Or from the command line:
~$ jay input seat default set-repeat-rate 40 200
Per-device keymaps
You can override the keymap for specific input devices using the [[inputs]]
array. For example, to use a different layout for an external keyboard:
[[inputs]]
match.name = "My External Keyboard"
keymap.rmlvo = { layout = "de" }
See the Input Devices chapter for more on matching and configuring individual devices.
Shortcuts
Shortcuts bind key combinations to actions. They are the primary way to interact with Jay.
Basic syntax
Shortcuts are defined in the [shortcuts] table. The left side is a key
combination; the right side is an action:
[shortcuts]
alt-q = "quit"
alt-Return = { type = "exec", exec = "alacritty" }
alt-shift-c = "close"
Key format
Key combinations follow the pattern MODIFIER-MODIFIER-KEYSYM:
(MOD-)*KEYSYM
Keysym names are unmodified XKB keysym names from xkbcommon-keysyms.h
with the XKB_KEY_ prefix removed. Use the unmodified keysym – write
shift-q, not shift-Q.
Modifiers
The available modifiers are:
shift- Shift key
ctrl- Control key
alt- Alt key
logo- Super/Meta/Windows key
lock- Lock modifier
caps- Caps Lock
num- Num Lock
mod1- Mod1 (typically Alt)
mod2- Mod2 (typically Num Lock)
mod3- Mod3
mod4- Mod4 (typically Super)
mod5- Mod5
release- Fire on key release instead of press
The release modifier is special: it causes the action to trigger when the key
is released rather than when it is pressed.
Simple actions
Simple actions are written as plain strings. Here are the most commonly used ones:
Focus and movement:
[shortcuts]
alt-h = "focus-left"
alt-j = "focus-down"
alt-k = "focus-up"
alt-l = "focus-right"
alt-shift-h = "move-left"
alt-shift-j = "move-down"
alt-shift-k = "move-up"
alt-shift-l = "move-right"
Layout:
[shortcuts]
alt-d = "split-horizontal"
alt-v = "split-vertical"
alt-t = "toggle-split"
alt-m = "toggle-mono"
alt-f = "focus-parent"
Window management:
[shortcuts]
alt-u = "toggle-fullscreen"
alt-shift-f = "toggle-floating"
alt-shift-c = "close"
Compositor control:
[shortcuts]
alt-q = "quit"
alt-shift-r = "reload-config-toml"
Other useful simple actions:
consume– consume the key event (prevent it from reaching applications). Key-press events that trigger shortcuts are consumed by default; key-release events are forwarded by default. Consuming key-release events can cause keys to get stuck in the focused application.forward– forward the key event to the focused application (the inverse ofconsume)none– unbind this key combination (useful for overriding defaults or inherited mode bindings)disable-pointer-constraint– release a pointer lock/confinementfocus-parent– move focus to the parent containertoggle-bar,show-bar,hide-bar– control the status baropen-control-center– open the Jay control center GUIwarp-mouse-to-focus– warp the cursor to the center of the focused windowkill-client– forcefully disconnect a client (in a window rule, kills the window’s client; in a client rule, kills the matched client; has no effect in plain shortcuts)focus-below,focus-above– move focus to the layer below or above the current layerfocus-tiles– focus the tile layercreate-mark,jump-to-mark– interactively create or jump to a mark (the next pressed key identifies the mark). See Marks below.enable-window-management,disable-window-management– programmatically enable or disable window management modereload-config-so– reload the shared-library configuration (config.so)
See the specification for the full list of simple actions.
Parameterized actions
Actions that need arguments are written as tables with a type field:
Launching programs
[shortcuts]
alt-Return = { type = "exec", exec = "alacritty" }
alt-p = { type = "exec", exec = "bemenu-run" }
Switching virtual terminals
[shortcuts]
ctrl-alt-F1 = { type = "switch-to-vt", num = 1 }
ctrl-alt-F2 = { type = "switch-to-vt", num = 2 }
Workspaces
[shortcuts]
alt-F1 = { type = "show-workspace", name = "1" }
alt-F2 = { type = "show-workspace", name = "2" }
alt-shift-F1 = { type = "move-to-workspace", name = "1" }
alt-shift-F2 = { type = "move-to-workspace", name = "2" }
Moving workspaces to outputs
[shortcuts]
logo-ctrl-shift-Right = {
type = "move-to-output",
direction = "right",
}
logo-ctrl-shift-Left = {
type = "move-to-output",
direction = "left",
}
Other parameterized actions
set-keymap– change the active keymapset-repeat-rate– change the keyboard repeat rateset-env– set environment variables for future spawned programsunset-env– remove environment variablesconfigure-connector– enable/disable a monitorconfigure-input– change input device settingsconfigure-output– change output settingsconfigure-idle– change the idle timeoutconfigure-direct-scanout– enable or disable direct scanoutconfigure-drm-device– apply settings to a DRM deviceset-theme– change theme settingsset-log-level– change the compositor log levelset-gfx-api– set the graphics API for new DRM devices (usually only effective at startup)set-render-device– set the render device for compositingdefine-action– define or redefine a named action at runtimeundefine-action– remove a named actioncreate-mark– create a mark with an explicit ID (see Marks)jump-to-mark– jump to a mark with an explicit IDcopy-mark– copy a mark from one ID to anothercreate-virtual-output– create a virtual outputremove-virtual-output– remove a virtual output
See the specification for the complete list.
Running multiple actions
Use an array to run several actions from a single shortcut:
[shortcuts]
alt-q = [
{ type = "exec", exec = ["notify-send", "Goodbye!"] },
"quit",
]
The exec action in detail
The exec field accepts three forms:
A simple string – the program name with no arguments:
alt-Return = { type = "exec", exec = "alacritty" }
An array of strings – the program name followed by arguments:
alt-n = { type = "exec", exec = ["notify-send", "Hello", "World"] }
A table – full control over execution. Exactly one of prog or shell
must be specified:
# Using prog + args
alt-n = {
type = "exec",
exec = {
prog = "notify-send",
args = ["Hello"],
env = { LANG = "en_US.UTF-8" },
},
}
# Using shell (runs as: $SHELL -c "command")
alt-s = {
type = "exec",
exec = {
shell = "grim - | wl-copy",
privileged = true,
},
}
Table fields:
prog- Program to execute (mutually exclusive with
shell) shell- Shell command to run via
$SHELL -c(mutually exclusive withprog) args- Arguments array (only with
prog) env- Per-process environment variables
privileged- If
true, grants access to privileged Wayland protocols (default:false) tag- Tag to apply to all Wayland connections spawned by this process
Practical examples
Volume control with pactl:
[shortcuts]
XF86AudioRaiseVolume = {
type = "exec",
exec = ["pactl", "set-sink-volume", "0", "+5%"],
}
XF86AudioLowerVolume = {
type = "exec",
exec = ["pactl", "set-sink-volume", "0", "-5%"],
}
XF86AudioMute = {
type = "exec",
exec = ["pactl", "set-sink-mute", "0", "toggle"],
}
Taking a screenshot and copying to clipboard:
[shortcuts]
Print = {
type = "exec",
exec = {
shell = "grim - | wl-copy",
privileged = true,
},
}
Complex shortcuts
Complex shortcuts provide additional control via the [complex-shortcuts]
table. They support:
mod-mask– controls which modifiers are considered when matching. Set to""to ignore all modifiers.action– the action to run on key press (defaults to"none").latch– an action to run when the key is released.
Volume keys regardless of modifiers
The volume keys should work whether or not Alt, Shift, etc. are held:
[complex-shortcuts.XF86AudioRaiseVolume]
mod-mask = ""
action = {
type = "exec",
exec = ["pactl", "set-sink-volume", "0", "+5%"],
}
[complex-shortcuts.XF86AudioLowerVolume]
mod-mask = ""
action = {
type = "exec",
exec = ["pactl", "set-sink-volume", "0", "-5%"],
}
Push-to-talk
Unmute audio while a key is held, mute on release:
[complex-shortcuts.alt-x]
action = {
type = "exec",
exec = ["pactl", "set-sink-mute", "0", "0"],
}
latch = {
type = "exec",
exec = ["pactl", "set-sink-mute", "0", "1"],
}
The latch action fires when the triggering key (x in this case) is
released, regardless of any other keys pressed at that time.
Marks
Marks let you tag a window and quickly jump back to it later, similar to marks in Vim.
Interactive marks
The simplest way to use marks is interactively. Bind create-mark and
jump-to-mark as simple string actions:
[shortcuts]
alt-m = "create-mark"
alt-apostrophe = "jump-to-mark"
When you press alt-m, Jay waits for the next key press (e.g. a) and
assigns the mark to the currently focused window. When you press
alt-apostrophe followed by a, Jay focuses the marked window.
Hard-coded marks
You can skip the interactive step by specifying a mark ID directly:
[shortcuts]
alt-shift-1 = { type = "create-mark", id.key = "1" }
alt-1 = { type = "jump-to-mark", id.key = "1" }
Mark IDs can be identified by a key name (id.key) or by an arbitrary string
(id.name):
[shortcuts]
alt-shift-b = { type = "create-mark", id.name = "browser" }
alt-b = { type = "jump-to-mark", id.name = "browser" }
Key names use Linux input event code names with the KEY_ prefix removed, all
lowercase (see the Linux input event codes).
Copying marks
The copy-mark action copies a mark from one ID to another:
[shortcuts]
alt-c = { type = "copy-mark", src.key = "a", dst.name = "backup" }
Named actions
Named actions provide another layer of reuse. Define them in the [actions]
table and reference them with $name:
[actions]
my-layout = [
"split-horizontal",
{ type = "exec", exec = "alacritty" },
]
[shortcuts]
alt-l = "$my-layout"
You can redefine named actions at runtime using the define-action and
undefine-action parameterized actions:
[shortcuts]
alt-shift-q = {
type = "define-action",
name = "my-layout",
action = "quit",
}
Virtual outputs
Virtual outputs can be created and removed via actions. A virtual output has
the connector name VO-{name} and the serial number {name}. A newly created
virtual output is initially disabled.
[shortcuts]
alt-shift-v = {
type = "create-virtual-output",
name = "screen-share",
}
alt-shift-x = {
type = "remove-virtual-output",
name = "screen-share",
}
You can pre-configure the virtual output using connector and output match rules:
[[connectors]]
match.name = "VO-screen-share"
enabled = true
[[outputs]]
match.connector = "VO-screen-share"
mode = {
width = 1920,
height = 1080,
refresh-rate = 120.0,
}
Actions in window rules
When certain simple actions are used inside a window rule,
they apply to the matched window instead of the focused window. The
affected actions are: move-left, move-down, move-up, move-right,
split-horizontal, split-vertical, toggle-split, tile-horizontal,
tile-vertical, show-single, show-all, toggle-fullscreen,
enter-fullscreen, exit-fullscreen, close, toggle-floating, float,
tile, toggle-float-pinned, pin-float, and unpin-float.
Similarly, kill-client applies to the matched window’s client in a window
rule, or to the matched client in a client rule.
Startup Actions
Jay provides hooks that run actions at specific points during compositor startup and during idle transitions.
on-graphics-initialized
This hook runs after the GPU has been initialized and the compositor is ready to display graphical content. It is the right place to start graphical applications such as notification daemons, system tray bridges, status bars, and similar programs.
on-graphics-initialized = { type = "exec", exec = "mako" }
To start multiple programs, use an array of actions:
on-graphics-initialized = [
{ type = "exec", exec = "mako" },
{ type = "exec", exec = "wl-tray-bridge" },
]
Note
The built-in default configuration starts mako (notification daemon) and wl-tray-bridge (system tray bridge) in
on-graphics-initialized. Once you create a config file, these defaults are replaced – include them in your config if you want to keep them.
This hook runs when the config is first loaded after compositor startup. It does not re-run on config reload.
on-startup
This hook runs as early as possible when the compositor starts – before graphics are initialized. Do not start graphical applications here; they will likely fail to connect to the display.
Use on-startup for tasks like setting environment variables or other
non-graphical initialization:
on-startup = {
type = "set-env",
env = { XDG_CURRENT_DESKTOP = "jay" },
}
This hook has no effect on config reload – it only runs once when the compositor first starts.
on-idle
This hook runs when the compositor transitions to the idle state (i.e., after the configured idle timeout expires with no user input). The most common use case is starting a screen locker.
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
Note
Screen lockers need
privileged = trueto access the privileged Wayland protocols required for locking the session.
You can combine idle with a grace period. The idle timeout and grace period are
configured separately in the [idle] section (see Idle & Screen
Locking):
idle = { minutes = 10 }
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
Like the other hooks, on-idle accepts arrays of actions:
on-idle = [
{ type = "exec", exec = ["notify-send", "Going idle..."] },
{
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
},
]
Environment Variables
Jay can set environment variables that are inherited by all programs it spawns.
Setting environment variables at startup
Use the [env] table in your config to define variables that apply to every
application launched by the compositor:
[env]
GTK_THEME = "Adwaita:dark"
QT_QPA_PLATFORM = "wayland"
MOZ_ENABLE_WAYLAND = "1"
These variables are set when the config is loaded. Programs started after that point will inherit them.
Changing variables at runtime
set-env
Use the set-env action to add or update environment variables while the
compositor is running. This affects all programs started after the action
runs:
[shortcuts]
alt-F11 = {
type = "set-env",
env = { GTK_THEME = "Adwaita:dark" },
}
alt-F12 = {
type = "set-env",
env = { GTK_THEME = "Adwaita" },
}
unset-env
Use the unset-env action to remove environment variables:
[shortcuts]
alt-F10 = { type = "unset-env", env = ["GTK_THEME"] }
You can unset multiple variables at once with an array:
[shortcuts]
alt-F10 = {
type = "unset-env",
env = ["GTK_THEME", "QT_QPA_PLATFORM"],
}
Per-process environment variables
When using the table form of the exec action, you can set environment
variables that apply only to that specific process:
[shortcuts]
alt-Return = {
type = "exec",
exec = {
prog = "alacritty",
env = { TERM = "xterm-256color" },
},
}
These per-process variables are merged with (and override) the global environment for that single execution.
Outputs (Monitors)
Jay configures monitors through the [[outputs]] array. Each entry matches
one or more connected displays and applies settings such as position, scale,
mode, and color management.
Note
Output configuration defined in
config.tomlis only applied the first time a matching output is connected after the compositor starts. To change settings at runtime, usejay randror theconfigure-outputaction.
Matching outputs
Every [[outputs]] entry requires a match field that selects which monitors
the settings apply to. You can match by serial number, connector name,
manufacturer, or model.
When match is a table, all specified fields must match (AND logic). When
match is an array, any entry matching is sufficient (OR logic).
By serial number (recommended)
The serial number is a unique identifier that stays the same regardless of which port the monitor is plugged into:
[[outputs]]
match.serial-number = "33K03894SL0"
scale = 1.5
Run jay randr to find the serial number of your connected displays.
By connector name
[[outputs]]
match.connector = "DP-1"
scale = 1.25
Connector names (like DP-1, HDMI-A-1, eDP-1) can change if you move
cables between ports.
By manufacturer and model
[[outputs]]
match = { manufacturer = "BNQ", model = "BenQ GW2480" }
scale = 1.25
When multiple fields appear in a single table, all must match.
Combining criteria (OR)
Use an array to match any of several outputs with the same settings:
[[outputs]]
match = [
{ serial-number = "33K03894SL0" },
{ serial-number = "ETW1M02062SL0" },
]
scale = 2.0
Naming outputs
Assign a name to reference an output from other parts of the config – for
example, when mapping a tablet to a specific monitor or using shortcuts to
reconfigure outputs at runtime:
[[outputs]]
name = "left"
match.serial-number = "33K03894SL0"
x = 0
y = 0
[[outputs]]
name = "right"
match.serial-number = "ETW1M02062SL0"
x = 1920
y = 0
Other rules can then reference these names:
# Map a drawing tablet to the left monitor
[[inputs]]
match.name = "Wacom Intuos Pro M Pen"
output.name = "left"
# Rotate a named output with a shortcut
[shortcuts]
alt-r = {
type = "configure-output",
output = {
match.name = "right",
transform = "rotate-90",
},
}
Position
The x and y fields place outputs in compositor space. Coordinates are
integers >= 0 and represent the top-left corner of the output:
[[outputs]]
match.serial-number = "33K03894SL0"
x = 0
y = 0
[[outputs]]
match.serial-number = "ETW1M02062SL0"
x = 2560
y = 0
Tip
The control center (
alt-cby default) includes a visual output arrangement editor where you can drag monitors into position.
Scale
Set fractional scaling with a number greater than 0:
[[outputs]]
match.serial-number = "33K03894SL0"
scale = 1.5
Common values: 1.0 (no scaling), 1.25, 1.5, 2.0.
Transform
Rotate or flip the output. The available values are:
none- No transformation
rotate-90- Rotate 90 degrees counter-clockwise
rotate-180- Rotate 180 degrees
rotate-270- Rotate 270 degrees counter-clockwise
flip- Flip around the vertical axis
flip-rotate-90- Flip vertically, then rotate 90 degrees counter-clockwise
flip-rotate-180- Flip vertically, then rotate 180 degrees
flip-rotate-270- Flip vertically, then rotate 270 degrees counter-clockwise
[[outputs]]
match.serial-number = "33K03894SL0"
transform = "rotate-90"
Mode
Set the resolution and refresh rate with the mode field. If refresh-rate is
omitted, the first available mode with the specified resolution is used:
[[outputs]]
match.serial-number = "33K03894SL0"
mode = {
width = 2560,
height = 1440,
refresh-rate = 144.0,
}
Use jay randr to see all available modes for each output:
~$ jay randr show --modes
Variable Refresh Rate (VRR)
VRR (also known as FreeSync or Adaptive Sync) allows the display to vary its refresh rate to match the content being rendered, reducing stuttering and tearing.
Configure VRR with the vrr field:
[[outputs]]
match.serial-number = "33K03894SL0"
vrr = { mode = "variant1", cursor-hz = 90 }
VRR modes
never- VRR is always off (default)
always- VRR is always on
variant1- VRR is on when one or more applications are displayed fullscreen
variant2- VRR is on when exactly one application is displayed fullscreen
variant3- VRR is on when a single application is displayed fullscreen and describes its
content type as video or game via the
wp_content_type_v1protocol
Cursor refresh rate
When VRR is active, cursor movement can cause the screen to spike to maximum
refresh rate. The cursor-hz field limits cursor-triggered updates:
vrr = { mode = "always", cursor-hz = 90 }
Set cursor-hz = "none" for unbounded cursor updates (the default). A numeric
value means the cursor is updated at that rate in Hz, or faster if the
application is already driving updates above that rate.
You can also set default VRR settings for all outputs at the top level:
vrr = { mode = "variant1", cursor-hz = 90 }
Per-output settings override the top-level default.
Tearing
Tearing allows frames to be presented immediately instead of waiting for vertical blank, reducing input latency at the cost of visible tearing artifacts.
[[outputs]]
match.serial-number = "33K03894SL0"
tearing.mode = "variant3"
Tearing modes
never- Tearing is never enabled
always- Tearing is always enabled
variant1- Tearing is enabled when one or more applications are displayed fullscreen
variant2- Tearing is enabled when a single application is displayed fullscreen
variant3- Tearing is enabled when a single application is displayed and has requested tearing (default)
The default tearing mode is variant3. Like VRR, per-output settings override
top-level defaults:
tearing.mode = "never"
Framebuffer format
The default framebuffer format is xrgb8888. You can change it to any DRM
fourcc format:
[[outputs]]
match.serial-number = "33K03894SL0"
format = "rgb565"
Common formats include xrgb8888, argb8888, xbgr8888, abgr8888,
rgb565, and many others. See the
auto-generated specification for the full list.
HDR and color management
Jay supports HDR output through color space, transfer function, and brightness settings. These require the Vulkan renderer. For a conceptual overview and step-by-step guide, see HDR & Color Management.
Color space
[[outputs]]
match.serial-number = "33K03894SL0"
color-space = "bt2020"
Values: default (usually sRGB) or bt2020.
Transfer function (EOTF)
[[outputs]]
match.serial-number = "33K03894SL0"
transfer-function = "pq"
Values: default (usually gamma 2.2) or pq (Perceptual Quantizer, used for
HDR10).
Brightness
Set SDR content brightness in cd/m^2 or use "default":
[[outputs]]
match.serial-number = "33K03894SL0"
brightness = 80
The default depends on the transfer function:
- With
defaultEOTF: the maximum brightness of the display, anchored at 80 cd/m^2. Setting a value below 80 creates HDR headroom. - With
pq: 203 cd/m^2.
This setting has no effect unless the Vulkan renderer is in use.
Blend space
Controls how colors are blended when compositing overlapping surfaces:
[[outputs]]
match.serial-number = "33K03894SL0"
blend-space = "linear"
srgb- Classic desktop blending in sRGB space (default)
linear- Physically correct blending in linear light – produces brighter results
Native gamut
By default, Jay assumes displays use sRGB primaries (matching the behavior of most other compositors). In reality, many displays have a wider gamut.
Setting use-native-gamut = true tells Jay to use the primaries advertised in
the display’s EDID. This can produce more accurate colors and allows
color-managed applications to use the full gamut:
[[outputs]]
match.serial-number = "33K03894SL0"
use-native-gamut = true
This has no effect when the display is explicitly operating in a wide color space (e.g. BT.2020).
Connector configuration
The [[connectors]] array lets you enable or disable physical display
connectors. This is useful for permanently disabling a port:
[[connectors]]
match.name = "eDP-1"
enabled = false
Connector configuration is applied when the connector is first discovered by the compositor, which typically happens only at startup.
Lid switch (auto-disable laptop screen)
On laptops, you can automatically disable the built-in display when the lid is
closed using the [[inputs]] array. The lid switch is an input device:
[[inputs]]
match.name = "<lid switch name>"
on-lid-closed = {
type = "configure-connector",
connector = {
match.name = "eDP-1",
enabled = false,
},
}
on-lid-opened = {
type = "configure-connector",
connector = {
match.name = "eDP-1",
enabled = true,
},
}
Run jay input to find the name of your lid switch device. See the
Input Devices chapter for more details.
Runtime changes
Output settings in config.toml are only applied when a display is first
connected after compositor startup. For runtime changes, use the jay randr
CLI or the configure-output action.
Listing outputs
~$ jay randr
This shows all connected outputs with their connector names, serial numbers, current modes, scales, transforms, and available modes.
Changing settings at runtime
~$ jay randr output <name-or-connector> scale 1.5
~$ jay randr output <name-or-connector> mode 2560 1440 144.0
~$ jay randr output <name-or-connector> position 1920 0
~$ jay randr output <name-or-connector> transform rotate-90
~$ jay randr output <name-or-connector> enable
~$ jay randr output <name-or-connector> disable
Using shortcuts
The configure-output action lets you change output settings from a
keybinding:
[shortcuts]
alt-F7 = {
type = "configure-output",
output = {
match.name = "right",
transform = "none",
},
}
alt-F8 = {
type = "configure-output",
output = {
match.name = "right",
transform = "rotate-90",
},
}
Full reference
For the exhaustive list of all output fields, match criteria, and related types, see the auto-generated specification.
Input Devices
Jay configures input devices through the [[inputs]] array. Each entry matches
one or more devices and applies settings such as acceleration, tap behavior,
and device-to-output mapping.
Note
Input configuration defined in
config.tomlis only applied to devices connected after the configuration is loaded. To change settings for already-connected devices, usejay inputor theconfigure-inputaction.
Matching input devices
Every [[inputs]] entry requires a match field. When match is a
table, all specified fields must match (AND logic). When match is an
array, any entry matching is sufficient (OR logic).
By device name
[[inputs]]
match.name = "Logitech G300s Optical Gaming Mouse"
left-handed = true
Run jay input to see the names of all connected input devices.
By device type
Match all devices of a given type using boolean flags:
[[inputs]]
match.is-pointer = true
natural-scrolling = true
Available type flags: is-keyboard, is-pointer, is-touch,
is-tablet-tool, is-tablet-pad, is-gesture, is-switch.
By syspath or devnode
The syspath is usually stable across reboots and useful when you have
multiple identical devices:
[[inputs]]
match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.4/usb5/5-1/5-1.1/5-1.1.2/5-1.1.2:1.0"
left-handed = true
The devnode (e.g. /dev/input/event4) is typically not stable across
reboots.
Combining criteria
AND – all fields in a single table must match:
[[inputs]]
match = { name = "SynPS/2 Synaptics TouchPad", is-pointer = true }
tap-enabled = true
OR – any entry in an array may match:
[[inputs]]
match = [
{ name = "Logitech G300s Optical Gaming Mouse" },
{ name = "Razer DeathAdder V2" },
]
left-handed = true
Tagging devices
Assign a tag to reference a device from shortcuts or actions:
[[inputs]]
tag = "mouse"
match.is-pointer = true
[shortcuts]
alt-l = {
type = "configure-input",
input = {
match.tag = "mouse",
left-handed = true,
},
}
alt-r = {
type = "configure-input",
input = {
match.tag = "mouse",
left-handed = false,
},
}
Tags work similarly to output names – they let you refer to matched devices elsewhere in the configuration.
Libinput settings
These settings map directly to libinput device options. See the libinput documentation for detailed explanations of each.
Acceleration
[[inputs]]
match.is-pointer = true
accel-profile = "Flat"
accel-speed = 0.0
| Field | Values | Description |
|---|---|---|
accel-profile | Flat or Adaptive | Pointer acceleration curve |
accel-speed | -1.0 to 1.0 | Speed within the selected profile |
Tap and click
[[inputs]]
match.is-pointer = true
tap-enabled = true
tap-drag-enabled = true
tap-drag-lock-enabled = false
click-method = "clickfinger"
| Field | Values | Description |
|---|---|---|
tap-enabled | true / false | Tap-to-click on touchpads |
tap-drag-enabled | true / false | Tap-and-drag on touchpads |
tap-drag-lock-enabled | true / false | Keep drag active after lifting finger |
click-method | none, button-areas, clickfinger | How physical clicks are interpreted |
Other libinput options
[[inputs]]
match.is-pointer = true
left-handed = true
natural-scrolling = true
middle-button-emulation = true
left-handed- Swap left and right buttons
natural-scrolling- Reverse scroll direction (“macOS-style”)
- Simultaneous left+right click produces a middle click
Scroll speed
Control how many pixels each scroll wheel detent produces:
[[inputs]]
match.is-pointer = true
px-per-wheel-scroll = 30
This setting maps to the legacy wl_pointer.axis event that is mostly unused
nowadays. It has no effect on applications that don’t use this event.
Transform matrix
Apply a 2x2 matrix to relative motion events. This is useful for adjusting pointer speed independently of libinput acceleration:
[[inputs]]
match.is-pointer = true
transform-matrix = [[0.35, 0], [0, 0.35]]
The example above reduces pointer speed to 35% of normal. The identity matrix
is [[1, 0], [0, 1]].
Calibration matrix
A 2x3 matrix for absolute input devices (touchscreens). This is passed directly to libinput:
[[inputs]]
match.is-touch = true
calibration-matrix = [[0, 1, 0], [-1, 0, 1]]
The example above rotates touch input by 90 degrees.
Per-device keymap
Override the global keymap for a specific keyboard:
[[inputs]]
match.name = "ZSA Technology Labs Inc ErgoDox EZ"
keymap.rmlvo = {
layout = "us",
options = "compose:ralt",
}
The override becomes active when a key is pressed on that device. See the Keymaps & Repeat Rate chapter for the full range of keymap options.
Mapping to outputs
Map tablets and touchscreens to a specific output so that the input area corresponds to the correct display:
[[outputs]]
name = "left"
match.serial-number = "33K03894SL0"
[[inputs]]
match.name = "Wacom Bamboo Comic 2FG Pen"
output.name = "left"
You can also map by connector:
[[inputs]]
match.name = "Wacom Bamboo Comic 2FG Pen"
output.connector = "DP-1"
To remove a mapping at runtime, use the remove-mapping field in a
configure-input action:
[shortcuts]
alt-x = {
type = "configure-input",
input = {
match.tag = "wacom",
remove-mapping = true,
},
}
Lid switch events
Lid switch devices report when a laptop lid is opened or closed. Use the
on-lid-closed and on-lid-opened fields to trigger actions. These fields
only work in the top-level [[inputs]] array:
[[inputs]]
match.name = "<lid switch name>"
on-lid-closed = {
type = "configure-connector",
connector = {
match.name = "eDP-1",
enabled = false,
},
}
on-lid-opened = {
type = "configure-connector",
connector = {
match.name = "eDP-1",
enabled = true,
},
}
Run jay input to find the name of your lid switch device.
Convertible (2-in-1) events
For convertible laptops that switch between laptop and tablet form factors:
[[inputs]]
match.name = "<switch name>"
on-converted-to-laptop = "$enable-keyboard"
on-converted-to-tablet = "$disable-keyboard"
These fields only work in the top-level [[inputs]] array.
Runtime changes
Listing devices
~$ jay input
This shows all connected input devices with their names, syspaths, devnodes, type flags, and current settings.
Changing settings at runtime
~$ jay input device <id> set-accel-profile flat
~$ jay input device <id> set-accel-speed 0.5
~$ jay input device <id> set-tap-enabled true
~$ jay input device <id> set-left-handed true
~$ jay input device <id> set-natural-scrolling true
~$ jay input device <id> set-transform-matrix 0.35 0 0 0.35
Using shortcuts
The configure-input action applies settings from a keybinding:
[shortcuts]
alt-n = {
type = "configure-input",
input = {
match.tag = "touchpad",
natural-scrolling = true,
},
}
Full reference
For the exhaustive list of all input fields, match criteria, and related types, see the auto-generated specification.
GPUs
Jay configures graphics cards (DRM devices) through the [[drm-devices]]
array. This is primarily useful on multi-GPU systems for selecting the render
device, choosing a graphics API, and tuning per-device settings.
Note
DRM device configuration in
config.tomlis only applied when a device is first discovered after the configuration is loaded. To change settings at runtime, usejay randror theconfigure-drm-deviceaction.
Matching GPUs
Every [[drm-devices]] entry requires a match field. When match is a
table, all specified fields must match (AND). When match is an array,
any entry matching is sufficient (OR).
By PCI vendor and model (recommended)
PCI IDs are stable, unique identifiers. Use jay randr to find them:
~$ jay randr
[[drm-devices]]
match = {
pci-vendor = 0x1002,
pci-model = 0x73ff,
}
gfx-api = "Vulkan"
By vendor or model name
[[drm-devices]]
match.vendor = "Advanced Micro Devices, Inc. [AMD/ATI]"
[[drm-devices]]
match.model = "Raphael"
By syspath or devnode
The syspath is usually stable across reboots:
[[drm-devices]]
match.syspath = "/sys/devices/pci0000:00/0000:00:08.1/0000:14:00.0"
The devnode (e.g. /dev/dri/card0) is typically not stable.
Naming GPUs
Assign a name to reference a device from other parts of the config:
[[drm-devices]]
name = "dedicated"
match = {
pci-vendor = 0x1002,
pci-model = 0x73ff,
}
[[drm-devices]]
name = "integrated"
match = {
pci-vendor = 0x1002,
pci-model = 0x164e,
}
Names can then be used in render-device, shortcuts, and actions:
render-device.name = "dedicated"
[shortcuts]
alt-v = {
type = "configure-drm-device",
dev = {
match.name = "dedicated",
gfx-api = "Vulkan",
},
}
alt-o = {
type = "configure-drm-device",
dev = {
match.name = "dedicated",
gfx-api = "OpenGl",
},
}
Graphics API
Jay supports two rendering backends per device:
Vulkan- Uses libvulkan. The primary renderer – use this unless you have a specific reason not to. Required for HDR. All devices in the system must support DRM format modifiers (most do, except AMD GPUs older than RX 5000).
OpenGl- Uses libEGL + libGLESv2. Maintained for backwards compatibility only. No new features will be added to this renderer.
Per-device API
[[drm-devices]]
match = {
pci-vendor = 0x1002,
pci-model = 0x73ff,
}
gfx-api = "Vulkan"
Default API for all devices
Set the top-level gfx-api to apply to any device without a per-device
override:
gfx-api = "Vulkan"
This only takes effect for devices discovered after the config is loaded.
Direct scanout
Direct scanout lets the compositor hand a client’s buffer directly to the display hardware, bypassing composition. This can reduce latency and power usage, but may cause visual glitches with some hardware or applications.
Per-device
[[drm-devices]]
match = {
pci-vendor = 0x1002,
pci-model = 0x73ff,
}
direct-scanout = false
Global toggle
direct-scanout = false
Flip margin
The flip margin is the time (in milliseconds) between the compositor initiating a page flip and the output’s vertical blank event. It determines the minimum achievable input latency. The default is 1.5 ms.
[[drm-devices]]
match = {
pci-vendor = 0x1002,
pci-model = 0x73ff,
}
flip-margin-ms = 2.0
If the margin is set too small, the compositor will dynamically increase it to avoid missed frames.
Explicit sync
Explicit sync coordinates buffer access between the compositor and GPU drivers. It is enabled by default and generally should not be disabled:
explicit-sync = true
Warning
This setting cannot be changed after the compositor has started. It can only be set in
config.tomlbefore launching Jay.
Render device
On multi-GPU systems, select which GPU performs compositing with the
render-device field. The first matching device is used:
render-device.name = "dedicated"
[[drm-devices]]
name = "dedicated"
match = {
pci-vendor = 0x1002,
pci-model = 0x73ff,
}
[[drm-devices]]
name = "integrated"
match = {
pci-vendor = 0x1002,
pci-model = 0x164e,
}
gfx-api = "OpenGl"
You can also match directly without naming:
render-device = {
pci-vendor = 0x1002,
pci-model = 0x73ff,
}
Note
Changing the render device at runtime (via the
set-render-deviceaction) may cause windows to become invisible until they are resized or otherwise redrawn.
Runtime changes
Listing GPUs
~$ jay randr
This shows all DRM devices with their PCI IDs, vendor/model names, current API, and other settings.
Changing settings at runtime
Use jay randr subcommands:
~$ jay randr card <card> api vulkan
~$ jay randr card <card> direct-scanout enable
~$ jay randr card <card> primary
Using shortcuts
The configure-drm-device action applies settings from a keybinding:
[shortcuts]
alt-F5 = {
type = "configure-drm-device",
dev = {
match.name = "dedicated",
gfx-api = "Vulkan",
},
}
alt-F6 = {
type = "configure-drm-device",
dev = {
match.name = "dedicated",
gfx-api = "OpenGl",
},
}
The set-render-device action switches the compositing GPU:
[shortcuts]
alt-F7 = { type = "set-render-device", dev.name = "dedicated" }
alt-F8 = { type = "set-render-device", dev.name = "integrated" }
Hardware cursor
The use-hardware-cursor top-level setting controls whether the hardware cursor
plane is used. Disabling this forces software cursor rendering, which can be
useful for debugging.
use-hardware-cursor = true # default
Full reference
For the exhaustive list of all DRM device fields, match criteria, and related types, see the auto-generated specification.
Idle & Screen Locking
Jay can detect when the system is idle and automatically run an action – most commonly launching a screen locker. The idle timeout, grace period, and on-idle action are all configurable.
Idle timeout
Set how long the system must be idle before the on-idle action fires. Specify
minutes and/or seconds:
idle.minutes = 10
idle = { minutes = 5, seconds = 30 }
If all values are explicitly set to 0, the idle timeout is disabled entirely:
idle = { minutes = 0 }
Note
The idle timeout defined in
config.tomlcannot be changed by reloading the configuration. Usejay idleor theconfigure-idleaction to change it at runtime.
Grace period
The grace period is a warning phase between the timeout expiring and the actual
idle event. During the grace period, the screen goes black but outputs are not
yet disabled and the on-idle action has not yet fired. Any input during this
period cancels the idle transition.
The default grace period is 5 seconds. Configure it with:
idle.grace-period.seconds = 3
idle.grace-period = { minutes = 0, seconds = 10 }
Set both values to 0 to disable the grace period (immediately fire the on-idle action when the timeout expires):
idle.grace-period = { seconds = 0 }
On-idle action
The on-idle field defines what happens when the idle timeout (plus grace
period) elapses. The most common use is launching a screen locker:
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
Important
Screen lockers that use the Wayland session lock protocol (like swaylock) need
privileged = truein the exec configuration. This grants the process the necessary permissions to lock the session.
You can also combine multiple actions:
on-idle = [
{
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
},
{ type = "exec", exec = ["notify-send", "System locked"] },
]
Complete example
A typical idle and screen-locking setup:
idle = {
minutes = 10,
grace-period = { seconds = 5 },
}
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
This means:
- After 10 minutes of inactivity, the screen goes black (grace period begins).
- If no input occurs within 5 seconds, swaylock is launched and outputs are disabled.
- Any input during the grace period cancels the transition and restores the display.
Runtime changes
Checking idle status
~$ jay idle status
Changing the idle timeout
~$ jay idle set 5m
~$ jay idle set 1m30s
~$ jay idle set disabled
Changing the grace period
~$ jay idle set-grace-period 10s
~$ jay idle set-grace-period 0s
Duration format
The CLI accepts durations in a flexible format:
| Example | Meaning |
|---|---|
1m | 1 minute |
1m5s | 1 minute 5 seconds |
1min 5sec | 1 minute 5 seconds |
90s | 90 seconds |
disabled | Disable the timeout |
Unlocking
If the compositor is locked (e.g. the screen locker crashed), you can unlock
it from another TTY or by SSH-ing into the machine. You must set
WAYLAND_DISPLAY to the socket of the Jay compositor, since you are running
the command outside the compositor session:
~$ WAYLAND_DISPLAY=wayland-1 jay unlock
Use jay pid with the same WAYLAND_DISPLAY value to verify you are
targeting the correct compositor instance.
Using shortcuts
The configure-idle action lets you change idle settings from a keybinding:
[shortcuts]
alt-F9 = {
type = "configure-idle",
idle = {
minutes = 5,
grace-period = { seconds = 3 },
},
}
alt-F10 = {
type = "configure-idle",
idle = { minutes = 0 },
}
Full reference
For the exhaustive list of all idle-related fields and types, see the auto-generated specification.
Theme & Appearance
Jay’s visual appearance – colors, fonts, sizes, and layout – is controlled by
the [theme] table and a handful of top-level toggles. Every setting described
here can also be adjusted at runtime from the control center’s Look and Feel
pane, which provides color pickers and live preview.
Colors
Colors are specified as hex strings in one of four formats:
#rgb(e.g.#f00)- Short RGB
#rrggbb(e.g.#ff0000)- Full RGB
#rgba(e.g.#f008)- Short RGB + alpha
#rrggbbaa(e.g.#ff000080)- Full RGB + alpha
The available color keys in the [theme] table are:
bg-color- Desktop background
bar-bg-color- Bar background
bar-status-text-color- Status text in the bar
border-color- Borders between tiled windows
focused-title-bg-color- Background of the focused window’s title
focused-title-text-color- Text color of the focused window’s title
unfocused-title-bg-color- Background of unfocused window titles
unfocused-title-text-color- Text color of unfocused window titles
focused-inactive-title-bg-color- Background of focused-but-inactive titles
focused-inactive-title-text-color- Text color of focused-but-inactive titles
attention-requested-bg-color- Background of titles that have requested attention
captured-focused-title-bg-color- Background of focused titles that are being recorded
captured-unfocused-title-bg-color- Background of unfocused titles that are being recorded
separator-color- Separator between title bars and window content
highlight-color- Accent color used to highlight parts of the UI
“Focused-inactive” refers to a window that was most recently focused in its container but whose container is not the active one. The “captured” colors apply when a window is being recorded (e.g. via screen sharing).
Example
[theme]
bg-color = "#1e1e2e"
bar-bg-color = "#181825"
bar-status-text-color = "#cdd6f4"
border-color = "#313244"
focused-title-bg-color = "#89b4fa"
focused-title-text-color = "#1e1e2e"
unfocused-title-bg-color = "#313244"
unfocused-title-text-color = "#cdd6f4"
attention-requested-bg-color = "#f38ba8"
highlight-color = "#f5c2e7"
Sizes
border-width- Width of borders between windows (px)
title-height- Height of window title tabs (px)
bar-height- Height of the bar (px). Defaults to the same as
title-height. bar-separator-width- Width of the bar’s bottom separator (px). Default:
1.
[theme]
border-width = 2
title-height = 24
bar-height = 28
Fonts
font- General font for the compositor
title-font- Font used in window title bars. Defaults to the same as
font. bar-font- Font used in the status bar. Defaults to the same as
font.
[theme]
font = "JetBrains Mono 10"
title-font = "Inter 10"
bar-font = "Inter 10"
Bar Position
The bar-position field controls whether the bar appears at the top or bottom
of each output. The default is top.
[theme]
bar-position = "bottom"
Changing the Theme at Runtime
Use the set-theme action in a shortcut to change theme properties on the fly:
[shortcuts]
alt-F9 = { type = "set-theme", theme.bg-color = "#000000" }
Only the fields you include are changed; everything else stays the same.
Showing and Hiding UI Elements
These top-level settings control whether the bar and title bars are visible:
show-bar- Show the built-in status bar. Default:
true. show-titles- Show window title bars. Default:
true.
show-bar = false
show-titles = false
Corresponding actions let you toggle these at runtime:
show-bar- Shows the bar
hide-bar- Hides the bar
toggle-bar- Toggles bar visibility
show-titles- Shows title bars
hide-titles- Hides title bars
toggle-titles- Toggles title bars
[shortcuts]
alt-b = "toggle-bar"
alt-t = "toggle-titles"
Workspace Display Order
The workspace-display-order top-level setting controls how workspace tabs
appear in the bar:
manual- Workspaces can be reordered by dragging (default)
sorted- Workspaces are displayed in alphabetical order
workspace-display-order = "sorted"
Status Bar
Jay includes a built-in bar that displays workspace tabs, status text, tray
icons (via wl-tray-bridge), and a
clock. The status text is provided by an external program that you configure
in the [status] table.
Configuring a Status Program
The [status] table has three fields:
format- Message format:
plain,pango, ori3bar. Optional. exec- How to start the program (string, array, or table). Required.
i3bar-separator- Separator between i3bar components (default
" | "). Optional.
Format
plain- Plain text output
pango- Output containing Pango markup
i3bar- JSON output in i3bar protocol format
Exec
The exec field accepts the same forms used elsewhere in the configuration:
- String – the program name:
exec = "i3status" - Array – program and arguments:
exec = ["i3status", "-c", "~/.config/i3status/config"] - Table – full control over program, arguments, and environment (see the spec for details)
Example: i3status
i3status is a popular status line generator.
[status]
format = "i3bar"
exec = "i3status"
Note
i3status defaults to plain-text output. You must explicitly configure it to use i3bar format by adding
output_format = "i3bar"to your~/.config/i3status/config:general { output_format = "i3bar" }
Example: Custom Script
A simple shell script that prints the date every second:
[status]
format = "plain"
exec = { shell = "while true; do date '+%Y-%m-%d %H:%M:%S'; sleep 1; done" }
Changing the Status Program at Runtime
The set-status action lets you switch the status program from a shortcut.
Omit the status field to reset the status text to empty.
[shortcuts]
# Switch to i3status
alt-F10 = {
type = "set-status",
status = {
format = "i3bar",
exec = "i3status",
},
}
# Clear the status text
alt-F11 = { type = "set-status" }
Bar Appearance
The bar’s visual appearance (height, background color, text color, position, and
font) is configured in the [theme] table. See the
Theme & Appearance chapter for details. The bar can be shown or
hidden with the show-bar top-level setting or the toggle-bar action.
Xwayland
Xwayland provides compatibility with legacy X11 applications inside a Wayland session. Jay starts Xwayland automatically by default.
Configuration
The [xwayland] table controls Xwayland behavior:
enabled- Whether Xwayland is started. Default:
true. scaling-mode- How X11 windows are scaled on HiDPI outputs. Default:
default.
[xwayland]
enabled = true
scaling-mode = "default"
Scaling Modes
default- Render at the lowest scale, then upscale to other outputs
downscaled- Render at the highest integer scale, then downscale to match each output
The downscaled mode produces sharper text and UI on HiDPI monitors but has a
significant performance cost. For example, on a 3840x2160 output at 1.5x scale,
a fullscreen X11 window would be rendered at 5120x2880 (integer scale 2) and
then downscaled – roughly doubling the pixel count. This mode also requires
X11 applications to handle scaling themselves (e.g. GDK_SCALE=2).
CLI
You can inspect and change Xwayland settings from the command line:
~$ jay xwayland status
~$ jay xwayland set-scaling-mode default
~$ jay xwayland set-scaling-mode downscaled
Xwayland settings are also available in the control center’s Xwayland pane.
Disabling Xwayland
If you don’t need X11 compatibility, disabling Xwayland avoids starting the X server entirely:
[xwayland]
enabled = false
Matching X11 Windows in Rules
Window rules can target X11 windows using properties that only exist on X
clients. These fields are available in the match table of [[windows]]
rules:
x-class/x-class-regex- Match the X11 WM_CLASS class (verbatim or regex)
x-instance/x-instance-regex- Match the X11 WM_CLASS instance (verbatim or regex)
x-role/x-role-regex- Match the X11 WM_WINDOW_ROLE (verbatim or regex)
For example, to float all GIMP tool windows:
[[windows]]
match.x-class = "Gimp"
match.x-role-regex = "gimp-(toolbox|dock)"
initial-tile-state = "floating"
Matching at the Client Level
Client rules support the is-xwayland field to match (or exclude) the
Xwayland client itself:
[[clients]]
match.is-xwayland = true
# ... grant capabilities, etc.
Miscellaneous
This chapter covers smaller configuration options that don’t warrant their own chapter.
Color Management
The Wayland color management protocol lets applications communicate color space information to the compositor. It is disabled by default.
[color-management]
enabled = true
Note
Changing this setting has no effect on applications that are already running.
The CLI and control center (Color Management pane) can also toggle it:
~$ jay color-management enable
~$ jay color-management disable
~$ jay color-management status
See HDR & Color Management for a complete guide to HDR output and the color management protocol.
Libei
libei allows applications to
emulate input events. By default, applications can only access libei through
the portal (which prompts the user for permission). Setting enable-socket
exposes an unauthenticated socket that any application can use without a prompt.
libei.enable-socket = false # default
UI Drag
Controls whether workspaces and tiles can be dragged with the mouse, and how far the pointer must move before a drag begins.
ui-drag = { enabled = true, threshold = 10 } # defaults
Set enabled = false to disable drag-and-drop rearrangement entirely. Increase
threshold if you find yourself accidentally starting drags.
Floating Window Pin Icon
Floating windows can show a small pin icon. This is hidden by default.
[float]
show-pin-icon = true
Workspace Capture
Controls whether newly created workspaces can be captured (e.g. for screen
sharing). The default is true.
workspace-capture = false
Simple Input Method
Jay includes a built-in XCompose-based input method. It is enabled by default but only activates when no external input method is running.
[simple-im]
enabled = true # default
Related actions for use in shortcuts:
enable-simple-im- Enable the built-in input method
disable-simple-im- Disable the built-in input method
toggle-simple-im-enabled- Toggle the built-in input method
reload-simple-im- Reload XCompose files without restarting
enable-unicode-input- Start Unicode codepoint input (requires active IM)
Log Level
Sets the compositor’s log verbosity. Valid values: trace, debug, info,
warn, error.
log-level = "info"
This setting cannot be changed by reloading the configuration. Use the CLI instead:
~$ jay set-log-level debug
Focus Follows Mouse
When enabled, moving the pointer over a window automatically gives it keyboard focus.
focus-follows-mouse = true # default
Window Management Key
Designates a key that, while held, enables window management mode. In this mode, the left mouse button moves floating windows and the right mouse button resizes any window.
window-management-key = "Alt_L"
The value should be a keysym name (see the
xkbcommon keysym list
with the XKB_KEY_ prefix removed).
Middle-Click Paste
Controls whether middle-clicking pastes the primary selection. Changing this has no effect on running applications.
middle-click-paste = true # default
Pointer Revert Key
Pressing this key cancels any active grabs, drags, or selections, returning the
pointer to its default state. The default is Escape.
pointer-revert-key = "Escape" # default
Set it to NoSymbol to disable this functionality entirely:
pointer-revert-key = "NoSymbol"
Fallback Output Mode
Determines which output is used when no particular output is specified – for
example, when placing a newly opened window or choosing which workspace to move
with move-to-output.
fallback-output-mode = "cursor" # default
Focus History
Configures the behavior of the focus-prev and focus-next actions.
only-visible- Only cycle to windows that are already visible. Default:
false. same-workspace- Only cycle to windows on the current workspace. Default:
false.
If only-visible is false, switching to a non-visible window will make it
visible first.
[focus-history]
only-visible = true
same-workspace = true
Control Center Fonts
The [egui] table configures fonts used by the control center (an egui-based
GUI).
[egui]
proportional-fonts = ["sans-serif", "Noto Sans", "Noto Color Emoji"] # default
monospace-fonts = ["monospace", "Noto Sans Mono", "Noto Color Emoji"] # default
Override these lists to use your preferred fonts in the control center UI.
Tiling
Jay uses an i3-like tiling layout. Windows are arranged automatically in containers that can be split horizontally or vertically. Containers can be nested to create complex layouts.
Splitting Containers
When you split a window, Jay wraps it in a new container with the specified direction. Subsequent windows opened in that container are placed side by side (horizontal split) or stacked top to bottom (vertical split).
alt-d–split-horizontal- Split the focused window horizontally
alt-v–split-vertical- Split the focused window vertically
alt-t–toggle-split- Toggle the container’s split direction
You can also set the direction explicitly without toggling:
[shortcuts]
alt-shift-d = "tile-horizontal"
alt-shift-v = "tile-vertical"
The tile-horizontal action sets the container to horizontal, and
tile-vertical sets it to vertical – unlike split-horizontal/split-vertical
which wrap the window in a new container first.
Moving Focus
Move keyboard focus between windows with the directional focus actions:
alt-h–focus-left- Move focus left
alt-j–focus-down- Move focus down
alt-k–focus-up- Move focus up
alt-l–focus-right- Move focus right
Focus crosses container boundaries, so you can navigate across your entire layout with these four keys.
Moving Windows
Move the focused window within or between containers:
alt-shift-h–move-left- Move window left
alt-shift-j–move-down- Move window down
alt-shift-k–move-up- Move window up
alt-shift-l–move-right- Move window right
When a window reaches the edge of its container, the move action pushes it into the adjacent container.
Focus Parent
Press alt-f (focus-parent) to move focus from a window to its parent
container. This is useful when you want to operate on an entire group of
windows at once. For example, focusing a parent container and then using
move-left moves the whole group rather than a single window.
Mono Mode
By default, a container shows all its children side by side. Mono mode changes this so only one child is visible at a time, similar to a tabbed view.
alt-m–toggle-mono- Toggle between mono and side-by-side
You can also right-click any title in a container to toggle mono mode.
In mono mode, scroll over the title bar to cycle between windows in the container.
For explicit control without toggling:
[shortcuts]
alt-s = "show-single" # Enter mono mode
alt-a = "show-all" # Exit mono mode
Fullscreen
Press alt-u (toggle-fullscreen) to make the focused window fill the entire
output, hiding the bar and other windows. Press it again to return to the tiled
layout.
For explicit control:
[shortcuts]
alt-shift-u = "enter-fullscreen"
alt-ctrl-u = "exit-fullscreen"
Resizing Tiles
Drag the separators between tiles with the mouse to resize them. The separator changes the cursor to a resize indicator when hovered.
In window management mode, you can also right-drag anywhere on a tile to resize it without needing to target the separator.
Closing Windows
Press alt-shift-c (close) to request the focused window to close. This
sends a polite close request to the application – it is not a forceful kill.
[shortcuts]
alt-shift-c = "close"
Toggling Floating
Double-click a tile’s title bar to toggle it between tiled and floating. See Floating Windows for more details.
Summary of Tiling Actions
split-horizontal- Wrap focused window in a horizontal container
split-vertical- Wrap focused window in a vertical container
toggle-split- Toggle container split direction
tile-horizontal- Set container direction to horizontal
tile-vertical- Set container direction to vertical
focus-left/right/up/down- Move keyboard focus
move-left/right/up/down- Move focused window
focus-parent- Focus the parent container
toggle-mono- Toggle mono mode
show-single- Enter mono mode
show-all- Exit mono mode
toggle-fullscreen- Toggle fullscreen
enter-fullscreen- Enter fullscreen
exit-fullscreen- Exit fullscreen
close- Request focused window to close
toggle-floating- Toggle between tiled and floating
Workspaces
Workspaces are virtual desktops that group windows together. Each workspace lives on an output (monitor) and contains its own tiling layout. Jay creates workspaces on demand and automatically manages them when monitors are connected or disconnected.
Switching Workspaces
Use the show-workspace action to switch to a workspace. In the default
configuration, alt-F1 through alt-F12 switch to workspaces named “1”
through “12”:
[shortcuts]
alt-F1 = { type = "show-workspace", name = "1" }
alt-F2 = { type = "show-workspace", name = "2" }
alt-F3 = { type = "show-workspace", name = "3" }
# ... and so on through alt-F12
If the workspace does not yet exist, it is created on the output that currently contains the cursor. You can override this by specifying an output:
[shortcuts]
alt-F1 = {
type = "show-workspace",
name = "1",
output.name = "left",
}
You can also scroll over the bar to cycle through workspaces on that output.
Moving Windows to Workspaces
Use the move-to-workspace action to send the focused window to a different
workspace. The default bindings are alt-shift-F1 through alt-shift-F12:
[shortcuts]
alt-shift-F1 = { type = "move-to-workspace", name = "1" }
alt-shift-F2 = { type = "move-to-workspace", name = "2" }
alt-shift-F3 = { type = "move-to-workspace", name = "3" }
# ... and so on through alt-shift-F12
You can also drag a tile’s title onto a workspace tab in the bar to move it to that workspace. Dragging a tile onto the bar outside any workspace tab creates a new workspace for it.
Moving Workspaces Between Outputs
The move-to-output action moves a workspace to a different output. You can
target the output by name or by direction:
[shortcuts]
# Move the current workspace to a named output
alt-o = { type = "move-to-output", output.name = "right" }
# Move the current workspace in a direction
logo-ctrl-shift-Right = {
type = "move-to-output",
direction = "right",
}
logo-ctrl-shift-Left = {
type = "move-to-output",
direction = "left",
}
logo-ctrl-shift-Up = {
type = "move-to-output",
direction = "up",
}
logo-ctrl-shift-Down = {
type = "move-to-output",
direction = "down",
}
You can also move a specific workspace by name:
[shortcuts]
alt-o = {
type = "move-to-output",
workspace = "1",
output.name = "right",
}
If workspace is omitted, the currently active workspace is moved.
Workspace Display Order
Workspaces appear as tabs in the bar. Their order can be configured in two modes:
- manual (default) – workspaces appear in the order they were created and can be reordered by dragging their titles in the bar.
- sorted – workspaces are sorted alphabetically. Dragging to reorder is disabled.
Set the order in your configuration:
workspace-display-order = "sorted"
You can also change this at runtime in the control center.
Hot-Plug and Hot-Unplug
Jay handles monitor connections gracefully:
- When a monitor is unplugged, its workspaces are automatically migrated to one of the remaining monitors.
- When the monitor is plugged back in, those workspaces are restored to it.
This means you never lose your workspace layout when docking or undocking a laptop.
Workspace Capture
By default, newly created workspaces can be captured for screen sharing. You can disable this globally:
workspace-capture = false
When workspace capture is enabled, screen-sharing applications can share individual workspaces (in addition to full outputs and individual windows). See Screen Sharing for more details.
Matching Windows by Workspace
In window rules, you can match windows based on the workspace they are on:
[[windows]]
match.workspace = "music"
action = "enter-fullscreen"
The workspace-regex field is also available for pattern matching:
[[windows]]
match.workspace-regex = "^(music|video)$"
action = "enter-fullscreen"
Since window rules are reactive, these rules are re-evaluated whenever a window moves to a different workspace.
Floating Windows
Floating windows are not part of the tiling layout. They hover above tiled windows and can be freely moved and resized. Any tiled window can be made floating, and any floating window can be returned to the tiling layout.
Toggling Between Tiled and Floating
alt-shift-f–toggle-floating- Toggle the focused window between tiled and floating
You can also double-click a window’s title bar to toggle between the two states.
For explicit control without toggling:
[shortcuts]
alt-shift-t = "tile" # Make the focused window tiled
alt-shift-g = "float" # Make the focused window floating
Moving Floating Windows
Drag a floating window’s title bar to move it. The window follows the cursor until you release the mouse button.
In window management mode, you can left-drag anywhere on a floating window to move it – you do not need to target the title bar.
Resizing Floating Windows
Drag a floating window’s border to resize it. The cursor changes to a resize indicator when hovering over the border.
In window management mode, you can right-drag anywhere on a floating window to resize it.
Pinning
A pinned floating window stays visible across workspace switches. This is useful for things like a sticky terminal, a video player, or a chat window that you want on screen at all times.
pin-float- Pin the focused floating window
unpin-float- Unpin the focused floating window
toggle-float-pinned- Toggle pinning on the focused floating window
Example shortcut configuration:
[shortcuts]
alt-shift-p = "toggle-float-pinned"
You can also right-click a floating window’s title bar to pin or unpin it.
Pin Icon
By default, the pin icon is hidden. Enable it to get a clickable pin indicator on floating windows:
[float]
show-pin-icon = true
When the pin icon is visible, click it to pin or unpin the window.
Float Above Fullscreen
By default, floating windows are hidden below fullscreen windows. You can change this so floating windows render above fullscreen windows:
enable-float-above-fullscreen- Floating windows render above fullscreen windows
disable-float-above-fullscreen- Floating windows are hidden below fullscreen windows
toggle-float-above-fullscreen- Toggle the behavior
Example:
[shortcuts]
alt-shift-a = "toggle-float-above-fullscreen"
This is a global setting – it affects all floating windows, not just the focused one.
Window Rules: Initial Tile State
You can force specific windows to start as floating (or tiled) using the
initial-tile-state field in a window rule:
[[windows]]
match.app-id = "pavucontrol"
initial-tile-state = "floating"
[[windows]]
match.app-id = "mpv"
initial-tile-state = "floating"
Valid values are "floating" and "tiled".
Window Management Mode
Window management mode makes it easier to move and resize windows without
needing to target specific UI elements like title bars or borders. Set the
window-management-key to a keysym – holding that key activates the mode:
window-management-key = "Alt_L"
While the key is held:
- Left-drag anywhere on a floating window to move it.
- Right-drag anywhere on a floating window to resize it.
This mode also works with tiled windows and popups. See Mouse Interactions for the full list of interactions available in this mode.
Note
Entering window management mode disables all pointer constraints. This can be used to break out of games or other applications that grab the pointer, but it also means pointer-dependent applications will behave differently while the key is held.
Mouse Interactions
Jay supports a range of mouse-driven interactions for managing windows, workspaces, and layout. This page is a comprehensive reference for all of them.
Tiling
Resizing tiles. Drag the separator between two tiles to resize them. The cursor changes to a resize indicator when hovering over a separator.
Moving tiles. Drag a tile’s title bar to move it within or between containers. While dragging:
- Drop it onto another position in a container to rearrange tiles.
- Drop it onto a workspace tab in the bar to move it to that workspace.
- Drop it onto the bar outside any workspace tab to create a new workspace for it.
Double-click a tile’s title to toggle it between tiled and floating. See Floating Windows for details.
Right-click any title in a container to toggle mono mode (showing one window at a time vs. all side by side). See Tiling – Mono Mode.
Scroll over a title in mono mode to cycle between the tiles in that container.
Floating Windows
Drag a floating window’s title to move it.
Drag a floating window’s border to resize it.
Double-click a floating window’s title to toggle it back to tiled.
Right-click a floating window’s title to pin or unpin it. Pinned floating windows stay visible across workspace switches.
Click the pin icon (if visible) to pin or unpin the window. The pin icon can be enabled with:
[float]
show-pin-icon = true
Workspaces
Scroll over the bar to switch between workspaces on that output.
Drag workspace titles in the bar to reorder them. This only works in manual display order mode (the default). See Workspaces – Display Order.
Window Management Mode
Window management mode enables additional mouse interactions that do not require
targeting specific UI elements. Configure it by setting window-management-key
to a keysym:
window-management-key = "Alt_L"
Hold the configured key to enter window management mode. While held:
- Left-drag anywhere on a floating window, tile, popup, or fullscreen window to move it.
- Right-drag anywhere on a floating window, tile, or popup to resize it.
This is especially useful for:
- Moving or resizing floating windows without needing to precisely target the title bar or border.
- Moving tiled windows without targeting the title bar.
- Moving fullscreen windows (not possible without this mode).
- Resizing tiles without targeting the separator.
Note
Entering window management mode disables all pointer constraints. This means you can use it to move the pointer out of applications that have grabbed it (such as games in fullscreen), but pointer-dependent applications will behave differently while the key is held.
Other
Toplevel selection. Some actions (like screen sharing) ask you to select a window, indicated by a purple overlay. During this selection, right-click a tile’s title to select the entire container instead of an individual tile.
Canceling interactions. Press Escape to cancel any in-progress mouse
interaction (dragging, resizing, selection, etc.). The cancel key can be changed
with the pointer-revert-key configuration:
# Use a different key to cancel mouse interactions
pointer-revert-key = "grave"
# Disable the cancel key entirely
pointer-revert-key = "NoSymbol"
The default is Escape.
Input Modes
Jay supports an input mode stack that enables vim-style modal keybindings. When a mode is active, its shortcuts take effect instead of (or in addition to) the top-level shortcuts. Multiple modes can be stacked, and the topmost mode always wins.
Defining Modes
Modes are defined in the [modes] table. Each mode is a named sub-table that
can contain its own shortcuts and complex-shortcuts:
[modes."move".shortcuts]
h = "move-left"
j = "move-down"
k = "move-up"
l = "move-right"
Escape = "pop-mode"
Inheritance
By default, a mode inherits all shortcuts from the top-level [shortcuts]
table. Any key not explicitly defined in the mode falls through to the
inherited shortcuts.
To inherit from a different mode instead, set the parent field:
[modes."navigation"]
parent = "base"
[modes."navigation".shortcuts]
w = "focus-up"
a = "focus-left"
s = "focus-down"
d = "focus-right"
Here, navigation inherits from the mode called base rather than from the
top-level shortcuts.
Unbinding Inherited Shortcuts
Use "none" as the action value to unbind a shortcut that would otherwise be
inherited:
[modes."locked".shortcuts]
# Disable alt-q (quit) while in this mode
alt-q = "none"
Activating and Deactivating Modes
Jay provides four actions for managing the mode stack:
push-mode- Push a named mode onto the stack. It stays active until explicitly removed.
latch-mode- Temporarily push a mode. It auto-pops after the next shortcut fires.
pop-mode- Remove the topmost mode from the stack.
clear-modes- Clear the entire mode stack, returning to top-level shortcuts.
push-mode
[shortcuts]
alt-r = { type = "push-mode", name = "resize" }
After pressing alt-r, the resize mode’s shortcuts take effect. It remains
active until something pops it.
latch-mode
[shortcuts]
alt-x = { type = "latch-mode", name = "navigation" }
After pressing alt-x, the navigation mode is pushed. The very next shortcut
that fires will use the mode’s bindings, and then the mode is automatically
popped. This is ideal for leader key patterns.
pop-mode
[modes."resize".shortcuts]
Escape = "pop-mode"
Pressing Escape while in resize mode removes it from the stack.
clear-modes
[modes."resize".shortcuts]
ctrl-c = "clear-modes"
This removes all modes from the stack at once, returning to the top-level shortcuts regardless of how many modes have been stacked.
Practical Examples
Move Mode
A dedicated mode for moving windows with h/j/k/l, activated by
alt-r and exited with Escape:
[shortcuts]
alt-r = { type = "push-mode", name = "move" }
[modes."move".shortcuts]
h = "move-left"
j = "move-down"
k = "move-up"
l = "move-right"
Escape = "pop-mode"
While in move mode, pressing h moves the focused window left, l moves it
right, and so on – without needing a modifier key. All other inherited
shortcuts (like alt-q to quit) remain available. Press Escape to return
to normal.
Leader Key
A leader key pattern where pressing alt-x followed by one key triggers a
mode shortcut, then immediately returns to normal:
[shortcuts]
alt-x = { type = "latch-mode", name = "leader" }
[modes."leader".shortcuts]
t = { type = "exec", exec = "alacritty" }
b = { type = "exec", exec = "firefox" }
f = { type = "exec", exec = "thunar" }
q = "quit"
Press alt-x then t to launch a terminal. Because latch-mode auto-pops
after one shortcut, you are immediately back to normal shortcuts.
Nested Modes
Modes can push other modes, creating layers of context:
[shortcuts]
alt-n = { type = "push-mode", name = "navigate" }
[modes."navigate".shortcuts]
m = { type = "push-mode", name = "move" }
Escape = "pop-mode"
[modes."move".shortcuts]
h = "move-left"
l = "move-right"
Escape = "pop-mode"
alt-n enters navigation mode. From there, pressing m enters move mode
on top of it. Escape pops the current mode one level at a time. Use
clear-modes if you want a shortcut that returns directly to the top level.
See spec.generated.md for the full
specification of InputMode, push-mode, latch-mode, and related actions.
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.
Screen Sharing
Jay supports screen sharing via xdg-desktop-portal. Three capture types are available:
- Window capture – share a single window.
- Output capture – share an entire monitor.
- Workspace capture – like output capture, but only a single workspace is shown.
Requirements
PipeWire must be installed and running. Verify with:
~$ systemctl --user status pipewire
Portal Setup
Jay implements its own portal backend for the ScreenCast and RemoteDesktop
interfaces. Two configuration files must be installed so that
xdg-desktop-portal knows to use Jay’s backend.
If the Repository is Checked Out
~$ sudo cp etc/jay.portal /usr/share/xdg-desktop-portal/portals/jay.portal
~$ sudo cp etc/jay-portals.conf /usr/share/xdg-desktop-portal/jay-portals.conf
If Installed via cargo install
Create the files manually:
~$ sudo tee /usr/share/xdg-desktop-portal/portals/jay.portal > /dev/null << 'EOF'
[portal]
DBusName=org.freedesktop.impl.portal.desktop.jay
Interfaces=org.freedesktop.impl.portal.ScreenCast;org.freedesktop.impl.portal.RemoteDesktop;
EOF
~$ sudo tee /usr/share/xdg-desktop-portal/jay-portals.conf > /dev/null << 'EOF'
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=jay
org.freedesktop.impl.portal.RemoteDesktop=jay
org.freedesktop.impl.portal.Inhibit=none
org.freedesktop.impl.portal.FileChooser=gtk4
EOF
Restart the Portal
After installing the files, restart the portal service:
~$ systemctl --user restart xdg-desktop-portal
Configuration
workspace-capture
The top-level workspace-capture setting controls whether newly created
workspaces can be captured via workspace capture. The default is true:
workspace-capture = false
Set this to false if you want to prevent workspace-level capture by default.
Capture Indicator Colors
When a window is being recorded, its title bar color changes to make the
capture visually obvious. You can customize these colors in the [theme]
table:
[theme]
captured-focused-title-bg-color = "#900000"
captured-unfocused-title-bg-color = "#5f0000"
captured-focused-title-bg-color– background color of focused title bars that are being recorded.captured-unfocused-title-bg-color– background color of unfocused title bars that are being recorded.
The jay portal Command
Jay’s portal backend is normally started automatically when a screen-sharing request comes in via D-Bus activation. If you need to start it manually for debugging purposes:
~$ jay portal
Troubleshooting
If screen sharing does not work:
- Verify PipeWire is running:
systemctl --user status pipewire - Verify the portal files are installed in
/usr/share/xdg-desktop-portal/. - Restart the portal:
systemctl --user restart xdg-desktop-portal - Check the Jay log for errors:
jay log
HDR & Color Management
Jay supports HDR (High Dynamic Range) output and the Wayland color management protocol. This chapter explains how HDR works in Jay and walks through enabling it.
Prerequisites
HDR in Jay requires:
- The Vulkan renderer. The OpenGL renderer does not support color
management or HDR. Vulkan is the default when available; you can verify
with
jay randr show(look for the “Api” field under your GPU) or switch to it in the control center GPUs pane. - A monitor that supports HDR. The display must advertise PQ (Perceptual
Quantizer) support and BT.2020 colorimetry through its EDID. Run
jay randr showto see which color spaces and EOTFs your display supports.
Quick start
Enable HDR10 on a specific output using the CLI:
~$ jay randr output DP-1 colors set bt2020 pq
Or in your configuration file:
[[outputs]]
match.serial-number = "33K03894SL0"
color-space = "bt2020"
transfer-function = "pq"
Enable the color management protocol so that applications can communicate their color spaces to the compositor:
~$ jay color-management enable
Or in your configuration file:
[color-management]
enabled = true
To return to SDR:
~$ jay randr output DP-1 colors set default default
Note
Run
jay randrto find connector names and serial numbers for your displays.
Concepts
Color space
The color space determines the range of colors (gamut) the output uses.
default- sRGB gamut. This is the standard gamut for desktop displays.
bt2020- BT.2020 gamut. A much wider gamut used by HDR10 and modern cinema standards.
Transfer function (EOTF)
The transfer function (technically the Electro-Optical Transfer Function) controls how encoded pixel values are mapped to display luminance.
default- Gamma 2.2. The traditional SDR transfer function.
pq- Perceptual Quantizer (SMPTE ST 2084). The HDR10 transfer function, designed to cover luminance levels from 0 to 10,000 cd/m^2.
Setting color-space = "bt2020" and transfer-function = "pq" together
activates HDR10 mode. Jay sends the appropriate HDR metadata infoframe to the
display, including the display’s mastering luminance and primaries from its
EDID.
Brightness
The brightness setting controls SDR content luminance in cd/m^2. This determines how bright non-HDR content appears on an HDR display.
With the default (gamma 2.2) transfer function, the default brightness is
the maximum the display supports, anchored at 80 cd/m^2. Setting brightness
below 80 makes SDR content dimmer while creating HDR headroom above it.
With the pq transfer function, the default brightness is 203 cd/m^2 (the
ITU reference level for SDR content in an HDR signal).
[[outputs]]
match.serial-number = "33K03894SL0"
color-space = "bt2020"
transfer-function = "pq"
brightness = 250
~$ jay randr output DP-1 brightness 250
~$ jay randr output DP-1 brightness default
Note
Brightness has no effect unless the Vulkan renderer is in use.
Blend space
When Jay composites overlapping translucent surfaces, the blend space determines in which color space the alpha blending is performed.
srgb- Classic desktop blending in sRGB space. This is the default and matches the behavior of most other compositors.
linear- Blending in linear light. This is physically correct but can make semi-transparent elements appear brighter than expected.
[[outputs]]
match.serial-number = "33K03894SL0"
blend-space = "linear"
~$ jay randr output DP-1 blend-space linear
Native gamut
By default, Jay assumes all displays use sRGB primaries. This matches the behavior of most other compositors, but most modern displays actually have a wider gamut (e.g. 95% DCI-P3 coverage). Because the display interprets sRGB-intended color values in its wider native gamut, colors appear more saturated than they should.
Setting use-native-gamut = true tells Jay the display’s actual primaries
from its EDID, allowing the compositor to map colors correctly. This produces
less saturated but more accurate colors, and allows color-managed applications
to address the full display gamut.
[[outputs]]
match.serial-number = "33K03894SL0"
use-native-gamut = true
~$ jay randr output DP-1 use-native-gamut true
This setting has no effect when the display is already operating in a wide color space like BT.2020.
Framebuffer format
The default framebuffer format (xrgb8888) uses 8 bits per channel, which
can cause banding in HDR content. For HDR, consider a higher-precision format:
xrgb2101010orargb2101010– 10 bits per channel. Good balance between precision and performance.xbgr16161616forabgr16161616f– 16-bit floating point per channel. Maximum precision.
[[outputs]]
match.serial-number = "33K03894SL0"
color-space = "bt2020"
transfer-function = "pq"
format = "xrgb2101010"
~$ jay randr output DP-1 format set xrgb2101010
Tip
Run
jay randr show --formatsto see which formats your display supports.
Color management protocol
The Wayland color management protocol (wp_color_manager_v1) lets
applications communicate their color space to the compositor. This enables
color-aware applications to render correctly regardless of the output’s color
settings – for example, an image viewer can tag its surfaces as sRGB and Jay
will map the colors to the output’s actual gamut and transfer function.
The protocol is disabled by default and must be enabled explicitly:
[color-management]
enabled = true
Or at runtime:
~$ jay color-management enable
Note
Changing this setting has no effect on applications that are already running. They must be restarted to discover the protocol.
Color management availability requires both the protocol to be enabled and the Vulkan renderer to be active. Check with:
~$ jay color-management status
This prints one of:
Enabled- The protocol is enabled and available to clients.
- The protocol is enabled but unavailable – usually because the OpenGL renderer is in use.
Disabled- The protocol is disabled.
The control center Color Management pane shows the same information as a toggle and a read-only availability indicator.
Supported capabilities
Jay’s color management implementation supports:
- Parametric image descriptions with custom primaries, luminances, and transfer functions (including power curves).
- Named primaries: sRGB, BT.2020, DCI-P3, Display P3, Adobe RGB, and others.
- Named transfer functions: sRGB, PQ (ST 2084), gamma 2.2, gamma 2.4, BT.1886, linear, and others.
- Mastering display metadata for HDR content.
- Windows scRGB compatibility.
ICC profile creation is not supported.
Checking display capabilities
Use jay randr show to inspect what your display supports:
~$ jay randr show
The output for each connector includes:
- Color spaces – which color spaces the display supports (e.g.
default,bt2020), with the current setting marked. - EOTFs – which transfer functions the display supports (e.g.
default,pq), with the current setting marked. - Brightness range – minimum and maximum luminance from the EDID, in cd/m^2.
- Current brightness – displayed if a custom value is set.
- Blend space – current blend space setting.
- Native gamut – the display’s CIE xy primaries for red, green, blue, and white point.
Complete example
A typical HDR configuration for a monitor that supports HDR10:
[[outputs]]
match.serial-number = "33K03894SL0"
color-space = "bt2020"
transfer-function = "pq"
brightness = 203
format = "xrgb2101010"
blend-space = "linear"
[color-management]
enabled = true
This sets the output to HDR10 with 10-bit color, physically correct blending, and enables the color management protocol so applications can communicate their color spaces.
Control center
The Outputs pane in the control center (alt-c by
default) provides GUI controls for all HDR-related settings per output:
Colorimetry, EOTF, Custom Brightness, Format, Blend Space, Use Native Gamut,
and a read-only Native Gamut display. See
Outputs (Monitors) for details on each field.
The Color Management pane has a toggle to enable/disable the protocol and a read-only indicator showing whether color management is currently available.
See also
- Outputs (Monitors) – per-output configuration reference including all color and HDR fields
- Miscellaneous – the
[color-management]config table - Command-Line Interface –
jay randr outputcolor commands andjay color-management - GPUs – renderer selection (Vulkan is required for HDR)
Command-Line Interface
Jay provides a comprehensive CLI for controlling the compositor, managing displays, input devices, clients, and more. All subcommands communicate with the running compositor over the Wayland protocol unless otherwise noted.
Tip
Generate shell completions for tab-completion support:
~$ jay generate-completion bash > ~/.local/share/bash-completion/completions/jay ~$ jay generate-completion zsh > ~/.zfunc/_jay ~$ jay generate-completion fish > ~/.config/fish/completions/jay.fish ~$ jay generate-completion elvish # pipe to appropriate location ~$ jay generate-completion powershell # pipe to appropriate location
Every subcommand accepts a global --log-level option (trace, debug,
info, warn, error, off) that controls the verbosity of the CLI tool itself.
JSON Output
Most query and status commands can output machine-readable JSON instead of
human-readable text. Pass the global --json flag before the subcommand:
~$ jay --json randr
~$ jay --json clients
~$ jay --json idle
Each command prints one or more JSON objects, one per line (JSONL format). This
makes it easy to process with tools like jq:
~$ jay --json randr | jq '.drm_devices[].connectors[].name'
~$ jay --json clients | jq 'select(.pid != null) | .pid'
By default, fields that are empty arrays, null, or false are omitted from
the output to reduce noise. To include every field, pass --all-json-fields:
~$ jay --all-json-fields --json randr
Supported Commands
The following commands support --json:
jay clients- One JSON object per client.
jay color-management status- Color management enabled/available status.
jay config path- The config file path as a JSON string.
jay idle status- Idle interval, grace period, and inhibitors.
jay input show,jay input seat <seat> show,jay input device <id> show- Seats and input devices with all properties.
jay log --path- The log file path as a JSON string.
jay pid- The compositor PID as a JSON number.
jay randr show- DRM devices, connectors, outputs, modes, and display properties.
jay seat-test- Streaming JSONL – one JSON object per input event (key, pointer, touch, gesture, tablet, switch).
jay tree query- One JSON object per root node, with children nested recursively.
jay version- The version string as a JSON value.
jay xwayland status- Xwayland scaling mode and implied scale.
Tip
Mutating commands (e.g.,
jay idle set,jay randr output ... enable) produce no output, so--jsonhas no effect on them.
Running
jay run
Start the compositor.
~$ jay run
Optionally specify which backends to try (comma-separated, tried in order):
~$ jay run --backends x11,metal,headless
The default order is x11,metal. The first backend that can be started is
used. metal is the native DRM/KMS backend for bare-metal sessions.
jay quit
Stop the running compositor.
~$ jay quit
jay pid
Print the PID of the running compositor.
~$ jay pid
jay version
Print the Jay version.
~$ jay version
Configuration
jay config init
Generate a configuration file pre-populated with all defaults.
~$ jay config init
If a config already exists, pass --overwrite to replace it (the old file is
backed up):
~$ jay config init --overwrite
Important
Once a config file exists, the entire built-in default configuration is replaced – not merged. An empty config file means no shortcuts, no startup actions, nothing. Always use
jay config initto start with a working configuration.
jay config path
Print the path to the config file.
~$ jay config path
jay config open-dir
Open the configuration directory with xdg-open.
~$ jay config open-dir
Display & Graphics
All display and GPU commands live under jay randr.
Showing Current Settings
~$ jay randr
~$ jay randr show
~$ jay randr show --modes # include all available modes
~$ jay randr show --formats # include all available framebuffer formats
GPU / Card Commands
Set the primary (render) GPU:
~$ jay randr card card0 primary
Set the graphics API:
~$ jay randr card card0 api vulkan
~$ jay randr card card0 api opengl
Toggle direct scanout:
~$ jay randr card card0 direct-scanout enable
~$ jay randr card card0 direct-scanout disable
Adjust the page-flip margin (in milliseconds, default 1.5):
~$ jay randr card card0 timing set-flip-margin 1.5
Output Commands
Enable or disable an output:
~$ jay randr output DP-1 enable
~$ jay randr output DP-1 disable
Set the position:
~$ jay randr output DP-1 position 1920 0
Set the scale (fractional scaling supported):
~$ jay randr output DP-1 scale 1.5
~$ jay randr output DP-1 scale --round-to-float 1.333
Set the mode (width, height, refresh rate in Hz):
~$ jay randr output DP-1 mode 2560 1440 144.0
Set the transform:
~$ jay randr output DP-1 transform rotate-90
~$ jay randr output DP-1 transform none
Available transforms: none, rotate-90, rotate-180, rotate-270, flip,
flip-rotate-90, flip-rotate-180, flip-rotate-270.
Override the non-desktop setting:
~$ jay randr output DP-1 non-desktop true
~$ jay randr output DP-1 non-desktop default
Configure VRR (variable refresh rate):
~$ jay randr output DP-1 vrr set-mode always
~$ jay randr output DP-1 vrr set-mode variant3
~$ jay randr output DP-1 vrr set-cursor-hz 240
~$ jay randr output DP-1 vrr set-cursor-hz none
VRR modes: never, always, variant1 (one or more fullscreen apps),
variant2 (single fullscreen app), variant3 (single fullscreen app with
game/video content type). Pass none to set-cursor-hz to remove the cursor
Hz cap.
Configure tearing:
~$ jay randr output DP-1 tearing set-mode variant3
Tearing modes: never, always, variant1 (one or more applications
displayed fullscreen), variant2 (single application displayed fullscreen),
variant3 (default – single displayed application that requested tearing).
Set the framebuffer format:
~$ jay randr output DP-1 format set xrgb8888
Set the output color space and EOTF (for HDR):
~$ jay randr output DP-1 colors set bt2020 pq
~$ jay randr output DP-1 colors set default default
Set the brightness (in cd/m^2, or default):
~$ jay randr output DP-1 brightness 203
~$ jay randr output DP-1 brightness default
Set the blend space:
~$ jay randr output DP-1 blend-space linear
~$ jay randr output DP-1 blend-space srgb
Enable or disable native gamut usage:
~$ jay randr output DP-1 use-native-gamut true
~$ jay randr output DP-1 use-native-gamut false
Virtual Outputs
~$ jay randr virtual-output create my-virtual-display
~$ jay randr virtual-output remove my-virtual-display
Input
All input commands live under jay input.
Showing Input Devices
~$ jay input
~$ jay input show
~$ jay input show -v # verbose output with device details
Seat Commands
Show seat information:
~$ jay input seat default show
~$ jay input seat default show -v
Set keyboard repeat rate (repeats per second, initial delay in ms):
~$ jay input seat default set-repeat-rate 25 600
Set the keymap from RMLVO names:
~$ jay input seat default set-keymap-from-names -l de
~$ jay input seat default set-keymap-from-names -l us -v intl -o compose:ralt
Set the keymap from a file (or stdin):
~$ jay input seat default set-keymap /path/to/keymap.xkb
Retrieve the current keymap:
~$ jay input seat default keymap > current.xkb
Toggle hardware cursor:
~$ jay input seat default use-hardware-cursor true
~$ jay input seat default use-hardware-cursor false
Set cursor size:
~$ jay input seat default set-cursor-size 24
Configure the simple (XCompose-based) input method:
~$ jay input seat default simple-im enable
~$ jay input seat default simple-im disable
~$ jay input seat default simple-im reload
Device Commands
Show device information:
~$ jay input device 42 show
Set acceleration profile and speed:
~$ jay input device 42 set-accel-profile flat
~$ jay input device 42 set-accel-profile adaptive
~$ jay input device 42 set-accel-speed -0.5
Configure tap behavior:
~$ jay input device 42 set-tap-enabled true
~$ jay input device 42 set-tap-drag-enabled true
~$ jay input device 42 set-tap-drag-lock-enabled false
Set left-handed mode:
~$ jay input device 42 set-left-handed true
Set natural scrolling:
~$ jay input device 42 set-natural-scrolling true
Set pixels per scroll-wheel step:
~$ jay input device 42 set-px-per-wheel-scroll 30.0
Set the transform matrix (2x2):
~$ jay input device 42 set-transform-matrix 1.0 0.0 0.0 1.0
Set the calibration matrix (2x3, for touchscreens):
~$ jay input device 42 set-calibration-matrix 1.0 0.0 0.0 0.0 1.0 0.0
Set the click method:
~$ jay input device 42 set-click-method clickfinger
~$ jay input device 42 set-click-method button-areas
~$ jay input device 42 set-click-method none
Toggle middle button emulation:
~$ jay input device 42 set-middle-button-emulation true
Set a per-device keymap:
~$ jay input device 42 set-keymap /path/to/keymap.xkb
~$ jay input device 42 set-keymap-from-names -l de
~$ jay input device 42 keymap > device.xkb
Attach / detach a device from a seat:
~$ jay input device 42 attach default
~$ jay input device 42 detach
Map a device to a specific output:
~$ jay input device 42 map-to-output DP-1
~$ jay input device 42 remove-mapping
Idle & Locking
jay idle
Show idle status, including the current interval, grace period, and active inhibitors:
~$ jay idle
~$ jay idle status
jay idle set
Set the idle interval. Durations can be specified in flexible formats:
~$ jay idle set 10m
~$ jay idle set 1m 30s
~$ jay idle set disabled
jay idle set-grace-period
Set the grace period (screens go black but are not locked/disabled):
~$ jay idle set-grace-period 30s
~$ jay idle set-grace-period disabled
jay unlock
Unlock the compositor. This is useful when the screen locker crashes and the
session remains locked. Run it from another TTY or via SSH. You must set
WAYLAND_DISPLAY to the socket of the Jay compositor:
~$ WAYLAND_DISPLAY=wayland-1 jay unlock
Logging
jay log
Open the log file in less:
~$ jay log
~$ jay log -f # follow mode (like tail -f)
~$ jay log -e # jump to end of log
~$ jay log --path # print the log file path instead
jay set-log-level
Change the log level at runtime:
~$ jay set-log-level debug
~$ jay set-log-level info
Available levels: trace, debug, info, warn, error, off.
Screenshots
jay screenshot
Take a screenshot of the entire display:
~$ jay screenshot
~$ jay screenshot --format png
~$ jay screenshot --format qoi
~$ jay screenshot my-screenshot.png
If no filename is given, the screenshot is saved as
%Y-%m-%d-%H%M%S_jay.<ext> in the current directory. The filename supports
strftime format specifiers.
Clients & Windows
jay clients
List all connected clients:
~$ jay clients
~$ jay clients show all
Show a specific client by ID:
~$ jay clients show id 42
Interactively select a window and show its client:
~$ jay clients show select-window
Kill a client by ID:
~$ jay clients kill id 42
Interactively select a window and kill its client:
~$ jay clients kill select-window
jay tree query
Inspect the compositor’s surface tree:
~$ jay tree query root
~$ jay tree query -r root # recursive
~$ jay tree query -r --all-clients root # show client details for every node
~$ jay tree query workspace-name main
~$ jay tree query select-workspace
~$ jay tree query select-window
Xwayland
jay xwayland
Show Xwayland status (scaling mode, implied scale):
~$ jay xwayland
~$ jay xwayland status
jay xwayland set-scaling-mode
~$ jay xwayland set-scaling-mode default
~$ jay xwayland set-scaling-mode downscaled
In downscaled mode, X11 windows are rendered at the highest integer scale and
then downscaled, which can improve sharpness on HiDPI displays.
Color Management
jay color-management
Show color management status:
~$ jay color-management
~$ jay color-management status
jay color-management enable / disable
~$ jay color-management enable
~$ jay color-management disable
Other Commands
jay control-center
Open the Control Center GUI:
~$ jay control-center
jay portal
Run the Jay desktop portal (provides screen sharing and other XDG desktop portal interfaces):
~$ jay portal
Normally the portal is started automatically. This command is for running it manually or debugging.
jay seat-test
Test input events from a seat. Prints all keyboard, pointer, touch, gesture, tablet, and switch events to stdout:
~$ jay seat-test
~$ jay seat-test default
~$ jay seat-test -a # test all seats simultaneously
jay run-privileged
Run a program with access to a privileged Wayland socket:
~$ jay run-privileged my-program --arg1
jay run-tagged
Run a program with a tagged Wayland connection. All Wayland connections from the spawned process tree will carry the specified tag, which can be matched in client rules:
~$ jay run-tagged my-tag firefox
jay generate-completion
Generate shell completion scripts:
~$ jay generate-completion bash
~$ jay generate-completion zsh
~$ jay generate-completion fish
~$ jay generate-completion elvish
~$ jay generate-completion powershell
Troubleshooting
Jay doesn’t start / black screen
Jay requires at least one working renderer – without one, no GPU can be initialized and nothing will be displayed. It supports Vulkan (via libvulkan plus a GPU-specific driver) and OpenGL (via libEGL and libGLESv2). These libraries are loaded at runtime, not linked at build time. Vulkan is the primary renderer and should be used whenever possible; the OpenGL renderer is maintained only for backwards compatibility.
Check that the required libraries are installed:
~$ ls /usr/lib/libEGL.so* /usr/lib/libGLESv2.so* /usr/lib/libvulkan.so* 2>/dev/null
Note
On some distributions (e.g. Fedora, Debian), 64-bit libraries live in
/usr/lib64/or/usr/lib/x86_64-linux-gnu/instead.
If nothing is listed, install the appropriate packages. See the Installation chapter for package names by distribution.
Nvidia users:
- You need Linux 6.7 or later for explicit sync support, which is required for Nvidia GPUs.
- Make sure the Nvidia Vulkan ICD (Installable Client Driver) is installed. Jay’s Vulkan renderer is the recommended option on Nvidia hardware.
No applications open
The built-in default configuration binds:
If neither application is installed, nothing will happen when you press these keys. Either install them or create a custom configuration with your preferred applications:
~$ jay config init
Then edit ~/.config/jay/config.toml to change the terminal and launcher
bindings.
Important
If you created a config file manually (e.g. by touching an empty file), it will have no shortcuts at all. Jay replaces the entire built-in default when any config file exists. Always use
jay config initto start with a working configuration.
Application doesn’t have access to a protocol
Jay splits Wayland protocols into unprivileged and privileged. By default, applications only have access to unprivileged protocols. If a program like a screen locker, status bar, clipboard manager, or screen-capture tool is not working, it likely needs access to one or more privileged protocols.
Common symptoms include:
- swaylock does nothing or fails to lock the screen (needs
session-lock). - waybar or i3bar shows no workspace information (needs
foreign-toplevel-list). - wl-copy / cliphist cannot access the clipboard (needs
data-control). - grim or slurp cannot capture the screen (needs
screencopy).
Quick fix – grant all privileges:
The simplest approach is to launch the program with full access to all
privileged protocols. In your config, set privileged = true in the exec
action:
on-idle = {
type = "exec",
exec = {
prog = "swaylock",
privileged = true,
},
}
Or from the command line:
~$ jay run-privileged waybar
Better fix – grant only the capabilities needed:
Use a client rule to grant specific capabilities:
[[clients]]
match.comm = "waybar"
capabilities = ["layer-shell", "foreign-toplevel-list"]
See Granting Privileges for the full list of capabilities and more advanced approaches using connection tags.
Wrong keyboard layout
The default keyboard layout is US QWERTY. To change it:
Option 1 – edit the config:
~$ jay config init
Then edit the keymap.rmlvo section in ~/.config/jay/config.toml:
[keymap.rmlvo]
layout = "de"
Option 2 – change it at runtime:
~$ jay input seat default set-keymap-from-names -l de
~$ jay input seat default set-keymap-from-names -l us -v intl
This takes effect immediately but does not persist across restarts unless configured in the config file.
Screen sharing doesn’t work
Screen sharing requires PipeWire and the Jay desktop portal.
1. Check that PipeWire is running:
~$ systemctl --user status pipewire
If it is not running, start it:
~$ systemctl --user start pipewire
2. Check that the portal files are installed:
Jay needs two files to be found by the XDG desktop portal framework:
- A portal definition file (e.g.
/usr/share/xdg-desktop-portal/portals/jay.portal). - A portal configuration file (e.g.
/usr/share/xdg-desktop-portal/jay-portals.conf).
These files are included in the Jay repository under etc/. If you built Jay
from source and did not install them, copy them manually:
~$ sudo cp etc/jay.portal /usr/share/xdg-desktop-portal/portals/
~$ sudo cp etc/jay-portals.conf /usr/share/xdg-desktop-portal/
3. Restart the portal:
~$ systemctl --user restart xdg-desktop-portal
See the Screen Sharing chapter for more details.
X11 applications don’t work
Jay uses Xwayland to run X11 applications.
1. Install Xwayland:
- Arch Linux:
sudo pacman -S xorg-xwayland - Fedora:
sudo dnf install xorg-x11-server-Xwayland - Debian/Ubuntu:
sudo apt install xwayland
2. Check your configuration:
If you have a config file, make sure Xwayland is not explicitly disabled:
[xwayland]
# Make sure this is not set to false:
# enabled = false
Xwayland is enabled by default. You can also check its status at runtime:
~$ jay xwayland status
Display manager doesn’t show Jay
1. Check that the session file exists:
~$ ls /usr/share/wayland-sessions/jay.desktop
If it does not exist, create it:
~$ sudo tee /usr/share/wayland-sessions/jay.desktop > /dev/null << 'EOF'
[Desktop Entry]
Name=Jay
Comment=A Wayland Compositor
Exec=jay run
Type=Application
DesktopNames=jay
EOF
2. Check that jay is in the system PATH:
If you installed Jay via cargo install, the binary is at ~/.cargo/bin/jay.
Display managers typically do not include ~/.cargo/bin in their PATH. Either:
- Add
~/.cargo/binto the system PATH, or - Create a symlink:
~$ sudo ln -s ~/.cargo/bin/jay /usr/local/bin/jay
How to check logs
Open the log file in a pager:
~$ jay log
Follow the log in real time (like tail -f):
~$ jay log -f
Print just the log file path:
~$ jay log --path
Increase log verbosity at runtime:
~$ jay set-log-level debug
To set the log level at startup, add it to your config:
log-level = "debug"
Note
The
log-levelconfig setting is read at startup and cannot be changed by reloading the configuration. Usejay set-log-levelfor runtime changes.
Performance issues
If you experience dropped frames, stuttering, or high latency, try the following:
1. Use the Vulkan renderer:
The Vulkan renderer is generally faster and supports more features (e.g. HDR, direct scanout). Check your current API and switch if needed:
~$ jay randr show
~$ jay randr card card0 api vulkan
2. Set CAP_SYS_NICE:
Granting CAP_SYS_NICE allows Jay to use real-time scheduling and high-priority
Vulkan queues, which improves responsiveness under load:
~$ sudo setcap cap_sys_nice=p $(which jay)
You need to re-run this command each time you update the Jay binary.
3. Adjust the flip margin:
The flip margin controls the time between initiating a page flip and the display’s vblank. A smaller margin reduces input latency but risks missed frames. The default is 1.5 ms:
~$ jay randr card card0 timing set-flip-margin 1.5
If you see missed frames, try increasing it. If you want lower latency, try decreasing it – Jay will dynamically increase it if the margin is too small.
4. Enable direct scanout:
Direct scanout allows fullscreen applications to bypass the compositor’s rendering pipeline entirely, reducing both latency and GPU usage:
~$ jay randr card card0 direct-scanout enable