スリープをカーネル機能拡張からコントロールする

insomnia=不眠症不眠症というだけあって、InsomniaXMacBookのスリープを強力に制限してくれる。

  • ディスプレイを閉じた時のスリープ。
  • 電源ボタンを押して、終了ダイアログからのスリープ。
  • アップルメニューからのスリープ。
  • command-option-Ejectによるショートカットのスリープ。
  • pmset sleepnowコマンドによるスリープ。
  • AppleScriptによるスリープ。

以上、ありとあらゆるスリープが無効となるのだ。スリープする唯一の方法は、InsomniaXのアイコンメニューから、Sleep System を選択するのみ。

ところで今時のMacBookは、Wake on Demandによるスリープ中のアクセスが保証されているので、あえて不眠症にする理由はほとんどない。しかし、うっかりディスプレイを閉じておくと、Wake on Demandの恩恵は受けられなくなってしまう。そんな時、InsominaXが起動していれば、ディスプレイを閉じてスリープした状態からでも、Wake on Demandによるアクセスが可能になる。そう思って、やってみたのが以下の日記。

ディスプレイを閉じてしまうと、画面は見えない、かつキーボードやトラックパッドも使えなくなってしまうので、AppleScriptでInsomniaXの Sleep System を実行するようにしてみたのだ。ところが、InsomniaXのアイコンメニューは、GUIスクリプティングを使っても操作できなかった*1。そのため、cliclickというマウスのクリックをシミュレートするコマンドを利用して操作するようにしたのだ。

当初はうまく動いていたが、欠点がある。cliclickコマンドは、ディスプレイ左上を原点とした絶対座標でクリック位置を指定するので、アイコンメニューの並びが変わると、全く無意味な操作になってしまうのだ。アイコンメニューの並び順を指定するツールもあるが、新規インストールや削除することもあるので、InsomniaXのアイコン位置が固定される訳ではない。何より、開発したマシン環境限定のスクリプトとなってしまう。他の環境で使おうと思ったら、cliclickコマンドの座標を調整しなくてはならない今イチなスクリプトだった。

どうにかして、もっとスマートにInsomniaXをコントロールする方法はないのか?以前から模索していたのだが、久々にInsomniaXの本家ページを閲覧して、そのヒントに気付いた。

ヒント

2つの情報がヒントとなった。

64ビット対応
ラッパー


つまり、InsomniaX.app(メニューバーアイコンとして動作するアプリケーション)は、Insomnia.kext(カーネル機能拡張)を使い易く補佐する立場にあり、実際の仕事(スリープを無効にするという仕事)は、Insomnia.kext が担っていると想像した。だから、Insomnia.kextをコントロールできれば、InsomniaX.appを使わなくてもスリープを無効にできるはず。

カーネル機能拡張のコントロール

  • InsomniaX.appに頼らないで、Insomnia.kextをコントロールする方法は、たった二つしかない。

kextloadとkextunload。

  • kextloadコマンドで、Insomnia.kextをカーネルにロードして有効にする。
  • kextunloadコマンドで、Insomnia.kextをカーネルからアンロードして無効にする。
  • なるほど!さっそく試してみた。
$ sudo kextload /Applications/InsomniaX.app/Contents/Resources/Insomnia_r6.kext
Password:
/Applications/InsomniaX.app/Contents/Resources/Insomnia_r6.kext failed to load - (libkern/kext) authentication failure (file ownership/permissions); check the system/kernel logs for errors or try kextutil(8).
  • しかし、残念ながらエラー発生!どうやらカーネルはアクセス権限に厳格らしい。
  • sudoで実行するのはもちろんのこと、ロードするカーネル機能拡張も 所有者: root、グループ: wheel でないと受け付けてくれないらしい。
  • それではInsomniaXはどうやってロードしているのだろう?と調べてみると...
$ ls -l ~/Library/Application\ Support/InsomniaX/
total 0
drwxr-----  3 root  wheel  102  5 31 19:37 Insomnia_r6.kext/
  • ~/Library/Application Support/InsomniaX/ フォルダに、すでにアクセス権限を変更した Insomnia_r6.kext が見つかった。
  • 素直にこれを活用させてもらうことにした。
$ sudo kextload ~/Library/Application\ Support/InsomniaX/Insomnia_r6.kext
  • 実行はすぐに完了した。どうやら、ロードできたようだ。
  • ディスプレイを閉じてみた。
  • 全くスリープする様子はない。

成功した!(ようだ)

  • カーネル機能拡張のロード状況を確認するコマンドもある。
$ kextstat
  • しかし、これでは画面がカーネル機能拡張で溢れてしまうので、apple以外のkextに限定してみた。
