AppleScript の新機能 (5) - コード署名

AppleScript で作るアプリケーションにコード署名がないと起動できないんですね...。

このサイトでは AppleScript のアプレットや Automator のアプリケーション、インストーラーなどを配布していたりするのですが、軒並み起動できない。困ったことに自分で作ったものをインターネット経由でダウンロードしても起動できない。俺が作ったんだけどね、それ...。

もし、アプリケーションを配布するのなら、Mac のデベロッパープログラムに参加する必要があります。年間 8,400 円(2013 年 11 月 27 日調べ)。

まぁ、システム環境設定でちょこっと設定をいじると起動できますが。

システム環境設定の『セキュリティとプライバシー』にある『ダウンロードしたアプリケーションの実行許可』を『全てのアプリケーションを許可』に変更することで、コード署名がないアプリケーションでも実行することができます。

システム環境設定 - セキュリティとプライバシー

お勧めではないですが...。通常は『Mac App Store と確認済みの開発元からのアプリケーションを許可』にしておくのがいいでしょう(デフォルトの設定です)。

では、自分で作ったアプレットやドロップレットにコード署名をつけるにはどうするか?

最初に Mac のデベロッパープログラムに参加してください(回し者?ちゃうちゃう!)。これは、絶対条件です。が、Mac App Store で配布したりするような開発者でもなく、私のようにブログなんかで細々とスクリプトやアプレットをダウンロードしているような人間には年間 8,400 円は悩んでしまう金額です。

ちなみに『セキュリティとプライバシー』の設定を変えずにコード署名がないアプリケーションを起動させるには、Finder でアプリケーションを選択し、コンテキストメニューから「開く」を選択するとできます。

Open_Application_with_Finder.png

ダイアログが表示されます。

Confirm_Application.png

これで「開く」を選択するとアプリケーションが起動します。以降、通常のアプリケーションの起動方法で利用できます。しかし、毎回ユーザーにこの方法を強いるのも...。非常時の回避手段ということで。

OS X Mavericks の AppleScript Editor 2.6 では、コード署名を自分が作ったアプレットやドロップレットに付加することができます。

AppleScript Editor の「ファイル」メニューにある「書き出し」を選択します。ファイル保存のシートが表示されるのでここで「コード署名」から適切な ID を選択します。

Applet_Export_Sheet.png

図では iPhone デベロッパーのものが表示されていますが...。ちなみに iPhone デベロッパーの ID でも試してみましたが、やはりアプリケーションは起動できませんでした(署名は追加されますが)。

ID を選択し、保存すると以下のようなダイアログが何回か表示されます。ここでは「許可」を選択します。

Permission_Dialog.png

保存が完了したらコード署名が付加されたアプレットが出来上がります。適切にコード署名が付加されているかどうかは Terminal で確認できます。

$ codesign --display -vvv MyApp.app

これでどのようなコード署名が付加されているか分かります。もちろん codesign を使って Terminal 上でコード署名を付加することもできます。

最初にアプリケーション(アプレット、ドロップレット)のバンドル ID を設定します。

Setting_BundleID_Drawer.png

ここでは com.ashplannings.MyApp としました。

次にアプリケーションパッケージ内の Resources 以下にある全てのスクリプトファイル(拡張子 scpt、scptd)を書き込み禁止にします。

$ chmod a-w MyApp.app/Contents/Resoucrces/Scripts/main.scpt

そして、codesign で以下のようにします。

$ codesign --force --sign 'Developer ID Application: YourDevName' -i com.ashplannings.MyApp MyApp.app

このとき、デベロッパとしての証明書を持っていないとエラーになります。これはデベロッパとして登録していると Apple Developer Member Center から入手できます。

アプレットやドロップレット(Automator で作ったものも)は、上記のように『書き出し』メニューを利用するか、Terminal などでコード署名を付けることができます。ですが、個人的に最も困っているのが Safari。

このサイトでは AppleScript のコードをそのまま AppleScript Editor で新規スクリプトとして作成できるリンクを掲載しているのですが、このリンクをクリックすると Safari で以下のように表示されます。

