用 Hammerspoon 给 Chrome 侧边栏加一个快捷键
Chrome 有个内置的垂直标签页侧边栏(Vertical Tab Sidebar),体验不错,但有一个致命问题:没有键盘快捷键来切换它。每次想收起或展开侧边栏,都得用鼠标去点那个小按钮。
作为一个键盘流用户,这无法忍受。于是我写了一个 Hammerspoon 脚本来解决这个问题。
思路
macOS 有一个 Accessibility API(无障碍 API),可以遍历任何应用的 UI 元素树,找到按钮并模拟点击。Chrome 的侧边栏切换按钮在无障碍树中的标识是"Expand Tabs"或"Collapse Tabs"。
所以核心逻辑很简单:
- 通过
hs.axuielement.applicationElement()获取 Chrome 的 AX 根元素 - 递归遍历窗口的子元素,找到目标按钮
- 调用
performAction("AXPress")模拟点击
这个方案参考了 ChromeSidebarToggleRaycast 的思路,但把它移植到了 Hammerspoon 上,并增加了鼠标触发的支持。
两种触发方式
脚本提供三个 scheme,通过顶部的SCHEME变量切换:
| Scheme | 值 | 触发方式 |
|---|---|---|
| 仅键盘 | 1 | Cmd+S切换侧边栏 |
| 仅鼠标 | 2 | 鼠标悬停屏幕左边缘展开,移出 380px 收起 |
| 键盘 + 鼠标 | 3 | 两种都生效(默认) |
鼠标触发的原理是每 50ms 轮询一次鼠标位置。当鼠标进入屏幕左边缘 2px 范围内并停留超过 0.15 秒,触发展开;当鼠标移出 380px,触发收起。
关键实现细节
Grace Period 防误触
切换应用时,鼠标位置和按键事件可能被误判。脚本在 Chrome 获得/失去焦点、系统唤醒/休眠时都会设置一个 grace period(宽限期),期间所有触发都被忽略。
看门狗机制
鼠标轮询器偶尔会意外停止。键盘事件处理器中内置了一个看门狗:如果超过 5 秒没有收到鼠标轮询的心跳,自动重建所有服务。
生命周期管理
脚本监听了三个层面的事件:
- 应用层:Chrome 激活/失焦/启动/退出
- 系统层:系统休眠/唤醒
- 初始化层:启动时检测当前是否在 Chrome 中
每个状态转换都会正确地启动或停止对应的监听器,避免资源浪费和误触发。
安装
# 安装 Hammerspoonbrewinstall--caskhammerspoon# 复制脚本cpinit.lua ~/.hammerspoon/init.lua# 授权辅助功能# 系统设置 → 隐私与安全性 → 辅助功能 → 添加 Hammerspoon然后在 Hammerspoon 菜单栏图标点击 “Reload Config” 即可。
自定义快捷键
默认快捷键是Cmd+S。改这里就行:
-- Cmd+S -> toggle sidebarifflags.cmdandnotflags.ctrlandnotflags.altandnotflags.shiftandkeyCode==keycodes.map["s"]then比如改成Ctrl+Shift+B:
ifflags.ctrlandnotflags.cmdandflags.shiftandnotflags.altandkeyCode==keycodes.map["b"]then调试
三个调试快捷键:
Cmd+Alt+D:显示服务状态(scheme、监听器运行状态、事件计数)Cmd+Alt+B:dump Chrome 所有 AX 按钮到 ConsoleCmd+Alt+R:强制重启所有服务
文件结构
项目只有两个 Lua 文件:
init.lua— 完整版,支持三种 schemeinit-keyboard-only.lua— 纯键盘版,没有鼠标检测,代码更简洁
如果你不需要鼠标边缘触发,用 keyboard-only 版本就好,少了一半代码,也少了轮询的开销。
写在最后
这个脚本解决了一个很小但很烦的问题。Hammerspoon + Accessibility API 的组合在 macOS 上非常强大,几乎可以给任何应用的任何 UI 元素绑定快捷键。
项目地址:Chrome-Vertical-Tab-Sidebar-Toggle