モニタを閉じてスリープ中のMacBookをリモート操作できる環境にする

MacBookのモニタを閉じると普通はスリープする。スリープしたMacBookは、Wake on LAN あるいは Wake on Demand の仕組みを利用して呼び覚ますことができるが、モニタを閉じた状態ではそれも不可能。

そんな不便を解消してくれるのがInsomnia。Insomniaが有効な状態であれば、OSX標準のスリープを無効にしてくれる。モニタを閉じてもスリープしない。

しかし、閉じた状態で稼働し続けるのも省エネに反するので、そんな時はInsomniaのシステムスリープを実行してからモニタを閉じれば、モニタを閉じた状態で Wake on LAN あるいは Wake on Demand に反応するMacBookになる。理想的な環境だ。

でも、いつもの習慣で、Insomniaのシステムスリープを忘れてモニタを閉じてしまうと、MacBookは永遠と稼働し続けることになってしまう...。モニタを閉じた時にInsomniaのシステムスリープが自動実行されれば一番良いのだ。果たして、そんなことができるかどうか、いつものAppleScriptでやってみることにする。

環境

Insomniaのシステムスリープを実行するAppleScript

GUIスクリプティングの限界
  • メニューやショートカットキー、ウィンドウ、ボタン等のOSが提供するGUIを指定して操作するGUIスクリプティングは、AppleScriptに対応していないアプリケーションに対しても自動操作を可能にしてくれる。
  • しかし、唯一GUIスクリプティングでも操作できないGUIがあって、それがメニューバー右側に表示されるアイコンのメニュー。アイコンのメニューの中でもMenu Extraと呼ばれるタイプのものは操作可能なのだけど、それ以外のアプリケーション独自のメニューバーアイコン(例:EvernoteQuicksilver、InsomniaXなど)を操作する方法が分からない。
  • SimpleCapのようにショートカットが割り当てられているならともかく、おそらくマウスでクリックする以外にメニューを表示する手段はないものと思われる。
cliclickコマンドで限界を超える
cliclick 286 11
  • 上記のコマンドを実行すると、X座標:286、Y座標:11のポイントをクリックする。
  • 座標を調べるなら、command-shift-4で範囲指定のスクリーンショットのモードにすると、簡単に確認できる。
クリックする時の環境が大事

cliclickは、マウスのクリックを実行するだけなので、刻々と変化するGUIの状況によっては、予想外のものをクリックしてしまう可能性がある。だから、クリックする前にはアクティブなアプリケーションを指定して、Spacesのスペース番号やウィンドウの位置・大きさ、Dockの表示・非表示などに注意しておく必要がある。具体的には、以下のような手順。

  • InsomniaXをアクティブにする。(ログイン項目に登録せず、アイコンメニューとして最後に起動することで、常にメニューバーの一番左の位置に表示される)
  • MinimunMenuをアクティブする。(メニューバーの文字メニューを最小にして、アイコンメニューを確実に表示するため)
  • InsomniaXのアイコンメニューをクリックする。
  • ↓キーを3回押して、"Sleep System"を選択する。


以上を踏まえると、目指すAppleScriptは以下のようになった。

      • ファイル名:~/Library/Scripts/Sleep System by Insomnia.scpt


tell application "InsomniaX" to activate
tell application "minu" to activate
do shell script "/usr/local/bin/cliclick 286 11"
tell application "System Events"
repeat 3 times
delay 0.1
key code 125 --キー
end repeat
keystroke return
end tell

cliclickコマンドの底力

ところで、cliclickコマンドのヘルプを見てみると、連続クリックにも対応しているようだ。