Safari_Caution_Dialog.png

これ、どうやったら開発元を追加できるんだろう...。

AppleScript の新機能 (4) - use 構文

正直...よく分からない use 構文について。

このページを見るとその効用と使い方が説明されているんですけどね。でも、今のところ必要ないかな。というか、使えない?

それとも、使い方を間違えているかな?

最初に説明を読んだときは、AppleScript Editor の環境設定にある「"tell application" ポップアップメニュー」の代替になるものだと思っていました。でも、そうではなかったのです...。

use 構文の機能は色々とあるのですが、主要なところは以下の3つ。

  • tell 構文の代替
  • ライブラリスクリプトのインポート
  • Cocoa フレームワークのインポート

それぞれみていきますが、それぞれに「?」と思うような挙動があります。

tell 構文の置き換え

use 構文でアプリケーションを指定することができます。指定することで、そのアプリケーションが持っている命令やプロパティを取り込むことができます。

AppleScript ってタイプ量が多く、スクリプトが横に横に長くなってしまいがちです。use 構文が使えるなら、タイプ長もスクリプトの見通しもかなり改善されるはずなのです。

例えば、Finder で選択しているファイルを取得するために以下のようなことをしてみます。

Script Editor で開く

use application id "com.apple.finder"

selection

AppleScript Editor 上で実行すると、AppleScript Editor で選択しているテキストを返します。おお。予想をかなり裏切っているんだけど...。

つまり、use 構文って、current application(命令等が送られる対象)を指定するものでは『ない』のですね。だから、上記のスクリプトを AppleScript Editor 上で実行すると current application である AppleScript Editor に命令が送られ、その結果が返ってくる、と。

しかし、指定したアプリケーションの用語辞書は取り込んではいる。なので同じ命令や属性を持っているアプリケーションでは用語の衝突が起きたりもします。例えば、次のスクリプトだとエラー(というか、構文確認が通らない)になります。

Script Editor で開く

use application "Finder"
use application "System Events"

disks

これを回避する方法が...ないんですね。困ったものです。一応、以下のようにするといいんですけど。

Script Editor で開く

use application "Finder"
use application "System Events"

tell application "Finder"
    disks
end tell

って、今までと一緒かーい。

と、単純に tell application "appname" が不要になるかと思えば、そう簡単でもないというお話でした。

ライブラリの指定

Script Libraries のスクリプトを指定して、その中で定義されているハンドラをインポートすることができます。

おそらく、use 構文はこのためだけに利用するのが一番もっともらしい使い方なのかも知れません。ただし、『そのスクリプトで用語辞書を定義している』なら。

Script Libraries は単純に自分で定義したハンドラ群を放り込んでおいて利用する(つまり、従来の load script 命令の代替として)だけなら便利なものなのですが、use 構文が絡むと上記の tell のときと同じように使いにくいものになります。

use script "TextUtility"

tell application "Finder"
    set theFile to file 1 of desktop
    set filename to displayed name of theFile
    toUpper(filename) -- script "TextUtility" で定義しているハンドラ
end tell

このように自分で作ったハンドラを tell 構文の中で呼び出そうとしてもエラーになるだけです。use 構文があるから使えそうなものですが、用語辞書を定義していないのでハンドラ未定義のエラーになります。誰のものかを指定する必要があります。

use script "TextUtility"

tell application "Finder"
    set theFile to file 1 of desktop
    set filename to displayed name of theFile
    toUpper(filename) of script "TextUtility" -- 誰のハンドラかを指定する
end tell

つまり、こういった用途では use 構文の出番はありません。

-- use script "TextUtility" -- 不必要なのでコメントアウト

tell application "Finder"
    set theFile to file 1 of desktop
    set filename to displayed name of theFile
    toUpper(filename) of script "TextUtility" -- 誰のハンドラかを指定する
end tell

単純にこれだけで事足ります。もし、use 構文を使ってライブラリにあるスクリプトを取り込むのなら、用語辞書が必須です。

