Mac(四)自定义按键工具 Hammerspoon 的安装和使用

发布于:2025-08-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

🚀 Hammerspoon 是 macOS 上最强大的自动化工具,通过 Lua 脚本实现:

  • 全局快捷键自定义
  • 窗口管理自动化
  • 应用快速切换
  • 系统状态监控
  • 任意功能扩展

比 Karabiner 更轻量,比 BetterTouchTool 更自由!


一、安装 Hammerspoon

1. 官方安装(推荐)

brew install --cask hammerspoon

2. 手动安装

官网下载 之后,双击打开 → 拖拽到 Applications 文件夹

3. 首次配置

  1. 打开应用后,菜单栏会出现「🔨」图标
  2. 点击图标 → 「Open Config」打开配置文件目录
  3. 系统会要求授予「辅助功能」权限:
    • 系统设置 → 隐私与安全性 → 辅助功能
    • 勾选 Hammerspoon

4. 配置文件结构

~/.hammerspoon/
├── init.lua          -- 主入口文件
└── modules/          -- 自定义模块目录

5. 示例配置

init.lua 内容如下:

-- 超实用快捷键:⌃⌥⌘ + D 显示桌面
hs.hotkey.bind({'ctrl', 'alt', 'cmd'}, 'D', function()
    hs.osascript.applescript('tell application "Finder" to set desktop visible to true')
end)

-- 窗口管理:⌃⌥ + 方向键快速调整窗口位置
hs.hotkey.bind({'ctrl', 'alt'}, 'Left', function()
    local win = hs.window.focusedWindow()
    win:moveToUnit({0, 0, 0.5, 1}) -- 左半屏
end)

3. 重载配置

点击菜单栏图标 → 「Reload Config」


二、实用代码片段

1. 重新加载配置

-- 示例快捷键:Ctrl+Alt+Cmd+R 重新加载配置
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "R", function()
  hs.reload()
end)

2. 快捷输入文字

-- 定义通用函数:发送文本并回车
-- 终极稳定版(带输入法状态检测)
function sendTextAndEnter(text)
    -- 检测是否中文输入法
    local isChinese = (hs.keycodes.currentLayout():find("Chinese") ~= nil)
    
    if isChinese then
        -- 中文模式:切换英文布局
        hs.keycodes.setLayout("U.S.")
        hs.timer.usleep(50000) -- 等待50ms
    end
    
    -- 分段输入核心逻辑
    for i = 1, #text, 3 do -- 每3个字符一组
        hs.eventtap.keyStrokes(text:sub(i, i+2))
        hs.timer.usleep(50000) -- 组间延迟50ms
    end
    
    -- 恢复中文布局(如果需要)
    hs.timer.delayed.new(0.2, function()
        if isChinese then
            hs.keycodes.setLayout("Chinese")
        end
        hs.eventtap.keyStroke({}, "return")
    end):start()
end

hs.hotkey.bind({"ctrl"}, "s", function() sendTextAndEnter("收到") end)     -- Command+S → "收到"
hs.hotkey.bind({"ctrl"}, "h", function() sendTextAndEnter("好的") end)     -- Command+H → "好的"

3. 打开网页

-- 定义通用函数:打开URL
function openURL(url)
    hs.execute('open "' .. url .. '"')
end
-- 绑定网页快捷键
hs.hotkey.bind({"cmd"}, "1", function() openURL("https://www.baidu.com") end)      -- Command+1 → 百度
hs.hotkey.bind({"cmd"}, "2", function() openURL("https://blog.csdn.net/qq_33204709?type=blog") end) -- cmd+2 → 博客
hs.hotkey.bind({"cmd"}, "3", function() openURL("https://google.com/") end)        -- Command+3 → 谷歌
hs.hotkey.bind({"cmd"}, "4", function() openURL("https://cn.bing.com/") end)       -- Command+4 → 必应

5. 文字缩写填充

-- =============================================
-- 文本替换核心引擎
-- 特点:零误删、100%精确、支持所有输入法
-- =============================================

