IPC
Control Sweets at runtime over its Unix socket with the sweetctl CLI.
Sweets exposes a small control socket for querying state and dispatching
commands. The bundled sweetctl CLI is the easiest way to use it.
Socket
The compositor listens on a Unix socket:
$XDG_RUNTIME_DIR/sweets-<WAYLAND_DISPLAY>.sockand exports SWEETS_SOCKET to processes it spawns. sweetctl finds the socket
via $SWEETS_SOCKET, falling back to deriving the path from $WAYLAND_DISPLAY.
Protocol
One request per connection: the client sends a single command line, the server
replies and closes. Responses begin with OK or ERR <message>; query data
follows on subsequent lines. Unknown commands are rejected (ERR). sweetctl
prints the response and exits non-zero on ERR.
The one exception is subscribe: that connection stays open and the server keeps
streaming event lines until the client disconnects.
Pass -j/--json before the command to get the read queries as
machine-readable JSON instead of text.
The server documents itself: sweetctl help lists every command it accepts, so
the authoritative reference is always the running compositor, not this page.
sweetctl help # every command, with a one-line summary
sweetctl help output # just the forms of one command
sweetctl -j help # the command list as JSONhelp <command> filters to one command's forms; an unknown name replies ERR.
commands is an alias for help.
Commands
| Command | Reply |
|---|---|
help / commands | list every command with a one-line summary; help <command> details one |
version | compositor version |
config | active general/layout settings, one key value per line |
config-sources | files the running config was built from (main plus every include) |
config-error | last config load/reload failure, if any (key value per line) |
outputs | one line per output: name, mode, position, scale, active workspace |
output <name> | detailed geometry for one output (key value per line) |
output <name> scale <factor> | set the output scale (0.1–16) at runtime |
output <name> mode <w> <h> [hz] | set the output mode at runtime |
output <name> position <x> <y> | set the output's layout position at runtime |
output <name> transform <t> | set the output transform (normal, 90, 180, 270, flipped, flipped-90, flipped-180, flipped-270) |
output <name> power <on|off> | power the output on or off (DPMS-style) |
output all power <on|off> | power all connected outputs on or off |
output <name> enabled <true|false> | raw alias for output power state |
workspaces | active + non-empty workspaces with window counts, layout, and master position, plus a special line for the special workspace |
views | one line per window: output, workspace, app-id, any of floating/minimized/fullscreen/maximized/focused, optional icon="<name>" (xdg-toplevel-icon override, when set), and quoted title |
inputs | one line per input device: kind, pointer type, libinput state, quoted name |
input <name> | detailed info for the single device matching a case-insensitive name substring |
input <name> <setting> <value> | change one physical libinput pointer setting until config reload, reconnect, or restart |
workspace <1-10> | switch the current output to that workspace |
focus next | focus the next window |
focus prev | focus the previous window |
close | close the focused window |
minimize | minimize the focused window (hide from layout and focus, keep taskbar entry) |
unminimize | restore the last minimized window on the active workspace |
reload | manually reload the Lua config (fail-safe) |
action <name> [arg] | run any window-manager action — the same set the keybinds use (focus/move by direction, toggles, resize, layout, workspaces, scratchpad, spawn) |
subscribe <event>... | keep the connection open and stream events (focus, view, workspace, layout, title, or all) |
quit | exit the compositor |
Examples
sweetctl version
sweetctl config
sweetctl config-sources
sweetctl outputs
sweetctl output DP-1
sweetctl output DP-1 scale 1.5
sweetctl output DP-1 mode 2560 1440 144
sweetctl output DP-1 position 0 0
sweetctl output DP-1 transform 90
sweetctl output DP-1 power off
sweetctl output all power on
sweetctl inputs
sweetctl input ELAN
sweetctl input ELAN accel_speed -0.25
sweetctl input "Logitech USB Optical Mouse" accel_profile flat
sweetctl views
sweetctl workspace 2
sweetctl reload
sweetctl action toggle_floating
sweetctl action focus_left
sweetctl action workspace 3
sweetctl action layout tabbed
sweetctl action spawn foot
sweetctl subscribe focus view workspace layout
sweetctl subscribe focus titleSetting scale, mode, position, transform, power, or enabled
updates the in-memory monitor config and applies it to the connected output
immediately. The change is runtime-only: it is not written back to
sweets.lua, so reload (or restart) reverts to the file. output all power ... affects connected outputs only. An unsupported mode falls back to
the preferred mode and replies ERR. Powering an output off keeps its windows
assigned; they reappear when it is powered on again.
Sweets also reloads sweets.lua automatically when it — or any
included file — changes. The IPC command
is useful as a fallback or for scripts; config-sources lists exactly which
files the watcher is tracking.
JSON output
Add -j (or --json) before the command and the read queries emit a single
JSON value on stdout instead of the line-oriented text — the shape scripts and
status bars actually want to parse:
sweetctl -j outputs
sweetctl -j views | jq '.[] | select(.focused) | .title'
sweetctl -j workspaces | jq '.workspaces[] | select(.active) | .index'
sweetctl -j config | jq -r .mod_keyThe flag applies to every read query: version, outputs, output <name>,
workspaces, views, config, config-sources, config-error, inputs, and
input <name>. Arrays (outputs, views, inputs) come back as JSON arrays;
everything else is a JSON object. Strings are properly escaped, so window titles
and device names are always valid JSON.
How the client behaves in -j mode, so it composes cleanly in a pipeline:
- Success prints only the JSON document to stdout (the
OKstatus line is stripped), exit code0. Pipe straight intojq. - Errors print the message to stderr (not stdout) and exit non-zero, so a failed query never feeds malformed data into a parser.
- Mutating commands (
output ... scale,action ...,workspace,close,reload, …) accept-jtoo: on success they print nothing and exit0; on failure the message goes to stderr with a non-zero exit. Use the exit code.
-j is for the one-shot queries. subscribe still streams the plain event <name> lines; re-query the detail you need in JSON when an event fires.
Example shapes:
// sweetctl -j outputs
[{"name":"DP-1","width":2560,"height":1440,"refresh":144.000,"x":0,"y":0,"scale":1.00,"workspace":1}]
// sweetctl -j views
[{"output":"DP-1","workspace":1,"app_id":"foot","title":"~","floating":false,
"minimized":false,"fullscreen":false,"maximized":false,"focused":true,"icon":"foot"}]
// sweetctl -j workspaces
{"workspaces":[{"output":"DP-1","index":1,"windows":2,"layout":"tile",
"master_position":"left","active":true}],"special":null}
// sweetctl -j config-error (when the config is valid)
{"error":false}Nullable fields are explicit: app_id, icon, and a window title-less client
report null, and special is null when the scratchpad is empty.
Query output
output <name>
Reports the live geometry of a single output, e.g.:
OK
output DP-1
enabled true
position 0,0
size 2560x1440
refresh 144.000
scale 1.00
transform normal
usable 0,32 2560x1408
active_workspace 1
master 50.0config
Reports the live values after any reloads, e.g.:
OK
mod_key SUPER
background #12121C
gap_inner 8
gap_outer 8
smart_gaps true
master 50
new_window master
border_width 2
border_focused #C27AFF
border_unfocused #333338
pointer_follow_focus true
pointer_follow_focus_restore true
pointer_follow_workspace true
pointer_follow_new_window true
pointer_hide_when_following falseconfig-sources
Lists every Lua file the running config was built from — the main file plus each
sweets.include target, in load order.
This is exactly the set the reload watcher
tracks, so it is handy for confirming an include actually took effect:
OK
count 4
source /usr/share/sweets/sweets.lua
source /home/ns/.config/sweets/sweets.lua
source /home/ns/.config/sweets/parts/binds.lua
source /home/ns/.config/sweets/parts/rules.luaPaths are absolute and canonical. count 0 with no source lines means no
config file was found and the compositor is running on built-in defaults.
Status-bar recipe: a config-health module
Pairing config-error with config-sources gives a bar a small "is my config
OK" indicator: show a warning when the last reload failed, and list the loaded
files in the tooltip so a hover reveals exactly what is active. This
Waybar custom module does both — it reads
the two queries and prints one JSON line (using jq so the multi-line tooltip is
escaped into valid JSON):
#!/bin/sh
# config-error -> health; config-sources -> tooltip file list
err=$(sweetctl config-error)
if printf '%s' "$err" | grep -q '^error true'; then
msg=$(printf '%s' "$err" | sed -n 's/^message //p')
jq -cn --arg t "config ⚠" --arg tip "$msg" \
'{text:$t, class:"error", tooltip:$tip}'
else
files=$(sweetctl config-sources | sed -n 's/^source //p')
count=$(printf '%s\n' "$files" | grep -c .)
tip=$(printf 'config OK — %s files\n%s' "$count" "$files")
jq -cn --arg t "config ✔" --arg tip "$tip" \
'{text:$t, class:"ok", tooltip:$tip}'
fi"custom/config": {
"exec": "~/.config/sweets/scripts/config-health.sh",
"return-type": "json",
"interval": 5,
"on-click": "sweetctl reload"
}Because the file set only changes on a reload, a short interval (or a
signal-driven refresh after sweetctl reload) is plenty — there is no event
to subscribe to for config changes.
config-error
Reports the most recent config load or reload failure, so a status bar can show it without scraping the log. The compositor keeps running on the last good configuration; this is the same error the on-screen bar displays.
When the current config is valid:
OK
error falseAfter a failed load or reload, error true is followed by the failing file, the
1-based line (omitted when unknown), and the full message:
OK
error true
file /home/ns/.config/sweets/sweets.lua
line 9
message /home/ns/.config/sweets/sweets.lua:9: '}' expected (to close '{' at line 6) near '='The error clears the moment a later edit reloads cleanly. Poll this after a
reload, or pair it with the bar described in
Error reporting.
inputs
Reports connected input devices. Physical libinput pointers include the same
touchpad / mouse type used by sweets.input(selector, { ... }); touch,
tablet, tablet-pad, and switch devices are listed too:
OK
input keyboard libinput=true name="AT Translated Set 2 keyboard"
input pointer type=touchpad libinput=true name="ELAN1200:00 04F3:30BA Touchpad"
input pointer type=mouse libinput=true name="Logitech USB Optical Mouse"
input touch libinput=true name="ELAN Touchscreen"
input tablet libinput=true name="Wacom Pen and multitouch sensor Pen stylus"
input tablet_pad libinput=true name="Wacom ExpressKey Remote Pad"
input switch libinput=true name="Lid Switch"Use a unique part of the quoted name with sweets.input("name:TEXT", { ... }).
Use the same unique substring with input <name> to inspect one device:
OK
kind pointer
type touchpad
name "ELAN1200:00 04F3:30BA Touchpad"
libinput true
sysname event4
id bus=0x0018 vendor=0x04f3 product=0x30ba
tap_fingers 3
tap supported=true current=true default=false
tap_drag supported=true current=true default=true
tap_drag_lock supported=true current=false default=false
natural_scroll supported=true current=true default=false
left_handed supported=true current=false default=false
middle_emulation supported=false
disable_while_typing supported=true current=true default=true
accel supported=true current=-0.250 default=0.000
accel_profiles flat adaptive
accel_profile current=adaptive default=adaptiveIf the substring matches more than one device, Sweets replies with ERR and
asks for a more specific substring.
Live input tuning
Use the same unique device-name substring to change one supported setting right away:
sweetctl input ELAN tap true
sweetctl input ELAN natural_scroll true
sweetctl input ELAN accel_speed -0.25
sweetctl input "Logitech USB Optical Mouse" accel_profile flatBoolean values accept true / false or on / off. The supported settings
are tap, tap_drag, tap_drag_lock, natural_scroll, left_handed,
middle_emulation, disable_while_typing, accel_speed (-1.0 through
1.0), and accel_profile (adaptive or flat). Sweets rejects settings a
device cannot support.
Live changes are deliberately temporary. A config reload, device reconnect,
or compositor restart resets the device and reapplies sweets.input(...).
Use this command to find the comfortable value, then put it in Lua.
Actions
action <name> [arg] runs a window-manager command by name. The names and
arguments are exactly the ones sweets.bind
accepts, so anything reachable from a keybind is reachable from a script,
launcher, or status bar without binding a key for it.
sweetctl action focus_right
sweetctl action toggle_fullscreen
sweetctl action workspace 3
sweetctl action cycle_layout prev
sweetctl action master_position right
sweetctl action spawn foot -e htopThe reply is OK, or ERR <message> when the action name is unknown or its
argument is missing or invalid (an out-of-range workspace, an unknown layout,
and so on) — the compositor state is left untouched in that case.
| Action | Argument | Effect |
|---|---|---|
spawn | command line | launch a detached process (rest of the line, spaces allowed) |
close | — | close the focused window |
focus_next / focus_prev | — | cycle focus in stacking order |
focus_left / focus_right / focus_up / focus_down | — | focus by direction |
move_left / move_right / move_up / move_down | — | move the focused window by direction |
toggle_floating | — | float/tile the focused window |
toggle_fullscreen / toggle_maximize | — | toggle window display mode |
minimize / unminimize | — | minimize the focused window / restore the last one |
workspace | 1–10 | switch the current output to that workspace |
workspace_move | 1–10 | move the focused window to that workspace |
special_toggle / special_move | — | show/hide the scratchpad / send the focused window to it |
resize_grow / resize_shrink | — | resize the master split (horizontal) |
resize_grow_vertical / resize_shrink_vertical | — | resize the focused window vertically |
layout | layout name | set the active workspace's layout |
cycle_layout | next (default) / prev | step through the layout cycle |
master_position | left / right / top / bottom | place the master area |
cycle_master_position | — | rotate the master area |
grid_balance | — | even out the grid layout |
reload / quit / dismiss_error | — | reload config / exit / dismiss the config-error bar |
Tile-only actions (master resize, master_position, grid_balance) no-op when
the active workspace is not that layout, matching keybind behavior. See
Key bindings for the full argument
reference and aliases.
action targets the focused window and current output, exactly like a keybind
press. It does not take a window selector — to act on a specific window, focus
it first.
Events
Status bars and scripts usually want to redraw when something changes instead of
polling on a timer. subscribe turns a connection into a live event stream:
sweetctl subscribe focus view workspace layout
# or, for everything:
sweetctl subscribe allThe server replies OK once, then writes one line per event for as long as the
connection stays open. Press Ctrl-C (or close the socket) to stop.
Each line is event <name> followed by a compact payload describing what
changed, so a bar can update straight from the stream without an extra query:
| Event | Line |
|---|---|
focus | event focus output=<name> ws=<N> app_id="<id>" title="<title>" (or event focus none) |
view | event view — the window set changed (mapped, unmapped, minimized, or restored); re-query views |
workspace | event workspace output=<name> index=<N> |
layout | event layout output=<name> index=<N> layout=<name> |
title | event title output=<name> ws=<N> app_id="<id>" title="<title>" |
app_id and title are always double-quoted and escaped exactly like the
--json strings, so a title with spaces, quotes, or newlines
never breaks the line — one event is always one line. app_id is "" when the
client set none. The output/ws pair is present whenever the window is on a
workspace.
title is split out from view on purpose: titles change often (every shell
command, every browser tab), so a bar that only tracks the window set can
subscribe to view and avoid being woken on every title tick, while a bar that
displays the focused title subscribes to focus and title.
view stays a bare notification — "the set changed, re-read it" — because no
single window identifies the change. Everything else carries enough to redraw
without a round trip:
# Print the focused window's title as it changes, no re-query needed
sweetctl subscribe focus title | while read -r _ _ rest; do
echo "$rest" # e.g. output=DP-1 ws=2 app_id="foot" title="~"
doneWhen you do need fuller state (window list, counts), re-query — ideally the
--json form — on the matching event:
# Re-render a workspace/window indicator on every relevant change
sweetctl subscribe view workspace layout | while read -r _; do
sweetctl -j workspaces
sweetctl -j views
doneThis is all a bar needs to track a monocle stack
indicator like [2/5]: workspaces reports windows=N layout=monocle, and
views marks the visible (focused) window and its order.
A subscriber that stops reading is dropped once its backlog grows too large, so a stuck bar can never make the compositor buffer without bound. Just reconnect.
title events currently fire for native Wayland (xdg-shell) windows. XWayland
windows don't yet push title changes; a bar can still pick them up by
re-querying views on the next focus or view event.