$ cliclick -h

 cliclick - Command Line Interface Click
 Version 1.2
 Carsten Bluem, 2008-08-18
 http://www.bluem.net/downloads/cliclick-en/

 Usage: cliclick [-v] [-w n] x y [x2 y2] [x3 y3] [...]
   x and y are integer numbers which specify the screen coordinate(s)
   where the mouse click(s) should be emulated. (Upper left corner is 0 0.)
   If you need a doubleclick, prefix the x coordinate with "d".
   If you need a control click (context.menu, use prefix "c".

 Options:
   -w  You can pass multiple coordinate pairs as argument. But if you do,
          it is often useful to have a small delay between events -- that is
          what the -w option is for: It will cause cliclick to wait for the
          specified number of milliseconds after each event.
   -v     Makes cliclick more verbose.

 Examples:
   'cliclick 26 12' will click the apple menu
   'cliclick -w 50 26 11 26 33' will open the "About this Mac" panel
   'cliclick 50 60 c70 80' will click at 50/60, then Control-click at 70/80
   'cliclick d50 60' will doubleclick at 50/60
  • cliclick X座標 Y座標 X座標 Y座標... のようにクリックするポイントを複数指定すれば、連続してクリックしてくれる。
  • さらに、cliclick -w 50 X座標 Y座標 X座標 Y座標... のようにオプション指定すれば、50msの間(GUIが反応するための任意の時間)を空けて次のクリックを実行してくれる。
  • cliclick cX座標 Y座標... で、右クリック(control-クリック)
  • cliclick dX座標 Y座標... で、ダブルクリック

これなら、かなり複雑な操作もcliclickコマンドのワンライナーで実現できそうだ!
そして、cliclickコマンドを活用すると、先のAppleScriptは3行になった。

      • ファイル名:~/Library/Scripts/Sleep System by Insomnia.scpt


tell application "InsomniaX" to activate
tell application "minu" to activate
do shell script "/usr/local/bin/cliclick -w 50 286 11 310 84"

モニタが閉じたことを検出する

  • 上記スクリプトをモニタが閉じた時に実行したいのだが、あいにくAppleScriptにはそのような状況を検出する命令は、多分ない...。
  • そこで、アプリケーション >> ユーティリティ >> コンソール.appを起動してログを確認してみると、Insomniaがモニタの開閉状況をログに書き込んでいることがわかった。
  • ログには、デスクトップ上には現れない、OSやアプリケーション内部の状況が随時、記録されている。
  • これを検出すれば、様々な処理のきっかけに利用することができる。とっても利用価値が高い情報だ!

AppleScriptは、以下のようにしてみた。

      • ファイル名:~/Library/Scripts/Lid was closed.scpt


property InsomniaLog : ""

--kernel.logから「Insomnia: Lid was closed」の行を抽出する
try
set newLog to do shell script "/usr/bin/tail /var/log/kernel.log | /usr/bin/grep -i 'Insomnia: Lid was closed'"
on error
set newLog to ""
end try

--新たな「Insomnia: Lid was closed」メッセージが追記されていなかったら何もしない
if newLog is "" then return
if newLog is InsomniaLog then return

--モニタが閉じたら、Sleep System by Insomnia.scptを実行する
set InsomniaLog to newLog
set script_path to (path to scripts folder as text) & "Sleep System by Insomnia.scpt"
run script file script_path

newLog

Launchdで効率的なファイル監視をする

  • あとは上記スクリプトをkernel.logが更新されたタイミングで実行すれば良いはず。
  • しかし、AppleScriptにはファイルが更新されたことを知るメソッドが用意されていない...。
  • フォルダアクションに用意されているのは、以下のイベント駆動のみ。

メソッド名 実行されるタイミング
on opening folder フォルダを開いた時
on closing folder window フォルダウィンドウを閉じた時
on moving folder window フォルダウィンドウを移動した時
on adding folder items フォルダに追加した時
on removing folder items フォルダから削除した時

  • on adding folder items が使えそうな気がするが、フォルダにファイルが追加された時しか反応してくれない。ファイルが更新された時は無反応...。
  • こんな時は、いつもなら on idle で定期的に繰り返す設定にしていた。数秒間隔で監視すれば、ログの変化を確認できる。


  • 基本的に3つの要素を決めるだけで、launchdが管理してくれるようになる。(さらに詳細な設定をする場合はxmlを編集することになるが)
    • 識別名称(ファイル名になる)
    • 何を(シェルコマンド、あるいは実行ファイル)
    • いつ(どのタイミングで実行するか)
      • 注意:/usr/bin/osascript "~/Library/Scripts/Lid was closed.scpt" のように設定したくなってしまうが、プロパティリストの中では「~」記号をホームフォルダと認識してくれない。(エラーで何も実行されない。シェルスクリプトファイルを指定して、その中で /usr/bin/osascript "~/Library/Scripts/Lid was closed.scpt" とするならOK。)
  • 保存すると、~/Library/LaunchAgents/com.bebekoubou.kernel.log.plist が追加された。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>Label</key>
	<string>com.bebekoubou.kernel.log</string>
	<key>ProgramArguments</key>
	<array>
		<string>/usr/bin/osascript</string>
		<string>/Users/zari/Library/Scripts/Lid was closed.scpt</string>
	</array>
	<key>QueueDirectories</key>
	<array/>
	<key>WatchPaths</key>
	<array>
		<string>/var/log/kernel.log</string>
	</array>
</dict>
</plist>
  • Lingonを使っていて気付いたが、実は、フォルダアクションが駆動する仕組みは launchd で監視することで実現されていた。
    • ~/Library/LaunchAgents/com.apple.FolderActions.enabled.plist
    • ~/Library/LaunchAgents/com.apple.FolderActions.folders.plist


以上で、モニタを閉じるとInsomniaのシステムスリープが自動実行され、スリープ中でもリモート操作でアクセスが可能になった!3つのファイルが連携して、モニタを閉じたMacBookのリモート操作を可能にしている。処理の流れは以下のようになる。

  • ~/Library/LaunchAgents/com.bebekoubou.kernel.log.plist
    • launchdの設定ファイル。/var/log/kernel.logが更新されたら、~/Library/Scripts/Lid was closed.scptを実行する。
  • ~/Library/Scripts/Lid was closed.scpt
    • 更新内容に'Insomnia: Lid was closed'が含まれていたら、モニタが閉じられたと判定して~/Library/Scripts/Sleep System by Insomnia.scptを実行する。
  • ~/Library/Scripts/Sleep System by Insomnia.scpt
    • cliclickコマンドでInsomniaのアイコンメニューを操作して、システムスリープさせる。

その他の設定とか、心配ごと、所感など

  • Insomniaは、有効な状態にしておく。(Preferences >> Load on AC にチェックを入れて、電源アダプタに接続中は常に有効になる設定にした)
  • モニタを閉じると、wifiの受信感度が落ちる。AirMacの電波が弱い状況では、オフラインになって、リモート操作できなくなるかもしれない...。
  • モニタを閉じた状態では熱がこもりやすい。夏場は、負荷の高い処理・長時間の使用では、温度の上昇に気をつける必要があるかもしれない...。
  • launchdでログ(ファイル)を監視して、状況に応じて必要なAppleScriptを実行する手順は、いろいろな場面で役立ちそうだ。