Sweets
Configuration

Window Rules

Match XDG and XWayland windows and select opening behavior or live appearance.

sweets.window_rule({ ... }) matches the retained initial identity of a managed window. One declaration can choose how the window opens, select live root or popup appearance, or do both. Opening properties include placement, floating geometry, focus, state, stacking, adaptive-sync eligibility, and corner radius.

sweets.window_rule({ app_id = "org.wezfurlong.wezterm", workspace = 2 })
sweets.window_rule({ title = "Picture-in-Picture", floating = true, focus = false })
sweets.window_rule({ backend = "xwayland", app_id = "steam", monitor = "DP-1" })
sweets.window_rule({ app_id = "foot", opacity = 0.88, blur = true })
sweets.window_rule({ app_id = "org.gnome.Nautilus", popups = { opacity = 0.9, blur = true, corner_radius = 18 } })
sweets.window_rule({
  title = "Picture-in-Picture",
  size = { 480, 270 },
  position = "top-right",
  stacking = "above",
  corner_radius = {
    top_left = 18,
    top_right = 18,
    bottom_right = 8,
    bottom_left = 8,
  },
})

Rules are optional. Windows that do not match a rule use the normal Sweets and client opening policy, full compositor opacity, and no compositor-forced blur.

Layer-shell surfaces use their own namespace/current-layer identity. Configure panels, launchers, notification surfaces, and wallpapers with sweets.layer_rule({ ... }).

Matchers

A rule must contain at least one matcher and at least one opening or appearance property. When a rule contains several matchers, all of them must match the same window.

FieldTypeMatches
backendstringExactly "xdg" or "xwayland"
app_idstringXDG app ID for a native window, or X11 class for an XWayland window
titlestringInitial window title
dialogbooleanWhether the initial protocol metadata identifies a dialog
transientbooleanWhether the window initially has a parent or transient-for relationship

String matching is exact and case-sensitive. Sweets does not interpret substrings, globs, regular expressions, or Lua callbacks. app_id and title cannot be empty.

For example, this rule matches only native XDG dialogs from the named application:

sweets.window_rule({
  backend = "xdg",
  app_id = "org.example.Editor",
  dialog = true,
  floating = true,
})

XWayland app_id matching uses the X11 class, not the X11 instance. Restrict the rule with backend = "xwayland" when a native application may use the same identifier.

Finding window identities

List mapped windows and their IDs:

sweets msg windows

Then inspect the immutable opening identity and resolved rule policy for one window:

sweets msg window-rule WINDOW_ID

The rule query reports the initial backend, app ID, title, X11 class and instance when applicable, dialog and transient facts, the accepted config generation, the effective policy, and any diagnostics. Add --json before the command when you need machine-readable output.

Titles commonly contain document names or other changing text. Prefer app_id rules and use title only when the initial title is stable.

Opening properties

Matcher fields select a window. The remaining fields contribute its opening policy:

FieldTypeEffect when specified
workspaceintegerOpen on numbered workspace 1 through 10
monitorstringOpen on the connected monitor's currently visible workspace
floatingbooleantrue forces floating; false forces tiling
focusbooleanfalse suppresses automatic opening focus
sizetableSet floating content size with { width, height }
positionstring or tableSet a floating anchor or { x, y } coordinates
maximizedbooleanSet the initial maximized state
fullscreenbooleanSet the initial fullscreen state
adaptive_syncbooleanAllow this view to activate on-demand VRR while fullscreen
stackingstringSelect "normal", "above", or "below"
corner_radiusinteger or tableOverride sweets.window uniformly, or with a complete named four-corner table; each value is 0 through 1024

Omitting a property leaves the normal compositor or client decision in place. A property explicitly set to false still participates in rule merging.

Without an explicit floating value, Sweets automatically floats a native XDG window with a fixed-size axis. It also floats one whose initial maximum size is smaller than its prospective tile on either axis. The comparison uses the workspace selected by opening rules. Set floating = false to keep a matching window tiled despite those initial constraints.

