Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

Discord server (unofficial)

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.

ProtocolVersionPrivileged
ext_data_control_manager_v11Yes
ext_foreign_toplevel_image_capture_source_manager_v11
ext_foreign_toplevel_list_v11Yes
ext_idle_notifier_v12Yes
ext_image_copy_capture_manager_v111Yes
ext_output_image_capture_source_manager_v11
ext_session_lock_manager_v11Yes
ext_transient_seat_manager_v112Yes
ext_workspace_manager_v11Yes
jay_popup_ext_manager_v11
jay_tray_v11
org_kde_kwin_server_decoration_manager1
wl_compositor7
wl_data_device_manager4
wl_drm2
wl_fixes1
wl_output4
wl_seat10
wl_shm2
wl_subcompositor1
wp_alpha_modifier_v11
wp_color_manager_v12
wp_color_representation_manager_v11
wp_commit_timing_manager_v11
wp_content_type_manager_v11
wp_cursor_shape_manager_v12
wp_drm_lease_device_v11
wp_fifo_manager_v11
wp_fractional_scale_manager_v11
wp_linux_drm_syncobj_manager_v11
wp_pointer_warp_v11
wp_presentation2
wp_security_context_manager_v11
wp_single_pixel_buffer_manager_v11
wp_tearing_control_manager_v11
wp_viewporter1
xdg_activation_v11
xdg_toplevel_drag_manager_v11
xdg_toplevel_tag_manager_v11
xdg_wm_base7
xdg_wm_dialog_v11
zwlr_data_control_manager_v12Yes
zwlr_foreign_toplevel_manager_v13Yes
zwlr_gamma_control_manager_v11Yes
zwlr_layer_shell_v15No3
zwlr_output_manager_v14Yes
zwlr_screencopy_manager_v13Yes
zwlr_virtual_pointer_manager_v12Yes
zwp_idle_inhibit_manager_v11
zwp_input_method_manager_v21Yes
zwp_linux_dmabuf_v15
zwp_pointer_constraints_v11
zwp_pointer_gestures_v13
zwp_primary_selection_device_manager_v11
zwp_relative_pointer_manager_v11
zwp_tablet_manager_v22
zwp_text_input_manager_v31
zwp_virtual_keyboard_manager_v11Yes
zxdg_decoration_manager_v12
zxdg_output_manager_v13

  1. Cursors are always composited.

  2. Seat creation is always rejected.

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

LibraryArch LinuxFedoraDebian / Ubuntu
libinputlibinputlibinput-devellibinput-dev
libgbmmesamesa-libgbm-devellibgbm-dev
libudevsystemd-libssystemd-devellibudev-dev
libpangocairopangopango-devellibpango1.0-dev
libfontconfigfontconfigfontconfig-devellibfontconfig-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.

LibraryPurposeArch LinuxFedoraDebian / Ubuntu
libEGL + libGLESv2OpenGL renderer (legacy)mesa or libglvndmesa-libEGL + mesa-libGLESlibegl1 + libgles2
libvulkanVulkan renderer (recommended)vulkan-icd-loadervulkan-loaderlibvulkan1

For Vulkan, you also need the driver for your GPU:

GPUArch LinuxFedoraDebian / Ubuntu
AMDvulkan-radeonmesa-vulkan-driversmesa-vulkan-drivers
Intelvulkan-intelmesa-vulkan-driversmesa-vulkan-drivers
Nvidianvidia-utilsxorg-x11-drv-nvidianvidia-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.

~$ 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 setcap command 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.so exists in the config directory, Jay skips the SCHED_RR elevation (elevated Vulkan queues are still created).
  • If Jay has already elevated to SCHED_RR, it refuses to load config.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.

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, the jay binary lives in ~/.cargo/bin/. Your display manager may not include this directory in its PATH. Either add ~/.cargo/bin to the system PATH, 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 the open-control-center action).

  • 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.
Middle Button Emulation
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.

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: default or downscaled (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.toml exists, 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-toml action 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.

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:

FieldEnvironment VariableDefault
rulesXKB_DEFAULT_RULESevdev
modelXKB_DEFAULT_MODELpc105
layoutXKB_DEFAULT_LAYOUTus
variantsXKB_DEFAULT_VARIANTS(none)
optionsXKB_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 second
  • delay – 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
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 of consume)
  • none – unbind this key combination (useful for overriding defaults or inherited mode bindings)
  • disable-pointer-constraint – release a pointer lock/confinement
  • focus-parent – move focus to the parent container
  • toggle-bar, show-bar, hide-bar – control the status bar
  • open-control-center – open the Jay control center GUI
  • warp-mouse-to-focus – warp the cursor to the center of the focused window
  • kill-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 layer
  • focus-tiles – focus the tile layer
  • create-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 mode
  • reload-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 keymap
  • set-repeat-rate – change the keyboard repeat rate
  • set-env – set environment variables for future spawned programs
  • unset-env – remove environment variables
  • configure-connector – enable/disable a monitor
  • configure-input – change input device settings
  • configure-output – change output settings
  • configure-idle – change the idle timeout
  • configure-direct-scanout – enable or disable direct scanout
  • configure-drm-device – apply settings to a DRM device
  • set-theme – change theme settings
  • set-log-level – change the compositor log level
  • set-gfx-api – set the graphics API for new DRM devices (usually only effective at startup)
  • set-render-device – set the render device for compositing
  • define-action – define or redefine a named action at runtime
  • undefine-action – remove a named action
  • create-mark – create a mark with an explicit ID (see Marks)
  • jump-to-mark – jump to a mark with an explicit ID
  • copy-mark – copy a mark from one ID to another
  • create-virtual-output – create a virtual output
  • remove-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 with prog)
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 = true to 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.toml is only applied the first time a matching output is connected after the compositor starts. To change settings at runtime, use jay randr or the configure-output action.

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

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-c by 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_v1 protocol

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 default EOTF: 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.toml is only applied to devices connected after the configuration is loaded. To change settings for already-connected devices, use jay input or the configure-input action.

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
FieldValuesDescription
accel-profileFlat or AdaptivePointer acceleration curve
accel-speed-1.0 to 1.0Speed 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"
FieldValuesDescription
tap-enabledtrue / falseTap-to-click on touchpads
tap-drag-enabledtrue / falseTap-and-drag on touchpads
tap-drag-lock-enabledtrue / falseKeep drag active after lifting finger
click-methodnone, button-areas, clickfingerHow 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”)
middle-button-emulation
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.toml is only applied when a device is first discovered after the configuration is loaded. To change settings at runtime, use jay randr or the configure-drm-device action.

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

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.toml before 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-device action) 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.toml cannot be changed by reloading the configuration. Use jay idle or the configure-idle action 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 = true in 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:

  1. After 10 minutes of inactivity, the screen goes black (grace period begins).
  2. If no input occurs within 5 seconds, swaylock is launched and outputs are disabled.
  3. 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:

