Skip to content

Requests and replies

Ask the compositor questions and tell it to act: every request, type vs key, and what the replies carry. All of these work as scootctl <request> or scoot msg <request>.

Request What it does
version Version and IPC protocol of the running compositor — needs a session. scootctl --version (or scoot --version) answers locally with no session, printing the binaries’ own line (scoot <version> (ipc protocol <N>)) so a client can check compatibility before connecting.
outputs Every output’s name, rectangle, usable rectangle, scale and power state.
windows Every window: id, app id, title, icon, output, workspace, adoption, focus, popup grab.
keyboard The active keyboard layout’s name and index — what a layout indicator shows, and which layout the next type will produce.
output-power ID|all on|off Switch an output’s panel off or on — the IPC half of zwlr_output_power_v1 (see protocols.md), for agents and scripts that do not speak Wayland. all powers every output at once; an unknown id is refused with an error. Session-level like outputs, not an action: it applies while locked (the idle cycle is off-after-lock, on-at-resume). On the wire an additive request tag ({"type":"output_power","output":1,"powered":false}, output omitted for all); PROTOCOL_VERSION did not change.
action ACTION [ARGUMENT...] Run a layout action — see Actions.
reload Re-read the config file the session started from and re-apply what can be re-applied live (layout, output scale – the default and each [[outputs]] entry’s, appearance, keybindings, new autostart spawn entries; an entry’s mode is refused as outputs.<name>.mode, pending a restart) — see configuration.md. Answers reloaded with applied-vs-refused field lists, or error (running config untouched) when the file cannot load or validate.
screenshot [--output ID] [--out FILE] [--no-cursor] Capture the screen as PNG. Without --out, the PNG goes to stdout. --output names which output to capture; every output has a framebuffer of its own, so the capture is that output’s own pixels. An id naming no output is refused rather than answered with another output’s pixels. Omitting it always means the first output (id 1). The pointer is drawn in unless --no-cursor — see The pointer in a screenshot.
pointer move X Y Move the pointer to logical coordinates.
pointer click X Y [left|right|middle] Move, then press and release.
pointer button left|right|middle press|release Half a click, for drags.
pointer scroll DX DY Scroll by a delta. Sent as a wheel scroll that also carries its detents (eight v120 units per delta unit, so pointer scroll 0 15 is exactly one detent) – clients that only listen for steps still see it.
key COMBO Press one key combination — see type vs key.
type TEXT Type text on the active keyboard layout.
wait-idle [--quiet-ms N] [--timeout-ms N] Block until nothing on screen has redrawn for --quiet-ms (default 200), giving up after --timeout-ms (default 5000).
subscribe [EVENT...] Dedicate this connection to events of the named kinds (output, keyboard, workspace; naming none is refused — bare scootctl subscribe sends output), streaming them until the session ends or drops the subscription — see Events. A fresh keyboard subscription starts silent, so issue one keyboard query for the baseline and listen for changes after it. A fresh workspace subscription starts silent too — read windows once for the baseline and apply snapshots after it.
Terminal window
scootctl windows
scootctl action focus-column left
scootctl reload
scootctl screenshot --out /tmp/shot.png
scootctl type "hello"
scootctl keyboard
scootctl wait-idle --quiet-ms 200
scootctl subscribe
scootctl subscribe keyboard

scootctl type TEXT types text the way a person would, on whatever keyboard layout the session is running (ask scootctl keyboard which one that is): for each character it finds the key that carries it and holds down whatever modifiers that key’s level needs — Shift for A or !, AltGr for a German layout’s @ — so a client receives the same key and modifier events it would see from a real keyboard, not just a bare keysym. \n and \t are sent as Return and Tab.

A character no single keypress produces goes through a second path: the two-key dead-led sequence from the session-locale compose table (dead_acute then e for é on a German layout), pressed as the two keypresses a person would type, each with its own level’s modifiers. All 95 printable ASCII characters type on every one of the fourteen swept Latin layouts (us, us(intl), gb, de, de(neo), fr, fr(oss), es, it, pt, se, no, dk, pl).

  • A character the active layout can’t produce is an error naming it (no key for `é` in this layout). The compose table counts as well as the keymap: a character with no two-key dead-led sequence there stays refused — plain us carries no dead keys and no Compose key, so é is still “no key” on it, and three-key Multi_key sequences are not driven even on a layout with a Compose key. A character on an inactive layout group is refused too: nothing here switches the session’s layout to go and find it. A character on a level the layout only reaches through a locking or latching modifier gets its own message ([character] needs a modifier this layout only locks or latches) — scoot will not press Caps Lock to type a capital, since that would leave it on for everything afterwards. Sequences come from your own XCompose file if you have one ($XCOMPOSEFILE, then $XDG_CONFIG_HOME/XCompose, then ~/.XCompose), otherwise from the session locale’s table (LC_ALL, then LC_CTYPE, then LANG, else C, as the compositor process sees them — the same source toolkits read; like setlocale, an empty variable counts as unset). In every case the characters before the failure have already been typed: the request stops at the first character it can’t type rather than rolling back.
  • Keybindings apply to what it types, exactly as they would to a real keypress. That only matters for a bind with no modifiers, or one on Shift plus a key; if a character does hit a bind, the compositor logs a warning naming it rather than swallowing it silently.