When such a window also receives automatic keyboard focus, Sweets confirms its activated XDG state after focus delivery. Synchronous confirmation dialogs are therefore visible immediately without an extra focus or resize action.

Opening properties resolve once before the first configure or map. Reloading them affects only unresolved future windows; relaunch an existing application to test an opening-policy edit.

Live appearance properties

These properties use the same matcher and ordered rule declaration:

FieldTypeEffect when specified
opacitynumberWhole-content opacity from 0.0 through 1.0
blurbooleanForce or suppress compositor blur for the matching window
blur_modestringOverride the global mode with "optimized" or "scene"
popupstableDescendant XDG popup opacity, blur, and blur_mode

Appearance properties resolve from the retained initial identity whenever a valid configuration reload is accepted. They update already-open managed windows without moving, resizing, restacking, or refocusing them. Blur still requires the global sweets.blur master switch.

sweets.window_rule({
  app_id = "foot",
  workspace = 2,       -- opening: applies after relaunch
  opacity = 0.88,      -- live: applies on valid reload
  blur = true,         -- live
  blur_mode = "optimized",
})

The nested popups table styles every XDG popup owned by the matched native window, including nested menus and tooltips.

opacity is absolute. It does not multiply the owning window's opacity. Client alpha still multiplies the configured popup value.

sweets.window_rule({
  app_id = "org.gnome.Nautilus",
  popups = {
    opacity = 0.9,
    blur = true,
    blur_mode = "optimized",
    corner_radius = 18,
  },
})

Popup blur requires the global blur master. blur = false vetoes client popup blur. true supplies whole-popup blur when no non-empty client region exists.

An exact non-empty client region wins. blur_mode selects "optimized" or "scene" for either form.

Popup opacity affects client content only. The backdrop remains fully filtered, so translucency does not mix sharp source pixels back into the menu.

corner_radius clips the backdrop effect to the popup's visual corners. It accepts the same uniform integer or four-corner table as the window radius, defaults to 0, and does not clip client pixels or popup input. Wayland does not expose the radius, so configure it to match the application or toolkit.

Omitting popup blur preserves the window veto and mode for client requests. Root blur = true never forces popup blur.

The popup tree remains mapped and interactive even at 0.0. This policy does not affect XWayland override-redirect menus, input-method popups, ordinary subsurfaces, or menus drawn inside the application's root surface.

Rule order and merging

Every matching rule contributes properties in declaration order. A later rule replaces only the properties it specifies; omitted properties remain from earlier matching rules.

sweets.window_rule({
  dialog = true,
  floating = true,
  focus = false,
})

sweets.window_rule({
  app_id = "org.example.Editor",
  dialog = true,
  focus = true,
  size = { 900, 700 },
})

A matching editor dialog receives floating = true from the first rule, then the second rule replaces focus and adds size. Installed rules run first; user rules append afterward. Rules also retain their declaration order across files loaded with sweets.include.

A later rule cannot clear an earlier property. Keep placement rules disjoint: adding monitor does not remove an inherited workspace, and the effective combination is invalid.

Workspace and monitor placement

workspace selects a logical numbered workspace using the active shared or per-monitor workspace model and any configured workspace pin. A hidden target can receive the new window without being made visible.

monitor selects the numbered workspace currently visible on that connector. Connector names are exact and case-sensitive; find them with:

sweets msg outputs

A rule cannot select both workspace and monitor. If the named monitor is not connected, Sweets keeps the window on its normal fallback workspace and reports monitor_unavailable through sweets msg window-rule WINDOW_ID.

A generic placement rule does not detach a transient window from its parent workspace. To move dialogs or transients deliberately, make the placement rule explicit by including a dialog or transient matcher:

sweets.window_rule({
  app_id = "org.example.Editor",
  transient = true,
  workspace = 3,
})

Rules target numbered workspaces only. They do not select the special workspace directly.

Floating size and position

size and position are floating-only. Either property implies floating = true when floating is omitted. Combining geometry with floating = false is invalid.

