消えた NSMovieView

リモート AppleEvent の続きが気になるところですが(気にならない?)、ちょっと中断。

iTunes のポッドッキャスト。音声/映像の配信に興味があって試してみたりしています。ポッドキャストは、RSS なこともあって RSS 同様(わけの分からない説明です)ほっておくとどんどん記事(音声)が溜まっていきます。

テキスト情報ならまとめて読むことも可能ですが...音声をまとめて聞くのは少し気合いを入れないと消化できません。iTunes は、管理するのには最適なんですが。

どうにかならないものか...と思案して、AppleScript Studio でポッドキャストの再生、通知アプリケーションを作ろうと思い立ちました。

現在、こんな画面になっています。プログラム的になんら難しいことをしていないのに、ここまでくるのに 3 日間かかっています。問題は、NSMovieView。これ、Interface Builder からなくなっていませんか?探したが見当たらず。仕方がないので Developer Tools 付属のサンプル Talking Head の NSMovieView をコピーしていました。しかし、低機能すぎます。

もしかして、QTMovieView を使えってことなのでしょうか?といっても、AppleScript Studio には QTMovieView を使うための Class は用意されていません。QTMovieView に tell movie view 〜 としてもエラーになるばかり。

最終的に NSMovieView はあきらめて、おとなしく QTMovieView を使うことに決定。もちろん、call method で操作します。って、なんで音声や映像を再生するだけのために call method を使わにゃならんねん。

リモート AppleEvent (3)

有線 LAN で接続した Mac を遠隔操作。一回目は、前準備。二回目は、スクリプトを書いて Mac を動かしました。今回は、前回提起された問題。認証ダイアログの抑制について。

そもそも、パスワードやユーザ名などは、Mac の場合「キーチェーンアクセス」というアプリケーションが一括管理してくれます。もちろん、リモート AppleEvent でのホスト側のこういった情報も管理してくれています。

キーチェーンアクセスは、/Application/Utilities/ にあります。起動します。デフォルトの設定を変えていないなら、ログインというキーチェーンがあると思います。前回のスクリプトを実際に試してみたなら、ホスト側の名前がついたインターネットパスワードという種類のキーがあると思います(この場合、iBook800.local)。ない場合は、作成してください(作成方法は割愛)。

