Hammerspoon 深度技术报告:用 Lua 掌控你的 Mac
> 写于 2026-03-17 | 技术向 · 中文 · 适合 macOS 开发者/高级用户
目录
2. 核心原理:Lua Bridge to macOS APIs
3. 主要功能模块
4. 典型使用场景与代码示例
5. 与同类工具对比
6. 进阶技巧与最佳实践
7. 推荐社区资源
什么是 Hammerspoon
Hammerspoon 是一款免费开源的 macOS 自动化工具,它的核心思想极为简洁:将 macOS 系统 API 通过 Lua 脚本语言完整暴露出来,让用户用编程的方式控制操作系统的一切。
与其他自动化工具不同,Hammerspoon 不提供图形化配置界面。你的配置文件就是代码——一个或多个 Lua 脚本文件,存放在 ~/.hammerspoon/init.lua(及其引用的模块)。
关键特性:
- 完全免费,MIT 许可,代码托管于 GitHub
- 轻量级:常驻内存约 30-50MB,CPU 占用接近零
- 对开发者友好:支持控制台 REPL、错误即时反馈、reload 热更新
- 功能无上限:只要 macOS 暴露了 API,Hammerspoon 就能驱动它
Hammerspoon 于 2014 年从 Mjolnir(一个更早期的类似工具)fork 而来,由社区维护至今,已有超过 11,000 GitHub Star。
核心原理
Lua Bridge 架构
Hammerspoon 的架构本质上是一个 Objective-C / Swift + Lua 的双向桥接层:
┌─────────────────────────────────────────┐
│ Hammerspoon App (ObjC) │
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ LuaSkin │ │ CoreFoundation │ │
│ │ (Lua 5.4) │◄──► NSWindow, etc. │ │
│ └─────────────┘ └─────────────────┘ │
│ ▲ ▲ │
│ │ │ │
│ 用户 Lua 脚本 macOS APIs │
│ init.lua AX, CGEvent │
│ NSWorkspace │
└─────────────────────────────────────────┘
LuaSkin 是 Hammerspoon 的核心库,它:
1. 内嵌了完整的 Lua 5.4 运行时
2. 将 macOS 的 Objective-C 对象(NSWindow、NSScreen、NSApplication 等)包装成 Lua 表(table)
3. 将 macOS 事件(键盘、鼠标、窗口焦点变化等)转换为 Lua 回调
4. 允许 Lua 代码调用 CoreGraphics、Accessibility API、NSWorkspace 等系统级接口
初始化流程
1. macOS 启动 Hammerspoon.app
2. Hammerspoon 初始化 Lua 虚拟机(LuaSkin)
3. 加载所有内置扩展(hs.window, hs.hotkey, hs.screen 等)
4. 执行 ~/.hammerspoon/init.lua
5. Lua 代码注册事件监听器(EventTap、Watcher、Timer 等)
6. Hammerspoon 进入 RunLoop,等待事件触发回调
当你修改配置后执行 hs.reload(),Hammerspoon 会销毁当前 Lua 状态,重新走步骤 3-6,实现热更新。
关键技术:Accessibility API
Hammerspoon 大量依赖 macOS 的 Accessibility API(AX)——这是苹果为辅助功能设计的一套接口,允许应用程序查询和控制其他应用的 UI 元素(窗口、按钮、文本框等)。
因此,首次使用需要在「系统偏好设置 → 隐私与安全 → 辅助功能」中给 Hammerspoon 授权。
主要功能模块
Hammerspoon 的功能通过 hs.* 命名空间的模块组织。以下是核心模块概览:
hs.hotkey — 快捷键绑定
最常用的模块,支持绑定任意组合键到 Lua 函数:
-- 基础绑定:Cmd+Alt+T 打开 Terminal
hs.hotkey.bind({"cmd", "alt"}, "T", function()
hs.application.launchOrFocus("Terminal")
end)
-- 带 pressedfn / releasedfn / repeatfn 的完整形式
local pressed = function() print("key down") end
local released = function() print("key up") end
hs.hotkey.bind({"ctrl"}, "F19", pressed, released)
-- 创建可启用/禁用的热键对象
local hk = hs.hotkey.new({"cmd", "shift"}, "R", function()
hs.reload()
hs.notify.new({title="Hammerspoon", informativeText="Config reloaded!"}):send()
end)
hk:enable()
hs.window — 窗口管理
提供窗口查询、移动、缩放、聚焦等操作:
-- 获取当前聚焦窗口
local win = hs.window.focusedWindow()
-- 获取窗口所在屏幕
local screen = win:screen()
local frame = screen:frame()
-- 将窗口移到屏幕左半边
win:setFrame({
x = frame.x,
y = frame.y,
w = frame.w / 2,
h = frame.h
})
-- 窗口最大化
win:maximize()
-- 按名称查找窗口
local safariWin = hs.window.find("Safari")
hs.screen — 多屏幕支持
-- 获取所有屏幕
local screens = hs.screen.allScreens()
for i, s in ipairs(screens) do
print(i, s:name(), s:frame())
end
-- 获取主屏幕
local main = hs.screen.mainScreen()
print(main:currentMode()) -- {w=2560, h=1440, scale=2}
hs.eventtap — 事件拦截与模拟
可以监听甚至修改键盘鼠标事件,是实现键位重映射的核心:
-- 将 Caps Lock 重映射为 Escape(需配合 Karabiner 或系统设置关闭 Caps Lock)
local capsEsc = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(event)
if event:getKeyCode() == hs.keycodes.map["f18"] then -- F18 代表 Caps Lock
event:setKeyCode(hs.keycodes.map["escape"])
end
return false -- false = 继续传递事件
end)
capsEsc:start()
-- 模拟按键
hs.eventtap.keyStroke({"cmd"}, "c") -- 模拟 Cmd+C
hs.eventtap.keyStroke({}, "return") -- 模拟回车
hs.application — 应用程序控制
-- 启动或聚焦应用
hs.application.launchOrFocus("Finder")
-- 获取所有运行中的应用
local apps = hs.application.runningApplications()
for _, app in ipairs(apps) do
print(app:name(), app:pid())
end
-- 向应用发送菜单命令
local app = hs.application.get("Finder")
app:selectMenuItem({"File", "New Finder Window"})
hs.timer — 定时器
-- 5秒后执行一次
hs.timer.doAfter(5, function()
hs.alert.show("5秒到了!")
end)
-- 每60秒重复执行
local t = hs.timer.new(60, function()
-- 检查某些状态...
end)
t:start()
-- 在特定时间执行
hs.timer.doAt("09:00", function()
hs.notify.new({title="早安", informativeText="今天也要加油!"}):send()
end)
hs.notify — 系统通知
-- 发送简单通知
hs.notify.new({
title = "Hammerspoon",
informativeText = "任务完成",
soundName = NSUserNotificationDefaultSoundName
}):send()
-- 带点击回调的通知
local n = hs.notify.new(function(notif)
hs.application.launchOrFocus("Safari")
end, {
title = "打开浏览器",
informativeText = "点击打开 Safari"
})
n:send()
hs.wifi — WiFi 事件监听
-- 监听 WiFi 网络变化
local wifiWatcher = hs.wifi.watcher.new(function()
local network = hs.wifi.currentNetwork()
if network == "HomeNetwork" then
-- 连接到家庭网络,关闭 VPN
hs.execute("launchctl unload ~/Library/LaunchAgents/com.vpn.plist")
hs.alert.show("🏠 到家了,VPN 已关闭")
elseif network then
-- 连接到其他网络,开启 VPN
hs.execute("launchctl load ~/Library/LaunchAgents/com.vpn.plist")
hs.alert.show("🌐 公共网络,VPN 已开启")
end
end)
wifiWatcher:start()
hs.battery — 电池监控
local battWatcher = hs.battery.watcher.new(function()
local pct = hs.battery.percentage()
local charging = hs.battery.isCharging()
if pct <= 20 and not charging then
hs.notify.new({
title = "⚡ 电量不足",
informativeText = string.format("剩余 %.0f%%,请及时充电", pct)
}):send()
end
end)
battWatcher:start()
hs.drawing / hs.canvas — 自定义 UI 绘制
-- 在屏幕上绘制自定义文字提示(类似 OSD)
local function showOSD(text)
local screen = hs.screen.mainScreen()
local frame = screen:frame()
local canvas = hs.canvas.new({
x = frame.w / 2 - 150,
y = frame.h - 120,
w = 300,
h = 60
})
canvas:appendElements({
type = "rectangle",
action = "fill",
fillColor = {red=0, green=0, blue=0, alpha=0.75},
roundedRectRadii = {xRadius=10, yRadius=10}
}, {
type = "text",
text = text,
textFont = "SF Pro Display",
textSize = 20,
textColor = {white=1, alpha=1},
textAlignment = "center",
frame = {x=0, y=10, w=300, h=40}
})
canvas:show()
hs.timer.doAfter(2, function() canvas:delete() end)
end
showOSD("🎉 配置已重载")
典型使用场景
场景一:完整的窗口管理系统
这是 Hammerspoon 最广泛的使用场景。以下是一套完整的窗口布局方案,支持左半、右半、左三分之一等常用布局:
-- window-management.lua
-- 引入到 init.lua: require("window-management")
local wm = {}
-- 屏幕边距(像素)
local MARGIN = 8
-- 获取可用屏幕区域(减去边距)
local function getFrame(screen)
local f = screen:frame()
return {
x = f.x + MARGIN,
y = f.y + MARGIN,
w = f.w - MARGIN * 2,
h = f.h - MARGIN * 2
}
end
-- 布局定义(比例)
local layouts = {
left_half = {x=0, y=0, w=0.5, h=1},
right_half = {x=0.5, y=0, w=0.5, h=1},
top_half = {x=0, y=0, w=1, h=0.5},
bottom_half = {x=0, y=0.5, w=1, h=0.5},
left_third = {x=0, y=0, w=0.333,h=1},
center_third = {x=0.333,y=0, w=0.333,h=1},
right_third = {x=0.667,y=0, w=0.333,h=1},
left_two_third = {x=0, y=0, w=0.667,h=1},
right_two_third= {x=0.333,y=0, w=0.667,h=1},
top_left = {x=0, y=0, w=0.5, h=0.5},
top_right = {x=0.5, y=0, w=0.5, h=0.5},
bottom_left = {x=0, y=0.5,w=0.5, h=0.5},
bottom_right = {x=0.5, y=0.5,w=0.5, h=0.5},
maximize = {x=0, y=0, w=1, h=1},
}
-- 应用布局到当前窗口
function wm.applyLayout(name)
local win = hs.window.focusedWindow()
if not win then return end
local screen = win:screen()
local sf = getFrame(screen)
local l = layouts[name]
if not l then
hs.alert.show("未知布局: " .. name)
return
end
win:setFrame({
x = sf.x + sf.w * l.x,
y = sf.y + sf.h * l.y,
w = sf.w * l.w - MARGIN,
h = sf.h * l.h - MARGIN
})
end
-- 将窗口移到下一个屏幕
function wm.moveToNextScreen()
local win = hs.window.focusedWindow()
if not win then return end
win:moveToScreen(win:screen():next(), true, true)
end
-- 快捷键绑定
local M = {"ctrl", "alt"} -- modifier keys
hs.hotkey.bind(M, "left", function() wm.applyLayout("left_half") end)
hs.hotkey.bind(M, "right", function() wm.applyLayout("right_half") end)
hs.hotkey.bind(M, "up", function() wm.applyLayout("maximize") end)
hs.hotkey.bind(M, "down", function() wm.applyLayout("bottom_half") end)
hs.hotkey.bind(M, "1", function() wm.applyLayout("left_third") end)
hs.hotkey.bind(M, "2", function() wm.applyLayout("center_third") end)
hs.hotkey.bind(M, "3", function() wm.applyLayout("right_third") end)
hs.hotkey.bind(M, "n", function() wm.moveToNextScreen() end)
return wm
场景二:应用切换器(Hyper Key 模式)
将 Caps Lock 改造为 Hyper 键(Cmd+Ctrl+Alt+Shift),实现一键直达应用:
-- hyper.lua
-- 前提:用 Karabiner-Elements 将 Caps Lock 映射为 F18
local hyper = {"cmd", "ctrl", "alt", "shift"}
local appHotkeys = {
["T"] = "iTerm",
["B"] = "Safari",
["C"] = "Visual Studio Code",
["S"] = "Slack",
["F"] = "Finder",
["M"] = "Mail",
["N"] = "Notion",
["P"] = "Spotify",
["X"] = "Xcode",
["D"] = "Discord",
}
for key, app in pairs(appHotkeys) do
hs.hotkey.bind(hyper, key, function()
hs.application.launchOrFocus(app)
end)
end
-- Hyper+R 重载配置
hs.hotkey.bind(hyper, "R", function()
hs.reload()
end)
场景三:基于上下文的自动化(Context Switching)
不同工作场景自动切换配置,比如开会时静音、在家时关 VPN、连显示器时切换布局:
-- context.lua
local contexts = {}
-- 应用焦点变化监听
local appWatcher = hs.application.watcher.new(function(name, event, app)
if event == hs.application.watcher.activated then
if name == "zoom.us" or name == "Microsoft Teams" then
-- 进入会议模式:关闭通知,静音提醒
hs.alert.show("📹 会议模式:通知已暂停")
elseif name == "Final Cut Pro" or name == "DaVinci Resolve" then
-- 视频编辑模式:禁用可能干扰快捷键的热键
hs.alert.show("🎬 视频编辑模式")
end
end
end)
appWatcher:start()
-- 屏幕数量变化(连接/断开外接显示器)
local screenWatcher = hs.screen.watcher.new(function()
local screens = hs.screen.allScreens()
local count = #screens
hs.timer.doAfter(2, function() -- 等待屏幕稳定
if count >= 2 then
hs.alert.show(string.format("🖥️ 检测到 %d 块屏幕,应用多屏布局", count))
-- 在这里重新排列应用窗口
applyMultiScreenLayout()
else
hs.alert.show("💻 单屏模式")
end
end)
end)
screenWatcher:start()
-- 多屏布局:把常用应用分配到不同屏幕
function applyMultiScreenLayout()
local screens = hs.screen.allScreens()
if #screens < 2 then return end
local mainScreen = hs.screen.mainScreen()
local secondScreen = mainScreen:next()
local layout = {
{"Safari", nil, mainScreen, hs.layout.left50, nil, nil},
{"Visual Studio Code", nil, mainScreen, hs.layout.right50, nil, nil},
{"iTerm2", nil, secondScreen, hs.layout.maximized, nil, nil},
{"Slack", nil, secondScreen, hs.layout.left50, nil, nil},
}
hs.layout.apply(layout)
end
场景四:Spoon —— 模块化插件系统
Spoon 是 Hammerspoon 的官方插件标准,一个 Spoon 就是一个 .spoon 目录,包含 init.lua 和元数据:
-- 使用 SpoonInstall 管理 Spoons
hs.loadSpoon("SpoonInstall")
-- 安装并配置 ReloadConfiguration Spoon(文件变化自动重载)
spoon.SpoonInstall:andUse("ReloadConfiguration", {
watch_paths = {os.getenv("HOME") .. "/.hammerspoon"}
})
-- 安装并配置 WindowScreenLeftAndRight(窗口在屏幕间移动)
spoon.SpoonInstall:andUse("WindowScreenLeftAndRight", {
hotkeys = {
screen_left = {{"ctrl", "alt", "cmd"}, "Left"},
screen_right = {{"ctrl", "alt", "cmd"}, "Right"}
}
})
-- 安装 ClipboardTool(剪贴板历史)
spoon.SpoonInstall:andUse("ClipboardTool", {
hotkeys = {show_clipboard = {{"ctrl", "alt", "cmd"}, "v"}},
config = {
max_size = 100,
show_in_menubar = false,
},
start = true
})
场景五:URL Scheme 处理与 Alfred/Raycast 联动
-- url-handler.lua
-- 注册自定义 URL Scheme,用于从 Alfred/Raycast/脚本触发 Hammerspoon 操作
-- 在 macOS 上注册需要额外步骤,这里用 hs.urlevent 模块
hs.urlevent.bind("windowLeft", function(eventName, params)
local win = hs.window.focusedWindow()
if win then
local f = win:screen():frame()
win:setFrame({x=f.x, y=f.y, w=f.w/2, h=f.h})
end
end)
hs.urlevent.bind("openApp", function(eventName, params)
if params["name"] then
hs.application.launchOrFocus(params["name"])
end
end)
-- 触发方式(在终端或脚本中):
-- open "hammerspoon://windowLeft"
-- open "hammerspoon://openApp?name=Safari"
场景六:菜单栏小工具
-- menubar.lua
-- 在菜单栏显示当前网络 IP 或其他状态
local menubar = hs.menubar.new()
local function updateMenubar()
local ip = hs.network.primaryIPAddress()
if ip then
menubar:setTitle("🌐 " .. ip)
else
menubar:setTitle("❌ 无网络")
end
end
-- 创建下拉菜单
menubar:setMenu(function()
return {
{title = "复制 IP", fn = function()
local ip = hs.network.primaryIPAddress()
if ip then
hs.pasteboard.setContents(ip)
hs.alert.show("IP 已复制: " .. ip)
end
end},
{title = "-"}, -- 分隔线
{title = "重载 Hammerspoon", fn = function() hs.reload() end},
{title = "退出 Hammerspoon", fn = function() hs.quit() end},
}
end)
updateMenubar()
-- 每30秒刷新一次
hs.timer.new(30, updateMenubar):start()
工具对比
| 特性 | Hammerspoon | Keyboard Maestro | BetterTouchTool | Raycast |
|---|---|---|---|---|
| **价格** | 免费开源 | $36 一次买断 | $22/年 或 $45 买断 | 免费+$96/年 Pro |
| **配置方式** | Lua 代码 | 图形化流程 | 图形化 | 图形化+插件 |
| **上手难度** | 高(需懂 Lua) | 低 | 中 | 极低 |
| **窗口管理** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| **快捷键** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| **触控板手势** | ❌ | ❌ | ⭐⭐⭐⭐⭐ | ❌ |
| **自动化深度** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| **启动器** | ⭐⭐(需手写) | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| **可扩展性** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐(插件市场) |
| **系统资源** | 极低 | 中 | 低 | 中 |
| **脚本语言** | Lua | AppleScript/Shell | AppleScript/Shell | Swift/TypeScript |
各工具定位
Hammerspoon 是「程序员的工具」——如果你愿意写代码,它能做任何事,但开箱即用的体验几乎为零。
Keyboard Maestro 是「超级用户的工具」——强大的图形化宏编辑器,几乎不需要写代码就能实现复杂自动化,剪贴板、表单填写、应用控制一应俱全,是 Hammerspoon 的图形化替代品。
BetterTouchTool 的核心优势是触控板手势——自定义 Magic Trackpad 手势、Force Touch 操作、Touch Bar(老 MacBook)按钮是其他工具无法替代的领域。窗口管理也做得不错。
Raycast 是「现代启动器」——专注于 Spotlight 增强、快速启动、AI 集成,有活跃的插件市场,是 Alfred 的强力竞争者,但窗口管理相对有限。
最佳实践:很多高级用户会同时使用多个工具——用 Hammerspoon 做窗口管理和系统自动化,用 Raycast 做应用启动和快速查询,用 BetterTouchTool 做手势。工具不冲突。
进阶技巧
1. 模块化组织配置
随着配置增长,建议按功能拆分文件,用 require 组织:
-- ~/.hammerspoon/init.lua
-- 工具函数
local utils = require("utils")
-- 功能模块
require("window-management") -- 窗口管理
require("hyper") -- Hyper 键绑定
require("context") -- 上下文感知
require("menubar") -- 菜单栏小工具
require("wifi-watcher") -- WiFi 自动化
-- Spoons
require("spoons") -- Spoon 管理
-- 重载通知
hs.notify.new({
title = "Hammerspoon",
informativeText = "✅ 配置已重载 " .. os.date("%H:%M:%S")
}):send()
2. 使用 hs.inspect 调试
-- 在控制台(Console)中调试 Lua 值
local win = hs.window.focusedWindow()
print(hs.inspect(win:frame()))
-- 输出: {h = 900.0, w = 1440.0, x = 0.0, y = 0.0}
-- 打印所有屏幕信息
for i, s in ipairs(hs.screen.allScreens()) do
print(i, hs.inspect(s:frame()))
end
3. 防止重复绑定(reload 安全)
-- 使用 hs.hotkey.deleteAll() 或追踪热键对象
local hotkeyList = {}
local function bindKey(mods, key, fn)
-- 先删除已有的同组合键绑定
if hotkeyList[mods .. key] then
hotkeyList[mods .. key]:delete()
end
local hk = hs.hotkey.bind(mods, key, fn)
hotkeyList[mods .. key] = hk
return hk
end
4. 异步执行(避免阻塞 UI)
-- 错误做法:直接 execute 会阻塞
-- local output = hs.execute("sleep 5 && echo done") -- ❌ 冻结 5 秒
-- 正确做法:使用 hs.task 异步执行
local task = hs.task.new("/usr/bin/env", function(exitCode, stdout, stderr)
-- 完成回调
hs.alert.show("任务完成: " .. stdout)
end, {"bash", "-c", "sleep 2 && echo '异步完成'"})
task:start()
5. 持久化配置状态
-- 用 hs.settings 持久化跨重载的配置
local SETTINGS_KEY = "myConfig"
-- 读取
local savedConfig = hs.settings.get(SETTINGS_KEY) or {}
-- 写入(自动持久化到 ~/Library/Preferences)
savedConfig.lastReload = os.time()
hs.settings.set(SETTINGS_KEY, savedConfig)
-- 删除
-- hs.settings.clear(SETTINGS_KEY)
6. 性能优化:避免频繁窗口查询
-- 缓存窗口引用,避免每次触发都查询
local windowCache = {}
local appWatcher = hs.application.watcher.new(function(name, event, app)
if event == hs.application.watcher.launched then
-- 应用启动时缓存
hs.timer.doAfter(0.5, function()
windowCache[name] = app:mainWindow()
end)
elseif event == hs.application.watcher.terminated then
windowCache[name] = nil
end
end)
appWatcher:start()
7. 构建自定义选择器(Chooser)
-- 快速切换窗口(类似 Cmd+Tab 但更强大)
local windowChooser = hs.chooser.new(function(choice)
if choice then
-- 通过 windowID 找到窗口并聚焦
local win = hs.window.get(choice.windowID)
if win then
win:focus()
end
end
end)
local function buildWindowList()
local choices = {}
for _, win in ipairs(hs.window.allWindows()) do
if win:isStandard() then
local app = win:application()
table.insert(choices, {
text = win:title(),
subText = app and app:name() or "Unknown",
image = app and hs.image.imageFromAppBundle(app:bundleID()) or nil,
windowID = win:id()
})
end
end
return choices
end
hs.hotkey.bind({"cmd", "alt"}, "W", function()
windowChooser:choices(buildWindowList())
windowChooser:show()
end)
8. 与 Shell 脚本深度集成
-- 执行 shell 命令并处理输出
local function getClipboardHTML()
-- 获取剪贴板中的 HTML(如果有的话)
local types = hs.pasteboard.typesAvailable()
for _, t in ipairs(types) do
if t == "public.html" then
return hs.pasteboard.readDataForUTI("public.html")
end
end
return nil
end
-- 调用外部脚本
hs.hotkey.bind({"cmd", "alt"}, "G", function()
hs.task.new("/usr/local/bin/python3", function(code, out, err)
if code == 0 then
hs.pasteboard.setContents(out:gsub("\n$", ""))
hs.alert.show("✅ 已处理并复制到剪贴板")
else
hs.alert.show("❌ 脚本出错: " .. err)
end
end, {os.getenv("HOME") .. "/scripts/process.py", hs.pasteboard.getContents()}):start()
end)
社区资源
官方资源
- 官网: https://www.hammerspoon.org
- GitHub: https://github.com/Hammerspoon/hammerspoon
- API 文档: https://www.hammerspoon.org/docs/ — 极其详尽,每个模块都有说明和示例
- Spoon 仓库: https://www.hammerspoon.org/Spoons/ — 官方维护的 Spoon 目录
- Getting Started 教程: https://www.hammerspoon.org/go/
优质配置参考
- awesome-hammerspoon: https://github.com/ashfinal/awesome-hammerspoon — 高质量预设配置,窗口管理、应用切换、ViMode 全有
- Hammerspoon Config Samples: https://github.com/Hammerspoon/hammerspoon/wiki/Sample-Configurations — 官方 wiki 的社区配置集合
学习资源
- Lua 5.4 参考手册: https://www.lua.org/manual/5.4/ — Lua 语言本身的完整文档
- Learn Lua in Y Minutes: https://learnxinyminutes.com/docs/lua/ — 快速上手 Lua 语法
- r/hammerspoon: Reddit 社区,活跃度一般但有不少解决方案
相关工具(与 Hammerspoon 配合使用)
- Karabiner-Elements: https://karabiner-elements.pqrs.org/ — 键位重映射,常与 Hammerspoon 配合(如把 Caps Lock 改为 F18/Hyper)
- yabai: https://github.com/koekeishiya/yabai — 平铺窗口管理器,可与 Hammerspoon 热键系统联动
- skhd: https://github.com/koekeishiya/skhd — 轻量级热键守护进程,常配合 yabai 使用
总结
Hammerspoon 的本质是一个以代码为配置的 macOS 超级自动化平台。它的学习曲线陡峭,但一旦上手,几乎没有它做不到的事情——从简单的窗口布局快捷键,到复杂的上下文感知自动化,再到自定义 UI 组件。
适合你的情况:
- 你是开发者,不怕写代码
- 你的自动化需求复杂或高度个性化
- 你希望完全掌控工具的每个细节
- 你不想为工具付费
也许不适合的情况:
- 你只需要简单的窗口管理(考虑 Magnet 或 Rectangle)
- 你需要复杂的触控板手势(BetterTouchTool 更专业)
- 你的主要需求是快速启动和 AI 助手(Raycast 更合适)
最后,Hammerspoon 和其他工具并不互斥。很多高效的 macOS 用户会把 Hammerspoon 作为自动化基础设施,同时搭配 Raycast 做启动器、BetterTouchTool 做手势——让每个工具做它最擅长的事。
本报告基于 Hammerspoon 0.9.100+,macOS 14 Sonoma / 15 Sequoia 环境。API 细节以 官方文档 为准。