scootctl key COMBO is not the same. It presses exactly the combination named and holds exactly the modifiers named, nothing more. Name the key as it is with nothing held, plus the modifiers: shift+1, not exclam; shift+a, not A. A name this layout only carries above its unmodified level is refused, because the key that carries it types a different character when pressed bare — scootctl key exclam would press the 1 key and deliver 1. Some characters can’t be named as a combination at all (@ on a German layout needs AltGr, which key has no name for); type is the one that works the modifiers out from the layout, and the one to reach for when the goal is text rather than a chord. Modifiers resolve from the active layout too — whichever key actually holds Shift/Control/Alt/Super is what gets held (either hand’s key, or the one a layout option like grp:lshift_toggle left in place) — so a combo is refused for its modifier only when no key on the layout can hold it.

outputs, one entry per output:

Field Meaning
id The output’s id, which a window’s output names.
name HDMI-A-1, eDP-1, … under --tty — the DRM connector name; headless, headless-2, … otherwise.
rect The output’s full rectangle, in logical pixels. “How big is the screen.”
usable The full output minus whatever a bar reserved at its edges (layer-shell exclusive zones) — where windows actually go. “Where can a window be.”
scale This output’s own scale – outputs need not share one (see [[outputs]]; rect and usable are logical, screenshots are physical.
powered Whether the output is powered on — false while output-power (or wlopm over zwlr_output_power_v1) has it switched off. A powered-off output does no render work and takes no screenshots (refused, naming the recovery).

windows, one entry per window, in layout order:

Field Meaning
id The window id. What action focus-window-id N takes, and what follows the last dash of an ext-foreign-toplevel-list-v1 identifier.
app_id The toplevel’s app id, or "" before the toolkit has sent one. An X11 window’s (under --xwayland is its WM_CLASS class – XTerm for xterm – falling back to the instance; X windows are listed like any other, with nothing on the wire to mark them.
title The toplevel’s title, same caveat. An X11 window’s is _NET_WM_NAME, falling back to WM_NAME. An X title or class is cut at its first NUL byte (X allows one; a Wayland string cannot carry it).
icon The freedesktop icon name the client committed through xdg-toplevel-icon-v1, or null. Read off the surface’s current state when asked, so it is never stale. A client that supplied raw pixel buffers instead of a name reads as null, and so does every X11 window (_NET_WM_ICON is not read).
output The id of the output the window is on.
workspace Which workspace of that output the window sits on, 0-based — the same numbering focus-workspace-index N and move-window-to-workspace-index N take, so an agent can switch to the window’s workspace without converting. (Bars see the 1-based twin over ext-workspace-v1.)
adopted Whether the window’s workspace was adopted from an unplugged monitor. With origin, what tells an agent where an unplugged monitor’s windows went.
origin Which connector the window’s workspace was adopted from ("DP-1"), or null for a workspace that was never adopted.
rect Where the window is, in logical pixels — what you click. That is the part of its layout slot the window has actually drawn: the slot’s top-left corner, and the smaller of the slot and what the window last committed on each axis. Normally that is the whole slot. It is smaller for a window that draws less than it was given (a fixed-size dialog, a video player keeping its own size), and for the frame or two after its slot grows until the window’s larger frame arrives. It is the same area the focus ring surrounds and rounded corners cut, and it never reaches past the slot. A window that has drawn nothing yet reports its whole slot. Its toplevel surface only: its open menus and other popups can draw outside rect. Whatever the window draws, only the part inside its own output’s rect (scootctl outputs) is shown and clickable: a column scrolled part-way past its output’s edge is cut there (so is a menu crossing it, unless it lets the compositor adjust it — toolkit menus do — in which case it is flipped or slid back onto the window’s output when it opens; see protocols.md), and a click past that edge lands on whatever the neighbouring output shows there — or on nothing, past the last output. A window that is not visible still reports the frame it would have — except a window stacked in the same column as a fullscreen one, which reports that fullscreen window’s frame (it is behind it) until the fullscreen ends. The drawn-area rule applies only to visible windows. A window that is not visible reports its layout frame unchanged, because it draws nothing there.
visible false when the window is scrolled out of view, on an inactive workspace, or hidden behind a fullscreen window (every other window on an output a fullscreen window covers, including windows stacked in its own column, and every floating window on it). A floating window is also false before its first frame, and while it is fullscreen without focus.
focused Compositor window focus — not necessarily where keystrokes go; see below.
popup_grab Whether this window’s own popup tree holds the keyboard — see below.
fullscreen Whether the window is fullscreen. While its column is focused it covers its output: rect equals that output’s rect (for a window that draws its whole frame, as fullscreen windows do; see rect), and every other window on the output reports visible: false. Focused away, it keeps that size and sits in the strip where a column that wide would, one ordinary gap from its neighbours — it may still be partly visible beside the focused window, never overlapping it, and like any window is drawn and clickable only within its own output, however far its rect reaches past that output’s edge.
maximized Whether the window is maximized. While its column is focused (and no fullscreen window covers the output) it fills the output’s usable area: rect equals that usable area minus the layout gap, and every other tiled window on the output reports visible: false (floating windows stay above it). Focused away, it keeps that size and sits in the strip where a column that wide would, one ordinary gap from its neighbours. Fullscreen wins while both hold.
floating Whether the window floats above its workspace’s strip: a dialog, transient or fixed-size window floated as it mapped, a [[window_rule]] match, or toggle-floating/set-floating. Its rect is where it really is: where it was last moved to (by a drag or move-floating), else centred on its parent (when that is visible on the same workspace) or its output, inside the output’s usable area, at the size it drew. It is drawn above, and takes clicks before, every tiled window on its output. A floating window that has not drawn its first frame yet is visible: false, as is one on an inactive workspace or under a fullscreen window that covers the output.

keyboard, the seat keyboard’s currently effective layout (xkb group):

Field Meaning
index The active group, 0-based — the same numbering type resolves each character in, so an agent reads off which layout its next type will produce.
name The keymap’s own name for that group (us reads as "English (US)", ru as "Russian") — what a bar shows.

Read live off the compositor’s keymap on every request: there is no scoot-side copy to go stale. Read-only, like the event below — scoot has no layout-switch bind, option or action, so nothing over IPC switches the layout; the group moves only through the keymap’s own mechanics (a toggle key from the XKB_DEFAULT_OPTIONS the session started with, e.g. grp:caps_toggle).

Every success reply also carries locked: the session-lock state it was built under. An agent typing a password over IPC learns the unlock landed from the very next reply.

locked, usable, scale, icon, popup_grab, fullscreen, maximized and floating are additive and defaulted — an older server omits them rather than bumping PROTOCOL_VERSION, so read each asymmetrically. locked: true, popup_grab: true, fullscreen: true, maximized: true and floating: true are always truthful, while false means “no, or a server predating the field”; an all-zero usable means the same (fall back to rect), and an omitted scale decodes as 1.0.

reloaded, the answer to reload, is the exception that proves the rule above: it is a new reply variant, not a defaulted field, and it moved PROTOCOL_VERSION 2 → 3 (an older client handed one would fail its decode — in practice only a client new enough to send reload ever receives one). Read it asymmetrically from the other direction: a reload sent to a server predating the request answers an ordinary error, not a kill — an unknown request tag is a decode error the server answers and keeps serving.

{ "type": "reloaded", "applied": ["layout.gap", "binds"],
"refused": ["tty.gpu (takes effect on restart: the session already drives its device)"] }

applied names the fields re-applied live (including autostart.commands when new spawn entries started – a spawn entry that fails to start is refused by name instead, and stays pending for the next reload – and wallpaper / wallpaper.command when the [wallpaper] section or its command changed: handed to scootbg, whose outcome is in the compositor log, not in this reply), refused the ones that differed but cannot be (each with its reason: the two restart fields, a non-spawn autostart entry by name, a locked-skipped autostart delta, an unusable [[window_rule]] by its position in the file, or a [wallpaper] section with a problem, named – an unknown key inside [wallpaper] is this refusal, not an error). Both name only fields that differed: two empty lists together mean the reload changed nothing it was asked to – except an unusable window rule and a [wallpaper] section with a problem, each refused on every reload that finds it, since neither is ever in effect. A reload that could not load or validate the file answers error with the running config untouched (scootctl exits non-zero).