これを選択するとキーチェーンアクセスのウィンドウ上部に情報が表示されると思います。アカウント(ユーザー名)と場所(eppc://iBook800.local:3031)が書かれています。前回、ホストを指定するのに eppc://iBook800.local しか指定していませんでした。最後のコロン以下の 3031 は、リモート AppleEvent が利用するポート番号です。

キーチェーンアクセスにこうやって保存されているなら、AppleScript では「Keychain Scripting」というバックグラウンドアプリケーションを使ってキーチェーンや登録されているキーの情報を取得することができます。

ここで問題が出てきます。Mac OS X 10.3 と 10.4 の Keychain Scripting は、バグがあってそのままではすんなりと利用できません。10.4.2 のアップデータでなおったとありますが、本当はなおっていません。この問題を回避するためにアップルは情報を公開しています。しかし、ここで紹介されているスクリプトを試してもエラーになります。というのも、このスクリプト自体が間違っているから。

Keychain Scripting は、パスワードを要求されると「Keychain Scripting でエラーが起きました:アプリケーションは実行されていません。」とエラーが表示されます。これが、Keychain Scripting のバグです。

tell application "Keychain Scripting"
    password of key 1 of keychain 1
end tell

-- Keychain Scripting でエラーが起きました:アプリケーションは実行されていません。

なぜ、こんなエラーが起きるかの原因は分からないですが、次のような手順で回避できます。(1) Keychain Scripting が起動しているなら、一度終了させる。(2) Finder で開く。アップルも同じような解決方法を掲載しているわけですが、(1) の部分に問題がありそのままでは利用できないものになっています。

掲載されているものでは System Events で Keychain Scripting のプロセスを調べて System Events の中で Keychain Scripting を終了させています。

tell application "System Events"
    quit targetApp
end tell

これが間違い。System Events ではアプリケーションを指定して終了させることはできません。結果、プロセスはそのまま残るので再度エラーになります。まずは、このバグを回避するために次のようなスクリプトを作成します。

on run
    startKeychainScripting()

    tell application "Keychain Scripting"
        password of key 1 of keychain 1
    end tell
end run

on startKeychainScripting()
    -- Keychain Scripting の起動確認
    tell application "System Events" to set bool to exists process "Keychain Scripting"

    -- 起動しているなら終了
    if bool then
        keychainScriptingKiller()
        -- System Events からプロセスが見えなくなるまで
        --これがないと Finder でエラーが発生する
        repeat
            tell application "System Events" to set bool to exists process "Keychain Scripting"
            if not bool then exit repeat
        end repeat
    end if

    -- Keychain Scripting の再起動
    restartKeychainScripting()
end startKeychainScripting

on restartKeychainScripting()
    -- kscr は、Keychain Scripting のクリエータータイプ
    -- com.apple.KeychainScripting でも可
    -- Finder で Keychain Scripting を起動
    tell application "Finder" to open application file id "kscr"

    -- System Events からプロセスが見えるようになるまで
    --これがないと次の処理でエラーになる
    repeat
        tell application "System Events" to set bool to exists process "Keychain Scripting"
        if bool then exit repeat
    end repeat
end restartKeychainScripting

on keychainScriptingKiller()
    tell application "Keychain Scripting" to quit
end keychainScriptingKiller

いつもよりコメントを多めにしています。安全のために Keychain Scripting を利用する度に「終了/Finder で開く」の処理を行っておきます。

これが他の環境でも動くかどうか分からないですが。シェルを使っての起動、終了でもいいと思います。もし、動かない場合はシェルでやってみるといいかもしれません。

しかし、キーチェーンを利用するまでに時間がかかりますね...。

リモート AppleEvent (2)

さて、前回の準備はできたでしょうか? Ethernet で接続した iBook と iMac。iMac の方から iBook を AppleScript で操作しましょうというこの試み。この手の情報って探してみてもなかなか見つからなかったりします。

「システム環境設定」の「共有」の「サービス」タブにある「リモート AppleEvent」にチェックを入れたらこれだけで Mac を遠隔操縦することができます(要注意。セキュリティについてはここでは触れません)。では、スクリプトを書いて試してみます。

通常、Finder のスクリプトを書くときは、次のようにして Finder を指定します。

tell application "Finder"
    (* ここに処理を記述 *)
end tell

リモート AppleEvent で接続された Mac(ホスト)の Finder を操作するには、次のように Finder を指定します。

application "Finder" of machine "eppc://Mac の名前"

ホスト側の Mac の URL 指定が追加されます。「eppc://」は、リモート AppleEvent が利用するプロトコルです。「Mac の名前」は、「システム環境設定」の「共有」で設定したコンピューターの名前になります。iBook には、iBook800 という名前を付けました。これは、「共有」の「コンピューター名」の下の「ローカルサブネット上のほかのコンピュータから、iBook800.local でこのコンピュータにアクセスできます」というように書かれている「iBook800.local」になります。Bonjour で使われるコンピューター名になるのですが、同じものをリモート AppleEvent でも利用します。

先ほどの Finder の指定は、次のようになります。.

tell application "Finder" of machine "eppc://iBook800.local"
    (* ここに処理を記述 *)
end tell

なんらかの処理を記述して構文確認を行うと、ホスト(iBook)の認証ダイアログが表示されます。構文確認を行うだけでも認証が必要なのですね。それではいささかめんどうなので、通常は using terms from を使ってクライアント側(iMac)のアプリケーションの用語辞書を使って構文確認を行います。

using terms from application "Finder"
    tell application "Finder" of machine "eppc://iBook800.local"
        version
    end tell
end using terms from

これで構文確認はできます...?できないですね。思いっきり認証ダイアログが表示されます。これを回避するためにホスト側を変数に入れてしまいます。

set remoteFinder to application "Finder" of machine "eppc://iBook800.local"

using terms from application "Finder"
    tell remoteFinder
        name of startup disk
    end tell
end using terms from

これでクライアント側(iMac)の Finder を使って構文確認ができます。では、実行してみましょう。

...また、認証ダイアログが表示されましたね。このダイアログに「キーチェーンに追加しますか?」というチェックボックスがあるのでホスト(iBook)のユーザー名とパスワードを入力してキーチェーンに追加しておきます。

認証は、キーチェーンに追加して全て解決、というわけには(なぜか)いきません。たとえば、AppleScript を終了して再度スクリプトを実行するときに先ほどの認証ダイアログが表示されます。どうしてでしょう?安全のため?ともかく、これでは認証ばかりで面倒です。次は、これを解決してみましょう。

リモート AppleEvent (1)

複数台の Mac を持っているとやってみたくなるじゃないですか。AppleScript で他の Macintosh を操縦って。そんなことないか?

AppleScript を使い始めたときからやってみたかったのです。が、複数台の Macintosh を持っていないし、セキュリティ的にどうなのかもよく分からない。今でも分かっていません。その辺りは調べていないので、これを読んでも不安な方は利用しない方がいいと思います。

ともかく、複数台の Macintosh を持っているのだから、試してみようと調べてみました。これが大変だったりします。で、防備録として。まだ、よく分かっていないのでこうできるとか、無線 LAN でとかインターネットを利用してこうできるとかの突っ込みもあるかと思いますが、掲示板にでも書いておいてもらえると嬉しいです。

まず、使う Mac は、有線 LAN でローカル接続です。インターネットに接続していません。単純に Ethernet でつないでいます。Mac は、iBook G3 800 GHz(Mac OS X 10.3.9) と iMac G5 2 GHz(Mac OS X 10.4.2)です。iMac から iBook を操作します。Ethernet をつないだらほとんど勝手に両者はつながります。これでファイル共有などができるようになります。どちらの Mac も管理者として起動しているとします。

つながったら操作される Mac(iBook)のシステム環境設定の「共有」の「サービス」タブを開き、「リモート AppleEvent」にチェックを入れます。ついでにコンピュータの名前も設定しておきます。ここでは、iBook800 にしておきました。これで iMac 側から iBook を AppleScript で操作できるようになります。設定は至って簡単。セキュリティはどうなのか知りませんが。

ここまでが準備段階です。

load nib

なんで、いまさら Mac OS X 10.3.9 と Mac OS X 10.4 をいったりきたりしているか?

Xcode 2.1...というより、AppleScript Studio が問題なんですね。新しいことはいいことだ、とばかりにインストールしたのはいいものの使いにくくない(=バグが多くない)でしょうか?。

AppleScript Studio を使っていると、挙動がおかしいところがよくある。スクリプティング自体が悪いのか、Xcode の問題なのか、AppleScript Studio か...どれかよく分からない。で、10.3.9 上の Xcode 1.5 を使って確認のためにいったりきたり。

Xcode ではなく、AppleScript Studio 1.4 の問題な気もします。具体的には、次のようなコード。

property notifWindow : missing value

on clicked theObject
    if name of theObject is "debug" then
        if notifWindow is missing value then
            load nib "Notification"
            set notifWindow to window "notification"
            show notifWindow
        else
            show notifWindow
        end if
    end if
end clicked

ウィンドウにボタンがあってクリックすると nib ファイルをロードしてその nib のウィンドウを表示する...ロードしたウィンドウを閉じて、再度ボタンをクリックしてウィンドウを表示させる。こういったことをしたいわけです。上記を実行すると、ウィンドウを閉じて再度表示するときにエラーになります。

このコード自体は珍しいものでもなく Dev Tools のサンプルの中にもありますし、今まで正常に動いていました。上記のコードを以下のようにすると動きます。

property notifWindow : missing value

on clicked theObject
    if name of theObject is "debug" then
        if not (exists notifWindow) then
            load nib "Notification"
            set notifWindow to window "notification"
            log ("Nib loaded")
            show notifWindow
        else
            log ("Nib unloaded")
            show notifWindow
        end if
    end if
end clicked

if の条件文を変更しただけです。実行すると分かるのですが、毎回ロードしています。プロパティの値を調べても、最初に nib をロードしたときのウィンドウの参照がちゃんと入っている。にもかかわらず、二度目の呼び出しでウィンドウがないと判断されてしまいます。もちろん、ウィンドウを閉じたときに解放しているのではありません。

結論。悪いのは、AppleScript Studio 1.4。また、Xcode にも問題はいっぱいあります。AppleScript からの操作がおかしい...ということがよくあります。特に、Xcode 2.1。

Xcode 1.5 は、まだまだ手放せそうにないです。

ハンドラを引数で渡す?

Perl や JavaScript や C 言語や...その他いろいろなプログラム、スクリプト言語では関数ポインタを使って汎用的な関数を作ることがあります。AppleScript で同じようなことができないか...と常々考えていました。

AppleScript は、ハンドラを変数に入れることができるのでこれが使えるかと思いましたが、上手くいきません。しかし、スクリプトオブジェクトを使えば関数ポインタのようなことができます。

Script Editor で開く

script MaxObject
    on comp(x, y)
        return x is less than y
    end comp
end script

script MinObject
    on comp(x, y)
        return x is greater than y
    end comp
end script

on run
    minOrMax({19, 16, 13, 10, 15, 20, 8}, MinObject)
end run

on minOrMax(theList, theObject)
    set firstItem to item 1 of theList
    set restList to rest of theList
    repeat with i from 1 to count of restList
        if theObject's comp(firstItem, item i of restList) then set firstItem to item i of restList
    end repeat
    return firstItem
end minOrMax

minOrMax() ハンドラは、リストの中の最小値(もしくは、最大値)を返すハンドラです。このハンドラにスクリプトオブジェクトを渡し、スクリプトオブジェクトで定義しているハンドラを呼び出しています。関数ポインタという感じではないですが...他に適切な呼び方を知りません。こういう方法をなんというのでしょう?

肝は、スクリプトオブジェクト内で同じ名前の比較ハンドラ(comp() ハンドラ)を定義していることです。こうすることで同一名のハンドラを複数作成することができ、かつ、渡すスクリプトオブジェクトによりハンドラの挙動を変えることができます。これって、よく知られているスクリプトオブジェクトの使い方なんでしょうか?

スクリプトオブジェクトは、まだまだ便利に使えそうです。もっと、研究しなくては...。

スリープの解除を検出

AppleScript でスリープから復帰したことを調べられないかと思い、探してみる。

なんのことはない。system.log にスリープとスリープ解除は書き出されているのですね。で、次のようなスクリプト。

Script Editor で開く

property theRes : ""

on run
    set theRes to do shell script "grep -i 'system wake' /var/log/system.log"
end run

on idle
    set tmp to do shell script "grep -i 'system wake' /var/log/system.log"
    if tmp is not theRes then
        set theRes to tmp
        display dialog "Wake up!" buttons {"OK"} default button 1 giving up after 15 with icon 1
    end if
    return 1
end idle

これだけでスリープ解除を調べることができました。詳しく動作の検証を行っていないのでもしかしたら動かなくなったりするかもしれません。

ちなみに、

Script Editor で開く

do shell script "grep -i 'system sleep' /var/log/system.log"

とすることでスリープを検出することができますが、何か動作を行う前にスリープしてしまい、スリープが解除されたときにスクリプトが実行されるのでこの方法は使えません。