Edited at

Macbook の内臓キーボードの無効/有効を自動で切り替える (macOS Sierra)

More than 1 year has passed since last update.

2017/07/02 追記

kextunload してもキーボードが無効化されなくなってしまっていました。

原因はわかりませんが、代替手段としては tekezo/Karabiner-Elements: The next generation Karabiner for macOS Sierra を使用するのが良いようです。

追記終了

macOS Sierra にしたら,Karabiner が使用できなくなりました.それに伴い,外部キーボードを接続しても内臓キーボードが無効化されなくなりました.これを解決するため,内臓キーボードの有効化/無効化の方法を調べ,その自動化を試みた,という話です.

結論から言うと,自動化してみたはいいものの,キーボードの接続/取り外しから有効/無効の切り替えまでに最悪5秒程かかってしまい,あまり実用的ではありません.しかし,


  • 外部キーボードの付け外しはそこまで頻繁に行わない

  • 内臓キーボードの有効化をし忘れを防げる


    • 無効化したままスリープし,次回 wake 時に内臓キーボードが効かずに焦ることがなくなる



という点で考えれば使えるかも?自分はしばらく使ってみます.

2016/10/29 追記

他のMacbookでも利用したかったので Github にリポジトリを公開しました.

tasuwo/toggleMacbookInternalKeyboard: This scripts detect an external keyboard connection and disable internal keyboard of macbook instead of karabiner


内臓キーボードの無効化

内臓キーボードは,対応する kext をアンロードすることで無効にできるようです.


  • KEXT (Kernel EXTension)


    • その他の通り,カーネルを拡張するためのモジュール

    • その多くはデバイスドライバ

    • その大体が /System/Library/Extensions にある



コマンド
効果

kextload
kextのロード

kextunload
kextのアンロード

kextstat
ロードされているkextの確認

以下のコマンドでロード/アンロード(有効化/無効化)が行えます.

# 内臓キーボードの無効化

$ sudo kextunload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/
# 内臓キーボードの有効化
$ sudo kextload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/

How to Load & Unload Kernel Extensions in OS X

ASCII.jp:不要なドライバーをスッキリ整理、KEXT関連コマンドを知る (1/2)|Apple Geeks


外部キーボードの存在判定

あるスクリプトを実行すると,外部キーボードが接続されているか否かで内臓キーボードの有効/無効を適切に切り替えられると良いと考えました.

外部キーボードが接続されているかどうかの確認には,lsusbioreg などのコマンドが使用できそうでした(他にもっと良い方法がありそう...) 前者は重かったので,後者のioregを使うことにしました.

以下の質問サイトの回答を拝借し,USB接続されているデバイス名一覧を表示 & 外部キーボード名を grep & 結果が空文字か判定することで,外部キーボードの接続有無を判定することにします.

List USB devices on OSX command line - Ask Different

最終的なスクリプトは以下です.

#!/bin/sh

IS_CONNECTED=`ioreg -p IOUSB -w0 | sed 's/[^o]*o //; s/@.*$//' | grep -v '^Root.*' | grep "USB Keyboard"`
IS_LOADED=`kextstat -a | grep AppleUSBTCKeyboard`
if [ -n "${IS_CONNECTED}" ]; then
if
[ -n "${IS_LOADED}" ]; then
`echo "your passowrd" | sudo -S kextunload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/`
fi
else
if
[ -z "${IS_LOADED}" ]; then
`echo "your password" | sudo -S kextload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/`
fi
fi

キーボードの存在判定のために grep している部分は "USB Keyboard" としてますが,これは自分の使用しているキーボードが ThinkPad Compact USB Keyboard with TrackPoint であるためです.

パスワードを直接記述しているので,セキュリティ的な問題があります.また,シェルスクリプトに関しては初心者なので,見苦しいコードになっているかもしれません.


自動化

できれば,キーボード接続時/取り外し時に自動的に上記のスクリプトを実行してくれると良いです.これを実現するために launchd を活用しました.

