Sweets

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

It exports SWEETS_SOCKET to spawned processes. sweetctl finds the socket via $SWEETS_SOCKET, falling back to deriving the path from $WAYLAND_DISPLAY.

Trust model

IPC is an owner-only control interface, not a multi-user authorization system. Any process running as the compositor's effective user can issue every command, including spawning programs, closing windows, and stopping Sweets.

Before publishing IPC, Sweets requires XDG_RUNTIME_DIR to be an absolute, real directory that is owned by its effective user, writable and searchable by that owner, and inaccessible to group and other. The session launcher enforces the same contract. If validation fails, no IPC socket is created.

The socket is set to mode 0600, independent of the process umask. Every accepted connection must report the same effective UID through kernel peer credentials — SO_PEERCRED on Linux, getpeereid on supported BSDs. A credential failure, mismatched UID, or unsupported platform is denied.

An inherited SWEETS_SOCKET is cleared and replaced only after the secure listener is ready.

sweetctl applies the runtime-directory checks when deriving a socket from WAYLAND_DISPLAY. Setting SWEETS_SOCKET explicitly is an intentional request to connect to that exact path.

Protocol

IPC is a bounded, line-framed stream protocol. A client sends one command terminated by a newline. The command may be at most 4096 bytes and may not contain NUL bytes. sweetctl adds the newline automatically.

Sweets waits for the complete frame before dispatching. EOF before the newline, an oversized frame, and malformed subscriptions are rejected without executing anything.

For a normal request the server replies and closes the connection, so EOF is the response boundary. Responses begin with OK or ERR <message>, with query data on following lines. A one-shot response is limited to 4 MiB. sweetctl prints the response and exits non-zero on ERR.

subscribe is the exception. After the initial OK, the connection stays open and streams event lines until the client disconnects. A subscriber more than 1 MiB behind is disconnected.

Limits: at most 64 simultaneous connections. Incomplete requests and one-shot clients that do not consume their response are closed after 5 seconds. Established subscriptions are exempt from the idle timeout.

The server documents itself, so the running compositor is always the authoritative reference:

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 JSON

commands is an alias for help. An unknown name replies ERR.

Commands

CommandReply
help / commandsevery command with a summary; help <command> details one
versioncompositor version
configactive general/layout settings, one key value per line
config-sourcesfiles the running config was built from
config-errorlast config failure, if any
outputsone line per output: name, mode, position, scale, active workspace
output <name>detailed geometry for one output
output <name> scale <factor>set the output scale (0.1–16)
output <name> mode <w> <h> [hz]set the output mode; refresh 0.001–1000 Hz
output <name> position <x> <y>set the layout position
output <name> transform <t>set the transform
output <name> power <on|off>power the output on or off
output all power <on|off>power all connected outputs
output <name> enabled <true|false>raw alias for output power state
output <name> adaptive_sync <on|off>enable or disable VRR when supported
workspacesactive and non-empty workspaces with counts, layout, master position
viewsone line per window (see views)
inputsone line per input device
input <name>detailed info for one matching device
input <name> <setting> <value>change one libinput setting temporarily
workspace <1-10>switch the current output to that workspace
focus next / focus prevfocus the next / previous window
closeclose the focused window
minimize / unminimizeminimize the focused window / restore the last one
reloadmanually reload the Lua config (fail-safe)
action <name> [arg]run any window-manager action
subscribe <event>...stream events
quitexit the compositor

Numeric arguments must be complete finite values. nan, infinity, trailing characters, overflow, and underflow are rejected rather than approximated.

Examples

sweetctl version
sweetctl config
sweetctl outputs
sweetctl output DP-1 scale 1.5
sweetctl output DP-1 mode 2560 1440 144
sweetctl output DP-1 transform 90
sweetctl output all power on
sweetctl inputs
sweetctl input ELAN accel_speed -0.25
sweetctl views
sweetctl workspace 2
sweetctl reload
sweetctl action toggle_floating
sweetctl action layout grid
sweetctl action spawn foot
sweetctl subscribe focus view workspace layout

Setting scale, mode, position, transform, power, enabled, or adaptive_sync stages the in-memory rule, tests the output, and publishes it only after the backend accepts. A rejected request replies ERR and changes nothing. Changes are runtime-only — reload reverts to the file.

output all power ... affects connected outputs only, applying each independently. If only some accept, it replies ERR, reports per-connector status, and keeps the successful changes. Powering an output off keeps its windows assigned; they reappear when it powers on.

Sweets also reloads sweets.lua automatically when it or any included file changes. The IPC command is a fallback for scripts.

JSON output

Add -j (or --json) before the command to emit a single JSON value instead of line-oriented text:

sweetctl -j outputs
sweetctl -j views | jq '.[] | select(.focused) | .title'
sweetctl -j workspaces | jq '.workspaces[] | select(.active) | .index'
sweetctl -j config | jq -r .mod_key

The 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 an object. Strings are escaped, so titles and device names are always valid JSON.

