macOS の Bluetooth 設定をコマンドラインから変更する

# TL;DR
2019-08-27 追記
本体のBluetoothをOffにしてしまってマウスキーボードが使えなくなってハマった、詰んだ人が手っ取り早く助かる為の方法

- 本体の外部接続機器を全部外してからリブートする(リブートできる場合)
- 百均やコンビニなどで優先接続のマウス(可能ならキーボードも)だけでも買ってきて繋ぐ

これだけでもBluetoothをOnにして復旧できると思いますが、別のMacやPCからリモートログイン(ssh)できる場合には、リブートせず、買い物にも行かずに復旧する方法があります。別のMac/PCでターミナルを開いてBluetoothがOffになっているMacにログイン後、以下のコマンドを入力する事で復旧できます。

```shell
$ sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 1
# bluetooth デーモンの再起動
$ sudo launchctl stop com.apple.bluetoothd # or blued based on macOS version
$ sudo launchctl start com.apple.bluetoothd
```

# 背景
iMac など、本体にキーボードやマウスが付属していないタイプの Mac で、普段は Bluetooth キーボードとマウスで利用している。
しかし誤って本体側の Bluetooth を切る、という操作をしてしまったためにマウスとキーボードが使えなくなってしまった。

幸い ssh は出来る状態だったため、リモートの(別の)Mac から ssh 接続で復旧する方法を模索した。

という実際にあった出来事が背景にあるものの、普通にキーボードでコマンドライン上から Bluetooth の設定を変更できる Blueutil というコマンドやその他の方法での設定方法の紹介です。

