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.
Usage
Register hooks in the configure() call:
spoon.Shoji:configure({ hooks = { after_tile = function(spaceID) print("Tiled space: " .. spaceID) end, },})Lifecycle hooks
Called when Shoji starts, stops, or screen configuration changes.
started
Called after start() completes.
| Property | Value |
|---|---|
| Arguments | none |
started = function() print("Shoji started")endstopped
Called after stop() completes.
| Property | Value |
|---|---|
| Arguments | none |
stopped = function() print("Shoji stopped")endscreen_changed
Called when screen configuration changes: monitor connected/disconnected or resolution changed.
| Property | Value |
|---|---|
| Arguments | none |
screen_changed = function() print("Screen configuration changed")endWindow hooks
Called when windows are created, destroyed, or change visibility.
window_created
Called when a new window is added to tiling.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_created = function(windowID) print("Window created: " .. windowID)endwindow_destroyed
Called when a window is closed or removed from tiling.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_destroyed = function(windowID) print("Window destroyed: " .. windowID)endwindow_focused
Called when a window receives focus.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_focused = function(windowID) print("Window focused: " .. windowID)endwindow_moved
Called when a window is moved.
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_moved = function(windowID) print("Window moved: " .. windowID)endwindow_visible
Called when a window becomes visible (unhidden or unminimized).
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_visible = function(windowID) print("Window visible: " .. windowID)endwindow_hidden
Called when a window becomes hidden (minimized or hidden).
| Property | Value |
|---|---|
| Arguments | windowID (WindowID) |
window_hidden = function(windowID) print("Window hidden: " .. windowID)endTiling hooks
Called before and after tiling operations.
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
Called immediately after tiling a space completes.
| Property | Value |
|---|---|
| Arguments | spaceID (SpaceID) |
after_tile = function(spaceID) print("Finished tiling space: " .. spaceID)endState change hooks
Called when layout or window state changes.
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
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
Called when switching to a different desktop/space.
| Property | Value |
|---|---|
| Arguments | spaceID (SpaceID) |
space_changed = function(spaceID) print("Switched to space: " .. spaceID)endspace_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
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
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
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
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
| 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
| 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
hooks = { after_tile = function(spaceID) local layout = spoon.Shoji.state:getSpaceLayout(spaceID) local count = #spoon.Shoji.state:getWindowOrder(spaceID) print(layout .. ": " .. count .. " windows") end,}