フレームワークの指定

最も分からないのが、これです。

Script Libraries のスクリプトには Objective-C を利用できます。そのために必要な Cocoa フレームワークの取り込みに利用できる...と書かれているんですが、use 構文を使わなくても Cocoa フレームワークを取り込んでいたりします。

次のスクリプトは「連絡先」アプリケーションに登録されている人を、電話番号で検索するもの。

Script Editor で開く

on findPeople(phoneNumber)
    (*
    電話番号文字列で検索
    *)
    set str to current application's NSString's stringWithString:phoneNumber
    set ab to current application's ABAddressBook's sharedAddressBook()
    set searchElement to current application's ABPerson's searchElementForProperty:(current application's kABPhoneProperty) label:(missing value) |key|:(missing value) value:str comparison:(current application's kABContainsSubString)
    set personFound to ab's recordsMatchingSearchElement:(searchElement)

    repeat with thisItem in personFound
        set lname to (thisItem's valueForProperty:(current application's kABLastNameProperty))
        set fname to (thisItem's valueForProperty:(current application's kABFirstNameProperty))
        log {lname as text, fname as text}
    end repeat
end findPeople

まぁ、わざわざ Cocoa フレームワークを利用しなくてもいいのですが、これは動きます。use 構文でフレームワークを指定していないにも関わらず。

いやいや。怒ってくるよ。

import "ABAddressBook.h"

なんてしている人が。なに、なんちゃってスクリプトでまともに動いてんだよ、って。

利用しているのがクラスメソッドだから?

WebKit なんかの init 関係のメソッドは use 構文で フレームワークを指定していないとエラーになります。でも、QuartzCore の CIImage ではエラーにならない。

ワケが分からない。法則性がない。

その他の機能

use 構文では、これらの他に読み込むアプリケーションのバージョン指定や、利用する AppleScript のバージョン指定、指定したアプリケーションの用語辞書を読み込むかどうかの指定ができたりもします。

アプリケーションのバージョンの指定って微妙。通常なら、対象アプリケーションのバージョンを調べてからスクリプトを実行するでしょう。バージョンの指定ができたからって調べる手間が省けるわけでもなく、利用するスクリプトを変更することもできない。しかも、バージョン指定はそれまでの全てのバージョンを含んでしまいます。

バージョン 5 を を指定するとバージョン 1 も 2 も 3 も 4 も含みます。1 から 4 は必要なく、5 だけを指定ってできない。微妙...ですよね?

結論

今のところ、積極的に利用する利便性がない。今後...なのでしょうが、どうもねぇ...。

ライブラリで 利用する Objective-C

AppleScript から Objective-C を利用できるなったのは、Mac OS X 10.6 の頃のことでした。

あまり、利用されていないような気もします。まぁ、敷居が高いですからね...。

今回はそんな AppleScript/Objective-C(略して ASOC)を AppleScript 2.3 で追加されたスクリプトライブラリで利用するお話。

といっても、そんな難しい話ではなく『AppleScript の新機能 (1) - ライブラリ - ASH Planning』と同じように、AppleScript のライブラリとなんら変わるものではありません。

それでは、Finder のファイルのアイコンを変更するライブラリを作ってみます。AppleScript から Finder 項目のアイコンの変更って以前は可能でしたが、今はできません。一方、NSWorkspace にはアイコンを設定するためのメソッドがあります。それを利用します。

以下の内容で新規スクリプトを作成します。

Script Editor で開く

property NSWorkspace : class "NSWorkspace"
property NSImage : class "NSImage"
property NSString : class "NSString"
property NSURL : class "NSURL"