-- 配置区(用户可修改)
local config = {
    triggers = {
        ["sf"] = "SELECT * FROM ",
        ["scf"] = "SELECT COUNT(*) FROM "
    },
    delay = 30000, -- 微秒级延迟 (30ms)
    maxBuffer = 10  -- 最大字符缓存
}

-- 引擎核心(不要修改)
local buffer = ""
local isProcessing = false

local function processBuffer()
    if isProcessing then return end
    isProcessing = true
    
    for trigger, replacement in pairs(config.triggers) do
        if buffer:sub(-#trigger) == trigger then
            -- 精确删除(不使用循环避免误差)
            hs.eventtap.keyStrokes(string.rep("\b", #trigger))
            hs.timer.usleep(config.delay)
            
            -- 插入新内容
            hs.eventtap.keyStrokes(replacement)
            
            -- 重置状态
            buffer = ""
            isProcessing = false
            return true
        end
    end
    
    isProcessing = false
    return false
end

-- 事件监听(优化版)
local eventTap = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(e)
    -- 获取按键字符
    local char = hs.keycodes.map[e:getKeyCode()] or ""
    
    -- 过滤非字母键
    if not char:match("%a") then
        buffer = ""
        return false
    end
    
    -- 更新缓冲区
    buffer = buffer .. char
    if #buffer > config.maxBuffer then
        buffer = buffer:sub(-config.maxBuffer)
    end
    -- 延迟处理确保稳定性
    hs.timer.doAfter(0.05, processBuffer) -- 50ms延迟
    
    return false
end)

-- 安全启动
if hs.eventtap then
    eventTap:start()
else
    hs.alert.show("初始化失败: 缺少eventtap模块", 2)
end

6. 窗口大小变化

-- 示例窗口管理:将当前窗口移到屏幕左侧
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "Left", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  f.x = max.x
  f.y = max.y
  f.w = max.w / 2
  f.h = max.h
  win:setFrame(f)
end)

-- 示例窗口管理:将当前窗口移到屏幕右侧
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "Right", function()
  local win = hs.window.focusedWindow()
  local f = win:frame()
  local screen = win:screen()
  local max = screen:frame()

  f.x = max.x + (max.w / 2)
  f.y = max.y
  f.w = max.w / 2
  f.h = max.h
  win:setFrame(f)
end)

-- 按下ctrl+alt+x实现窗口最大化
hs.hotkey.bind({"ctrl", "cmd"}, "X", function()
  local win = hs.window.focusedWindow()
  if win then
    -- 最大化当前窗口
    win:maximize()
  else
    hs.alert.show("没有聚焦的窗口")
  end
end)

7. 防沉迷提醒

-- 每30分钟提醒休息
workTimer = hs.timer.doEvery(30*60, function()
    hs.notify.new({
        title = "休息提醒",
        subTitle = "已经工作30分钟了",
        informativeText = "起来活动一下吧~"
    }):send()
end)

8. 剪切板历史

-- 需要安装clipboard插件
require("hs.clipboard")
hs.hotkey.bind({'cmd', 'shift'}, 'V', function()
    hs.clipboard.managerOnTop(true)
end)

9. 快速分屏(替代 Magnet)

-- 快速分屏
hs.hotkey.bind({'cmd', 'alt'}, 'Left', function()
    hs.window.focusedWindow():moveToUnit({0, 0, 0.5, 1})
end)

hs.hotkey.bind({'cmd', 'alt'}, 'Right', function()
    hs.window.focusedWindow():moveToUnit({0.5, 0, 0.5, 1})
end)

10. 应用启动

-- ⌃⌥⌘ + V 打开 VSCode
hs.hotkey.bind({'ctrl', 'alt', 'cmd'}, 'V', function()
    hs.application.launchOrFocus("Visual Studio Code")
end)

11. 电量监控

-- 显示电池状态
function showBattery()
    local battery = hs.battery.percentage()
    hs.alert.show("🔋 电量: " .. battery .. "%")
