Requests and replies
Every request, type vs key, and what the replies carry. All of these work as scoot msg <request>.
Requests
Section titled “Requests”| Request | What it does |
|---|---|
version |
Version and IPC protocol of the running compositor — needs a session. scoot --version answers locally with no session (scoot <version> (ipc protocol <N>)). <version> is the release (0.), or the last release plus commits since (0.); the reply’s version field carries the same string. Compare the protocol number, not the version. |
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. |
locked |
Whether the session is locked — the side-effect-free lock probe, answered locked or not ({"type":"locked","locked":false}). What tells an agent whether injected input would reach the lock screen instead of the desktop. |
binds [--json] |
The live keymap: every combo in canonical spelling, its action string, where it came from, its repeat/allow_when_locked flags, plus skipped config binds with reasons — see Show the keymap. The human view is an aligned table grouped like the keybindings page; --json is the same reply as JSON. Answered locked or not. |
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). all powers every output at once; an unknown id is refused. 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, with no PROTOCOL_VERSION change ({"type":"output_power","output":1,"powered":false}, output omitted for all). |
output-scale ID|NAME SCALE|reset |
Set an output’s scale live, by id or connector name (DP-1), or reset it to the config file’s. Range 0.5 to 4 (resolved to 1/120 like the file’s); a value outside it, or not a number, is refused instead of clamped; an unknown id or name is refused too. Runtime state, like output-power: it beats the file’s scale for that connector (a replugged monitor comes back at it), and a successful reload or restart returns to the file’s scales. A repeat set, or a reset with nothing to reset, changes nothing. Applies while locked; refused under --nested. What the display profiles watcher drives. On the wire an additive request tag: output is a number (id) or a string (name), scale a number or null for reset, required either way ({"type":"output_scale","output":"DP-1","scale":1.); answered with the existing ok; an older server refuses the unknown tag with an error. |
action ACTION [ARGUMENT... |
Run a layout action — see Actions. |
reload [--look] [--wait-wallpaper] |
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, [[outputs]] mode (a live resize per backend, refused under --nested), appearance, keybindings, new autostart spawn entries) — see configuration.md. A successful reload also drops every live output-scale scale, and each one the file disagrees with is listed in applied as outputs.. Answers reloaded with applied-vs-refused field lists plus the shadowed file keys that override the active look (and the invalid ones whose bad values fell back to it), or error (running config untouched, live scales kept) when the file cannot load or validate. With --look only the look layer is re-read, under a snapshot of the file from the last successful plain load: appearance and wallpaper swap, pending edits wait, no autostart entry spawns and no bind swaps. The reply carries the scope that ran ("scope":"look" on a scoped reload, absent on a plain one). With --wait-wallpaper the same plain reload parks its reloaded reply until the wallpaper run lands (or 30 s passes, reporting handed-over-but-unwaited-on) instead of answering once handed over — see below. --look with --wait-wallpaper is refused. |
look-apply NAME |
Swap the active look and wait for the swap to land — what scoot look apply does to local files, for a client with no files (a Mac driving a VM). Answers look_applied when the swap’s child exits, or error when the swap is refused (unknown look, a path as the name, a second swap while one runs) with nothing touched. Applies while locked. |
looks |
The look catalog in search order, the active look’s name, and the session’s state directory — what scoot look list and scoot look current read from local files, for a client with no files. |
shortcut-register APP_ID SHORTCUT_ID [--trigger COMBO] [--description TEXT] |
Register a portal global shortcut (the stage-1 half of the org. portal — see Global shortcuts). The trigger is a hint only, stored and listed but never auto-bound; the user assigns the chord in [global_shortcuts] or with shortcut-bind. Idempotent by (app_id, shortcut_id); answers shortcuts, re-listing. Bounded: at most 64 per connection and 512 compositor-wide, trigger at most 256 bytes, description at most 1024 bytes — past any of those the register is refused with an ordinary error and nothing is stored (a re-register of an owned id never counts against the count caps). |
shortcut-unregister APP_ID SHORTCUT_ID |
Remove one registration (unknown ids are refused; any same-user connection may drop any id). Answers shortcuts. |
shortcuts |
List every registered shortcut: app and shortcut id, the app’s description and preferred trigger, the user-assigned chord if bound, and whether it is shadowed (conflicted: a live compositor bind holds the chord, or another registration won the grab for it). Bounded by the register caps above. |
shortcut-bind APP_ID SHORTCUT_ID COMBO|reset |
Bind a registered shortcut to a user chord (reset, the wire null, clears it). A shortcut bound to a chord a live compositor bind holds stays listed but conflicted and never fires. Colliding with another registration’s chord does the same to the loser. Unknown ids and bad chords are refused. Any same-user connection may bind any id. Answers shortcuts. |
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, and 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 means the primary output. 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|back|forward] |
Move, then press and release (default button left). |
pointer button left|right|middle|back|forward 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), so clients that only listen for steps still see it. Runs scroll binds the way a wheel does, so a bind a held modifier (or none) matches takes the scroll and no client sees it; the request presses no modifier of its own, so it fires a scroll bind with no modifier (or one whose modifier is physically held). |
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, lock, look, global-shortcut; scoot msg subscribe with no names sends output; an empty events list on the raw socket is refused), streaming them until the session ends or drops the subscription — see Events. Every subscription starts silent. For a baseline, issue one keyboard query, read windows once (workspace), read locked once (lock) or read shortcuts once (global-shortcut), then apply events after it. A look subscription has no baseline: nothing has changed until the first look_changed arrives. |
scoot msg windowsscoot msg action focus-column leftscoot msg reloadscoot msg reload --wait-wallpaperscoot msg look-apply moonrisescoot msg looksscoot msg screenshot --out /tmp/shot.pngscoot msg type "hello"scoot msg keyboardscoot msg lockedscoot msg bindsscoot msg wait-idle --quiet-ms 200scoot msg subscribescoot msg subscribe keyboardscoot msg subscribe lockscoot msg subscribe lookscoot msg subscribe global-shortcutscoot msg shortcutsscoot msg shortcut-bind org.example.recorder toggle-record super+ctrl+rtype vs key
Section titled “type vs key”scoot msg type TEXT types text the way a person would, on the
session’s keyboard layout (ask scoot msg keyboard which one
that is): for each character it finds the key
that carries it and holds 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 as from a real keyboard.
\n and \t send 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 each of these fourteen 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. Plainuscarries no dead keys and no Compose key, soéis still “no key” on it, and three-keyMulti_keysequences are never driven. A character on an inactive layout group is refused too: nothing here switches the session’s layout to 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, because that would leave it on afterwards. Sequences come from your own XCompose file if you have one ($XCOMPOSEFILE, then$XDG_CONFIG_HOME/XCompose, then~/.), otherwise from the session locale’s table (XCompose LC_ALL, thenLC_CTYPE, thenLANG, elseC, as the compositor process sees them — likesetlocale, an empty variable counts as unset). In every case the characters before the failure are already 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.
scoot msg key COMBO is not the same. It presses exactly the
combination named and holds exactly the modifiers named. 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 — scoot msg 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 works the modifiers out from the layout; use it for text and key for
chords. 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.
What the replies carry
Section titled “What the replies carry”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. |
usable |
The full output minus whatever a bar reserved at its edges (layer-shell exclusive zones) — where windows actually go. |
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). |
live |
Whether the output is currently driven — false while a --tty session has lost DRM master (usually a VT switch seatd failed to re-acquire master for; see VT switching). Always true off --tty, and on servers predating the field. The composited frame outlives master, so a screenshot of a live: false output is stale. |
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. Under --xwayland, an X11 window’s 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 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. (Bars see the 1-based twin over ext-workspace-v1.) |
adopted |
Whether the window’s workspace was adopted from an unplugged monitor — with origin, 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: the slot’s top-left corner, and the smaller of the slot and what the window last committed on each axis. Normally the whole slot. Smaller for a window that draws less than it was given (a fixed-size dialog, a video player keeping its size), and for a frame or two after its slot grows until the larger frame arrives. The same area the focus ring surrounds and rounded corners cut; never past the slot. A window that has drawn nothing yet reports its whole slot. Toplevel surface only: open menus and popups can draw outside rect. Only the part inside its own output’s rect (scoot msg outputs) is shown and clickable: a column scrolled past its output’s edge is cut there (as is a menu crossing it, unless it lets the compositor adjust it — toolkit menus do — when it is flipped or slid back onto the window’s output on open; 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 reports its layout frame unchanged — except one stacked in the same column as a fullscreen window, which reports that fullscreen window’s frame until the fullscreen ends. |
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 last moved (by a drag or move-floating), else centred on its parent (when visible on the same workspace) or its output, inside the output’s usable area, at the size it drew. Drawn above, and taking clicks before, every tiled window on its output. A floating window with no first frame yet is visible: false, as is one on an inactive workspace or under a covering fullscreen window. |
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. Read-only: 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).
locked, whether the session is locked:
| Field | Meaning |
|---|---|
locked |
true while the session is locked, false otherwise, read live off the compositor’s lock state on every request. |
Unlike every action, this answers locked or not. Read-only: nothing
over IPC locks or unlocks the session.
$ scoot msg locked{ "type": "locked", "locked": false}binds, the live keymap as the running compositor uses it — what the
default Super+Shift+/ bind opens in a terminal:
| Field | Meaning |
|---|---|
combo |
The combo in canonical spelling: modifiers in super+shift+ctrl+alt order, then the xkb keysym name (super+shift+slash). |
action |
The action string: the same grammar scoot msg action takes and [binds] uses (focus-column left, spawn foot, show-keymap; change-vt 3 for the session-managed VT-switch binds, which have no config spelling). |
source |
Where the row came from: default (a built-in bind, unchanged), config (a combo the defaults leave unbound), config (replaces default: <old action>), config (unbinds default: <old action>) (a config unbind — action names what was removed), or session (VT switch) (the --tty recovery binds). |
repeat |
Whether the bind re-fires while its key is held. Never true on a default row. |
allow_when_locked |
Whether a spawn bind fires while the session is locked. Only ever true beside a spawn action. |
One row per effective binding — built-ins first in their canonical order,
then config binds (fresh combos and overrides alike) sorted by combo, then
any session-managed rows — followed by one row per default a config unbind
removed. Sorting config rows keeps --json stable from run to run. A second list, skipped, holds every config entry that never made
it in, sorted by combo — each with its bind (as written), its value (in
TOML form), and its reason (a parse error, a colliding group, or an unbind
with nothing to remove).
$ scoot msg binds --json{ "type": "binds", "bindings": [ { "combo": "super+h", "action": "focus-column left", "source": "default", "repeat": false, "allow_when_locked": false } ], "skipped": []}Derived by diffing the live table against the defaults on every request,
so it is always the merged result — never a re-read of the file. Like
locked, this answers locked or not: listing keys is not a
window-management operation (the Super+Shift+/ bind itself never fires
while locked). binds is a new reply variant and moved PROTOCOL_VERSION
8 → 9. An older client handed one would fail its decode, but only a client
new enough to have asked receives one; a binds sent to an older
server answers an ordinary error.
Every ok reply also carries a locked field: the session-lock state it was
built under. An agent typing a password over IPC learns the unlock landed
from the next reply.
locked, usable, scale, icon, popup_grab, fullscreen,
maximized and floating are additive and defaulted: an older server omits them, with no PROTOCOL_VERSION
bump, 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 a missing scale decodes as 1..
reloaded, the answer to reload, is a new reply variant, not a
defaulted field, and moved PROTOCOL_VERSION 2 → 3. An older client handed
one would fail its decode, but only a client new enough to send reload
ever receives one. 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)"] }A look-scoped reload ({"type":"reload_look"}, sent as
scoot msg reload --look) answers the same shape with
"scope":"look": only the look layer was re-read, so applied names
appearance and wallpaper fields only, and a half-written file cannot
turn it into an error. A plain reload omits scope. The scoped tag is
additive (PROTOCOL_VERSION did not move). An older compositor answers
it with an ordinary unknown-request error (the client prints the error
and exits non-zero, the running config untouched), never a silent plain
reload.
A wallpaper-wait reload ({"type":"reload_wait_wallpaper"}, sent as
scoot msg reload --wait-wallpaper) runs the same plain reload but parks
its reloaded reply until the wallpaper run lands instead of answering
once handed over. The reply shape is the same reloaded (no new variant,
scope absent like a plain reload); past 30 s it answers anyway with one
extra refused entry (wallpaper (timed out waiting for the wallpaper run; handed over, exit unwaited-on)), and the run’s exit is still logged
when it comes. A wait with no wallpaper run in flight answers at once,
like a plain reload; an error (unreadable file, bad layer) also answers
at once, never parked. Two waits park two replies, each answered when its
own run (or the newest, if superseded) exits; a disconnected client drops
its park. SIGHUP never parks: it has no reply channel, so it runs the
plain path. The tag is additive like the scoped one (PROTOCOL_VERSION does not
move). An older compositor answers it with an ordinary unknown-request
error, never a silent immediate answer (which would read as “landed”
when it only handed over).
applied names the fields re-applied live (including autostart.
when new spawn entries started, where a spawn entry that fails to start is
refused by name instead and stays pending for the next reload, and
wallpaper / wallpaper. when the [wallpaper] section or its
command changed, which is handed to scootbg: its 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 (scoot msg exits non-zero).
look_applied, the answer to look-apply, names the look now
active (name), the apps reloaded in order (applied: compositor,
bar <display>, …), and every user-file key that overrides a look token
(shadowed). refused is empty on this reply: a refusal fails the swap
and answers error instead, naming the rollback. The reply arrives when
the swap lands: the compositor runs scoot look apply in a child and
answers when the child exits, so only one swap runs at a time (a
second is refused). Ring, background and cursor are on screen with the
reply. The wallpaper follows through scootbg (handed over, never waited
on; its outcome is in the compositor log), so screenshot after
wait-idle to see it.
A timed preview ({"type":"look_apply","name":"moonrise","for_ms":20000}
on the raw socket; scoot msg sends a stay) swaps back when the hold
expires, unless a newer choice moved meanwhile. A swap still running when
the hold expires holds the revert until it finishes, then re-checks, so
the preview never sticks with no timer left. Zero holds nothing (a
stay, like omitting it). A restart drops a pending revert; the swap
stays.
{ "type": "look_applied", "name": "moonrise", "applied": ["compositor"], "refused": [], "shadowed": ["appearance.focus_ring_active_color"] }looks, the answer to looks, carries the catalog (looks: one
entry per look with name, title, polarity, origin and source),
the active look’s name (active, or null with no recorded choice),
and the session’s state directory (state_dir).
{ "type": "looks", "active": "moonrise", "state_dir": "/run/user/1000/scoot-look-wayland-1", "looks": [{ "name": "moonrise", "title": "Moonrise", "polarity": "dark", "origin": "shipped", "source": "/prefix/share/scoot/looks/moonrise" }] }look_applied and looks are new reply variants and moved
PROTOCOL_VERSION 11 → 12. An older client handed one would fail its
decode, but only a client new enough to have asked receives one. The
request halves are additive ({"type":"look_apply","name":"…"}
optionally with "for_ms", {"type":"looks"}): an older server answers
either with an ordinary error, never a misapplied swap.
