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, },})Both snake_case and camelCase are supported:
hooks = { window_created = function(windowID) end, -- snake_case onWindowCreated = function(windowID) end, -- camelCase (alias)}Lifecycle hooks
Called when Shoji starts, stops, or screen configuration changes.
started
Called after start() completes.
| Property | Value |
|---|---|
| Arguments | none |
| Aliases | onStart, onStarted |
started = function() print("Shoji started")endstopped
Called after stop() completes.
| Property | Value |
|---|---|
| Arguments | none |
| Aliases | onStop, onStopped |
stopped = function() print("Shoji stopped")endscreen_changed
Called when screen configuration changes: monitor connected/disconnected or resolution changed.
| Property | Value |
|---|---|
| Arguments | none |
| Alias | onScreenChanged |
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 (number) |
| Alias | onWindowCreated |
window_created = function(windowID) print("Window created: " .. windowID)endwindow_destroyed
Called when a window is closed or removed from tiling.
| Property | Value |
|---|---|
| Arguments | windowID (number) |
| Alias | onWindowDestroyed |
window_destroyed = function(windowID) print("Window destroyed: " .. windowID)endwindow_focused
Called when a window receives focus.
| Property | Value |
|---|---|
| Arguments | windowID (number) |
| Alias | onWindowFocused |
window_focused = function(windowID) print("Window focused: " .. windowID)endwindow_moved
Called when a window is moved.
| Property | Value |
|---|---|
| Arguments | windowID (number) |
| Alias | onWindowMoved |
window_moved = function(windowID) print("Window moved: " .. windowID)endwindow_visible
Called when a window becomes visible (unhidden or unminimized).
| Property | Value |
|---|---|
| Arguments | windowID (number) |
| Alias | onWindowVisible |
window_visible = function(windowID) print("Window visible: " .. windowID)endwindow_hidden
Called when a window becomes hidden (minimized or hidden).
| Property | Value |
|---|---|
| Arguments | windowID (number) |
| Alias | onWindowHidden |
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 (number) |
| Alias | onBeforeTile |
before_tile = function(spaceID) print("About to tile space: " .. spaceID)endafter_tile
Called immediately after tiling a space completes.
| Property | Value |
|---|---|
| Arguments | spaceID (number) |
| Aliases | onAfterTile, onTilingComplete |
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 (number), floating (boolean) |
| Aliases | onFloatToggled, onWindowFloatToggled |
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 (number), layout, prev (LayoutName|nil) |
| Alias | onLayoutChanged |
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 (number) |
| Alias | onSpaceChanged |
space_changed = function(spaceID) print("Switched to space: " .. spaceID)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 stops after 10 levels.
For example, calling retile() from after_tile triggers another after_tile.
This continues up to 10 times before stopping with a warning.