Sweets
Configuration

Workspaces

Numbered workspaces, monitor pins, switching, and moving windows

Sweets has numbered workspaces 1 to 10. Their count and multi-monitor model come from sweets.general; sweets.workspace optionally gives a number a panel name, pins it to a monitor, or sets its layout and appearance.

sweets.general({
  workspace_mode = "shared",
  workspace_shown_elsewhere = "select",
  workspace_count = 10,
  workspace_move_follow = false,
  workspace_wrap = false,
  workspace_skip_empty = false,
})
FieldDefaultDescription
workspace_mode"shared"One set across all monitors, or one set per monitor
workspace_shown_elsewhere"select"Select a workspace's current monitor, or "swap" it onto the selected one
workspace_count10Enable workspaces 1 through this number
workspace_move_followfalseFollow a window after moving it to another workspace
workspace_wrapfalseWrap relative navigation between the first and last workspace
workspace_skip_emptyfalsePass over workspaces with no non-pinned windows

Pinning a workspace to a monitor

sweets.workspace(1, { name = "web", monitor = "eDP-1" })
sweets.workspace(4, { name = "code", monitor = "DP-1" })
sweets.workspace(2, { monitor = { make = "DEL", model = "S2719DGF" } })

The number is 1 to 10. monitor accepts an exact, case-sensitive connector name or a description table naming one or more of make, model, and serial. Use the values from sweets msg outputs. If two panels match a description, the first connector in name order owns the pin; add serial to distinguish identical panels. Each number may be declared once per configuration layer, and your declaration replaces the installed one.

Several numbers can share a monitor, but only one is visible there at a time.

A name is a label shown by ext-workspace panels and sweets msg workspaces. Actions still address the number. Names may repeat. Each must contain 1 to 32 printable characters; control characters and blank names are rejected. In per_monitor mode the same number has the same name on every monitor. A valid reload updates the label immediately. Without a name, panels see the number.

A numbered workspace is urgent while any of its mapped windows asks for attention, including minimized windows. Pinned windows mark the workspace their output currently shows. Special workspaces remain separate.

In shared mode, a pinned workspace returns to its monitor whenever that monitor is connected. Switching to it selects that monitor, and moving a window there sends it to that monitor.

Set workspace_shown_elsewhere = "swap" to bring a visible, unpinned workspace from another monitor to the selected one when you press its number key. The workspaces exchange monitors. Pinned windows stay with their monitor; a pinned workspace never moves. The special workspace overlay is left alone, and an exchange is refused while it covers the selected monitor.

In per_monitor mode, every monitor normally has its own copy of each number. A pin makes one monitor the owner, and existing same-number contents from other monitors move into it.

Pin only the numbers that need a fixed home. Leave the rest unpinned so every monitor keeps its own copy.

If the monitor is missing, Sweets places the workspace elsewhere and moves it back when the monitor returns, even on a different connector, keeping windows and focus.

Layout and appearance

sweets.workspace(3, {
  layout = "monocle",
  gaps = { inner = 0, outer = 0, smart = false },
  border = { width = 0 },
  window = { corner_radius = 0 },
})
FieldDefaultDescription
layoutlayout.defaultThe layout this workspace starts in
open_as_floatingfalseOpen new ordinary windows floating on this numbered workspace
gaps, border, windowInheritedSame fields as sweets.layout_style
monitorNonePin to a monitor, as above
nameWorkspace numberLabel for panels and IPC

A rule needs at least one field. layout is only the starting layout: cycle_layout still changes it, and your choice survives a reload.

Set open_as_floating = true to make new ordinary windows float when they open on that workspace. For example:

sweets.workspace(5, { open_as_floating = true })

The opening destination can come from a window rule, parent, launch token, or selected workspace. An explicit window rule floating = true or floating = false wins. Dialogs and size hints keep their usual behavior. In per_monitor mode the setting applies to that workspace number on every monitor. It does not apply to special workspaces. Reloading changes later opening decisions; moving or remapping an existing window does not change its floating state. sweets msg window-rule ID reports the workspace number in workspace_floating_default when this setting made that window float.

Settings resolve in this order, the last one set winning: global, then monitor, then workspace, then the active layout's layout_style. A window rule wins over all four. layout_style reaches tiled windows only.

In per_monitor mode, a rule applies to that number on every monitor. Special workspaces take their monitor's values, not a workspace rule.

Workspace count

sweets.general({ workspace_count = 5 })

This enables workspaces 1 to 5. Nothing can activate a higher number, and a pin above the count waits until that number is enabled. Lowering the count moves windows off the disabled workspaces onto the last enabled one — nothing is discarded.

Switching and moving

for number = 1, 9 do
  sweets.bind("MOD+" .. number, "workspace", number)
  sweets.bind("MOD+Shift+" .. number, "workspace_move", number)
end

sweets.bind("MOD+0", "workspace", 10)
sweets.bind("MOD+Shift+0", "workspace_move", 10)

