Scripting
Automate Shoji through IPC and hooks. External scripts can query and control window layouts.
Prerequisites
Enable IPC in the Hammerspoon config:
require("hs.ipc")This enables the hs command-line tool. Verify:
hs -c 'return "hello"'# Should output: helloShell script examples
Cycle through layouts
#!/bin/bash# Cycle: tall -> wide -> bsp -> tallCURRENT=$(hs -c 'return spoon.Shoji:cmd("status").data.layout')
case "$CURRENT" in "tall") hs -c 'spoon.Shoji:cmd("set-layout", "wide")' ;; "wide") hs -c 'spoon.Shoji:cmd("set-layout", "bsp")' ;; *) hs -c 'spoon.Shoji:cmd("set-layout", "tall")' ;;esacContext-aware layout
Adjust layout based on window count:
#!/bin/bashCOUNT=$(hs -c 'return spoon.Shoji:cmd("status").data.windowCount')
if [ "$COUNT" -le 1 ]; then hs -c 'spoon.Shoji:cmd("set-layout", "fullscreen")'elif [ "$COUNT" -le 3 ]; then hs -c 'spoon.Shoji:cmd("set-layout", "tall")'else hs -c 'spoon.Shoji:cmd("set-layout", "bsp")'fiStatus bar integration
For tmux, sketchybar, or similar status bars:
#!/bin/bash# Output format: [tall 3]LAYOUT=$(hs -c 'return spoon.Shoji:cmd("status").data.layout')COUNT=$(hs -c 'return spoon.Shoji:cmd("status").data.windowCount')echo "[$LAYOUT $COUNT]"tmux integration
Bind Shoji commands to tmux keys:
bind-key W run-shell "hs -c 'spoon.Shoji:cmd(\"cycle-layout\")'"bind-key T run-shell "hs -c 'spoon.Shoji:cmd(\"set-layout\", \"tall\")'"bind-key B run-shell "hs -c 'spoon.Shoji:cmd(\"set-layout\", \"bsp\")'"Alfred and Raycast integration
Scripts for launcher workflows:
#!/bin/bashhs -c 'spoon.Shoji:cmd("set-layout", "tall")'In Alfred, create a workflow with a keyword trigger. In Raycast, create a Script Command.
Hook-based automation
Auto-layout by app
Switch layouts based on the focused app:
local appLayouts = { ["com.apple.Safari"] = "wide", ["com.microsoft.VSCode"] = "tall", ["com.apple.Terminal"] = "bsp",}
spoon.Shoji:configure({ hooks = { window_focused = function(windowID) local win = hs.window.get(windowID) if not win then return end
local app = win:application() if not app then return end
local layout = appLayouts[app:bundleID()] if layout then spoon.Shoji.tiling.setLayout(layout) end end, },})Time-based layout
Set the default layout based on time of day. Work hours get a focused layout; evenings get fullscreen:
local function timeBasedLayout() local hour = tonumber(os.date("%H")) if hour >= 9 and hour < 17 then return "tall" else return "fullscreen" endend
spoon.Shoji:configure({ default_layout = timeBasedLayout(),})Window count trigger
Switch layouts based on tiled window count:
local lastCount = 0
spoon.Shoji:configure({ hooks = { after_tile = function(_spaceID) local status = spoon.Shoji:cmd("status") if not status.success then return end
local count = status.data.windowCount if count ~= lastCount then lastCount = count
-- Auto-switch layout based on window count if count == 1 then spoon.Shoji.tiling.setLayout("fullscreen") elseif count <= 3 then spoon.Shoji.tiling.setLayout("tall") else spoon.Shoji.tiling.setLayout("bsp") end end end, },})Debugging scripts
Test IPC commands in the Hammerspoon console (Cmd+Option+C):
-- In the console:spoon.Shoji:cmd("status")spoon.Shoji:cmd("list-commands")Or from the shell:
# Get current statushs -c 'return hs.inspect(spoon.Shoji:cmd("status"))'
# List all available commandshs -c 'return hs.inspect(spoon.Shoji:cmd("list-commands"))'Hook errors appear in the Hammerspoon console.
Cron-style automation
Use launchd to run Shoji commands on a schedule. This example switches to tall at 9 AM daily:
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>Label</key> <string>com.user.shoji-morning</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/hs</string> <string>-c</string> <string>spoon.Shoji:cmd("set-layout", "tall")</string> </array> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>9</integer> <key>Minute</key> <integer>0</integer> </dict></dict></plist>Load the job:
launchctl load ~/Library/LaunchAgents/com.user.shoji-morning.plistThe hs path depends on installation method. On Apple Silicon with Homebrew,
use /opt/homebrew/bin/hs instead of /usr/local/bin/hs.