Hooks reference
Callbacks for responding to lifecycle and window events. This page is the canonical list of hooks and signatures. For guidance and examples, see Hooks guide.
Register hooks in the configure() call:
spoon.Shoji:configure({ hooks = { after_tile = function(spaceID) print("Tiled space: " .. spaceID) end, },})Lifecycle hooks
Section titled “Lifecycle hooks”Called when Shoji starts, stops, or screen configuration changes.
started
Section titled “started”Called after start() completes.
| Property | Value |
|---|---|
| Arguments | none |
started = function() print("Shoji started")endstopped
Section titled “stopped”Called after stop() completes.
| Property | Value |
|---|---|
| Arguments | none |
stopped = function() print("Shoji stopped")endscreen_changed
Section titled “screen_changed”Called when screen configuration changes: monitor connected/disconnected or
resolution changed.
Fires before tiling begins. Use after_tile for post-tiling actions.
| Property | Value |
|---|---|
| Arguments | none |
screen_changed = function() print("Screen configuration changed")endWindow hooks
Section titled “Window hooks”Called when windows are created, destroyed, or change visibility.
window_created
Section titled “window_created”Called when a new window is added to tiling.
Fires before tiling begins. Use after_tile for post-tiling actions.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_created = function(windowID) print("Window created: " .. windowID)endwindow_destroyed
Section titled “window_destroyed”Called when a window is closed or removed from tiling.
Fires before tiling begins. Use after_tile for post-tiling actions.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_destroyed = function(windowID) print("Window destroyed: " .. windowID)endwindow_focused
Section titled “window_focused”Called when a window receives focus.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_focused = function(windowID) print("Window focused: " .. windowID)endwindow_moved
Section titled “window_moved”Called when a window is moved.
Fires before tiling begins. Use after_tile for post-tiling actions.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_moved = function(windowID) print("Window moved: " .. windowID)endwindow_visible
Section titled “window_visible”Called when a window becomes visible (unhidden or unminimized).
Fires before tiling begins. Use after_tile for post-tiling actions.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_visible = function(windowID) print("Window visible: " .. windowID)endwindow_hidden
Section titled “window_hidden”Called when a window becomes hidden (minimized or hidden).
Fires before tiling begins. Use after_tile for post-tiling actions.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_hidden = function(windowID) print("Window hidden: " .. windowID)endTiling hooks
Section titled “Tiling hooks”Called before and after tiling operations.
before_tile
Section titled “before_tile”Called immediately before tiling a space.
| Property | Value |
|---|---|
| Arguments | spaceID (SpaceID) |
before_tile = function(spaceID) print("About to tile space: " .. spaceID)endafter_tile
Section titled “after_tile”Called immediately after tiling a space completes.
| Property | Value |
|---|---|
| Arguments | spaceID (SpaceID) |
after_tile = function(spaceID) print("Finished tiling space: " .. spaceID)endState change hooks
Section titled “State change hooks”Called when layout or window state changes.
float_toggled
Section titled “float_toggled”Called when a window’s floating state changes.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID), floating (boolean) |
float_toggled = function(windowID, isFloating) local state = isFloating and "floating" or "tiled" print("Window " .. windowID .. " is now " .. state)endlayout_changed
Section titled “layout_changed”Called when the active layout changes for a space.
| Property | Value |
|---|---|
| Arguments | spaceID (SpaceID), layout (LayoutName), prev (LayoutName|nil) |
layout_changed = function(spaceID, layout, prevLayout) print("Layout changed: " .. (prevLayout or "none") .. " -> " .. layout)endspace_changed
Section titled “space_changed”Called when switching to a different desktop/space.
Fires before tiling begins. Use after_tile for post-tiling actions.
| Property | Value |
|---|---|
| Arguments | spaceID (SpaceID) |
space_changed = function(spaceID) print("Switched to space: " .. spaceID)endspace_initialized
Section titled “space_initialized”Called when a space is tiled for the first time (no prior state). Fires once per
space, before reconcileWindowOrder creates the space entry. Use this to set
per-space defaults (layout, ratio, main count) before the first tile applies
them.
| Property | Value |
|---|---|
| Arguments | spaceID (SpaceID) |
space_initialized = function(spaceID) -- Set BSP as default layout for new spaces spoon.Shoji.state:setSpaceLayout(spaceID, "bsp")endMultiple callbacks
Section titled “Multiple callbacks”Pass an array to register multiple callbacks for the same hook:
hooks = { after_tile = { function(spaceID) print("First callback for space: " .. spaceID) end, function(spaceID) print("Second callback for space: " .. spaceID) end, },}Callbacks run in order. If one fails, the others still execute.
Error handling
Section titled “Error handling”Hooks run inside pcall. A failing hook logs an error but does not crash Shoji
or prevent other hooks from running:
hooks = { after_tile = function() error("This error is logged, not thrown") end,}Check the Hammerspoon console (Cmd+Alt+C) for hook errors.
Recursion limit
Section titled “Recursion limit”Hooks have a maximum depth of 10 to prevent infinite loops. If a hook triggers an action that fires the same hook, the chain is blocked at depth 10 (the 10th nested callback does not execute).
For example, calling retile() from after_tile triggers another after_tile.
The chain continues until depth 10, where it is blocked with a warning.
State queries for hooks and scripts
Section titled “State queries for hooks and scripts”Hooks and scripts can query Shoji’s state via
spoon.Shoji.state. These are the most useful methods
for hook callbacks and IPC scripting:
Reading state
Section titled “Reading state”| Method | Returns | Description |
|---|---|---|
getSpaceLayout(spaceID) |
LayoutName |
Active layout for the space |
getWindowOrder(spaceID) |
WindowID[] |
Tiling window IDs in order |
getMainRatio(spaceID) |
number |
Main area ratio (0.1–0.9) |
getMainCount(spaceID) |
integer |
Number of main windows |
isWindowFloating(windowID) |
boolean |
Whether the window is floating |
getLayoutState(spaceID) |
table |
Layout-specific state (e.g., BSP splits) |
Writing state
Section titled “Writing state”| Method | Description |
|---|---|
setSpaceLayout(spaceID, layout) |
Set the active layout |
setMainRatio(spaceID, ratio) |
Set the main ratio |
setMainCount(spaceID, count) |
Set the main window count |
Example
Section titled “Example”hooks = { after_tile = function(spaceID) local layout = spoon.Shoji.state:getSpaceLayout(spaceID) local count = #spoon.Shoji.state:getWindowOrder(spaceID) print(layout .. ": " .. count .. " windows") end,}