LaunchAgent に登録して、スクリプトを自動起動する - それマグで!

Backups With Rsync and Launchd - Stuff… And Things…

Logging with launchd | Erik's Lab

LaunchDaemons (launchctl, launchd.plist) の使い方 - maruko2 Note.

先ほど書いたスクリプトを toggleInternalKeyboard.sh として /usr/local/bin 配下に配置し,以下のような plist ファイルを作成しました.

<?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>com.example.toggleInternalKeyboard</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/toggleInternalKeyboard.sh</string>
</array>
<key>StartInterval</key>
<integer>1</integer>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/var/log/toggleInternalKeyboard_output.log</string>
<key>StandardErrorPath</key>
<string>/var/log/toggleInternalKeyboard_err.log</string>
</dict>
</plist>

<key>Label</key><string> 内の値で,デーモンの識別子を定義しています.

ProgramArguments で実行するスクリプトを指定しています.

StartInterval で1秒ごとにスクリプトを実行しています.

また,StandardOutputPathStandardErrorPath で,ログの出力先を指定しています.

上記の plist を /System/Library/LaunchDaemons/ 配下に配置します.

起動するには以下のコマンドを実行します.-w オプションを付加することで,OSブート時に自動的に起動するようにします.

$ sudo launchctl load -w /Library/LaunchDaemons/com.example.toggleInternalKeyboard.plist

停止させたいときは unload します.

$ sudo launchctl unload -w /Library/LaunchDaemons/com.example.toggleInternalKeyboard.plist


通知

ただでさえ反映までにラグがあるのに,通知も何もないとどのタイミングで切り替わったかがわかりません.切り替えた時に通知が来ると良い感じです.手軽に通知をとばすためには Apple Script を使用するのが良さそうでした.

例えば,以下のような単純な Apple Script でとりあえず通知を飛ばすことができるようです.

display notification "test" with title "TEST"

Apple Script はコマンドラインから osascript コマンドを通して実行できます.しかし,自分の環境では以下のようなエラーが出力されました.

$ osascript -e 'display notification "test" with title "TEST"'

2016-10-11 21:36:45.910 osascript[77739:1943672] NSNotificationCenter connection invalid

友人の Yosemite の環境では問題がなかったので,Sierra が悪いのかもしれないですが,詳細は不明です.

2016/10/27追記

私は tmux を使用していたのですが,tmux を抜けたところ上記エラーが出力されなくなりました.@q4yuu さん,ありがとうございました.以下の記事は上記のエラーが解決しなかった場合について記述しています.今後修正するかもしれません.

仕方がないので,無効化した場合/有効化した場合のための二種類の通知用 app を用意することにしました.

display notification "内部キーボードを有効化しました" with title "toggleInternalKeyboard"

display notification "内部キーボードを無効化しました" with title "toggleInternalKeyboard"

app の出力は,Script Editor の File > Export... から, File Format を Application にして保存するとできます.これらをとりあえず /usr/local/bin に配置しました.

最終的なスクリプトは以下です.

#!/bin/sh

IS_CONNECTED=`ioreg -p IOUSB -w0 | sed 's/[^o]*o //; s/@.*$//' | grep -v '^Root.*' | grep "USB Keyboard"`
IS_LOADED=`kextstat -a | grep AppleUSBTCKeyboard`
if [ -n "${IS_CONNECTED}" ]; then
if
[ -n "${IS_LOADED}" ]; then
`echo "your password" | sudo -S kextunload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/`
`open /usr/local/bin/notifyDisableInternalKeyboard.app`
fi
else
if
[ -z "${IS_LOADED}" ]; then
`echo "your passowrd" | sudo -S kextload /System/Library/Extensions/AppleUSBTopCase.kext/Contents/PlugIns/AppleUSBTCKeyboard.kext/`
`open /usr/local/bin/notifyEnableInternalKeyboard.app`
fi
fi

これで,外部キーボードの接続/取り外しに合わせて,内臓キーボードを有効/無効にできるようになりました.