# コマンドライン(CLI)で復旧する方法
上のような特殊な状況に陥った場合でも、以下の[おまけ-その他の復旧方法](#おまけ-その他の復旧方法)にあるように手元に有線接続のマウスを用意するか、システムをリブートするだけでも解決できます。

しかし、有線マウスが用意できず、リブートも出来ない(したくない)状況というのはあり得ます。
そのようなケースでは、やはり別の Mac or PC から以下の手順で Bluetooth 設定を ON にする事で、マウスとキーボードを復旧させる事が可能です。

## m

1. ssh でターゲットの Mac にログイン
2. curl (HomeBrew) で m-cli をインストール
3. m で Bluetooth 設定を変更

```shell-session
$ curl -fsSL https://raw.githubusercontent.com/rgcr/m-cli/master/install.sh | sudo sh
$ m bluetooth on # to power on
$ m bluetooth off # to power off
```


## blueutil

1. ssh でターゲットの Mac にログイン
2. HomeBrew で blueutil をインストール
3. blueutil で Bluetooth 設定を変更

```shell-session

$ brew install blueutil
$ blueutil --power 1 # to power on
$ blueutil --power 0 # to power off
```

## おまけ-その他の復旧方法
今回は ssh でコマンドラインからの復旧の例を示しましたが、VNC(画面共有)でも問題を解決する事が可能です(事前に画面共有が有効にしてあれば、ですが)。
しかし、VNC(画面共有)も ssh も出来ないケースでも助かる方法は当然ながら(?)ありますので、その方法も紹介しておきます。

- 有線マウスやキーボードを繋ぐ
手元に有線接続のマウスかキーボードがあればそれをつなぐだけで救われるはずです。今時コンビニや百均でもマウスくらいは売っていそうなので、とりあえずマウスだけでも用意できればどうにかなります。

- 再起動/リブート
しかし有線マウスもキーボードも用意できないケースでは、本体の外部接続機器を全部外してからリブートする、という手順を踏む事で復旧できます。リブートが許される状況であればこれが最も簡単ですね。

## おまけ2 (OS の設定項目をいじる)
Blueutil を使う以外にも bluetoothd の設定をいじる事でも目的が達成できるようです。
しかし、以下の参考にある情報は古く、一部(blued -> bluetoothd)変更の必要がある点、El Captain 以降に導入された System Integrity Protection(SIP)が有効な状態では実行できない点、などを考慮すると Blueutil を使う方が良いと思います。

```shell
$ sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 1

$ sudo launchctl unload /System/Library/LaunchDaemons/com.apple.bluetoothd.plist
$ sudo launchctl load /System/Library/LaunchDaemons/com.apple.bluetoothd.plist

# El Capitan 以前では com.apple.blued.plist に変更する
```
## おまけ3 (OS の設定項目をいじる、リベンジ編)
2018-07-18 追記
その後の調査で、おまけ2 の方針で設定を変更できる事が分かりました。
SIP の影響で /System/Library/LaunchDaemons 以下のデーモンプロセスの unload は出来ないようですが、stop/start はできますし、結果的に同様の事を行うプロセスに HUP シグナルを送る事で変更した設定をプロセスに再読み込みさせる事が出来ます。これによって目的が達成できますね。

```shell
# Bluetooth を ON にする
# まずは設定値を変える
$ sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 1
# bluetooth デーモンの再起動
$ sudo launchctl stop com.apple.bluetoothd # or blued based on macOS version
$ sudo launchctl start com.apple.bluetoothd

# 上の launchctl 2 行の代わりに以下の 1 行で、デーモンに設定を再読み込みさせる事もできる
$ sudo killall -HUP bluetoothd

# proctools が入っている環境であれば `killall` -> `pkill` でも可。

# Bluetooth を OFF にする場合
$ sudo defaults write /Library/Preferences/com.apple.Bluetooth ControllerPowerState -int 0
$ sudo killall -HUP bluetoothd
```

## おまけ4 (趣味の世界、オートメーション, JXA)
ここから先はかなり特殊なやり方になってきますが、RPA(Robotics Process Automation)が普及を始めている時代でもあり、macOS のオートメーションとして AppleScript や JXA も利用できることから、それらの方法を使ってオートメーション/ワークフロースクリプトを実行する、という方法も無くは無いですね。

この方法は、GUIのアプリケーションをオートメーションで操作する、というアプローチなので、コマンドラインのターミナルから実行したとしても、実際はあくまで GUI のアプリケーションやプログラムを介して設定を変更している事に注意してください。

※ もちろん、スクリプトの書き方次第では GUI は無関係に設定を変更できる場合もあると思いますが、今回の Bluetooth の ON/OFF の設定変更に限定すると、Apple の公開 API が存在しない為に難しいのでは無いかと思います。

さて、JXA によるオートメーションを実現する場合、以下のようにシステムUI のメニューバーから設定を切り替える方法と、システム環境設定の Bluetooth から切り替える方法とがあります。
※ High Sierra(10.13.5) 日本語環境で動作確認をしています。

```Javascript:bt-jxa.js
// お約束的な部分
se = Application("System Events");
sp = Application("System Preferences");
app = Application.currentApplication();
app.includeStandardAdditions = true;

// ここからメニューバーの Bluetooth 項目をクリックする操作をオートメーションで行う
// 以下の 7 という数字はユーザーのメニューバー環境毎に変わり得る数値の為、実際に動作させるにはメニューバーの Bluetooth アイコンが表示されている順番に置き換えてください
se.processes["SystemUIServer"].menuBars[0].menuBarItems[7].click() ; delay(0.2); se.processes["SystemUIServer"].menuBars[0].menuBarItems[7].menus[0].menuItems[1].click()

// システム環境設定からの場合
// オンにする場合
se.processes["System Preferences"].windows["Bluetooth"].buttons['Bluetoothをオンにする'].click()
// オフにする場合
se.processes["System Preferences"].windows["Bluetooth"].buttons['Bluetoothをオフにする'].click()

```
Mac の言語設定や、システムUI のメニューバーに表示しているプログラムの数など、自身の環境に合わせて若干の修正は必要ですが、上記のようなスクリプトを準備して、以下のように実行する事ができます。

```shell-session
$ osascript -l JavaScript bt-jxa.js
```

この方法であえてやりたい、やる必要がある場面というのは限られてくると思いますが、一つ大きな利点があるとすれば sudo をしないで良い点(アクセシビリティの許可さえしてあれば、別途管理者権限が要らない点)でしょうか。もしこの方法を使用する場合は以下のような注意事項がありますのでご留意ください。

- アクセシビリティの機能を使う為、実行環境(オートメーター、スクリプトエディタ、ターミナルなど)に対してアクセシビリティの使用を事前、或いは実行時に許可しなくてはいけないこと
- 上記のコードはシステムUIのメニューバーに表示されている項目の数や、UI の表示ラベルに依存している為、もう少し真面目にループやターゲットの判定処理を書き足さないと、OS のバージョンや言語設定、ユーザーの環境などの変更によって正しく動作しなくなる可能性がある


# 追記
Blueutil は便利ですが、IOBluetooth.framework のプライベート API を使用している為、macOS のバージョンアップに伴っていつか利用できなくなる可能性があります。
※ 2018/7 現在の macOS High Sierra(10.13.5) での動作は確認できています。

その為、総合的に判断すると、おまけ3 の方法が macOS やデーモン処理のお作法に乗っ取っていると言え、上記の中では最も正解に近いやり方なのでは無いかと思います。

また、内部的には上記おまけ3と同じ事を行う[**m**](https://github.com/rgcr/m-cli) という Bluetooth に限定しない便利ツールが存在する事が分かりました。
Bluetooth 設定に限っては、現在の macOS (Sierra 以降)には対応していなかった為、これを修正し Github 上で PR を送ってあります。無事に PR が取り込まれれば、こちらを使うのもありかと思います。(2018-08-03 に取り込まれました)


# Reference
- BlueUtil (GitHub): https://github.com/toy/blueutil
- m Swiss Army Knife for macOS !: https://github.com/rgcr/m-cli
    - Sierra 以降に対応していない為、修正したものの取り込み依頼中
- 画面共有(Apple Support): https://support.apple.com/ja-jp/guide/mac-help/mh11848/mac
- ハマった人 https://www.okigaru-migaru.net/entry/imac-bluetooth-off
- 本当に詰んだ人向け(英語) http://osxdaily.com/2016/07/16/enable-bluetooth-without-keyboard-mouse-mac/
- bluetoothd をいじる方法 https://apple.stackexchange.com/questions/47503/how-to-control-bluetooth-wireless-radio-from-the-command-line#comment-54464