General
Modifier key, background, workspaces, and cross-monitor behavior
sweets.general sets global compositor behavior.
sweets.general({
background = "#12121c",
mod_key = "SUPER",
workspace_mode = "shared",
workspace_count = 10,
workspace_move_follow = false,
workspace_back_and_forth = false,
workspace_wrap = false,
workspace_skip_empty = false,
focus_cross_monitor = true,
move_cross_monitor = true,
drag_center_cursor = true,
resize_on_border = false,
})Options
| Field | Type | Default | Description |
|---|---|---|---|
background | string | "#06080A" | Clear color behind windows, #RRGGBB or #RRGGBBAA |
mod_key | string | "SUPER" | What MOD means in bindings: "SUPER" or "ALT" |
workspace_mode | string | "shared" | One workspace set globally, or one per monitor |
workspace_count | integer | 10 | Usable workspaces, 1 to 10 |
workspace_move_follow | boolean | false | Follow a window after moving it to another workspace |
workspace_back_and_forth | boolean | false | Re-selecting the workspace you are on returns to the previous one |
workspace_wrap | boolean | false | Relative workspace actions wrap between the first and last number |
workspace_skip_empty | boolean | false | Relative workspace actions pass over empty workspaces |
focus_cross_monitor | boolean | true | Directional focus may continue onto the next monitor |
move_cross_monitor | boolean | true | Directional moves may continue onto the next monitor |
drag_center_cursor | boolean | true | Center a tiled window under the pointer when a MOD+drag starts |
snap | table | disabled | Snap a dragged or resized floating window to nearby edges |
resize_on_border | boolean | false | Resize a window by dragging its border, with no modifier held |
resize_border_grab | integer | 8 | Extra pixels outside the border you can still grab, 0 to 64 |
insert_hint | colour, gradient, or false | "#C27AFF80" | Show where a dragged tiled window will land |
render_unfocused_fps | integer | 15 | Frame rate for windows kept drawing by render_unfocused, 1 to 60 |
spawn_origin_placement | boolean | true | Open a launched app on the workspace you launched it from |
spawn_origin_timeout | integer | 30 | Seconds a launched app has to open its window, 1 to 600 |
lid_switch | string | "disable_panel" | What closing a laptop lid does to the built-in screen |
power_key | string | "system" | Who acts on the power key |
Omitted fields keep their installed value. The installed configuration sets
background = "#12121c" and insert_hint = false, and leaves the rest at the
defaults above.
mod_key
sweets.general({ mod_key = "ALT" })
sweets.bind("MOD+Return", "spawn", "foot") -- Alt+ReturnExplicit Ctrl, Shift, Alt, and Super in a binding are unaffected. See
Key bindings.
workspace_mode
shared keeps one workspace per number for the whole session. A workspace
is visible on at most one monitor at a time.
- Selecting a visible workspace focuses its monitor.
- A hidden workspace with windows returns to its remembered or pinned monitor.
- An empty workspace opens on the selected monitor.
per_monitor gives every monitor its own set, so two monitors can each
have a workspace 1. Number actions apply to the selected monitor.
A pin made with sweets.workspace wins over
the selected monitor in both modes.
Changing the mode on reload keeps your windows. per_monitor creates the
missing workspaces; shared merges equal numbers.
workspace_count
Requests above the count are ignored, and disabled workspaces disappear from panels. Bindings are independent of the count, so keep your loop in range:
sweets.general({ workspace_count = 5 })
for i = 1, 5 do
sweets.bind("MOD+" .. i, "workspace", i)
sweets.bind("MOD+Shift+" .. i, "workspace_move", i)
endLowering the count first moves windows off the disabled workspaces onto the last enabled one.
workspace_wrap and workspace_skip_empty control the relative workspace
bindings and workspace touchpad swipes. A minimized window keeps its workspace
non-empty. A pinned window does not, because it is carried onto every workspace
that its monitor shows. Reloading either option affects the next input and does
not move anything immediately.
Cross-monitor focus and movement
Sweets always looks for a target on the current monitor first. These settings only decide what happens when there is none in that direction.
focus_cross_monitor lets focus_left, focus_right, focus_up, and
focus_down reach the next monitor.
move_cross_monitor lets move_left, move_right, move_up, and move_down
carry the focused tiled window there. Floating, maximized, and fullscreen
windows never use this fallback, and the explicit move_output_* actions work
either way.
Both take effect on the next action; a reload never moves anything by itself.
drag_center_cursor
When enabled, a MOD+left-button drag on a tiled window centers that window
under the pointer for the rest of the drag. When disabled, the window keeps the
exact point you grabbed.
With animations on, the window slides to the
centered position over window_move. The slide never delays the drag — keep
moving, reorder, cross monitors, or drop while it runs.
Floating windows and client-requested moves always keep their grab point. A reload affects later drags only.
snap
Snapping pulls a floating window's edges onto the work area and onto the other floating windows on its monitor while you drag or resize it with the pointer.
sweets.general({
snap = { enabled = true, monitor_gap = 10, window_gap = 10, respect_gaps = false },
})| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Turn snapping on |
monitor_gap | integer | 10 | How close an edge must come to the work area to snap, in pixels. 0 turns this off |
window_gap | integer | 10 | How close an edge must come to another floating window to snap. 0 turns this off |
respect_gaps | boolean | false | Leave your configured gaps instead of snapping flush |
An edge snaps to the work area, to another floating window's matching edge, or against another floating window it lies beside. A snapped edge stays put until you drag twice the capture distance away, so it does not flicker at the boundary.
Snapping applies to floating windows only. A tiled MOD+drag is a reorder, so
there is nothing for its edges to land on.
The work area excludes panels and struts, so a snapped window never lands under a bar. Size limits set by the application still win. A reload affects later drags only.
resize_on_border
Drag a window's border, or the gap just outside it, to resize it. No modifier
key. MOD+right-drag keeps working either way.
sweets.general({
resize_on_border = true,
resize_border_grab = 8,
})A thin border is hard to hit, so resize_border_grab extends the grab area
outward by that many pixels. The cursor changes shape when you are on it.
The area never takes a click from a window. Your press goes to whatever is drawn under the pointer first: the application, a menu, a panel. Only when nothing claims the pixel does the nearest window's border area get it, which is why the extra pixels work over the wallpaper and in the gap between two windows.
Fullscreen and maximized windows have no border to grab. With border.width = 0
and resize_border_grab = 0 there is nothing to grab at all.
A tiled window resizes the layout boundary that edge belongs to, exactly as
MOD+right-drag does, so nothing happens on an edge its layout cannot move -
monocle, or a single tile filling the workspace. Floating windows follow
snap. A reload affects later presses only.
insert_hint
While you MOD+drag a tiled window, Sweets shades the spot it will land in if
you let go now.
sweets.general({ insert_hint = "#C27AFF80" })
sweets.general({ insert_hint = { "#C27AFF80", "#7AA2F780", angle = 90 } })
sweets.general({ insert_hint = false }) -- no hintIt takes the same colours as a border. What it shows:
| Drop target | Hint |
|---|---|
| A tiled layout | The slot the window will take |
| An empty workspace | The whole work area |
| The middle of a scrolling column | That column, which the window joins |
| Near a scrolling column's edge | A bar where the new column opens |
The hint appears once the window moves and disappears when you let go. Moving a floating window shows no hint.
spawn_origin_placement
An app started by a spawn binding opens on the workspace you pressed the key
on, even if you switched away while it was starting.
sweets.general({
spawn_origin_placement = true,
spawn_origin_timeout = 30,
})It only works for apps that read the XDG_ACTIVATION_TOKEN variable before they
show their first window. GTK 4 apps and foot do. Other apps open where you are
when their window appears, as before.
| Situation | Where the window opens |
|---|---|
A window rule sets workspace, monitor or pin | Where the rule says |
| A dialog with a parent window | With its parent |
The app took longer than spawn_origin_timeout | Where you are now |
| The launch workspace no longer exists | Where you are now |
The window does not take focus away from the workspace you moved to. Apps
started by exec_once are not affected.
render_unfocused_fps
The rate windows named by the
render_unfocused
window rule keep drawing at while they are on screen nowhere.
sweets.general({ render_unfocused_fps = 15 })It is a cap from 1 to 60, and it applies to every such window. 15 is enough
for a shared window; higher values spend power without being seen, since a
paced window is drawn on no monitor. With no window using the rule, nothing is
paced and the setting does nothing.
lid_switch
Closing a laptop lid turns the built-in screen off and moves its windows to your other monitors. Opening it brings both back.
sweets.general({ lid_switch = "disable_panel" })| Value | Effect |
|---|---|
"disable_panel" | The built-in screen is disabled while the lid is shut |
"none" | The lid never changes your outputs |
Built-in means the connector type, so eDP, LVDS, DSI and DPI screens are covered and nothing else is. A monitor rule that disables a screen still wins; the lid can only turn one off.
With no other monitor attached, the built-in screen stays on: a desktop with no screen at all would leave your windows nowhere to go. On most laptops the system suspends a moment later anyway.
A lid switch binding still runs whichever value you choose. A laptop booted or resumed with the lid already shut starts with its screen off.
power_key
Press the power key and the system powers off. "compositor" asks logind to
leave the key alone, so your own binding runs instead.
sweets.general({ power_key = "compositor" })
sweets.bind("XF86PowerOff", "spawn", { "veila", "lock" })| Value | Effect |
|---|---|
"system" | logind keeps the key and powers the machine off |
"compositor" | Sweets takes the key, so only your binding runs |
Bind XF86PowerOff before setting "compositor", or the key does nothing.
Sweets warns in its log when it is set with nothing bound.
With "system" a binding on XF86PowerOff still runs, and the machine powers
off as well, so the two are rarely useful together.
This needs logind and systemd-inhibit. Without them the key stays with the
system and Sweets says so once in its log.