end
hs.hotkey.bind({'ctrl', 'cmd'}, 'B', showBattery)

12. 网络切换

-- 切换WiFi
function toggleWIFI()
    hs.execute("/usr/sbin/networksetup -setairportpower en0 toggle")
end
hs.hotkey.bind({'ctrl', 'cmd'}, 'W', toggleWIFI)

三、调试与维护

1. 控制台调试

# 打开调试控制台
hs.openConsole()

2. 常用命令

hs.reload()          -- 重载配置
hs.alert("Hello!")   -- 显示提示
hs.openAbout()       -- 打开关于面板

3. 错误排查

  1. 查看控制台日志(菜单栏 → 「Console」)
  2. 检查权限设置
  3. 使用 hs.inspect(variable) 输出变量值

四、推荐插件及安装方式

1. 主流插件安装指南 🔌

所有插件需放置在 ~/.hammerspoon/Spoons/ 目录下,然后在 init.lua 中加载:

-- 加载插件示例
hs.loadSpoon("PluginName")
第1步 SpoonInstall(插件管理器)
-- 安装步骤:
-- 1. 下载插件
hs.execute('curl -fsSL https://github.com/Hammerspoon/Spoons/raw/master/Spoons/SpoonInstall.spoon.zip -o /tmp/SpoonInstall.zip')
-- 2. 解压到插件目录
hs.execute('unzip -oq /tmp/SpoonInstall.zip -d ~/.hammerspoon/Spoons/')
-- 3. 在配置中启用
hs.loadSpoon("SpoonInstall")

-- 使用示例:安装其他插件
spoon.SpoonInstall:andUse("ClipboardTool", {
    repo = "https://github.com/Hammerspoon/Spoons",
    hotkeys = true
})
第2步 WinWin(窗口管理增强)
# 手动安装
cd ~/.hammerspoon/Spoons/
git clone https://github.com/asmagill/hs._asm.undocumented.winwin.git WinWin.spoon
-- 配置示例
hs.loadSpoon("WinWin")
spoon.WinWin:bindHotkeys({
  left50 = {{"ctrl", "alt"}, "Left"},
  right50 = {{"ctrl", "alt"}, "Right"}
})
第3步 ClipboardTool(剪切板历史)
-- 通过 SpoonInstall 安装
spoon.SpoonInstall:andUse("ClipboardTool", {
    config = {
        max_items = 20,
        show_copied_alert = true
    },
    hotkeys = {
        toggle = {{"cmd", "shift"}, "V"}
    }
})
第4步 FadeLogo(状态栏动画)
# 手动安装
curl -fsSL https://github.com/Hammerspoon/Spoons/raw/master/Spoons/FadeLogo.spoon.zip -o /tmp/FadeLogo.zip
unzip /tmp/FadeLogo.zip -d ~/.hammerspoon/Spoons/
-- 使用示例
hs.loadSpoon("FadeLogo")
spoon.FadeLogo:start()

2. 插件管理技巧

自动更新插件
-- 添加到 init.lua
function updateSpoons()
    hs.execute("find ~/.hammerspoon/Spoons -maxdepth 1 -type d -exec git -C {} pull \\;")
    hs.reload()
end
hs.hotkey.bind({"cmd", "ctrl"}, "U", updateSpoons)
插件冲突排查
  1. 在控制台输入 hs.spoons.list() 查看已加载插件
  2. 使用 hs.spoons.use("PluginName", { lazy = true }) 延迟加载
  3. 通过注释法隔离问题插件

💡 推荐组合:SpoonInstall + WinWin + ClipboardTool 满足90%日常需求


3. 社区资源

资源类型 链接
官方插件库 Hammerspoon/Spoons
配置分享 Awesome Hammerspoon
插件开发指南 Writing Spoons

五、扩展资源

1. 官方文档

2. 精品配置

# 克隆流行配置方案
git clone https://github.com/ashfinal/awesome-hammerspoon ~/.hammerspoon

