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.luaCreate 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.luaIf 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:
| Declaration | Layering behavior |
|---|---|
| Singleton sections | Personal fields override matching installed fields; omitted fields remain unchanged |
| Layout styles | A personal declaration overlays fields for the same layout; other layouts remain unchanged |
| Key bindings | A personal binding replaces the same key; sweets.unbind removes it |
| Environment entries | A personal value replaces the same variable name |
| Monitor and workspace rules | A personal declaration replaces the rule with the same connector or workspace number |
| Window rules | Installed rules run first, then personal rules append; live properties restyle current managed windows |
| Layer rules | Installed rules run first, then personal rules append; valid reloads restyle current layer roots |
| Input selectors | The personal global block overlays the installed block; selector rules append |
| Startup commands | Commands from both layers remain in installed-then-personal order |
| Animations | Personal 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-configValidate one specific file without loading the installed defaults:
sweets --check-config ./test-config.luaA 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 reloadEvery 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 configThe 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.
| Call | Configures |
|---|---|
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
- Split the configuration with bounded relative includes
- General settings for workspaces, focus, and cross-monitor behavior
- Key bindings for actions, commands, and key sequences