on setIcon(targetFile, imageFile)
    (* 
        targetFile のアイコンを imageFile に設定します
    *)

    -- AppleScript の文字列を Objective-C の NSString に変換
    set targetFile to NSString's stringWithString:targetFile
    set imageFile to NSString's stringWithString:imageFile

    -- 画像のファイルパスからファイルを読み込み NSImage を作成
    set thisImage to NSImage's alloc()'s initWithContentsOfFile:imageFile

    -- NSWorkspace の setIcon:forFile:options: を使ってアイコンを設定
    -- 返り値は真偽値
    return NSWorkspace's sharedWorkspace()'s setIcon:thisImage forFile:targetFile options:(current application's NSExclude10_4ElementsIconCreationOption)
end setIcon

これを Script Libraries に保存するのですが、Objective-C を利用するライブラリにするにはいくつか注意点があります。

  1. スクリプトバンドル(拡張子 scptd)で保存する
  2. 保存したバンドルファイルの Info.plist を変更する

とりあえず、このスクリプトを Finder Utilities.scptd という名前で Script Libraries に保存します。

バンドル形式で保存すると、AppleScript Editor でバンドルの内容を編集するためのドロワーを開くことができるようになります。

バンドルの内容を表示

このドロワーに『AppleScript/Objective-C ライブラリ』というチェックボックスがあります。ここをチェックします。

Objective-C ライブラリのチェック

チェックがないと Objective-C の利用はできません。また、ここにチェックを入れることでバンドル内の Info.plist に『OSAAppleScriptObjCEnabled』という項目が追加されます。バンドル内の Info.plist を開いて手動で追加することもできますが、AppleScript Editor を利用するのが簡単です。

ちなみに、通常のスクリプトでも Objective-C が利用できるのか?と思いきや、それは無理なようです。

新しいスクリプトを作成し、以下のようにして呼び出します。

Script Editor で開く

on run
    set targetFile to POSIX path of (choose file)
    set imageFile to "/Library/User Pictures/Sports/Basketball.tif"

    tell script "Finder Utilities"
        setIcon(targetFile, imageFile)
    end tell
end run

選択した Finder 項目のアイコンを /Library/User Pictures/Sports/Basketball.tif に変更します。

いくつか注意点はありますが、AppleScript ライブラリを利用するのとさほど変わらない手間で Objective-C が利用できました。実際、Cocoa フレームワークのメソッドを使っている以外は通常のハンドラです。

Objective-C のメソッドは、コロン(:)に続いて引数がきます。AppleScript では Objective-C のメソッドを呼び出すときにアンダースコア(_)で置き換えていたのですが、AppleScript 2.3 ではコロンの利用が可能なっています。というか、コンパイルすると勝手に置き換わってしまいます。違和感ありまくり。

クラスの参照ですが、

property NSString : class "NSString"

このような書き方と、

property NSString : a reference to current application's NSString

このような書き方があります。前者は古くからの書き方ですね(タイプが少なくてすむのでよく使ってます)。どちらも同じように動きます。違いがよく分かっていないという方が正しいけど。ですが、current application が必要なときが必ずあります。それは、クラスで定義されている enum や const を利用するときです。

先ほどのライブラリでいうなら、NSExclude10_4ElementsIconCreationOption を利用している部分です。

return NSWorkspace's sharedWorkspace()'s setIcon:thisImage forFile:targetFile options:(current application's NSExclude10_4ElementsIconCreationOption)

こういうとき current application がないとエラーになります。Objective-C の利用方法や Cocoa フレームワークの紹介なんかはいろんな情報源があるのでそちらを参照してみてください(冷たいねぇ)。

で、いろいろとある Cocoa フレームワークの AppleScript での利用方法ですが...これは次回にでも。

AppleScript の新機能 (3) - ライブラリの補足

ライブラリ機能の続きになります。

まだ続くんかいってところですが、色々と変わっているんですね。久しぶりに触ってみると。

AppleScript/Objective-C(以降は ASOC と略します)は、以前(いつ頃からだっけ?)から利用できたのですが、それが AppleScript のライブラリ機能と組み合わさるとどうなるのか?

古くは ScriptingBridge。そして、Xcode での ASOC アプリケーションのサポート。最近では AppleScript Editor で ASOC アプレットサポート...。こうやって段階を踏んできたのですが、やっと、通常のスクリプトで ASOC が利用できる環境が整ってきました。

と、Objective-C 利用の話に進もうと思ったのですが、前回の補足を少し。

まず、前提として OS X Mavericks の AppleScript 2.3 で追加された機能...。

  • スクリプトライブラリ
  • use 構文
  • AppleScript Editor でのコード署名対応
  • 通知センターのサポート

等々...。まぁ、他にもありますが。以上のような機能は全て OS X Maverics 以降でしか利用できません。特にスクリプトライブラリと use 構文には後方互換がありません。これらを積極的に利用するなら、OS X Mavericks 以前の環境は切り捨て...になります。

ただ、スクリプト内で実行環境を調べることはできるので Mavericks 以前と以後でのスクリプトの挙動を制御することは可能です。

あと、use 構文....この構文については別で取り上げようと思っていたので詳しい説明は省いていました。ただ、一点だけ。

use FinderUtilities : script "Finder Utilities"

この構文ですが、

property FinderUtilities : script "Finder Utilities"

と意味的には同じです。変数へのアサインです。これ以上でも以下でもない。唯一の違いは use を利用すると『必ず、最新のライブラリを読み込む』ということです。

いろいろ試しているのですが use 構文は単純に tell 構文を置き換えるもの...でもなく、癖があって使いにくい。長くなるのでまた今度、ということで。

あと、ライブラリを色々なところから利用するときのこと。

handlerA()
handlerB()

displayCounter() of script "Counter"

on handlerA()
    countup() of script "Counter"
end handlerA

on handlerB()
    countup() of script "Counter"
end handlerB

このようなスクリプトなんですが、このときライブラリは何回呼び出されるのか?ライブラリはスクリプト内に取り込まれた後、それが使い回されます。シングルトン?とでもいえばいいのか...(複製はできるんだけど)。そして、ライフサイクルはスクリプトが始まってから終わるまで、です。

最後に。Script factory さんからの質問。

と、いうことなんですが...。

property にスクリプトオブジェクトを読み込むと、コンパイル時の状態が保たれます。例えば、まだスクリプトはデバッグ中だよ、というフラグを作ります。

property DEBUG : true

これを他のスクリプトの property にスクリプトオブジェクトとして読み込みます。当然、読み込んだ方のスクリプトではこの状態を保ったままにします。

property DEBUG : false

デバッグが終わったのでこのように変更します。しかし、読み込んでいる方のスクリプトでは以前の状態(デバッグ中!)のままです。変更は反映されません。どうしてかというと、 property がそういう性質のものだからです(手抜きだなぁ)。property はコンパイル時に評価され、コンパイル時のスクリプトオブジェクトをそのまま保持しているんですね。変更するには再度、コンパイルする必要があります。

つまり、おおもとの(状況によっては全ての関連する)ファイルを開いて編集して再コンパイルして再保存...面倒ですね。自分で使っているぶんにはまだしも、配布しているスクリプト、ましてや実行専用のものだと利用者に労力を割いてもらわなければなりません。こういう事態は避けたいものです。

では、実行時に最新の状態を property に読み込むようにするにはどうすればいいのか?ということなんですが、個人的には一番簡単な方法を使っています。つまり、実行時に読み込む。

property theObject : missing value

set theObject of me to load script file "/script/file/path"

もしくは、

set theObject of me to my makeObject(load script file "/script/file/path")

on makeObject(theObject)
    script
        property scriptObject : theObject
    end script
end makeObject

このような感じ。

おそらく、Script factory さんの求めている答えと激しく異なっていると思いますが...。

理由としては、『確実』だからでしょうか。問題としては依存している全てのファイルが必要、管理が面倒、融通が利かない...等々、問題点の方が多いのですが。

こういう問題って、みんなどうしているんでしょう?

激しく謎です。

以上。補足でした。って、結構な長さになってしまった...。Objective-C 利用の話に進もうと思っていたのに。

AppleScript の新機能 (2) - ライブラリの続き

簡単に AppleScript のライブラリ機能について書きましたが、今度はちょっとディープな話。

スクリプト内でライブラリスクリプトを手軽に利用できるのはいいのだけど、利用しているライブラリスクリプトはスクリプトオブジェクトとは違うのかといったことを。

まず、ライブラリスクリプトはどこからでも参照できます。そして、読み込んだライブラリスクリプトはスクリプト内でユニークな存在になります。つまり、そのスクリプト上では一つしか存在しないことになります。load script 命令との最大の違いはここではないかな、と。

試してみましょう。

まず、以下のようなスクリプトを Script Libraries に保存します。

Script Editor で開く

property counter : 0

on displayCounter()
    tell me
        activate
        display dialog (counter as text)
    end tell
end displayCounter

on countUp()
    set counter of me to counter + 1
end countUp

property の値を変更するだけのものです。これを Script Libraries に Counter.scpt という名前で保存します。

次に以下のスクリプトを作成。

Script Editor で開く

on run
    -- 別のハンドラから呼び出してみる
    anotherCounter()

    -- 通常の呼び出し
    script "Counter"'s countUp()
    script "Counter"'s displayCounter()

    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s displayCounter()

    -- set 命令で変数に代入
    set newCounter to script "Counter"

    newCounter's countUp()
    newCounter's displayCounter()
end run

on anotherCounter()
    script "Counter"'s countUp()
    script "Counter"'s displayCounter()

    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s displayCounter()
end anotherCounter

実行してみると分かるのですが、どの場所から呼び出してもライブラリ側の property の値は増えていきます。set 命令で変数に割り当てても同じライブラリスクリプトを参照しています。この辺りは通常のスクリプトオブジェクトと同じですね。

何回か繰り返し実行すると分かるのですが、property の値は常に初期値から始まります。保存はされません。

では、スクリプトオブジェクトのようにライブラリスクリプトを複数作成することはできるのでしょうか?

結論から言うと、copy 命令で複製できます。

Script Editor で開く

on run
    -- copy 命令で複製
    copy script "Counter" to copiedCounter
    copiedCounter's countUp()
    -- ここでの値は 1
    copiedCounter's displayCounter()

    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s countUp()
    script "Counter"'s countUp()
    -- ここでの値は 4
    script "Counter"'s displayCounter()

    -- 両方は同じものか?
    script "Counter" is copiedCounter
    --> false

    -- 再び複製
    copy script "Counter" to newObject
    newObject's countUp()
    -- ここでの値は 5
    newObject's displayCounter()

    -- 複製されたものは同じものか?
    copiedCounter is newObject
    -- false
end run

このように複製はできますが、その時点での状態(property)も複製します。初期値を持ったままの新しいオブジェクトが必要なら、初期化ハンドラなどを含めておくか、 load script 命令で読み込むなりする必要があります。

また、ライブラリスクリプト内のスクリプトから他のライブラリスクリプトを利用することもできますし、ライブラリスクリプトの proerty、parent 指定も可能です。

前回使った Finder Utilities.scpt を使って試してみます。Finder Utilities.scpt には finderSelection() というハンドラがあります。

Script Editor で開く

on finderSelection()
    tell application id "com.apple.finder" to selection
end finderSelection

これですね。これを利用する側でちょっと拡張してみます。

Script Editor で開く

property parent : script "Finder Utilities"

finderSelection()

on finderSelection()
    tell application id "com.apple.finder"
        set selectedItems to continue finderSelection()
        if selectedItems is {} then return {}
        return sort selectedItems by creation date
    end tell
end finderSelection

このように作成日でソートした結果を返します。continue 文も利用できます。

property や parent としてライブラリスクリプトを利用するときの注意どころとしては、ライブラリスクリプトは構文確認(コンパイル)時のみ読み込まれるということ。property の挙動としては当然なのですが、久しぶりの AppleScript なんですっかり忘れてました。

Script Editor で開く

finderSelection()

on finderSelection()
    tell application id "com.apple.finder"
        set selectedItems to script "Finder Utilities"'s finderSelection()
        if selectedItems is {} then return {}
        return sort selectedItems by creation date
    end tell
end finderSelection

このように利用するのであれば、常に最新のライブラリスクリプトが利用されます。

ここまでできれば後は工夫次第で、スクリプト同士が依存したライブラリスクリプトの作成も可能ではないのかと...(複雑なケースを検証した訳ではないので突っ込まれると困りますが)。

最後に関係ないのだけど、ちょっとハマったので。次のスクリプトを実行してみると...。

Script Editor で開く

tell application id "com.apple.Finder"
    set theList to selection
end tell

tell application "Finder"
    sort theList by name
end tell

はい。エラーになります。では、次のスクリプト。

Script Editor で開く

tell application id "com.apple.finder"
    set theList to selection
end tell

tell application "Finder"
    sort theList by name
end tell

はい。動きます。

何が違うのかというと、application id の文字列。具体的には com.apple.Finder か com.apple.finder かの違い。

Finder か finder か。

正しいのは com.apple.finder。なんだけど、どれも同じように動くのです。しかし、

application id "com.apple.Finder" is application id "com.apple.finder"
--> false

この結果が false のように両者は異なったものなのです。

application "Finder" is application id "com.apple.finder"
--> true

こっちが正しい。id でアプリケーションを指定する場合、正確な id を利用しましょう。

では、再見!!

AppleScript の新機能 (1) - ライブラリ

2013 年で 20 周年なんだってね。AppleScript。気づかなかったよ。だからというわけなのかどうか知らないけれど、OS X Mavericks 上の AppleScript 2.3 においていくつかの重要な機能が追加されました。

そのうちのひとつが、AppleScript Library。今までなかったのが不思議なぐらいなんだけど、やっとライブラリを簡単に取り扱える機能が言語ベースでサポートされました。

とは言うものの、あくまで機能としてサポートされただけで、最初から使えるスクリプトが付属しているわけではなく、ライブラリは自分で拡充していくしかないのですが。

なかなか手厳しい意見があったりもしますが、ともかく既存のスクリプトをライブラリとして手軽に活用できる環境が整いました。これをどう使うかはあなた次第。使い方は簡単。まずは使ってみましょう。

すでに AppleScript を活用していて自身でスクリプトを使い回している人なら、ユーザーの Library フォルダ以下に Script Libraries というフォルダを作成しましょう(~/Library/Script Libraries)。

このフォルダの中に既存のスクリプトを移動、または保存します。スクリプトはなんでもいいです。拡張子が scpt でありさえすれば(バンドル形式のスクリプトについては別途取り扱います)。

ここでは、次のようなスクリプトを保存してみましょう。

Script Editor で開く

on finderSelection()
    tell application id "com.apple.Finder"
        return selection
    end tell
end finderSelection

on filterByExtension(targetWindow, ext)
    tell application id "com.apple.Finder"
        return files of targetWindow whose name extension is ext
    end tell
end filterByExtension

on selectItems(theseItems)
    tell application id "com.apple.Finder"
        ignoring application responses
            set selection to {}
            set selection to theseItems
        end ignoring
    end tell
end selectItems

on remove_filetype(the_file)
    tell application "Finder"
        set creator type of the_file to missing value
        set file type of the_file to missing value
    end tell
end remove_filetype

なんということもない Finder でよく使うハンドラの集まりです。このスクリプトを Finder Utilities という名前で保存します。

ライブラリスクリプトを Script Libraries にスクリプト形式で保存

新しいスクリプトを作成します。

Script Editor で開く

tell application id "com.apple.Finder"
    set theseItems to script "Finder Utilities"'s finderSelection()
    if theseItems is {} then return

    set fileList to {}
    repeat with thisItem in theseItems
        set theWindow to container of thisItem
        set ext to name extension of thisItem

        if ext is not "" then
            set fileList to fileList & script "Finder Utilities"'s filterByExtension(theWindow, ext)
        end if
    end repeat

    script "Finder Utilities"'s selectItems(fileList)
end tell

Finder で選択しているファイルと同じ拡張子を持つファイルを全て選択するスクリプトです。Finder での並び順がバラバラだったりするのでちょっと重宝します。

実行するとなんの問題もなく動きます。当然ですね。では、解説。

先ほど Script Libraries に保存したライブラリスクリプトを他のスクリプトで利用するには次の書式を使います。

script "スクリプトファイル名"

スクリプトファイル名には拡張子は必要ありません。

script "Finder Utilities"

このような感じですね。ハンドラの呼び出しは通常の参照と同じです。finderSelection() というハンドラを呼び出したいなら、

finderSelection() of script "Finder Utilities"

または、

script "Finder Utilities"'s finderSelection()

となります。

AppleScript に慣れている人なら戸惑うこともないでしょう。もちろん、引数も通常どおり渡せます。

あっけないぐらい簡単にライブラリを利用できます。この書き方は冗長なので次のようにすることも可能です。

property FinderUtilities : script "Finder Utilities"

FinderUtilities's finderSelection()

これも新しく追加された use を使って次のように書くこともできます。

use FinderUtilities : script "Finder Utilities"

FinderUtilities's finderSelection()

これらの書式すら冗長なら parent に指定することもできます(なんでもかんでも parent に指定するのは、どうかと思いますが)。

property parent : script "Finder Utilities"

finderSelection()

It's Amazing!! Let's biginning the AppleScript!!!

ってそれほどでもないか。

まぁ、既存のスクリプトを手軽に扱えるようになったのはいいことでしょう。いろんな意見もあることでしょうが、こういうものはないよりはましです。気になること(例えば、ライブラリスクリプトからライブラリを利用できるのか?等)もあるでしょうが、そういったことはまた次回にでも。Coming Soon!!

Mavericks に 古い iWork をインストールした

OS X Marvericks にアップデートし、新しい Keynote と Numbers と Pages をインストールしたものの...。AppleScript が全くサポートされていないなんて。商売上がったりだよ...(嘘だけど)。

そんなわけで、iWork '09 をインストールしたよ、ってお話。

とりあえず、古い iWork '09 がないことには話にならない。

古いインストール DVD を探し出し、おもむろにインストール。特にエラーもなくインストール完了。Apple から最新のアップデータを適用する。ここまでは何の問題もなく完了。まだ Amazon なんかでインストール DVD が売っていたりするので必要なら今のうちに買っておくのがいいかも(ただ、Amazon のレビューをみている限りでは躊躇してしまうのだけど)。

これで最新と一つ前の iWork ソフトウェアが共存することになる。

が、問題はこれから。

最新の Keynote、Pages、Numbers は AppleScript サポートがほとんど全滅なんだけど、それ以外は古いものと同じなんですね。つまり、バンドル ID やクリエータータイプ等々。たいていの人にはほとんど関係ないようなことなんだけど、これだと困るのです。AppleScript で操作するときに。

例えば、AppleScript で次のようなことをすると、最新か古いのかどちらが反応するかは運次第。

Script Editor で開く

tell application "Keynote"
    activate
end tell

どうしたものか。こうしたときに使える AppleScript のバッドノウハウ(?)がどこかにあったような...。

試行錯誤を繰り返した末たどりついたのは、アプリケーションのパスを使う方法。

Script Editor で開く

tell application "System Events"
    set appList to application processes whose visible is true

    -- プロセスの中から Keynote '09 を探す
    set targetApp to missing value
    repeat with thisApp in appList
        set appFile to application file of thisApp
        if (short version of appFile is "5.3") and (creator type of appFile is "keyn") then
            set targetApp to appFile as text
            exit repeat
        end if
    end repeat
end tell

tell application targetApp
    name -- "Keynote"
    version --> "5.3"
end tell

うん。動く。上記は System Events を使って目的のアプリケーションのファイルパスを取得していますが、直接パスを記述するのもあり。

-- アプリケーションのパスを文字列で記述する
tell application "path:to:application"
    (* code is here *)
end tell

これで今まで使っていた AppleScript を使うことができる。まぁ、本当のところは最新のアプリケーションが AppleScript に対応してくれるのが一番なんだけどね。