蘋果環境

Hammerspoon

視窗管理與自動化

以 Lua 腳本自動化 macOS 操作,此處用於視窗管理快捷鍵。取代原本的 BetterTouchTool。

為什麼從 BetterTouchTool 換過來

BetterTouchToolHammerspoon
授權只涵蓋兩年,過期需重購或降版MIT 開源,無授權問題
廣告
設定二進位匯出檔,不易版控純文字 init.lua,直接進 Git
開機自啟GUI 勾選寫在設定檔內,換機即生效

原 BetterTouchTool 筆記保留於 backup/07-bettertouchtool.md

安裝步驟

  • 官網 - https://www.hammerspoon.org/
  • 安裝:brew install --cask hammerspoon
  • 開啟 Hammerspoon(首次啟動會要求輔助使用權限,見下節)

授權輔助使用權限

  • 開啟系統設定(option + F10)❯ 隱私權與安全性輔助使用
  • 找到 Hammerspoon - 打開開關
  • 也可用指令直接開啟該頁:open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"

❗️ 沒有此權限,快捷鍵按下去不會有任何反應(不會報錯,只是沒動作)。任何能操作視窗的工具都需要它。

設定檔

設定檔版控於本專案,~/.hammerspoon/init.lua 以 symlink 指向它:

mkdir -p ~/.hammerspoon
ln -sfn ~/Workspace/00_MacOS/02-setup/hammerspoon/init.lua ~/.hammerspoon/init.lua
  • 設定檔位置 - hammerspoon/init.lua
  • 修改後自動重新載入hs.pathwatcher 監看的是專案目錄,不是 ~/.hammerspoon/(symlink 的變更偵測不到)
  • 開機自動啟動 - 由設定檔內的 hs.autoLaunch(true) 處理,不必去 GUI 勾選
  • require("hs.ipc") - 讓終端機的 hs 指令能與 Hammerspoon 通訊,用於驗證與除錯

快捷鍵

快捷鍵功能
command + option + ↑將目前視窗移到下一個螢幕並填滿;只有一個螢幕時,原地填滿

實作重點

  • 不需要條件判斷hs.screen:next() 是封閉循環(A→B→A),單螢幕時回傳自己,自動退化為原地最大化
  • 不用 maximize()screen:frame() 已扣除選單列與 Dock,直接 setFrame() 即可
  • 綠燈全螢幕要先退出 - 全螢幕視窗自成一個 Space,無法跨螢幕移動,故先 setFullScreen(false) 再延遲重試
  • 關閉動畫hs.window.animationDuration = 0,避免動畫與 setFrame 的時序競爭

Finder 的快捷鍵衝突

command + option + ↑ 在 Finder 原本是「前往上層資料夾並關閉目前視窗」,會被本設定蓋掉。

但單純的「前往上層資料夾」是 command + ↑不受影響,代價很小,因此不做讓行 - 讓此快捷鍵在所有 app 中行為一致。

若日後想讓 Finder 保留原行為,可用 hs.application.watcher 在切換到 Finder 時 hotkey:disable(),離開時 enable()。此法已驗證可行(切到 Finder 時 getHotkeys() 會歸零),僅因代價不划算而未採用。

驗證

hs -c "print(hs.accessibilityState())"     # 輔助使用權限,應為 true
hs -c "print(hs.autoLaunch())"             # 開機自啟,應為 true
hs -c "print(#hs.hotkey.getHotkeys())"     # 已註冊的快捷鍵數,應為 1

列出所有螢幕與其可用區(可用區已扣掉選單列,故高度會比實際解析度少):

hs -c "for i,s in ipairs(hs.screen.allScreens()) do local f=s:frame(); print(i, s:name(), f.w..'x'..f.h, f.x..','..f.y) end"

踩雷

  • 有些視窗的尺寸是固定的 - macOS 的「系統設定」(Ventura 後的 SwiftUI 版)寬度鎖死不可調整,套用後只有高度會變。這不是設定的問題,驗證時要換一個可自由縮放的視窗
  • hs.eventtap.keyStroke 無法觸發自己註冊的快捷鍵 - 模擬按鍵測不出來,驗證要用實體按鍵
  • hs.reload() 期間 IPC 會短暫斷線hs 指令 timeout 屬正常,稍候再試;若持續無回應則重啟 Hammerspoon
  • 首次啟動時 hs 指令不通require("hs.ipc") 是後來才加進設定檔的,需重啟 Hammerspoon 一次才會生效
  • 部分 app 跨螢幕移動後視窗會消失 - 實測時 Docker Desktop 的視窗在移到另一螢幕並填滿後不見了(daemon 與容器不受影響,從選單列圖示可重開)。僅觀察到一次,未重現驗證

相關參考