Every size or coordinate component follows the same unit rule:

  • Values greater than 0 through 1 are fractions of the target work area.
  • Values greater than 1 through 16384 are logical pixels.

For example, { 0.5, 600 } means half the work-area width and 600 logical pixels high. Exactly 1 means the full corresponding work-area dimension, not one logical pixel. Zero, negative, non-finite, and larger values are invalid.

A position pair measures the window's top-left corner from the work area's top-left:

sweets.window_rule({
  app_id = "mpv",
  size = { 960, 540 },
  position = { 40, 0.1 },
})

position also accepts one of nine anchors:

center
top-left     top     top-right
left                  right
bottom-left bottom  bottom-right

Requested sizes respect client minimum and maximum hints. Oversized windows and coordinates are clamped into the target work area. When size is combined with maximized or fullscreen state, it becomes the floating geometry available after restoring the window.

Focus, state, and stacking

focus = false prevents the new window from taking automatic keyboard focus. focus = true permits the normal opening focus path, but it cannot override X11 focus intent, session lock, an interactive layer surface, a popup grab, or other higher-priority focus policy.

maximized and fullscreen set their respective initial states. A resolved policy cannot enable both at once. Maximized uses the usable work area; fullscreen uses the full output.

adaptive_sync = true opts the immutable opening decision into on-demand VRR. It does not make the window fullscreen and has no effect on outputs configured for fixed refresh or always-on VRR. On an output using monitor adaptive_sync = "on_demand", Sweets enables VRR only while this view is mapped, visible, fullscreen, unobscured by compositor modal policy, and confirmed by the rendered primary-output assignment. A later matching adaptive_sync = false rule removes an earlier opt-in. Reloading rules affects future unresolved views; relaunch an existing application to test a change.

stacking assigns a persistent class within Sweets' managed stacking order: "above" stays ahead of normal windows, "below" stays behind them, and "normal" uses ordinary stacking. The field works for tiled and floating windows.

corner_radius overrides the global sweets.window radius for this resolved window. A scalar applies to every corner. A table requires exactly top_left, top_right, bottom_right, and bottom_left; zero keeps the corresponding corner square. Later matching rules replace the complete earlier radius rather than merging individual corners, while omission leaves the earlier value in place. Fullscreen and edge-to-edge tiled presentations produced by smart gaps remain square. The retained rule radii return when fullscreen exits or the smart outer margin returns. Windows without an override continue to follow valid global radius reloads.

Invalid combinations

These effective property combinations are invalid:

  • workspace together with monitor.
  • floating = false together with size or position.
  • maximized = true together with fullscreen = true.

When one rule contains a conflict, configuration validation rejects it. A conflict can also be created only after several rules match the same identity; that cannot be detected without a window to resolve. In that case Sweets uses the normal protocol opening policy for that window, logs a warning, and reports the conflict in sweets msg window-rule WINDOW_ID.

One resolved configuration can retain up to 128 window rules across opening-only, appearance-only, and combined declarations. Each string can contain up to 256 UTF-8 bytes, and all retained matcher and opening-policy strings together can use up to 32 KiB.

Reload behavior

Sweets resolves one immutable decision from the window's initial protocol metadata before its first configure or map. Later title or app-ID changes do not run the rules again. The decision also remains attached across unmap and remap, including its corner-radius override.

A valid reload atomically replaces the complete rule set. Opening properties apply to windows that have not yet resolved and never move, resize, restack, or refocus existing windows. Appearance properties re-resolve all managed windows from their retained initial identity. Reload and relaunch the application when testing opening properties; appearance edits apply on reload:

sweets --check-config
sweets msg reload

If validation fails, Sweets keeps the last valid rule set active. Remember that --check-config can validate each rule and its static limits, but an identity-dependent conflict across multiple matching rules appears only when a window resolves them.

For one migration period, the old sweets.rule({ ... }) opening-only entry point and sweets.appearance_rule({ ... }) appearance-only entry point remain accepted. Either deprecated spelling produces one bounded warning. Use sweets.window_rule({ ... }) for all new and updated configuration.

On this page