macOS 连接指定 Wi-Fi 时,自动打开或退出应用

 

2019-11-16

前段时间写了一个批处理,运行后可以在两个 Wi-Fi 之间自动切换,当时虽然也想在 macOS 上实现一个,但没太大需求,便作罢。直到这周突然意识到,回家之后都会把微信和钉钉之类关掉,倒不是因为讨厌,而是开着浪费笔记本电量。所以便想着能不能在切换到指定网络之后,自动退出微信和钉钉。

简单版本

可能因为写批处理导致的惯性思维,导致想到这个需求之后,第一时间是想写一个 Shell 脚本,还特意去搜了有没有比较好的脚本管理工具(其实也没必要)。寻找一般无果后,起身上洗手间才恍然意识有 Hammerspoon 呀。

去官网一看,果然有 hs.wifi 模块,可以获取当前连接的 Wi-Fi 名称。下面是最开始比较简单的需求完成后的代码:

1
local workWifi = "Work"
2
local homeWifi = "Home"
3
4
-- 依据当前网络情况运行或退出应用
5
wifiWatcher = hs.wifi.watcher.new(function()
6
7
-- 获取当前 Wi-Fi 名称
8
local currWifi = hs.wifi.currentNetwork()
9
10
-- 如果当前 Wi-Fi 是 homeWifi,则退出指定应用
11
if currWifi == homeWifi then
12
hs.application.get("微信"):kill()
13
hs.application.get("钉钉"):kill()
14
15
-- 如果当前 Wi-Fi 是 workWifi,则运行指定应用
16
elseif currWifi == workWifi then
17
hs.application.launchOrFocus("WeChat")
18
hs.application.launchOrFocus("DingTalk")
19
end)
20
21
wifiWatcher:start()

是不是很简单,十行代码就实现了最开始的需求:判断当前连接的 Wi-Fi,如果是 Work,则运行微信和钉钉,如果是 Home,则退出微信和钉钉。

不过这其中并没有对这个两个应用是否运行进行判断,特别时第一句 if 语句,即使并未运行微信和钉钉,也会进行 kill 操作,而由于找不到指定进程 Console 便会报错,虽然不影响使用,但报错便意味着代码还不够好。

上面代码中用到的 API:

  • 和 Wi-Fi 相关的
    • hs.wifi.watcher.new():建立一个 Wi-Fi 观察者,在 Wi-Fi 改变时作出响应;
    • hs.wifi.currentNetwork():返回当前连接的 Wi-Fi 名称,未连接时则返回 nil;
  • 执行的操作
    • hs.application.launchOrFocus():启动指定 APP,如果已启动,则切换到前台;
    • hs.application.get():获取正在运行应用,如果未运行则返回 nil;
    • hs.application:kill():结束由 get 获得的应用,强制则是 kill9;

稍微复杂的版本

如果要对当前程序是否运行进行判断,用 if 语句即可:

1
-- 这里拿第一句 if 做示例,增加 if 判断之后
2
if currWifi == homeWifi then
3
4
local getApp = hs.application.get("微信")
5
if getApp then
6
getApp:kill()
7
end
8
9
local getApp = hs.application.get("钉钉")
10
if getApp then
11
getApp:kill()
12
end
13
end

相对与原来的版本,由两行变成了八行,如果对每个语句都进行判断,那就由原来的四行变成了十六行,看着相识的代码,是不是觉得那里不对。下面有一个办法,可以用更少的代码实现相同的功能,而且更优雅:

1
-- 同样拿第一句 if 做示例
2
if currWifi == homeWifi then
3
for app i pairs("微信", "钉钉") do
4
local getApp = hs.application.get(app)
5
if getApp then
6
getApp:kill()
7
end
8
end
9
end

相对与之前的代码,是不是更简洁,而且如果以后想增加 kill 的应用,可以直接在括号中增加(其实还是不方便,还有另一个判断,难不成要加两次?)。

稍微复杂版本的模块化

当今世界,不用函数都不好意思说会写代码,本着能函数则函数的基本原则改写了上面的代码,扩展性更强(不能白白模块化不是)。

下面是完整的代码:

1
local workWifi = "Work"
2
local homeWifi = "Home"
3
4
local appLists = {
5
WeChat = "微信",
6
DingTalk = "钉钉",
7
}
8
9
-- 判断指定 APP 是否运行,如果未运行,则运行
10
function launchApp()
11
for app, process in pairs(appLists) do
12
local getApp = hs.application(process)
13
if not getApp then
14
hs.application.launchOrFocus(app)
15
end
16
end
17
end
18
19
-- 判断指定 APP 是否运行,如果已运行,则退出
20
function killApp()
21
for app, process in pairs(appLists) do
22
local getApp = hs.application.get(process)
23
if getApp then
24
getApp:kill()
25
end
26
end
27
end
28
29
-- 依据当前网络情况启动或关闭应用
30
wifiWatcher = hs.wifi.watcher.new(function()
31
32
local currWifi = hs.wifi.currentNetwork()
33
34
-- 连接 homeWifi 时
35
if currWifi == homeWifi then
36
killApp()
37
38
-- 连接 workWifi 时
39
elseif currWifi == workWifi then
40
launchApp()
41
end
42
end)
43
44
wifiWatcher:start()

相较于之前的版本,主要是把 for 循环中的代码分别单独放到函数中,这样在 if 语句中只要调用这个函数即可,另外是把需要操作的应用放到一个表中,这样以后也便于修改。

其实如果注意观察会发现在最开始的代码中,launchOrFocus 用的是 WeChat,而 get 用的则是中文 微信,至于原因,对比下两个 API 的文档说明就明白了。

最后

上面的代码有一点不甚满意的是,当使用 launchOrFocus 打开应用时,会在前台打开,而不是后台运行,而使用 hide() 则会导致 Console 报错,因为应用并未启动完成。除非说再另外写一个函数去判断,但这样又增加了复杂性,感觉没必要(另外是增加延时操作,同样不怎么可行)。

此外,写这篇笔记的时候,实际代码比文中多了两个个判断,功能是当连接指定 Wi-Fi 时,设定 IP 为静态,而断开这个网络时,重新恢复为自动获取。是不是很酷,不过,下篇再说。

信息

版本

  • macOS:macOS Catalina 10.15.1
  • Hammerspoon:Version 0.9.76 (5104)

参考