Sweets
Configuration

Basic Configuration

Create, validate, layer, and reload your Sweets Lua configuration

Sweets is configured with a constrained Lua 5.4 API. It loads maintained defaults first, then applies your personal configuration as an override.

Create a personal configuration

Your personal configuration belongs at:

${XDG_CONFIG_HOME:-$HOME/.config}/sweets/sweets.lua

Create its directory, then open sweets.lua in your editor:

mkdir -p "${XDG_CONFIG_HOME:-$HOME/.config}/sweets"

A personal file can contain only the values you want to change:

sweets.general({
    mod_key = "SUPER",
})

sweets.gaps({
    inner = 12,
    outer = 12,
    smart = true,
})

sweets.bind("MOD+D", "spawn", { "fuzzel" })

Keep personal changes in your user file. Do not edit the installed sweets.lua; package upgrades or later source installations may replace it.

Load order

Installed defaults load first from the running executable's prefix:

PREFIX/share/sweets/sweets.lua

/usr/bin/sweets uses /usr/share/sweets/sweets.lua. /usr/local/bin/sweets uses /usr/local/share/sweets/sweets.lua.

Your personal file loads second when present:

${XDG_CONFIG_HOME:-$HOME/.config}/sweets/sweets.lua

If the personal file is missing, Sweets uses the installed defaults unchanged. If both files are missing, it starts with typed Rust defaults and a small set of emergency bindings.

XDG_CONFIG_HOME and HOME must resolve to absolute paths. Sweets reports a configuration path error when neither produces a usable location.

How the layers combine

The personal layer does not replace the installed configuration wholesale. Each API has a defined merge rule:

DeclarationLayering behavior
Singleton sectionsPersonal fields override matching installed fields; omitted fields remain unchanged
Layout stylesA personal declaration overlays fields for the same layout; other layouts remain unchanged
Key bindingsA personal binding replaces the same key; sweets.unbind removes it
Environment entriesA personal value replaces the same variable name
Monitor and workspace rulesA personal declaration replaces the rule with the same connector or workspace number
Window rulesInstalled rules run first, then personal rules append; live properties restyle current managed windows
Layer rulesInstalled rules run first, then personal rules append; valid reloads restyle current layer roots
Input selectorsThe personal global block overlays the installed block; selector rules append
Startup commandsCommands from both layers remain in installed-then-personal order
AnimationsPersonal fields overlay installed timing policy; the master switch defaults to off

A singleton such as sweets.general, sweets.layout, or sweets.gaps may be declared once in each layer. Included files are part of their parent layer, so declaring the same singleton twice across one layer and its includes is an error.

Validate before reloading

Validate the installed and personal layers together without opening a Wayland socket or acquiring the seat:

sweets --check-config

Validate one specific file without loading the installed defaults:

sweets --check-config ./test-config.lua

A successful check prints the resolved path and exits with status 0. Syntax errors, unknown fields, wrong types, duplicate declarations, invalid values, and unsafe include paths produce a nonzero exit status with the failing source and message.

Automatic and manual reload

Sweets watches the installed file, personal file, and every included file. Saving, creating, replacing, or deleting any watched source requests a debounced reload.

Reload manually with the default MOD+Shift+R binding or IPC:

sweets msg reload

Every reload builds and validates a complete candidate before changing runtime state. An accepted candidate is applied atomically. A rejected candidate leaves the last valid configuration active.

Individual setting pages explain when a valid reload affects only future actions, future windows, or requires a compositor restart.

Errors and recovery

  • Invalid startup configuration — Sweets shows the error on every monitor and starts with typed Rust defaults plus emergency bindings.
  • Invalid reload — Sweets shows the error, keeps the last valid configuration, and continues watching the failed sources.
  • Corrected reload — The accepted candidate replaces the active baseline and clears the recorded error.
  • Unavailable filesystem notifications — Sweets falls back to polling; manual reload remains available.

Inspect the active path, origin, generation, pending state, and latest error:

sweets msg config

The session log also records rejected and accepted reloads. See the installation guide for the default log location.

Configuration API

The global sweets table is the only compositor API exposed to Lua.

CallConfigures
sweets.general({ ... })Modifier, background, workspace mode, and cross-monitor policy
sweets.xwayland({ ... })Private rootless XWayland startup
sweets.layout({ ... })Layout defaults, master position, insertion, and cycling
sweets.layout_style(...)Per-layout tiled-window appearance overrides
sweets.gaps({ ... }), sweets.border({ ... }), sweets.window({ ... }), sweets.special({ ... })Gaps, borders, window corners, and special-workspace appearance
sweets.notify({ ... })Configuration-error bar appearance
sweets.keyboard({ ... }), sweets.cursor({ ... })XKB, repeat, Num Lock, and cursor theme
sweets.pointer({ ... })Automatic pointer-follow behavior
sweets.input(...)Global and per-device libinput settings
sweets.bind_switch(...)Native lid and tablet-mode command bindings
sweets.monitor(...)Output mode, adaptive sync, scale, transform, position, and enablement
sweets.workspace(...)Workspace-to-monitor pins
sweets.window_rule({ ... })Opening behavior and live root or XDG-popup appearance for matching windows
sweets.layer_rule({ ... })Live root or XDG-popup appearance for matching layer-shell surfaces
sweets.blur({ ... })Global backdrop-blur renderer policy
sweets.animations({ ... })Default-off window and same-output workspace transitions
sweets.env(...)Environment for programs launched by Sweets
sweets.exec_once(...)Programs started once per compositor session
sweets.bind(...), sweets.unbind(...)Key bindings and key sequences
sweets.include(...)Additional Lua files below the configuration root

Unknown API names and fields are rejected. Values are converted into validated Rust types before a configuration becomes active. Reads of undeclared Lua globals are rejected too. This catches values such as trueee, falsed, or an unquoted name instead of silently treating the affected field as absent.

local enabled = true
sweets.pointer({ follow_focus = enabled }) -- valid

sweets.input({ natural_scroll = falsed }) -- error: unknown global `falsed`

Declare variables before reading them. An explicit literal nil keeps normal Lua behavior and omits that table entry.

Lua sandbox

Configuration runs in a sandbox. Lua functions, loops, tables, and the string, table, math, and utf8 libraries are available.

The global environment is strict for reads. Local variables work normally, and a global assigned before use can be shared with later included files. Reading a missing global rejects the complete candidate and reports the source line.

Filesystem, process, and native-module access are not exposed. This includes io, os, package, require, debug, dofile, loadfile, and load. Sweets also removes protected calls and other functions that could bypass resource enforcement, including pcall, xpcall, collectgarbage, and print.

Use sweets.include(...) instead of require or dofile. Use sweets.exec_once(...) or a spawn binding to start programs. Commands are direct argument arrays and are not interpreted by a shell.

Each complete load is limited to:

  • 5 million Lua instructions
  • 250 milliseconds of evaluation time
  • 16 MiB of Lua-managed memory
  • 128 nested Lua calls
  • 32 configuration sources and 16 levels of include nesting
  • 256 KiB per source file and 512 KiB across all sources

Exceeding a limit rejects the complete candidate like any other configuration error. Numeric settings must also be finite; math.huge, -math.huge, and 0/0 are rejected.

Next steps

On this page