TerminalからfzfでBluetooth機器を選択して接続できると幸せになれる

f:id:rasukarusan:20200610212400p:plain:w500
TerminalからBluetoothの接続をする

MacでBluetoothのデバイスを接続するとき、ステータスバーからデバイスを選択しているやつおる?
エンジニアならTeminalからBluetoothデバイスを選択して接続しないといけない

AirPodsの接続をTerminalからやりたい

AirPodsは最後に接続された機器のみ自動で接続してくれる。なのでiPhoneで接続→接続解除→Macで接続、とするとMacでは接続ボタンを再度押さないといけない。

f:id:rasukarusan:20200610184825p:plain
ステータスバーから一々接続ボタンを押す必要がある

結構この作業をやることが多いので、Terminalから接続のON/OFFをできるようにする。

AppleScript in ShellScriptで実現

ShellScript内にAppleScriptを埋め込む形にして、fzfでデバイスを選択できようにした。

https://user-images.githubusercontent.com/17779386/84265271-8e8af100-ab5d-11ea-972b-0653090f6852.gif
fzfでデバイスを選択して接続。previewにはなんとなくデバイスの情報を出している。

github.com

ちゃちゃっと試したい人は以下

$ brew tap Rasukarusan/tap
$ brew install Rasukarusan/tap/fzf-bluetooth-connect
$ bluetooth-fzf

AppleScriptでframework読み込めるの知らんかった

実はAppleScript内ではuse framework "フレームワーク名"とすることでCocoaの機能が使える。
今回はBluetooth関連をいじりたかったのでuse framework "IOBluetooth"を使っている。

pairedDevices() {
osascript << EOF
    use framework "IOBluetooth"
    use scripting additions
    ...
EOF

SwiftやObjective-Cとは若干使い方が異なるが、ほとんど直感的にいける。
例えばBluetoothのペアリング済みのデバイスを取得したいときは下記のような感じ。
(IOBluetoothのpairedDevices()を使用)

pairedDevices() {
osascript << EOF
    use framework "IOBluetooth"
    use scripting additions
    current application's IOBluetoothDevice's pairedDevices() as list
EOF
}

# 実行すると下記のような出力が得られる
# «class ocid» id «data optr0000000070F2E86AB97F0000», «class ocid» id «data optr000000002006E96AB97F0000»

application's IOBluetoothDevice's pairedDevices()のように〇〇'sで繋いでいくだけでOK。

Bluetoothの接続情報はコマンドでも取得可能

今回はペアリング済みのデバイスを取得する際にAppleScriptを使っているが、下記コマンドでも取得できる。

$ system_profiler SPBluetoothDataType -json

出力結果

{
  "SPBluetoothDataType" : [
    {
      "apple_bluetooth_version" : "7.0.5f6",
      "device_title" : [
        {
          "tnk’s AirPods Pro" : {
            "device_addr" : "B4-40-A4-B2-79-EE",
            "device_classOfDevice" : "0x04 0x06 0x240418",
            "device_core_spec" : "5.0",
            "device_fw_version" : "2<200b>D<200b>15",
            "device_isconfigured" : "attrib_Yes",
            "device_isconnected" : "attrib_No",
            "device_ispaired" : "attrib_Yes",
            "device_majorClassOfDevice_string" : "Audio",
            "device_manufacturer" : "Apple (0x9, 0x7D31)",
            "device_minorClassOfDevice_string" : "Headphones",
            "device_productID" : "0x200E",
            "device_services" : "AAP Server, Audio Sink, AVRCP Controller, Handsfree, AVRCP Target",
            "device_supportsEDR" : "attrib_Yes",
            "device_supportsESCO" : "attrib_Yes",
            "device_supportsSSP" : "attrib_Yes",
            "device_vendorID" : "0x004C"
        },
        {
          "QY8" : {
            "device_addr" : "1C-52-16-06-A0-F1",
            "device_classOfDevice" : "0x04 0x01 0x240404",
            "device_core_spec" : "4.0",
            "device_isconfigured" : "attrib_Yes",
            "device_isconnected" : "attrib_No",
            "device_ispaired" : "attrib_Yes",
            "device_majorClassOfDevice_string" : "Audio",
            "device_manufacturer" : "Cambridge Silicon Radio (0x6, 0x21C8)",
            "device_minorClassOfDevice_string" : "Headset",
            "device_services" : "Headset, Hands-Free unit, CSR GAIA邃「",
            "device_supportsEDR" : "attrib_Yes",
            "device_supportsESCO" : "attrib_Yes",
            "device_supportsSSP" : "attrib_Yes"
          }
        },
....

jsonで吐き出すことができるので、あとはjqでイージーですね。

system_profilerで取得できる情報はSPBluetoothDataTypeの他にもあり、system_profiler -listDataTypesで一覧が出る。

終わり

AppleScript内でuse framework "xxx"ができるならもはや何でもありで、GUIのアプリも作れてしまう。
とはいえGUIアプリ作るなら素直にSwiftとかで書いたほうが絶対楽だと思うので、AppleScriptでやる意味はないと思う。いちいちAppleScriptの記法に変換するのはだるいしXcodeの補完も使えないしね。
Shellの可能性は広がったので知れてよかった。