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

简单版本

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

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

local workWifi = "Work"
local homeWifi = "Home"

-- 依据当前网络情况运行或退出应用
wifiWatcher = hs.wifi.watcher.new(function()

    -- 获取当前 Wi-Fi 名称
    local currWifi = hs.wifi.currentNetwork()

    -- 如果当前 Wi-Fi 是 homeWifi,则退出指定应用
    if currWifi == homeWifi then
        hs.application.get("微信"):kill()
        hs.application.get("钉钉"):kill()

    -- 如果当前 Wi-Fi 是 workWifi,则运行指定应用
    elseif currWifi == workWifi then
        hs.application.launchOrFocus("WeChat")
        hs.application.launchOrFocus("DingTalk")
end)

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 语句即可:

-- 这里拿第一句 if 做示例,增加 if 判断之后
if currWifi == homeWifi then

    local getApp = hs.application.get("微信")
    if getApp then
        getApp:kill()
    end

    local getApp = hs.application.get("钉钉")
    if getApp then
        getApp:kill()
    end
end

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

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

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

稍微复杂版本的模块化

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

下面是完整的代码:

local workWifi = "Work"
local homeWifi = "Home"

local appLists = {
    WeChat = "微信",
    DingTalk = "钉钉",
}

-- 判断指定 APP 是否运行,如果未运行,则运行
function launchApp()
    for app, process in pairs(appLists) do
        local getApp = hs.application(process)
        if not getApp then
            hs.application.launchOrFocus(app)
        end
    end
end

-- 判断指定 APP 是否运行,如果已运行,则退出
function killApp()
    for app, process in pairs(appLists) do
        local getApp = hs.application.get(process)
        if getApp then
            getApp:kill()
        end
    end
end

-- 依据当前网络情况启动或关闭应用
wifiWatcher = hs.wifi.watcher.new(function()

    local currWifi = hs.wifi.currentNetwork()

    -- 连接 homeWifi 时
    if currWifi == homeWifi then
        killApp()

    -- 连接 workWifi 时
    elseif currWifi == workWifi then
        launchApp()
    end
end)

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)

参考