$ kextstat|grep -v apple
Index Refs Address    Size       Wired      Name (Version) 
  113    0 0x5c86f000 0xe000     0xd000     com.iospirit.driver.rbiokithelper (1.5.4) <62 35 29 5 4 3 1>
  115    0 0x5bfc4000 0x3000     0x2000     com.eltima.ElmediaPlayer.kext (1.0) <4 1>
  116    0 0x55347000 0x5000     0x4000     com.google.driver.Gild (1.0.0) <5 4 3 1>
  117    0 0x5c49e000 0x18000    0x17000    org.pqrs.driver.KeyRemap4MacBook (7.3.0) <29 5 4 3 1>
  127    0 0x5c1f2000 0x2000     0x1000     com.bresink.driver.BRESINKx86Monitoring (2.0) <12 11 10>
  128    3 0x5c8d1000 0x25000    0x24000    org.virtualbox.kext.VBoxDrv (3.2.8) <7 5 4 3 1>
  129    0 0x5c397000 0x7000     0x6000     org.virtualbox.kext.VBoxUSB (3.2.8) <128 41 35 7 5 4 3 1>
  130    0 0x5c48c000 0x4000     0x3000     org.virtualbox.kext.VBoxNetFlt (3.2.8) <128 7 5 4 3 1>
  131    0 0x5c1f8000 0x3000     0x2000     org.virtualbox.kext.VBoxNetAdp (3.2.8) <128 5 4 1>
  204    0 0x5b44f000 0x3000     0x2000     com.protech.nosleep (1.2.1) <4 3>
  283    0 0x55307000 0x3000     0x2000     org.binaervarianz.driver.insomnia (1.0.0d1) <4 3 1>
  • 一番最後の行に、insomniaが確認できた!
  • Insomnia.kextを無効にするには、loadをunloadに変更して実行ればOK。
$ sudo kextunload ~/Library/Application\ Support/InsomniaX/Insomnia_r6.kext

一時的にスリープを許可する

  • 有効・無効の設定方法は分かった。しかし、自分が操作したいのはInsomniaXのSleep System。
  • 一体どうやって、Insomnia.kextにスリープを依頼しているのだろう?
  • Insomnia.kextの本家ページでも調べてみたが、kextload、kextunload以外の手段は無さそうだ。
  • そのヒントはkernel.logに隠されていた。以下は、InsomniaXでSleep Systemを実行したときのログ。
Jul 14 04:30:32 zari-MacBook kernel[0]: Insomnia: kIOPMEnableClamshell sent to root
Jul 14 04:30:32 zari-MacBook kernel[0]: Insomnia: Lid close is now processed again.
Jul 14 04:30:32 zari-MacBook kernel[0]: Insomnia finished
Jul 14 04:30:32 zari-MacBook kernel[0]: ==========================================
Jul 14 04:30:33 zari-MacBook kernel[0]: AFPSleepWakeHandler: going to sleep
Jul 14 04:30:33 zari-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 14
Jul 14 04:30:34: --- last message repeated 1 time ---
Jul 14 04:30:34 zari-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 15
Jul 14 04:30:35: --- last message repeated 1 time ---
Jul 14 04:30:35 zari-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 12
Jul 14 04:30:36 zari-MacBook kernel[0]: System Sleep
Jul 14 04:30:37 zari-MacBook kernel[0]: Wake reason = UHC3
Jul 14 04:30:37 zari-MacBook kernel[0]: System Wake
Jul 14 04:30:37 zari-MacBook kernel[0]: Previous Sleep Cause: 5
Jul 14 04:30:37 zari-MacBook kernel[0]: The USB device Apple Internal Keyboard / Trackpad (Port 2 of Hub at 0x5d000000) may have caused a wake by issuing a remote wakeup (2)
Jul 14 04:30:37 zari-MacBook kernel[0]: AirPort: Link Down on en1. Reason 4 (Disassociated due to inactivity).
Jul 14 04:30:37 zari-MacBook kernel[0]: enet_event_func     -  vendor 1, class 1, subclass 2, event code 12
Jul 14 04:30:38 zari-MacBook kernel[0]: Insomnia:init
Jul 14 04:30:38 zari-MacBook kernel[0]: Insomnia:start
Jul 14 04:30:38 zari-MacBook kernel[0]: Insomnia: kIOPMDisableClamshell sent to root
Jul 14 04:30:38 zari-MacBook kernel[0]: Insomina: enabled
Jul 14 04:30:38 zari-MacBook kernel[0]: ========================
  • 何と!スリープする前に、Insomnia.kextは一旦終了しているように見える。(kextunload)
  • そしてスリープが解除されると、Insomnia.kextは再び動き出しているように見える。(kextload)
  • 同じような動作になるように、コマンドで実行してみた。
$ pmset sleepnow
Unable to sleep system: error 0xe00002bc

# Insomnia.kextがロードされていると、
# 上記のようにエラーが発生してスリープできない
# ところがkextunloadとkextloadで挟んであげると...

$ sudo kextunload ~/Library/Application\ Support/InsomniaX/Insomnia_r6.kext;\
    
Sleeping now...
できた!
  • なんと、スリープする前に一旦 Insomnia.kext をアンロードしていただけ、だったのだ。
  • コマンドは、スリープ復帰後にまた Insomnia.kext をロードするので有効になる。
  • だからディスプレイを閉じた状態でも、Wake on Demandで接続して操作もできるのだ。
  • Insomnia.kextはあくまでもスリープを無効にすることに特化したカーネル機能拡張で、
  • InsomniaX.appは、それをロード・アンロードするタイミングを調整するだけで、便利な環境を提供してくれていたのだ。
これで、InsomniaX.appに頼らずに、スリープの有効・無効を自由にコントロールできるようになった!

*1:ステータスメニュー形式のアイコンメニューは操作できるが、それ以外のアイコンメニューはInsomniaXに限らず、GUIスクリプティングで操作できない。操作できる方法があれば、ぜひ知りたいです!