最近お外で作業する機会が増えて、MacBookを開く度にメニューバーからテザリングを開始するのが億劫だと感じるようになった。本当は単体でモバイル通信ができるMacBookがあれば良いのだけど、今のところそういうモデルはないので、自分が手動でやっていることを自動化することにした。この手順は「スリープ解除のイベントを捕まえる」と「Bluetoothのメニューバーを操作する」の2つに分解できる。少し調べてみると、前者はSleepWatcherで、後者はAppleScriptで実現できることがわかった。

SleepWatcherは今日まで全然知らなかったけど、歴史が長いらしい。ドキュメントではMac OS X 10.1についても言及されているので、2000年くらいから開発されていてるみたい。そして、macOS Majave(10.14)の今でもきちんと動く。こういうプロダクトを開発できる人間になりたい。

スリープ解除のイベントを捕まえる

SleepWatcherはHomebrewでインストールできる。

brew install sleepwatcher

これで完了と思って早速実行してみると、command not found: sleepwatcherとなった。Homebrewのformulaによると/usr/local/sbinにインストールするようだが、macOS Mojaveから/usr/local/sbinは存在しないらしい。で、brew doctorに聞いてみると以下のように言っていた。

Warning: The following directories do not exist:
/usr/local/sbin

You should create these directories and change their ownership to your account.
  sudo mkdir -p /usr/local/sbin
  sudo chown -R $(whoami) /usr/local/sbin

これに素直に従って再度リンクすると、無事に使えるようになった。

sudo mkdir -p /usr/local/sbin
sudo chown -R $(whoami) /usr/local/sbin
brew link sleepwatcher

SleepWatcherはMacのイベントを監視して、イベントが来たら実行ファイルを実行するだけのコマンドラインツールだ。これをMacに常駐させるには、LaunchAgentsとして登録する必要がある。ReadMe.rtfには細かい説明が色々と書かれているが、brew info sleepwatcherとの話を総合すると、要は単一ユーザーとして使う分には以下のコマンドを実行すれば良さそう。

mkdir -p ~/Library/LaunchAgents
cp /usr/local/opt/sleepwatcher/de.bernhard-baehr.sleepwatcher-20compatibility-localuser.plist ~/Library/LaunchAgents/de.bernhard-baehr.sleepwatcher.plist

つまり、LaunchAgents用に書かれた設定ファイルの例を実際に使う場所にコピーするだけ。設定ファイルの中身は次のようになっていて、スリープに入るときに~/.sleepの実行ファイルを、スリープから復帰するときに~/.wakeupの実行ファイルを実行する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>de.bernhard-baehr.sleepwatcher</string>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/local/sbin/sleepwatcher</string>
		<string>-V</string>
		<string>-s ~/.sleep</string>
		<string>-w ~/.wakeup</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>KeepAlive</key>
	<true/>
</dict>
</plist>

SleepWatcherに実行されるファイルをつくっておく。

touch ~/.sleep ~/.wakeup
chmod +x ~/.sleep ~/.wakeup

これでスリープ解除のイベントを捕まえられるようになった。

Bluetoothのメニューバーを操作する

下記のブログを参考に。

Bluetoothのメニューバーの操作スクリプトを~/bin/connectBluetoothTetheringにつくる。

mkdir -p ~/bin/
touch ~/bin/connectBluetoothTethering
chmod +x ~/bin/connectBluetoothTethering

~/bin/connectBluetoothTetheringの内容は以下の通り。元記事でも説明されているが、”ishkawa iPhone”はiPhoneの端末につけた名前、”ネットワークへ接続”はMacの言語設定によって異なる。

#!/usr/bin/osascript

tell application "System Events"
    tell application process "SystemUIServer"
        tell menu bar 1
            set bt to item 1 of (menu bar items whose description is "bluetooth")
        end tell
        tell bt
            click it
            click menu item "ishkawa iPhone" of menu 1
            click menu item "ネットワークへ接続" of menu 1 of menu item "ishkawa iPhone" of menu 1
        end tell
    end tell
end tell

以下のように実行してみると、正しく動作するか確認できる。

~/bin/connectBluetoothTethering

また、あとでショートカットキーでテザリングの開始/終了をしたいので、~/bin/connectBluetoothTetheringの”ネットワークへ接続”の部分を”ネットワークから接続解除”に置き換えたスクリプトを~/bin/disconnectBluetoothTetheringにつくる。

touch ~/bin/disconnectBluetoothTethering
chmod +x ~/bin/disconnectBluetoothTethering

これで、スクリプトからテザリングの開始/終了ができるようになった。元の目標だった「MacBookを開いた時に自動的にテザリングを開始する」は、~/.wakeupを次のように書き換えれば完了する。

~/bin/connectBluetoothTethering

動作の流れとしては、まずMacの起動時にlaunchd~/Library/LaunchAgentsを読み込み、SleepWatcherを起動する。するとSleepWatcherはMacのイベントの監視を開始し、スリープ解除が起きるたびに~/.wakeupを実行する。そして、~/.wakeup~/bin/connectBluetoothTetheringを実行し、Bluetoothのメニューバーが操作されてテザリングが有効となる。

Wi-Fiが使えるときはWi-Fiを優先する

今の契約の月のデータ通信量は50GBなので、モバイル通信しか使えない環境では常にテザリングで通信をしてもらって構わない。しかし、Wi-Fiが使える時はWi-Fiを使った方が速いし、Xcodeのようなデカいファイルも無限にダウンロードできて良い。「システム環境設定」→「ネットワーク設定」で「Wi-Fi」を「Bluetooth PAN」よりも上にしておくと、Wi-Fiが優先して使われるようになる。もし「Wi-Fi」の方が下に来ていたら、歯車ボタン→「サービスの順序を設定…」から優先順位を変えられる。

テザリングの開始/終了にショートカットキーを割り当てる

たまにテザリングを控えてほしいと言われる場面があるので、そのときに簡単にテザリングを開始/終了できるようにしておきたい。普段から使っているKarabiner-Elementsに、ショートカットキーにシェルコマンドを割り当てる機能があったので、これを使うことにした。自分はF3キーのMission ControlとF4キーのLaunchpadを使うことがなかったので、ここにテザリングの開始と終了を割り当てる。Karabiner-Elementsの設定ファイルは~/.config/karabiner/karabiner.jsonにあり、このJSONの中のcomplex_modificationsrulesの中に次の2つのオブジェクトを追加すると、割り当てが有効になる。

{
  "description": "F3でconnectBluetoothTetheringを実行",
  "manipulators": [
    {
      "from": {"key_code": "f3"},
      "to": [
        {"shell_command": "~/bin/connectBluetoothTethering"}
      ],
      "type": "basic"
    }
  ]
}
{
  "description": "F4でdisconnectBluetoothTetheringを実行",
  "manipulators": [
    {
      "from": {"key_code": "f4"},
      "to": [
        {"shell_command": "~/bin/disconnectBluetoothTethering"}
      ],
      "type": "basic"
    }
  ]
}