如果你是个极客,并且使用 Mac 有一段时间了,你可能会记得 Applescript。这是苹果开发的一种语言,旨在让中级到高级用户编写简单的脚本来控制 Mac 应用程序。它实际上是为了模仿英语而设计的,因此访问一个像素的代码可能会写成这样:
pixel 7 of row 3 of TIFF image "my bitmap"
或者甚至这样:
TIFF image "my bitmap"'s 3rd row's 7th pixel
不用说,现代编程语言不这样设计是有充分理由的:它不具备扩展性。任何长期使用 Applescript 的人都知道,你很快就会遇到它的局限性。苹果在 2016 年非正式地弃用了它,当时其创始人 Sal Soghoian 因“商业原因”被解雇(来源)。
在 Applescript 逐渐衰落的同时,一位名叫 Steven 的智者正在编写 Hammerspoon,这是一个连接 macOS API 的 Lua 桥接器。Lua 作为一种简单且现代的编程语言,非常适合这个任务。那么,让我们来看看如何使用它。
安装 Hammerspoon
使用 Homebrew。
brew install hammerspoon
设置
创建一个名为 ~/.hammerspoon/init.lua
的文件。
mkdir ~/.hammerspoon && touch ~/.hammerspoon/init.lua
打开应用程序。
open -a Hammerspoon
你应该会看到一个类似这样的 Lua 控制台(不用担心里面的文字)。
点击偏好设置,确保已启用辅助功能。
如果你愿意,也可以勾选 登录时启动 Hammerspoon
。
确保 init.lua 正常运行
将以下内容粘贴到你创建的 init.lua
文件中。
local logger = hs.logger.new("init.lua", "debug")
logger.d("SUCCESSFULLY RAN init.lua")
保存文件,并在控制台中点击 Reload
按钮。如果你看到输出,说明一切正常。
绑定系统全局热键
我主要使用 Hammerspoon 来创建“智能”热键,而不必去折腾偏好设置中那些有问题的热键,或者下载像 Karabiner 这样的另一个应用。以下是一些可以在 init.lua
中使用的入门函数,它们能让创建新热键变得极其简单。
local function stringsplit(inputstr, sep)
if sep == nil then
sep = "%s"
end
local t = {}
for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
table.insert(t, str)
end
return t
end
local function keyMapArray(global_modifiers, mappings)
for k, v in pairs(mappings) do
if type(v) == "string" then
-- 我们要映射到的修饰键
local modifiers = {}
local splitkey = stringsplit(v, "-")
local splitlen = #splitkey
-- 将 v 中包含的任何映射修饰键
-- 添加到修饰键数组中
for i = 1, splitlen - 1 do
modifiers[i] = splitkey[i]
end
-- 获取要传递给绑定函数的数字键码
local mappedKeyCode = hs.keycodes.map[splitkey[splitlen]]
if mappedKeyCode then
hs.hotkey.bind(global_modifiers, k, function()
hs.eventtap.keyStroke(modifiers, mappedKeyCode, 1)
end)
else
-- 数字键码为 nil,表示它不存在
-- 现在我们尝试将其视为系统键事件代码,例如
-- PLAY
hs.hotkey.bind(global_modifiers, k, function()
hs.eventtap.event.newSystemKeyEvent(v, true):post()
hs.eventtap.event.newSystemKeyEvent(v, false):post()
end)
end
elseif type(v) == "function" then
hs.hotkey.bind(global_modifiers, k, v)
end
end
end
local function processMaps(maps)
for globals, mappings in pairs(maps) do
keyMapArray(globals, mappings)
end
end
如果你不熟悉 Lua 并感到有些畏惧,别担心。这个函数的目的是让你无需学习 Lua 就能上手。让我们看看如何创建一个新的映射。假设你想要在系统范围内使用 Vim 风格的箭头键绑定,并通过 ctrl
激活。
-- 定义映射
local maps = {
[{ "ctrl" }] = {
-- 箭头键
["h"] = "left",
["j"] = "down",
["k"] = "up",
["l"] = "right",
},
}
-- 绑定映射
processMaps(maps)
只需将其放在文件底部,重新加载配置,你的键就应该被映射好了!
maps
的工作原理
maps
是一个数组,或者说是一个表格列表。每个表格的键是一个修饰键数组,值是一个将某个键映射到另一个键的表格。在上面的例子中,唯一的修饰键是 ctrl
,这意味着其表格中的所有映射仅在按住 ctrl
时才会激活。然后它将 h
映射到左箭头,j
映射到下箭头,依此类推。
如果你还是不明白,别担心。只需查看这些示例并根据你的需求进行修改即可。
示例
以下是一些 maps tables
的更多示例:
将 ctrl-n
映射为删除单词,ctrl-m
映射为删除字符,ctrl-.
映射为删除光标前的单词,ctrl-,
映射为删除光标前的字符。
-- 在 `maps` 中
[{ "ctrl" }] = {
-- 删除操作
["n"] = "alt-delete",
["m"] = "delete",
[","] = "forwarddelete",
["."] = "alt-forwarddelete",
},
将功能键映射为媒体控制。注意这些映射没有使用任何修饰键,因此只需按下 f1
即可激活绑定:
-- 在 `maps` 中
[{}] = {
-- 媒体控制
["f1"] = "MUTE",
["f2"] = "SOUND_DOWN",
["f3"] = "SOUND_UP",
["f5"] = "PREVIOUS",
["f6"] = "PLAY",
["f7"] = "NEXT",
},
使用 alt-ctrl-r
重新加载 Hammerspoon 配置
-- 在 `maps` 中
[{ "alt", "ctrl" }] = {
-- 重新加载 Hammerspoon 配置
["r"] = hs.reload,
},
使用 cmd-g
让笔记本电脑进入睡眠状态
-- 在 `maps` 中
[{ "cmd" }] = {
["g"] = function()
-- 该函数执行一个 shell 命令
os.execute("pmset sleepnow")
end,
},
使用 cmd-shift-o
启动 Obsidian 应用
[{ "cmd", "shift" }] = {
["o"] = function()
hs.application.launchOrFocus("Obsidian")
end,
},
在定义完所有内容后,记得运行 processMaps(maps)
!
local maps = {
[{}] = {
-- 内容
},
[{ "cmd" }] = {
-- 内容
},
[{ "cmd", "shift" }] = {
-- 内容
},
-- ...
}
processMaps(maps)
结论
Hammerspoon 是一款极其强大的工具。一旦你熟悉了 Lua,其可能性几乎是无限的。本文中我只重点介绍了热键功能,因为这是我发现 Hammerspoon 最大的应用场景。
如果你想查看 Hammerspoon 提供的所有 API 函数,请查阅他们的文档。你可以用这款应用做很多事情——比如将事件绑定到电池状态、调整显示器亮度、打开对话框提示、发送 HTTP 请求、复制和粘贴等等。此外,你还可以通过 Luarocks 利用整个 Lua 生态系统。
另外,别忘了看看 r/Hammerspoon,了解其他爱好者都在做些什么。我们那里需要更多成员加入 :)
实用链接
与 Hammerspoon 相关的资源集合。我会在遇到新内容时保持更新。
网站 | 描述 |
---|---|
文档 | 完整的 Hammerspoon API 文档 |
r/Hammerspoon | 包含各种 Spoon 和展示的 Reddit 子论坛 |
Spoons | Spoon 列表,Spoon 是 Hammerspoon 的插件。 |
ControlEscape.spoon | 将你的 Control 键映射为轻按时触发 ESC,长按时触发 Ctrl。对 Vim 用户很有用 |