The installed configuration already provides these. By default, switching to a workspace visible on another monitor selects that monitor. With workspace_shown_elsewhere = "swap", it exchanges the two monitors' workspaces.

workspace_move_follow decides whether you follow the window you moved.

Relative bindings need no number:

sweets.bind("MOD+Ctrl+Right", "workspace_relative_next")
sweets.bind("MOD+Ctrl+Left", "workspace_relative_prev")
sweets.bind("MOD+Ctrl+Shift+Right", "workspace_move_relative_next")
sweets.bind("MOD+Ctrl+Shift+Left", "workspace_move_relative_prev")

They resolve from the workspace shown on the selected monitor, then use the same switch or move path as a numbered binding. By default, a workspace already visible or pinned elsewhere selects that monitor; with workspace_shown_elsewhere = "swap", a visible, unpinned target exchanges workspaces with the selected monitor. workspace_wrap controls the ends of the range. workspace_skip_empty skips workspaces with no mapped, non-pinned windows; minimized windows still count.

To open the first free workspace on the selected monitor, bind the empty workspace actions:

sweets.bind("MOD+N", "workspace_empty")
sweets.bind("MOD+Shift+N", "workspace_move_empty")

They choose the lowest enabled number with no mapped, non-pinned windows; minimized windows count. A workspace visible or pinned to another connected monitor is skipped. In per_monitor mode, the search uses the selected monitor's workspace identities. When the lowest empty workspace is already shown, the action does nothing. workspace_move_empty obeys workspace_move_follow. Neither action uses workspace_wrap, workspace_skip_empty, or workspace_back_and_forth.

A numbered binding may launch an application after selecting its empty target:

sweets.bind("MOD+2", "workspace", 2, {
  spawn = { "firefox" },
  cooldown = 1000,
})

The command runs only while the requested workspace is selected and has no mapped, non-pinned windows. A repeated press can launch again before a slow client maps, so set a cooldown. With spawn_origin_placement = true, the launch token places its first window on that workspace; an opening window rule can override it. Disabling spawn-origin placement uses ordinary opening placement.

focus_or_workspace_left/right/up/down first uses ordinary directional focus, including focus_cross_monitor and an off-axis local window. It changes workspace only when there is no focus target, using previous for left/up and next for right/down.

The same actions over IPC:

sweets msg switch-workspace 4
sweets msg move-to-workspace 4

Moving a floating window to another monitor translates its remembered geometry into that monitor's coordinates.

Inspecting

sweets msg workspaces

Each entry shows its number, monitor, and whether it is active. In per_monitor mode the same number appears once per monitor, distinguished by the output field. Panels see the same set; numbers above workspace_count are not published.

Opening an application on a workspace

sweets.window_rule({ app_id = "org.mozilla.firefox", workspace = 2 })

See Window rules.

Special workspaces

Special workspaces are separate scratchpads. They cannot be pinned and do not appear in sweets msg workspaces.

special_toggle shows or hides one. special_move puts the focused window into one without showing it. Both take an optional name.

sweets.bind("MOD+S", "special_toggle")            -- the default scratchpad
sweets.bind("MOD+Shift+S", "special_move")

sweets.bind("MOD+M", "special_toggle", "music")   -- a second, separate one
sweets.bind("MOD+Shift+M", "special_move", "music")

sweets.bind("MOD+grave", "special_toggle", "notes", {
  spawn = { "foot", "--app-id", "sweets-notes" },
  cooldown = 1000,
})

A name is 1 to 32 characters of ASCII letters, digits, _ or -, and is case-sensitive. Omitting it uses the built-in default name.

Only one special workspace is shown at a time. Showing music hides whichever was up, so each name keeps its own windows.

On special_toggle, the spawn option launches into the named scratchpad. When notes holds no windows, Sweets shows it first and launches the direct argv command. The first ordinary window then opens there unless an explicit opening rule redirects it. Once any window belongs to notes—including a minimized one—the binding only shows or hides the workspace and does not launch again. A window rule can also put an application into a named scratchpad while it is hidden.

Set a cooldown to rate-limit another press while a slow program starts. It does not reserve the scratchpad for one client: any ordinary window that maps while the empty workspace is active follows the same placement policy. A launch failure is logged and leaves the empty overlay visible so you can retry.

You can have up to 16. One keeps its windows for as long as it holds any, even if you remove its binding; once the last window leaves, a workspace your configuration no longer names is discarded.

sweets msg special-workspace       # every scratchpad and what it holds
sweets msg special-toggle music

See Layout for the dimmer and overlay behavior.

Reload

A valid reload applies pin changes at once, keeping the selected workspace and focused window where it can. Layout and appearance changes apply at once too; a workspace whose layout you changed by hand keeps it. A new pin may move a workspace and its windows to the named monitor. Removing a pin stops enforcing it but does not move anything immediately.

On this page