hammerspoon mac版是一款简单而强大的macOS自动化工具,它为您提供了一个合适的环境,用于编写Lua代码脚本,用于设置可以使用全局热键组合触发的自动操作。简而言之,Hammerspoon将系统API桥接到一个简单的Lua脚本引擎,这意味着它使您能够编写Lua脚本以自动执行各种自动化任务。
Hammerspoon Mac版安装教程
hammerspoon mac版安装包下载完成后打开,将【Hammerspoon】拖到应用程序。
Hammerspoon Mac最新版软件介绍
Hammerspoon是OS X的桌面自动化工具。它将各种系统级API连接到Lua脚本引擎,通过编写Lua脚本,您可以对系统产生强大的影响。
这很简单的描述。您可以编写与应用程序,Windows,鼠标指针,文件系统对象,音频设备,电池,屏幕,低级键盘/鼠标事件,剪贴板,定位服务,WiFi等等的OS X API进行交互的Lua代码。通常,您将在Lua中编写一个将事件连接到操作的配置文件。您可能希望将键盘快捷键绑定到一系列窗口操作或applescript。当您的WiFi接口连接到家庭网络时,您可能需要运行一系列命令。当您的电池电量低于一定百分比时,您可能希望显示警报。你可能想做一些疯狂的事情,就像iTunes在Mac侦测到你在巴黎时自动开始播放的那样。
Hammerspoon for Mac功能介绍
你好,世界
所有优秀的编程教程都以某种类型的Hello World示例开头,因此我们将使用Hammerspoon绑定键盘热键来演示通过简单通知说出Hello World。
在您的init.lua
位置以下:
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()
hs.alert.show("Hello World!")
end)
然后保存文件,单击Hammerspoon菜单栏图标并选择Reload Config
。您现在应该发现按⌘+ ⌥+ ctrl+ W将在屏幕上显示Hello World通知。
这里发生的是我们告诉Hammerspoon将匿名函数绑定到特定的热键。热键是通过修饰键的表(指定⌘,⌥并且ctrl在这种情况下)和正常键(W)。匿名函数只是一个没有名称的函数。我们可以使用名称单独定义警报功能并将该名称传递给hs.hotkey.bind()
,但是Lua可以很容易地定义内联函数。
发烧友你好世界
虽然hs.alert
很有用,但您可能更喜欢使用OS X本机通知,只需将上一个示例修改为:
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "W", function()
hs.notify.new({title="Hammerspoon", informativeText="Hello World"}):send()
end)
窗户运动简介
使用Hammerspoon可以做的最直接有用的事情之一是操纵屏幕上的窗口。我们将从一个简单的例子开始,并构建一个更复杂的东西。
将以下内容添加到您的init.lua
:
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "H", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.x = f.x - 10
win:setFrame(f)
end)
这将导致⌘+ ⌥+ ctrl+ H使当前聚焦的窗口向左移动10个像素。您可以看到我们获取当前聚焦的窗口然后获取其帧。这描述了窗口的位置和大小。然后我们可以修改框架并使用它将其应用回窗口setFrame()
。
快速结束冒号语法
您可能已经注意到,有时我们在函数调用中使用点,有时我们使用冒号。冒号语法意味着您正在调用该对象的方法之一。它仍然是一个函数调用,但它隐式地将对象作为self
参数传递给方法。
快速关注可变生命周期
Lua使用垃圾收集来清理其内存使用情况 - 它认为不再使用的任何对象将在未来的某个时刻被销毁(确切地说,何时,可能非常不可预测,但它基于您的Lua代码的活跃程度) 。
这意味着一旦函数/循环执行完毕,只存在于函数/ loop / etc中的变量将可用于垃圾收集。这包括您的init.lua
,它被认为是在最后一行代码运行时完成的单个范围。
如果您在自己中创建任何对象init.lua
,则必须在变量中捕获它们,否则将来会在某些时候以静默方式销毁它们。例如:
hs.pathwatcher.new(.....):start()
此处返回的对象(一个hs.pathwatcher
对象)未被捕获,因此只要init.lua完成,它就可用于垃圾收集。它可能不会在几分钟/几小时后被销毁,但是你会感到困惑,为什么你的pathwatcher没有运行。相反,这个版本将继续存在,直到您重新加载配置,或退出Hammerspoon:
myWatcher = hs.pathwatcher.new(.....):start()
该myWatcher
变量是一个全局变量,所以永远不会走出去的范围。
关于变量的生命周期 - 在Console窗口中,每次键入一行并按Enter键时,都会创建,执行和完成不同的Lua范围。这意味着local
当您按Enter键时,在控制台窗口中创建的变量将立即变得不可访问,因为它们的范围已关闭。
更复杂的窗口移动
我们可以在简单的窗口移动示例的基础上构建一组键盘快捷键,允许我们使用nethack
移动键在所有方向上移动窗口:
y k u
h l
b j n
要做到这一点,我们只需要hs.hotkey.bind()
稍微不同的帧修改重复前一个调用:
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "Y", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.x = f.x - 10
f.y = f.y - 10
win:setFrame(f)
end)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "K", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.y = f.y - 10
win:setFrame(f)
end)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "U", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.x = f.x + 10
f.y = f.y - 10
win:setFrame(f)
end)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "H", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.x = f.x - 10
win:setFrame(f)
end)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "L", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.x = f.x + 10
win:setFrame(f)
end)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "B", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.x = f.x - 10
f.y = f.y + 10
win:setFrame(f)
end)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "J", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.y = f.y + 10
win:setFrame(f)
end)
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "N", function()
local win = hs.window.focusedWindow()
local f = win:frame()
f.x = f.x + 10
f.y = f.y + 10
win:setFrame(f)
end)
试试看!
窗口大小
在本节中,我们将实现移动窗口的常见窗口管理功能,使其占据屏幕的左半部分或右半部分,允许您为Productivity™平铺两个彼此相邻的窗口。
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "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)
这里我们将⌘+ ⌥+ ctrl+ ←(如左光标键)绑定到将获取聚焦窗口的函数,然后获取聚焦窗口所在的屏幕,获取屏幕的框架(注意hs.screen.frame()
不包括菜单栏)和停靠,看看hs.screen.fullFrame()
你是否需要)并设置窗口的框架占据屏幕的左半部分。
为了解决这个问题,我们将添加一个函数将窗口移动到屏幕的右半部分:
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "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.layout
扩展名:
local laptopScreen = "Color LCD"
local windowLayout = {
{"Safari", nil, laptopScreen, hs.layout.left50, nil, nil},
{"Mail", nil, laptopScreen, hs.layout.right50, nil, nil},
{"iTunes", "iTunes", laptopScreen, hs.layout.maximized, nil, nil},
{"iTunes", "MiniPlayer", laptopScreen, nil, nil, hs.geometry.rect(0, -48, 400, 48)},
}
hs.layout.apply(windowLayout)
为了解决这个问题,我们首先在Mac上创建一个带有主屏幕名称的变量。您可以:name()
在hs.screen
对象上使用该方法找到这些名称(例如hs.screen.allScreens()[1]:name()
,在Hammerspoon控制台中键入)。
然后,我们创建一个描述所需布局的表。表中的每个条目windowLayout
都是另一个表,它选择我们感兴趣的窗口,并指定它们所需的位置和大小。
表格中的第一项是我们希望影响的应用程序的名称,第二项是我们希望影响的窗口的标题。这些项目中的任何一项都可以nil
,但不能两者兼而有之。如果应用程序名称是,nil
那么我们将匹配所有应用程序中的给定窗口标题。如果窗口标题项是,nil
那么我们将匹配给定应用程序的所有窗口。
第四,第五和第六项用于以不同方式描述匹配窗口的布局。只有这些项目中的一个可具有一个值,并且该值应为包含四个项目,一个表x
,y
,w
和h
(水平位置,垂直位置,宽度和高度,分别地)。
第四项是将给予的矩形hs.window:moveToUnit()
。的x
,y
,w
,和h
该矩形的值,是值之间0.0
并且1.0
,允许你位置的窗口作为显示器的级分,而不必关心显示器的精确分辨率(例如hs.layout.left50
是一个预先定义的矩形{x=0, y=0, w=0.5, h=1}
)。
第五项是一个矩形,它将被赋予hs.window:setFrame()
并应该将位置/大小值指定为屏幕上的像素位置,但不考虑OS菜单栏和底座。
第六个项目类似于第五个项目,但它确实考虑了操作系统菜单栏和停靠。这在上面的示例中显示,它将iTunes Mini Player窗口置于屏幕的最左下方,即使底座在那里也是如此。请注意,我们使用hs.geometry.rect()
辅助函数来构造rect表,并且y
值为负,这意味着窗口的顶部应该在显示屏底部上方48像素处开始。
这可能看起来是一组相当复杂的选项,但是值得花一些时间学习,因为它允许非常强大的窗口布局,特别是对系统事件的反应(例如插入监视器时屏幕数量的变化,或者甚至只需按一个特定的热键即可恢复窗户的健康状况。
窗口过滤器
在某些上下文或应用程序中绑定热键而不是其他热键不是很有用吗?组织窗口并根据位置,大小,工作流程或其任何组合对事件做出反应?极其通用的hs.window.filter
模块允许您使用过滤规则和事件监视器创建复杂的窗口分组和行为。展示该模块功能的最佳方式是通过示例。
将内容从Safari复制并粘贴到Messages.app时,所有链接都在不断扩展,使文本难以阅读:
Thrushes make up the Turdidae, a family of passerine birds that occurs worldwide.
只有Safari / Messages对受此影响,而其他OS X应用程序通常会毫发意外地复制和粘贴。在窗口过滤器的帮助下,这种烦恼得到了彻底的纠正:
local function cleanPasteboard()
local pb = hs.pasteboard.contentTypes()
local contains = hs.fnutils.contains
if contains(pb, "com.apple.webarchive") and contains(pb, "public.rtf") then
hs.pasteboard.setContents(hs.pasteboard.getContents())
end
end
local messagesWindowFilter = hs.window.filter.new(false):setAppFilter('Messages')
messagesWindowFilter:subscribe(hs.window.filter.windowFocused, cleanPasteboard)
cleanPasteboard
在检查粘贴板内容元数据类型后,该函数用纯文本替换粘贴板上的Safari“富文本”。这确保了从Safari复制和粘贴图像仍然有效。false
默认情况下,通过初始化来创建空窗口过滤器以排除所有窗口。添加了消息'appfilter',以便此窗口过滤器仅观察消息窗口。然后我们订阅windowfilter,以便cleanPasteboard
每次Message窗口获得焦点时都会调用它。当某些窗口或应用程序具有焦点时,您可以类似地启用/禁用自定义热键。
Windowfilters是动态的,根据约束集在后台自动过滤。通过使用谓词函数初始化windowfilter,您可以创建任意复杂的过滤规则:
local wf = hs.window.filter.new(function(win)
local fw = hs.window.focusedWindow()
return (
win:isStandard() and
win:application() == fw:application() and
win:screen() == fw:screen()
)
end)
此窗口过滤器包含所有标准(非隐藏,非模态)窗口,这些窗口共享当前聚焦窗口的应用程序和屏幕。窗口过滤器不断更新,以便当前聚焦的窗口确定正在播放的窗口组。这可用于使用hs.window.switcher
或您自己的自定义循环器在当前屏幕上循环使用聚焦的应用程序窗口。
简单的配置重新加载
您可能已经注意到,在编辑配置时,Reload Config
每次进行更改时都必须继续选择菜单项,这有点烦人。我们可以通过添加键盘快捷键来重新加载配置来解决这个问题:
hs.hotkey.bind({"cmd", "alt", "ctrl"}, "R", function()
hs.reload()
end)
hs.alert.show("Config loaded")
我们现在将⌘+ ⌥+ ⌃+ 绑定R到一个函数,该函数将重新加载配置并在屏幕上显示一个简单的警报横幅几秒钟。
这里要提到的一个重要细节是hs.reload()
破坏当前的Lua解释器并创建一个新解释器。如果我们hs.reload()
在此函数后面有任何代码,则不会调用它。
花式配置重新加载
所以我们现在可以手动强制重新加载,但是为什么我们甚至必须在计算机为我们做的时候这样做呢‽
以下代码段引入了另一个新扩展,pathwatcher
它允许我们在文件更改时自动重新加载配置:
function reloadConfig(files)
doReload = false
for _,file in pairs(files) do
if file:sub(-4) == ".lua" then
doReload = true
end
end
if doReload then
hs.reload()
end
end
myWatcher = hs.pathwatcher.new(os.getenv("HOME") .. "/.hammerspoon/", reloadConfig):start()
hs.alert.show("Config loaded")
关于这个例子有几件值得打破的事情。首先,我们使用一个Lua函数来调用系统环境中os.getenv()
的HOME
变量。这将告诉我们您的主目录在哪里。然后我们使用Lua的..
运算符将该字符串连接到我们知道的配置文件路径的/.hammerspoon/
一部分。这为我们提供了Hammerspoon配置目录的完整路径。
然后,我们使用此路径创建一个新的路径观察器,并告诉它reloadConfig
在.hammerspoon
目录中发生更改时调用我们的函数。然后我们立即调用start()
路径观察器对象,因此它开始工作。
在这个例子中,我们将配置重新加载函数实现为一个单独的命名函数,我们将其作为参数传递给hs.pathwatcher.new()
。无论是传递命名函数还是在线使用匿名函数,完全取决于您。
此函数接受单个参数,该参数是包含已修改的文件的所有名称的表。它迭代该列表并检查每个文件以查看它是否以.lua
。如果任何Lua文件已被更改,它会告诉Hammerspoon销毁当前的Lua设置并重新加载其配置文件。
使用Spoons重新加载智能配置
Hammerspoon支持Lua插件,我们称之为“Spoons”。它们允许任何人使用Hammerspoon的API构建有用的功能,然后将其分发给其他人。
由于配置重新加载是许多用户可能想要的东西,因此它是Spoon的理想候选者,并且在此处的官方Spoons存储库中存在一个。
首先,单击Spoon网页上的“下载”链接 - 这应该下载Zip文件并将其解压缩到您的Downloads
文件夹,在该文件夹中将显示一个勺子图标。打开该文件,Hammerspoon将自动导入Spoon ~/.hammerspoon/Spoons/
。
然后将以下内容添加到您的init.lua
完成中:
hs.loadSpoon("ReloadConfiguration")
spoon.ReloadConfiguration:start()
与应用程序菜单交互
有时,自动化某事的唯一方法是与应用程序的GUI交互,这不是理想的,但通常需要完成某些事情。
为了说明这一点,我们将构建一个热键,在多个用户代理字符串之间循环Safari(即它如何向Web服务器标识自己)。要做到这一点,你需要有Safari浏览器Develop
启用菜单,您可以通过勾选做Show Develop menu in menu bar
在Safari→Preferences→Advanced
。
function cycle_safari_agents()
hs.application.launchOrFocus("Safari")
local safari = hs.appfinder.appFromName("Safari")
local str_default = {"Develop", "User Agent", "Default (Automatically Chosen)"}
local str_ie10 = {"Develop", "User Agent", "Internet Explorer 10.0"}
local str_chrome = {"Develop", "User Agent", "Google Chrome — Windows"}
local default = safari:findMenuItem(str_default)
local ie10 = safari:findMenuItem(str_ie10)
local chrome = safari:findMenuItem(str_chrome)
if (default and default["ticked"]) then
safari:selectMenuItem(str_ie10)
hs.alert.show("IE10")
end
if (ie10 and ie10["ticked"]) then
safari:selectMenuItem(str_chrome)
hs.alert.show("Chrome")
end
if (chrome and chrome["ticked"]) then
safari:selectMenuItem(str_default)
hs.alert.show("Safari")
end
end
hs.hotkey.bind({"cmd", "alt", "ctrl"}, '7', cycle_safari_agents)
我们在这里做的是首先启动Safari或将其带到前面(如果它已经运行)。这是任何菜单交互中的重要步骤 - 通常会禁用当前未关注的应用程序的菜单。
然后我们使用了对Safari本身的引用hs.appfinder.appFromName()
。使用此对象,我们可以搜索可用的菜单项并与它们进行交互。具体来说,我们正在寻找三个用户代理字符串的当前状态Develop→User Agent
。然后我们检查它们中的哪一个被勾选,然后选择下一个。
因此,反复按⌘+ ⌥+ ⌃+ 7将在默认用户代理字符串,IE10用户代理和Chrome用户代理之间循环。每次,我们都会显示一个简单的屏幕警报,其中包含我们已循环访问的用户代理的名称。
创建一个简单的菜单栏项目
许多Mac实用程序在系统菜单栏中放置一个小图标来显示其状态并让您与它们进行交互。我们将使用两个Hammerspoon的扩展来为流行的实用程序添加一个非常简单的替代品Caffeine
。
caffeine = hs.menubar.new()
function setCaffeineDisplay(state)
if state then
caffeine:setTitle("AWAKE")
else
caffeine:setTitle("SLEEPY")
end
end
function caffeineClicked()
setCaffeineDisplay(hs.caffeinate.toggle("displayIdle"))
end
if caffeine then
caffeine:setClickCallback(caffeineClicked)
setCaffeineDisplay(hs.caffeinate.get("displayIdle"))
end
此代码段将创建一个菜单栏项,SLEEPY
如果您的计算机在您不使用时允许其***睡眠状态,或者AWAKE
它将拒绝睡眠,则会显示该文本。的hs.caffeine
扩展提供以防止睡眠显示的能力,但hs.menubar
被提供的菜单栏项。
在这种情况下,我们创建菜单栏项并连接回调(在本例中caffeineClicked()
)以单击菜单栏项上的事件。您还可以使用图标而不是文本,方法是在您的菜单栏对象中放置小图像文件~/.hammerspoon/
并使用该:setIcon()
方法。有关此内容hs.menubar
的详细信息,请参阅完整的API文档。
对应用程序事件做出反应
使用hs.application.watcher
回调,我们可以对各种应用程序级事件做出反应,例如正在启动,退出,隐藏和激活的应用程序。
我们可以通过创建一个非常简单的回调来证明这一点,这将确保当您激活Finder应用程序时,它的所有窗口都将被带到显示器的前面。
function applicationWatcher(appName, eventType, appObject)
if (eventType == hs.application.watcher.activated) then
if (appName == "Finder") then
-- Bring all Finder windows forward when one gets activated
appObject:selectMenuItem({"Window", "Bring All to Front"})
end
end
end
appWatcher = hs.application.watcher.new(applicationWatcher)
appWatcher:start()
首先,我们定义一个接受三个参数的回调函数,并在其中检查触发该函数的事件类型是否是一个被激活的应用程序。然后我们检查被激活的应用程序是否是Finder。如果是,我们选择其菜单项将其所有窗口都放在前面。
然后,我们创建一个应用程序观察器对象,它将调用我们的函数,并告诉它启动。
请注意,我们保留了对watcher对象的引用,而不是简单地调用hs.application.watcher.new(applicationWatcher):start()
。这样做的原因是,:stop()
如果我们需要,我们可以稍后调用观察者(例如,在重新加载我们的配置的函数中 - 有关如何自动重新加载Hammerspoon配置的信息,请参阅Fancy Config Reloading示例)。
对wifi事件做出反应
如果你使用MacBook,那么你家里可能有一个WiFi网络。Hammerspoon在您到家并加入您的WiFi网络,或离家出走并离开网络时触发事件非常简单。在这种情况下,我们将做一些简单的事情并调整MacBook的音量,使其在您离家时为零(保护您免受在咖啡店打开MacBook并肆无忌惮地播放音乐的耻辱感)在家里玩!)
wifiWatcher = nil
homeSSID = "MyHomeNetwork"
lastSSID = hs.wifi.currentNetwork()
function ssidChangedCallback()
newSSID = hs.wifi.currentNetwork()
if newSSID == homeSSID and lastSSID ~= homeSSID then
-- We just joined our home WiFi network
hs.audiodevice.defaultOutputDevice():setVolume(25)
elseif newSSID ~= homeSSID and lastSSID == homeSSID then
-- We just departed our home WiFi network
hs.audiodevice.defaultOutputDevice():setVolume(0)
end
lastSSID = newSSID
end
wifiWatcher = hs.wifi.watcher.new(ssidChangedCallback)
wifiWatcher:start()
在这里,我们创建了一个回调函数,将当前WiFi网络的名称与之前网络的名称进行比较,并检查我们是否已从预定义的家庭网络迁移到其他地方,反之亦然,然后用于hs.audiodevice
调整系统容量。
对USB事件做出反应
如果你有一件你希望能够做出反应的USB硬件,那么它就是你hs.usb.watcher
的扩展。在下面的示例中,我们将在插入扫描仪时自动启动扫描仪软件,然后在拔下扫描仪时终止软件。
usbWatcher = nil
function usbDeviceCallback(data)
if (data["productName"] == "ScanSnap S1300i") then
if (data["eventType"] == "added") then
hs.application.launchOrFocus("ScanSnap Manager")
elseif (data["eventType"] == "removed") then
app = hs.appfinder.appFromName("ScanSnap Manager")
app:kill()
end
end
end
usbWatcher = hs.usb.watcher.new(usbDeviceCallback)
usbWatcher:start()
击败粘贴阻塞
您可能已经注意到某些程序和网站非常努力阻止您粘贴密码。他们似乎认为它使它们更安全,但在高度加密的密码管理器时代,这当然是无稽之谈。
幸运的是,我们可以通过发出虚假键盘事件来键入剪贴板内容来绕过它们的损坏:
hs.hotkey.bind({"cmd", "alt"}, "V", function() hs.eventtap.keyStrokes(hs.pasteboard.getContents()) end)
运行AppleScript
有时您需要的自动化被锁定在一个应用程序中,这似乎是不可能从Hammerspoon控制的,除了许多应用程序通过AppleScript公开它们的功能,Hammerspoon可以为您执行:
ok,result = hs.applescript('tell Application "iTunes" to artist of the current track as string')
hs.alert.show(result)
这将要求iTunes询问当前正在播放的曲目的艺术家,然后使用在屏幕上显示该曲目hs.alert
。
但是,在您急于开始编写大量与iTunes相关的AppleScript之前,请查看本指南中的下一个条目。
控制iTunes / Spotify
使用hs.itunes
和hs.spotify
我们可以查询/控制iTunes和Spotify的各个方面,例如,如果您需要在一个应用程序和另一个应用程序之间切换:
hs.itunes.pause()
hs.spotify.play()
hs.spotify.displayCurrentTrack()
在屏幕上绘图
有时你找不到你的鼠标指针。你确定你把它留在了某个地方,但它隐藏在你的一个显示器上,摆动鼠标并没有帮助你发现它。幸运的是,我们可以查询和控制鼠标指针,我们可以在屏幕上绘制内容,这意味着我们可以这样做:
mouseCircle = nil
mouseCircleTimer = nil
function mouseHighlight()
-- Delete an existing highlight if it exists
if mouseCircle then
mouseCircle:delete()
if mouseCircleTimer then
mouseCircleTimer:stop()
end
end
-- Get the current co-ordinates of the mouse pointer
mousepoint = hs.mouse.getAbsolutePosition()
-- Prepare a big red circle around the mouse pointer
mouseCircle = hs.drawing.circle(hs.geometry.rect(mousepoint.x-40, mousepoint.y-40, 80, 80))
mouseCircle:setStrokeColor({["red"]=1,["blue"]=0,["green"]=0,["alpha"]=1})
mouseCircle:setFill(false)
mouseCircle:setStrokeWidth(5)
mouseCircle:show()
-- Set a timer to delete the circle after 3 seconds
mouseCircleTimer = hs.timer.doAfter(3, function() mouseCircle:delete() end)
end
hs.hotkey.bind({"cmd","alt","shift"}, "D", mouseHighlight)
目前支持几种不同类型的绘图对象 - 线条,圆形,方框,文本和图像。不同的图纸类型可以具有不同的属性,这些属性都在API文档中完整记录。
绘图对象可以放在所有其他窗口的顶部,也可以放在桌面图标后面 - 这使得它们可以用于在屏幕顶部显示上下文叠加层(例如鼠标查找示例),并在所有窗口后面显示更多永久性信息(例如人们使用GeekTool的各种状态信息。
发送iMessage / SMS消息
coffeeShopWifi = "Baristartisan_Guest"
lastSSID = hs.wifi.currentNetwork()
wifiWatcher = nil
function ssidChanged()
newSSID = hs.wifi.currentNetwork()
if newSSID == coffeeShopWifi and lastSSID ~= coffeeShopWifi then
-- We have arrived at the coffee shop
hs.messages.iMessage("iphonefriend@hipstermail.com", "Hey! I'm at Baristartisan's, come join me!")
hs.messages.SMS("+1234567890", "Hey, you don't have an iPhone, but you should still come for a coffee")
end
end
wifiWatcher = hs.wifi.watcher.new(ssidChanged)
wifiWatcher:start()
正如您无疑注意到的,只要您的Mac到达您最喜爱的时尚咖啡店,这将向人们发送两条消息。您需要配置OS X的消息应用程序,并且可以发送iMessage和SMS(后者通过使用SMS Relay的iPhone)来实现此功能。
使用URL自动化Hammerspoon
有时您需要自动化自动化工具,而Hammerspoon可以通过多种方式实现自动化。我们在这里介绍的第一种方式是使用URL。特别是,以hammerspoon://
。开头的网址。鉴于这个简单的片段:
hs.urlevent.bind("someAlert", function(eventName, params)
hs.alert.show("Received someAlert")
end)
我们现在为一个名为的事件绑定了一个URL事件处理程序,该事件someAlert
将显示一些屏幕上的文本警报。要在终端中触发此事件,请运行open -g hammerspoon://someAlert
。许多应用程序都能够打开URL,因此这成为自动化Hammerspoon采取某些行动的一种非常简单的方法。有关此更具体(和复杂)的示例,请参阅下一节。请注意,该-g
选项open
会导致URL在后台打开,以避免打开Hammerspoon的控制台窗口,或者让它为键盘焦点。
具有Karabiner和URL的Hammerspoon的高级自动化
- 注意:Karabiner目前不在macOS Sierra中工作。它的作者正在研究一个新版本。
在我们的第一个例子中,我们曾经hs.hotkey
将键盘快捷键绑定到Lua函数,这是Hammerspoon可以做的最有用的事情之一。但是,这些热键是在OS X中使用Carbon API执行的,这在理解键盘事件方面是相当高的 - 例如,它无法区分⌘按下哪个键(因为有两个,一个在键盘左侧,右侧一个),也不能绑定涉及Fn键的热键。
一个能够以非常低的水平理解这些键盘事件的应用程序是Karabiner,它连接到OS X内核以读取来自键盘的原始事件。通常它用于将这些键事件重新映射到其他键事件(即更改键盘上键的行为),但是,它也可以将低级键事件转换为更高级别的系统操作,例如执行终端命令,或者打开一个URL。
因此,我们可以结合Karabiner和Hammerspoon来执行一些非常强大和灵活的热键绑定。在这个例子中,我们将把一些键盘修饰符从键盘的右侧绑定到Hammerspoon中的Lua函数。
首先,安装Karabiner并打开其配置应用程序在Misc & Uninstall
选项卡中,单击Open private.xml
。这将显示一个Finder窗口,显示一个名为的文件private.xml
。右键单击该文件并选择Open With → TextEdit.app
。您现在将看到TextEdit打开,显示XML文档。如果您之前从未使用过Karabiner,它将包含:
在该部分中,添加以下内容:
KeyCode::VK_OPEN_URL_HS_test1
hammerspoon://test1?someParam=hello
Hammerspoon test1
hammerspoon.test1
--KeyToKey--
KeyCode::CURSOR_RIGHT, ModifierFlag::CO***AND_R | ModifierFlag::OPTION_R,
KeyCode::VK_OPEN_URL_HS_test1
打破这一点,我们首先使用该部分为Karabiner定义一个URL 。这里需要注意的是,属性必须以KeyCode::VK_OPEN_URL
并且不应包含任何空格开头。该选项意味着Karabiner将在不激活Hammerspoon的情况下打开URL。几乎没有理由想要将焦点转移到Hammerspoon,因此建议您始终使用此选项。
将URL定义为虚拟键码后,我们告诉Karabiner听右手⌘,右手⌥和→光标键。如果按下这些键,它将打开URL hammerspoon://test1?someParam=hello
。
保存private.xml
文档并关闭TextEdit。现在,在Karabiner中,切换回Change Key
选项卡并单击Reload XML
。假设您没有收到任何错误,请勾选旁边的框Hammerspoon test1
并配置重新映射。
在您的Hammerspoon中init.lua
,添加以下内容:
hs.urlevent.bind("test1", function(eventName, params)
if params["someParam"] then
hs.alert.show(params["someParam"])
end
end)
你现在在Hammerspoon中有一个Lua函数,如果按下right ⌘+ right ⌥+ ,它将被触发→。
Hammerspoon Mac最新版功能演示
我都用 Hammerspoon 做了些什么?
- 菜单栏显示最近几天天气情况
- 请求免费的天气 API,在深圳这个多雨的城市里提醒我别忘记带伞。
- 剪切板历史记录
- 记录剪贴板历史,点击某一项再重新复制。
- 音量调节快捷键
- 当我使用外接键盘时,自定义快捷键
cmd + up/down
调节系统音量
- 当我使用外接键盘时,自定义快捷键
- 窗口管理
- 快捷键实现二分屏、三分屏和全屏
- Wi-Fi 自动脚本
- 根据 Wi-Fi SSID 判断是否在公司还是在家,例如在家里自动挂载 NAS 服务器,如果在公司 sshfs 挂载服务器目录等。
- 蓝牙耳机自动连接
- 电脑锁屏时,自动断开连接的蓝牙耳机,参考我的另一篇介绍。
- 输入法自动切换
- 在每个 App 界面自动切换成搜狗输入法,配合搜狗输入法自动中英文切换,再也不用在系统默认的英文输入法和搜狗输入法之间来回切换了。
- 定时自动提交代码
- 使用
hs.timer
定时器,定时自动推送我的笔记和下载的电子书到 Github 仓库。
- 使用
- USB 设备连接记录
- 记录插上你电脑的每一个 USB 设备信息,凡插过必留记录。
- 消息推送
- 推送任意消息提醒
- TTS 发声
- 调用
say hello world
合成 TTS,模拟真人发音,让 Mac 会说话。
- 调用
- 更多... (完全停不下来啊)
其他插件思路
- 番茄钟
- 应用搜索
- 桌面小部件
- ...