ExampleMeaning
1m1 minute
1m5s1 minute 5 seconds
1min 5sec1 minute 5 seconds
90s90 seconds
disabledDisable 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, or i3bar. 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.

cursor
Use the output the cursor is on (default)
focus
Use the output the focused window is on
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-dsplit-horizontal
Split the focused window horizontally
alt-vsplit-vertical
Split the focused window vertically
alt-ttoggle-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-hfocus-left
Move focus left
alt-jfocus-down
Move focus down
alt-kfocus-up
Move focus up
alt-lfocus-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-hmove-left
Move window left
alt-shift-jmove-down
Move window down
alt-shift-kmove-up
Move window up
alt-shift-lmove-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-mtoggle-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-ftoggle-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 ClientMatch specifying which clients this rule applies to.
action
An action to run when a client starts matching.
latch
An action to run when a client stops matching.
capabilities
Wayland protocol access granted to matching clients.
sandbox-bounding-capabilities
Upper bounds for protocols available to child sandboxes.

Client Match Criteria

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

sandboxed
Whether the client is sandboxed (true/false).
sandbox-engine / sandbox-engine-regex
The sandbox engine (e.g. org.flatpak).
sandbox-app-id / sandbox-app-id-regex
The app ID provided by the sandbox.
sandbox-instance-id / sandbox-instance-id-regex
The instance ID from the sandbox.
uid
The user ID of the client.
pid
The process ID of the client.
is-xwayland
Whether the client is Xwayland (true/false).
comm / comm-regex
The client’s /proc/pid/comm value.
exe / exe-regex
The client’s /proc/pid/exe path.
tag / tag-regex
The connection tag of the client.

Granting Privileges

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

See the Protocol Support 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, and pid are checked when a client connects. Any process with a matching name receives the specified capabilities. If you need to restrict privileges to programs you launch yourself, use connection tags (method 2) instead.

Bounding capabilities (sandboxes)

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

[[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 WindowMatch specifying which windows this rule applies to.
action
An action to run when a window starts matching.
latch
An action to run when a window stops matching.
initial-tile-state
"floating" or "tiled" – force the initial tile state.
auto-focus
true/false – whether the window gets focus on map. Without a matching rule, newly mapped windows always receive focus (except for Xwayland override-redirect windows such as menus and tooltips, which bypass the normal mapping path).

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

Window Match Criteria

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

types
Window type mask: none, any, container, xdg-toplevel, x-window, client-window.
client
A nested ClientMatch – matches the window’s owning client.
title / title-regex
The window title.
app-id / app-id-regex
The XDG app ID.
floating
Whether the window is floating.
visible
Whether the window is visible.
urgent
Whether the window has the urgency flag.
focused
Whether the window has keyboard focus.
fullscreen
Whether the window is fullscreen.
just-mapped
true for one compositor iteration after the window maps.
tag / tag-regex
The XDG toplevel tag.
x-class / x-class-regex
The X11 class (X windows only).
x-instance / x-instance-regex
The X11 instance (X windows only).
x-role / x-role-regex
The X11 role (X windows only).
workspace / workspace-regex
The workspace the window is on.
content-types
Content type mask: none, any, photo, video, game.

Combining Criteria

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

AND (Multiple Fields)

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

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

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

just-mapped

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

[[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

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:

  1. Verify PipeWire is running: systemctl --user status pipewire
  2. Verify the portal files are installed in /usr/share/xdg-desktop-portal/.
  3. Restart the portal: systemctl --user restart xdg-desktop-portal
  4. 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 show to 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 randr to 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:

  • xrgb2101010 or argb2101010 – 10 bits per channel. Good balance between precision and performance.
  • xbgr16161616f or abgr16161616f – 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 --formats to 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.
Enabled (Unavailable)
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 Interfacejay randr output color commands and jay 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 --json has 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 init to 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:

  • Super_L (left Windows key) – open Alacritty.
  • alt-p – open bemenu as an application launcher.

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 init to 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/bin to 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-level config setting is read at startup and cannot be changed by reloading the configuration. Use jay set-log-level for 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