3. 小编配置

-- =============================================
-- 初始化 Hammerspoon 配置
-- =============================================
hs.loadSpoon("SpoonInstall")

-- =============================================
-- 通用函数:发送文本并回车(终极稳定版)
-- 特点:带输入法状态检测,分段输入防止卡顿
-- =============================================
function sendTextAndEnter(text)
    -- 检测是否中文输入法
    local isChinese = (hs.keycodes.currentLayout():find("Chinese") ~= nil)
    
    if isChinese then
        -- 中文模式:切换英文布局
        hs.keycodes.setLayout("U.S.")
        hs.timer.usleep(50000) -- 等待50ms
    end
    
    -- 分段输入核心逻辑
    for i = 1, #text, 3 do -- 每3个字符一组
        hs.eventtap.keyStrokes(text:sub(i, i+2))
        hs.timer.usleep(50000) -- 组间延迟50ms
    end
    
    -- 恢复中文布局(如果需要)
    hs.timer.delayed.new(0.2, function()
        if isChinese then
            hs.keycodes.setLayout("Chinese")
        end
        hs.eventtap.keyStroke({}, "return")
    end):start()
end

-- =============================================
-- 通用函数:打开URL
-- =============================================
function openURL(url)
    hs.execute('open "' .. url .. '"')
end

-- =============================================
-- 通用函数:启动应用程序
-- 注意:Windows路径需要转换为Mac路径
-- =============================================
function launchApp(appPath)
    local macPath = string.gsub(appPath, "D:\\Program Files", "/Applications")
    hs.execute('open "' .. macPath .. '"')
end

-- =============================================
-- 快捷键绑定:常用短语
-- =============================================
hs.hotkey.bind({"ctrl"}, "s", function() sendTextAndEnter("收到") end) -- Command+S → "收到"
hs.hotkey.bind({"ctrl"}, "h", function() sendTextAndEnter("好的") end) -- Command+H → "好的"

-- =============================================
-- 快捷键绑定:网页快速访问
-- =============================================
hs.hotkey.bind({"ctrl"}, "1", function() openURL("https://www.baidu.com") end)      -- Command+1 → 百度
hs.hotkey.bind({"ctrl"}, "2", function() openURL("https://blog.csdn.net/qq_33204709?type=blog") end) -- cmd+2 → 博客
hs.hotkey.bind({"ctrl"}, "3", function() openURL("https://google.com/") end)        -- Command+3 → 谷歌
hs.hotkey.bind({"ctrl"}, "4", function() openURL("https://cn.bing.com/") end)       -- Command+4 → 必应

-- =============================================
-- 快捷键绑定:媒体控制
-- =============================================
-- hs.hotkey.bind({"cmd"}, "6", function() 
--     hs.execute('osascript -e "tell application \\"Music\\" to playpause"') 
-- end) -- cmd+6 → 播放/暂停

-- =============================================
-- 文本替换引擎
-- 特点:零误删、100%精确、支持所有输入法
-- =============================================
local config = {
    triggers = {
        ["sf"] = "SELECT * FROM ",
        ["scf"] = "SELECT COUNT(*) FROM "
    },
    delay = 30000, -- 微秒级延迟 (30ms)
    maxBuffer = 10  -- 最大字符缓存
}

local buffer = ""
local isProcessing = false