Behavior in -j mode:

  • Success prints only the JSON to stdout, exit 0. The OK line is stripped, so it pipes straight into jq.
  • Errors print to stderr and exit non-zero, so a failed query never feeds malformed data into a parser.
  • Mutating commands accept -j too. On success they print nothing and exit 0; on failure the message goes to stderr. Use the exit code.

-j is for one-shot queries. subscribe still streams plain event <name> lines — re-query 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 missing title report null, and special is null when the scratchpad is empty.

Query output

views

One line per window: output, workspace, app-id, any of floating/minimized/fullscreen/maximized/focused, an optional icon="<name>" when an xdg-toplevel-icon override is set, and the quoted title.

output <name>

Reports the live geometry of a single output:

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

config

Reports the live values after any reloads:

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 false

config-sources

Lists every Lua file the running config was built from, in load order. This is exactly the set the reload watcher tracks, so it confirms whether an include 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.lua

Paths are absolute and canonical. count 0 means no config file was found and the compositor is on built-in defaults.

config-error

Reports the most recent config failure, so a status bar can show it without scraping the log. This is the same error the on-screen bar displays.

When the config is valid:

OK
error false

After a failure, error true is followed by the failing file, the 1-based line (omitted when unknown), and the 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 as soon as a later edit reloads cleanly.

Status-bar recipe: a config-health module

Pairing config-error with config-sources gives a bar an "is my config OK" indicator. This Waybar custom module shows a warning on failure and lists the loaded files in the tooltip:

~/.config/sweets/scripts/config-health.sh
#!/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
~/.config/waybar/config.jsonc
"custom/config": {
  "exec": "~/.config/sweets/scripts/config-health.sh",
  "return-type": "json",
  "interval": 5,
  "on-click": "sweetctl reload"
}

The file set only changes on a reload, so a short interval is plenty. There is no event to subscribe to for config changes.

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", { ... }), or 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=adaptive

If the substring matches more than one device, Sweets replies ERR and asks for a more specific substring.

Live input tuning

Use the same unique substring to change one supported setting immediately:

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 flat

Booleans accept true/false or on/off. Supported settings are tap, tap_drag, tap_drag_lock, natural_scroll, left_handed, middle_emulation, disable_while_typing, accel_speed (-1.01.0), and accel_profile (adaptive or flat). Sweets rejects settings a device cannot support.

Live changes are temporary. A reload, reconnect, or restart resets the device and reapplies sweets.input(...). Find the comfortable value here, then put it in Lua.

Actions

action <name> [arg] runs a window-manager command by name. The names and arguments match sweets.bind, so anything reachable from a keybind is reachable from a script without binding a key.

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 htop

The reply is OK, or ERR <message> when the name is unknown or an argument is invalid. Compositor state is left untouched on error.

ActionArgumentEffect
spawncommand linelaunch a detached process (spaces allowed)
closeclose the focused window
focus_next / focus_prevcycle focus in stacking order
focus_left / focus_right / focus_up / focus_downfocus by direction
move_left / move_right / move_up / move_downmove by direction
toggle_floatingfloat or tile the focused window
toggle_fullscreen / toggle_maximizetoggle window display mode
minimize / unminimizeminimize / restore onto the current workspace
workspace110switch the current output to that workspace
workspace_move110move the focused window to that workspace
special_toggle / special_moveshow or hide the scratchpad / send a window to it
resize_grow / resize_shrinkresize the master split horizontally
resize_grow_vertical / resize_shrink_verticalresize vertically
layoutlayout nameset the active workspace's layout
cycle_layoutnext (default) / prevstep through the layout cycle
master_positionleft / right / top / bottomplace the master area
cycle_master_positionrotate the master area
grid_balanceeven out the grid layout
reload / quit / dismiss_errorreload / exit / dismiss the error bar

Tile-only actions no-op when the active workspace is not that layout, matching keybind behavior.

action targets the focused window and current output, exactly like a keybind press. There is no window selector — focus a window first to act on it.

Events

subscribe turns a connection into a live event stream, so bars redraw on change instead of polling:

sweetctl subscribe focus view workspace layout
# or, for everything:
sweetctl subscribe all

The server replies OK once, then writes one line per event while the connection stays open.

EventLine
focusevent focus output=<name> ws=<N> app_id="<id>" title="<title>" (or event focus none)
viewevent view — the window set changed; re-query views
workspaceevent workspace output=<name> index=<N>
layoutevent layout output=<name> index=<N> layout=<name>
titleevent title output=<name> ws=<N> app_id="<id>" title="<title>"

app_id and title are double-quoted and escaped like the --json strings, so 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 constantly, so a bar tracking only the window set can subscribe to view and avoid being woken on every title tick.

view is 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="~"
done

For fuller state, re-query 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
done

That is all a bar needs for a stack indicator like [2/5] on a tabbed group: workspaces reports windows=N, and views marks the 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. Reconnect.

title events fire for native Wayland (xdg-shell) windows. XWayland windows do not yet push title changes; re-query views on the next focus or view event.

On this page