Skip to content

Configuration

Daniel edited this page Jun 21, 2026 · 6 revisions

agate configuration

Auto-generated by zig build docs from annotations in src/config/lua.zig. Do not edit by hand.

All configuration is Lua, loaded from $WM_CONFIG, $XDG_CONFIG_HOME/agate/init.lua, ~/.config/agate/init.lua, or ./init.lua.

agate.config{} settings

Key Type Default Description
animation_duration number 150 Length of the frame animation in milliseconds (lower = faster; 0 disables). Only meaningful with animations = true.
space_animation string "instant" How much of the Space-switch transition plays: "fast", "very_fast", or "instant" (no perceptible animation).
gaps number 8 Pixels between adjacent tiles.
outer_gaps number 8 Pixels inset from the screen edge.
accordion_padding number 40 Stacked-window "peek": how far each window in a stack/accordion fans past the one in front. Alias: accordion.
hyper_key agate.HyperKey { enabled = true, keys = {"ctrl","alt","cmd","shift"} } Built-in hyper key (see agate.HyperKey), ported from LazyKeys. When enabled, agate remaps Caps Lock to F18 at the HID level (via hidutil) and treats a held Caps Lock as the modifier set in keys — for both agate keybindings and the focused app. The hyper macro in key specs expands to keys.
small_screen agate.SmallScreen { enabled = true, layout = "h_accordion", max_width = 0 } Small Screen Mode (see agate.SmallScreen): workspaces on a small main display trade the split layout for an accordion, and back when a big display takes over.
animations boolean false Animate tiling frame changes instead of snapping: the final size applies instantly, the position glides over (60 Hz, ease-out, capped at 8 windows per flush with an automatic snap when an app is too busy to keep up). Speed via animation_duration.
space_indicator boolean true Show the active space's number as a menu-bar status item.
drag_preview boolean true While dragging a window, highlight the tile it will swap into on drop with a translucent overlay.
smart_gaps boolean false When a workspace holds a single window, drop the outer gap so it fills the display edge-to-edge (Hyprland's no_gaps_when_only).

small_screen fields (Small Screen Mode)

On a small main display (the built-in panel, or anything at or under max_width points), workspaces still on the default split layout switch to layout — a straight tiling split is not useful on a tiny screen. They switch back when a big display takes over (dock/undock re-evaluates). Workspaces whose layout was set by hand are left alone in both directions. Pair with agate.gesture for trackpad-driven window cycling.

Key Type Description
enabled boolean Master switch (default true). (optional)
layout string Layout small workspaces get: any layout name (default "h_accordion"), or "tabs" for a zero-peek stack — full-area windows flipped through like tabs. (optional)
max_width number Width (points) at or under which a display counts as small, in addition to the built-in panel. 0 (default) = built-in display detection only. (optional)

hyper_key fields (built-in hyper key)

Ported from LazyKeys: when enabled, agate remaps Caps Lock to F18 at the HID level (via hidutil) and treats a held Caps Lock as the modifiers in keys — for both agate's own keybindings and the focused app, so hyper-based shortcuts work everywhere. The remap is restored when agate exits. No separate remapper (LazyKeys, Karabiner) is needed.

Key Type Description
enabled boolean Master switch (default true). When on, agate remaps Caps Lock to F18 via hidutil and treats it as the hyper key; the remap is restored on exit. Turn it off to leave Caps Lock alone. (optional)
keys string[] Modifier set the held hyper key — and the hyper macro in key specs — expands to. Any of: ctrl/control, alt/opt, cmd/command, shift. Default {"ctrl","alt","cmd","shift"}. (optional)

agate.rule{} fields

Key Type Description
app string POSIX extended regex matched against the owning application's name, e.g. "^Music$". (optional)
title string POSIX extended regex matched against the window title. (optional)
space integer 1-based Space position (Mission Control order, fullscreen included) matched windows are sent to — or a string name registered with agate.name_space (which also supplies the monitor). Required unless monitor is given (then it defaults to that monitor's first Space). (optional)
monitor integer 1-based monitor (spatial arrangement, left→right — same as agate.monitors() id) the space position counts on; pins the app to that display. Omit for the focused display. (optional)
follow boolean Switch to that space along with the window (default true). Set false to route the window in the background — usually what you want when pinning to a monitor. (optional)
floating boolean Float matched windows when they appear (like agate.toggle_float() applied automatically) — tracked but lifted out of the tiling. Can be the rule's only effect (no space needed), or combined with a Space/monitor assignment. (optional)

API

agate.config(config)

Apply global configuration. Call once near the top of init.lua.

  • config (agate.Config) — Settings table (see agate.Config).

agate.bind(spec, action)

Bind a key chord to an action.

  • spec (string) — Key chord, e.g. "hyper+shift+l".
  • action (fun()|string) — A Lua callback, or a string command (see Commands below).

agate.gesture(spec, action)

Bind a trackpad swipe to an action. The swipe tracks live with a Liquid Glass HUD and fires once when you lift — drag far enough or flick fast, like native macOS. The system gestures on the same finger count must be off or moved to the other count in Trackpad settings.

  • spec (string) — Finger count (3 or 4) and direction, e.g. "3:left" or "4:up".
  • action (fun()|string) — A Lua callback, or a string command (see Commands below).

agate.on(event, callback)

Run a Lua callback whenever agate performs an action. The callback receives a single table describing the event. Register more than one for the same event and they all run, in order. Events: space_changed ({ space = N } — the new 1-based Space position), mode_changed ({ mode = "name" } on enter, { mode = nil } on exit), window_created / window_destroyed ({ window = id }), monitors_changed ({ count = N } — a display was plugged/unplugged; inspect agate.monitors() to adapt the config). Use it to e.g. run a shell command on every Space switch via agate.exec, or re-pin apps when docking.

  • event (string) — Event name: "space_changed", "mode_changed", "window_created", "window_destroyed", or "monitors_changed".
  • callback (fun(event:table)) — A Lua function called with a table of event data (fields depend on the event).

agate.mode(name, bindings)

Define a modal keybind group (Hyprland-style submap): a named table of keyspec = action entries that are live only while the mode is active. Enter with agate.enter_mode(name), leave with agate.exit_mode(). While a mode is active only its bindings fire — global binds are suppressed and unbound keys pass through to the focused app. Bind escape to agate.exit_mode so there's always a way out.

  • name (string) — Mode name, referenced by enter_mode/exit_mode and the mode <name> command.
  • bindings (table) — Table mapping a key chord (e.g. "h", "shift+l") to a Lua function or string command.

agate.enter_mode(name)

Activate a mode defined with agate.mode. While active, only that mode's bindings fire; global binds are suppressed and unbound keys pass through. The active mode name shows in the menu-bar indicator.

  • name (string) — Name of a mode registered with agate.mode.

agate.exit_mode()

Leave the active mode and return to the normal keymap. Bind this to escape inside a mode so there's always a way out.

agate.cycle(dir)

Focus the next/previous window among the focused window's siblings, wrapping at the edges — the natural motion through an accordion/stack (Small Screen Mode), bindable to a swipe or a key.

  • dir ("next"|"prev") — Cycle direction.

agate.focus(dir)

Move focus to the nearest window in a direction, descending into and ascending out of nested containers (i3-style). Left/right traverse horizontal splits/stacks; up/down vertical ones.

  • dir (agate.Direction) — Direction to move focus.

agate.layout(mode)

Set the focused container's layout (the focused window's parent), falling back to the workspace for top-level windows.

  • mode (agate.Layout) — Layout mode to apply.

agate.join(dir, mode?)

Combine the focused window with its neighbour into a nested container, for mixed layouts (e.g. a row whose one slot is a stack of two windows).

  • dir (agate.Direction) — Neighbour to combine with.
  • mode (agate.Layout) (optional) — Layout of the new container. Default v_stack.

agate.zoom_fullscreen()

Toggle "zoom fullscreen" for the focused window (yabai's window --toggle zoom-fullscreen): the window fills the whole space, overlapping the other tiles; toggle again to drop it back into the tiling. Not native macOS fullscreen — it stays on the same Space with no transition.

agate.native_fullscreen()

Toggle native macOS fullscreen for the focused window — the green-button fullscreen that moves it to its own Space with the standard transition (toggle again to leave). Unlike zoom_fullscreen, this is real fullscreen, so you can drop Raycast/other tools' fullscreen keybind.

agate.toggle_float()

Toggle floating for the focused window (yabai's window --toggle float): lift it out of the tiling so it keeps its own free position and size on top while the other tiles reflow without it; toggle again to drop it back into the layout. The window stays on the same Space and is still tracked, focusable, and closes normally.

agate.exec(cmd)

Run a shell command in the background, like skhd's : commands. The command line is handed to $SHELL -c (falling back to /bin/sh -c), so pipes, globs, and && all work. agate does not wait for it — use it to launch apps or scripts from a keybind.

  • cmd (string) — The shell command line to run.

agate.space(target, monitor?)

Switch to a Space, given either a 1-based position or a name registered with agate.name_space. A number counts every Space the swipe passes through on the focused display, in Mission Control order — including native-fullscreen Spaces (so a fullscreened app at strip position N is reached by N). A name jumps to that named slot, switching the monitor it lives on (and focusing it). Pass a monitor to address a position on a specific display instead of the focused one.

  • target (integer|string) — 1-based Space position (Mission Control order, fullscreen included), or a name registered with agate.name_space.
  • monitor (integer) (optional) — 1-based monitor (arrangement order, like agate.monitors() id) the position counts on, switching and focusing that display. Omit for the focused display. Ignored when target is a name (the name carries its own monitor).

agate.space_next()

Switch to the next Space on the focused display (one step in Mission Control order, fullscreen Spaces included).

agate.space_prev()

Switch to the previous Space on the focused display (one step in Mission Control order, fullscreen Spaces included).

agate.resize(target, amount?)

Resize the focused tile, transferring the delta to its neighbour. Pass "smart" (recommended) to resize along the focused window's container axis without picking an edge: a positive amount grows it, a negative one shrinks it, taking from whichever neighbour exists — so the same binding always enlarges/shrinks the focused window wherever it sits. A direction (left/right/up/down) instead grows the window toward that edge, by stealing from the neighbour there.

  • target (agate.Direction|string) — "smart" to resize along the container axis, or an edge (left/right/up/down) to grow toward.
  • amount (number) (optional) — Pixels to resize by. Default 50. With "smart", a negative value shrinks.

agate.move(dir)

Swap the focused window with its neighbour in a direction. Works across nested containers.

  • dir (agate.Direction) — Direction to move the window.

agate.move_to_space(target, monitor?)

Send the focused window to a user space (does not follow focus), given either a 1-based position or a name registered with agate.name_space. A name carries its own monitor; with a number, pass a monitor argument to target a Space on another display.

  • target (integer|string) — 1-based Space position (Mission Control order, fullscreen included), or a name registered with agate.name_space.
  • monitor (integer) (optional) — 1-based monitor (spatial arrangement, left→right — same as agate.monitors() id) the position counts on. Omit for the focused display — pass it to assign the window to a Space on another monitor. Ignored when target is a name (the name carries its own monitor).

agate.name_space(name, target, monitor?)

Give a (monitor, space) slot a name, so binds and rules can refer to it by name instead of by number. The name then works anywhere a Space number does — agate.space, agate.move_to_space, and agate.rule's space field all accept it. Re-registering a name overwrites it, so a monitors_changed handler can remap names as displays come and go (e.g. a "music" space that lives on the built-in panel when docked and on the laptop's own Space otherwise).

  • name (string) — The name to register, e.g. "music".
  • target (integer|table) — A 1-based Space position, or a table { space = N, monitor = M } pinning it to monitor M (1-based arrangement, like agate.rule). With a bare number the monitor defaults to the focused display unless given as a third argument.
  • monitor (integer) (optional) — 1-based monitor the space lives on, when target is a number. Omit (or 0) for the focused display.

agate.focus_monitor(dir)

Move keyboard focus to another display, raising its most-recently-used window (or warping the cursor to an empty display). No-op with a single display.

  • dir (agate.MonitorDir) — Which display to focus.

agate.move_to_monitor(dir)

Move the focused window to an adjacent display's visible space, tile it there, and follow focus to it.

  • dir (agate.MonitorDir) — Which display to move the window to.

agate.monitors()

Return an array of tables describing the connected displays, in spatial order (left→right). Each entry has id (1-based arrangement number — the value agate.rule{monitor=...} / agate.move_to_space(n, monitor) expect), name, x, y, width, height, main (the primary display), and focused (the display with keyboard focus). Combine with agate.on("monitors_changed", ...) for configs that adapt to docking.

agate.monitor_count()

Return the number of connected displays. Handy for branching config, e.g. if agate.monitor_count() == 1 then ... end.

agate.focused_monitor()

Return the 1-based arrangement number of the display that currently has keyboard focus (0 if unknown) — the same numbering agate.monitors() reports and rules address.

agate.focus_app(name)

Focus a running app by name, wherever its window lives: switches the display that holds the window to its Space (across monitors too, regardless of the macOS "switch to a Space with open windows" setting) and raises it. Matches the app name as a substring (so "Zen" matches "Zen Browser (Beta)"). Returns true if a tracked window was found and focused, false otherwise — fall back to agate.exec("open -a Name") to launch it. Expandable: bind one per app.

  • name (string) — App name (or a substring of it) to focus.

agate.rule(rule)

Register a window assignment rule, like yabai's rule --add: windows whose app name/title match the given regexes are sent to a space (and optionally a specific monitor) and/or floated when they appear. At least one of app/title is required; both must match when both are given. Give space, monitor, floating = true, or a combination — a rule must have at least one effect. When several rules match a window, the last registered one wins.

  • rule (agate.Rule) — Rule table (see agate.Rule).

agate.clear_rules()

Remove all window assignment rules previously added with agate.rule. Use it before re-registering rules in a monitors_changed handler so conditional rule sets don't pile up across dock/undock cycles.

Commands

Strings passed as the second argument of agate.bind instead of a function:

Command Description
move <dir> Same as agate.move(dir).
focus <dir> Same as agate.focus(dir).
resize <smart|dir> [amount] Same as agate.resize(target, amount). resize smart 50 grows the focused window along its container axis (negative shrinks); resize <dir> [amount] grows toward an edge. Amount defaults to 50.
`cycle <next prev>`
layout <mode> Same as agate.layout(mode).
space <n> Same as agate.space(n).
move_to_space <n> Same as agate.move_to_space(n).
focus_monitor <dir> Same as agate.focus_monitor(dir).
move_to_monitor <dir> Same as agate.move_to_monitor(dir).
exec <cmd> Run a shell command in the background through $SHELL -c. Same as agate.exec(cmd).
zoom_fullscreen Same as agate.zoom_fullscreen().
native_fullscreen Same as agate.native_fullscreen().
toggle_float Same as agate.toggle_float().
mode <name> Same as agate.enter_mode(name).
exit_mode Same as agate.exit_mode().

Enumerations

agate.Layout

A layout mode. Synonyms: h_split/horizontal = h_tiles; v_split/vertical = v_tiles; v_accordion/stacking/stacked = v_stack/accordion; floating = float. toggle flips the split orientation.

  • "h_tiles"
  • "v_tiles"
  • "h_stack"
  • "v_stack"
  • "accordion"
  • "float"
  • "toggle"

agate.Direction

A focus/move/resize direction.

  • "left"
  • "right"
  • "up"
  • "down"

agate.MonitorDir

A monitor selector: next/prev cycle displays in spatial arrangement order (left→right); left/right/up/down step to the physically adjacent display.

  • "next"
  • "prev"
  • "left"
  • "right"
  • "up"
  • "down"

Example

agate.config({ gaps = 8, accordion_padding = 40, hyper_key = { enabled = true, keys = { "ctrl", "alt", "cmd" } } })
agate.bind("hyper+l", function() agate.focus("right") end)
agate.bind("hyper+shift+l", "move right")
agate.bind("hyper+s", function() agate.layout("accordion") end)
agate.bind("hyper+g", function() agate.join("right") end)
agate.rule({ app = "^Music$", space = 5 })
agate.rule({ app = "^Firefox$", title = "Library", space = 2, follow = false })