local function processBuffer()
    if isProcessing then return end
    isProcessing = true
    
    for trigger, replacement in pairs(config.triggers) do
        if buffer:sub(-#trigger) == trigger then
            -- 精确删除(不使用循环避免误差)
            hs.eventtap.keyStrokes(string.rep("\b", #trigger))
            hs.timer.usleep(config.delay)
            
            -- 插入新内容
            hs.eventtap.keyStrokes(replacement)
            
            -- 重置状态
            buffer = ""
            isProcessing = false
            return true
        end
    end
    
    isProcessing = false
    return false
end

local eventTap = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(e)
    -- 获取按键字符
    local char = hs.keycodes.map[e:getKeyCode()] or ""
    
    -- 过滤非字母键
    if not char:match("%a") then
        buffer = ""
        return false
    end
    
    -- 更新缓冲区
    buffer = buffer .. char
    if #buffer > config.maxBuffer then
        buffer = buffer:sub(-config.maxBuffer)
    end
    
    -- 延迟处理确保稳定性
    hs.timer.doAfter(0.05, processBuffer) -- 50ms延迟
    
    return false
end)

-- 安全启动事件监听
if hs.eventtap then
    eventTap:start()
else
    hs.alert.show("初始化失败: 缺少eventtap模块", 2)
end

-- =============================================
-- 快捷键绑定:快速访问路径
-- =============================================
hs.hotkey.bind({"cmd"}, "e", function()
    local path = "/Users/acgkaka/my"
    local attr = hs.fs.attributes(path)
    local exists = attr ~= nil
    
    if exists then
        hs.execute('open "' .. path .. '"')
    else
        hs.alert.show("路径不存在: " .. path, 2)
    end
end)

-- =============================================
-- 快捷键绑定:配置重载
-- =============================================
hs.hotkey.bind({"ctrl", "alt", "cmd"}, "R", function()
    hs.reload()
end)

-- =============================================
-- 窗口管理快捷键
-- =============================================

-- 将当前窗口移到屏幕左侧
-- hs.hotkey.bind({"ctrl", "alt", "cmd"}, "Left", function()
--     local win = hs.window.focusedWindow()
--     local f = win:frame()
--     local screen = win:screen()
--     local max = screen:frame()

--     f.x = max.x
--     f.y = max.y
--     f.w = max.w / 2
--     f.h = max.h
--     win:setFrame(f)
-- end)

-- -- 将当前窗口移到屏幕右侧
-- hs.hotkey.bind({"ctrl", "alt", "cmd"}, "Right", function()
--     local win = hs.window.focusedWindow()
--     local f = win:frame()
--     local screen = win:screen()
--     local max = screen:frame()

--     f.x = max.x + (max.w / 2)
--     f.y = max.y
--     f.w = max.w / 2
--     f.h = max.h
--     win:setFrame(f)
-- end)

-- -- 窗口最大化
-- hs.hotkey.bind({"ctrl", "cmd"}, "X", function()
--     local win = hs.window.focusedWindow()
--     if win then
--         win:maximize()
--     else
--         hs.alert.show("没有聚焦的窗口")
--     end
-- end)

-- =============================================
-- 通用应用程序启动函数
-- =============================================
function launchOrFocusApp(appPath, showNotification)
    -- 检查应用是否存在
    if not hs.fs.attributes(appPath) then
        hs.notify.new({title="Hammerspoon 错误", informativeText="未找到应用程序: "..appPath}):send()
        return false
    end
    
    -- 启动或聚焦应用程序
    hs.application.launchOrFocus(appPath)
    
    -- 显示通知(如果需要)
    if showNotification then
        local appName = appPath:match("([^/]+)%.app$") or appPath
        hs.notify.new({title="Hammerspoon", informativeText="已启动 "..appName}):send()
    end
    
    return true
end

-- =============================================
-- 应用程序快速启动快捷键
-- =============================================

-- 启动All应用
hs.hotkey.bind({"ctrl", "cmd", "shift"}, "f", function()
    launchOrFocusApp("/Applications/All.app", true)
end)

-- 启动终端
hs.hotkey.bind({"ctrl", "cmd", "shift"}, "c", function()
    launchOrFocusApp("/System/Applications/Utilities/Terminal.app", true)
end)

-- =============================================
-- 初始化完成提示
-- =============================================
hs.alert.show("Hammerspoon 配置已加载")

💡 终极提示:Hammerspoon 的威力在于 Lua 脚本的无限可能,从简单的快捷键到复杂的自动化工作流,只有想不到,没有做不到!

整理完毕,完结撒花~🌻


网站公告

今日签到

点亮在社区的每一天
去签到