AppleScript + Core Image

Cocoaフレームワークを簡単に AppleScript で利用できるようになった...のは確かなんですが、実際のコードがなきゃもう一つ理解が進みませんし、利用もされません。

AppleScript に新機能が追加されるのはいいのですが、いつもこのあたりの情報が少なすぎていまいち浸透しない。

ってなわけで色々と公開してみます。実際の利用例ってやつを、ね。

簡単に効果が分かるってことで Core Image を使ってみます。Mac OS X 10.4 の頃に鳴り物入りで導入されましたね。GPU を使った高速な画像処理がウリです。AppleScript で Core Image を使うことで Image Events 以上の画像処理が可能になります。

Core Image の分かりやすい解説は Apple のものだったりします。英語なんですがね。

では、使ってみましょう。

iOS 7 に新しく追加された カメラフィルタがあります。これらは Core Image のフィルタとして提供されています。

iPhone カメラフィルタ一覧

もちろん、これらのフィルタは iOS だけではなく、OS X でも利用できます。手順としては次のようになります。

  1. 画像ファイルを開く
  2. フィルタを生成
  3. 画像にフィルタを適用
  4. 適用された画像を保存

それぞれ見ていきます。

画像ファイルを開く

扱う画像ファイルは JPEG のみとします。

とりあえず、新規スクリプトファイルを作ります。作成したら以下のように記述し、スクリプトバンドル形式で ~/Library/Script Libraries に CoreImage.scptd という名前で保存します。

Script Editor で開く

use framework "QuartzCore"

property CIImage : class "CIImage"

property NSString : class "NSString"
property NSURL : class "NSURL"

保存します。

スクリプト保存ダイアログ

保存したら「バンドルの内容」を表示し、「AppleScript/Objective-C ライブラリ」にチェックを入れます。

ライブラリとして利用するため「AppleScript/Objective-C ライブラリ」をチェック

再度、保存します。これでライブラリとして活用できるようになりました。では、説明。

まず、Core Image を使うので Core Image を含むフレームワークの QuartzCore.framework を取り込んでいます。Objective-C では

import <QuartzCore/CoreImage.h>

で CoreImage だけを取り込めますが、どうもそういうことができないようで QuartzCore.framework 全てを取り込んでいます。

property として CIImage、NSString、NSURL を定義していますが、これは単純に

  • タイプを短くするため
  • スクリプトを少しでも見やすくするため

です。別名を付けてもいいのですが、分かりやすい方がいいでしょう。

では、AppleScript から渡した画像ファイルを CIImage として開きます。

Script Editor で開く

on openImageFile(imageFile)
    -- imageFile: POSIX path 形式のファイルパス

    -- AppleScript text を NSString に変換
    set imageFile to NSString's stringWithString:(imageFile)

    -- ファイルパスを NSURL(file://)に変換
    set fileURL to NSURL's fileURLWithPath:imageFile

    -- CIImage を生成
    return CIImage's alloc()'s initWithContentsOfURL:fileURL
end openImageFile

なんでいちいち NSString に変換しているのか?

変換しなくてもいきなり NSURL に AppleScript の text クラスを渡しても大丈夫なようなのですが、念のためです。基本的には AppleScriptObjC には Cocoa - AppleScript での型の自動変換といった機能はありません。そのため両者では明示的に型を変換しておいた方が変なエラーで悩まなくてすみます。

CIImage を生成するメソッドはいくつかありますが、ここでは initWithContentsOfURL: を使っています。ここで気になるのは『init で作ったオブジェクトの解放は誰が担うんだ』ということですが、多分、気にしなくてもいいと思います。

ちょっと調べきれていないのですが、ASOC は ARC を採用しています。いつの間にか GC じゃなくなっていました。確か、登場当初は GC だったと思うのですが、ARC が導入されたあたりで ASOC も ARC になったんだと思います。

なので、参照されなくなった時点で解放されると思います。ほんまかいな?

AppleScriptObjC でのメモリ管理に関する情報が少なすぎてなんともいえないのですが、少なくともやってはいけないことは分かっています。それは、明示的にオブジェクトを release することです。一発で落ちるということはありませんが、そのうち AppleScript Editor が落ちます。

ARC については以下のサイトを。

ARC が採用されているからといって ARC が AppleScript で利用できるというわけではありません。あくまで AppleScript から利用している Cocoa のオブジェクトが ARC で管理されているということです。

AppleScriptObjC のメモリ管理に関しては以下を。

これぐらいしか情報がない...。OS X Mavericks 以前は、また異なるんですけどね、状況が...。

画像ファイルから CIImage が生成できたので次は ファイルタを作成します。

フィルタを生成

Core Image ではフィルタの入力に画像(または動画)を渡し、出力でフィルタが適用された画像を取り出します。基本的には、

  1. フィルタに画像を渡す
  2. フィルタの設定値を変更する
  3. フィルタから画像を取り出す
  4. 取り出した画像を次の画像に渡す(1 に戻る)...

これらの繰り返しで処理が行われます。

フィルタは CIFilter を利用します。ライブラリスクリプトの先頭の property に CIFilter を追加します。

(* 前略 *)
....
property CIImage : class "CIImage"
property CIFilter : class "CIFilter"
...
(* 後略 *)

CIFilter は「フィルタの名前を指定して生成」と「フィルタの名前と入力値を同時に指定して生成」する 2 種類の方法があります。後者はスクリプトが読みにくくなるので前者を採用します。

Script Editor で開く

on filterWithName(filterName)
    -- CIFilter をフィルタの名前で生成
    set filter to CIFilter's filterWithName:filterName
    -- フィルタの設定を初期値に
    filter's setDefaults()

    return filter
end filterWithName

このハンドラをライブラリスクリプトに追加します。

フィルタを生成した後、フィルタの設定値をデフォルトにしています。iOS では必要ないのですが、OS X では必須です。

フィルタにどんな種類があるかは以下を参照。

iOS より OS X の方が利用できるフィルタは多いです。これでフィルタも生成できるようになったので画像に適用してみましょう。

画像にフィルタを適用

iOS 7 のカメラで利用できるフィルタは以下の通りです。

  • モノ(CIPhotoEffectMono)
  • 色調(CIPhotoEffectTonal)
  • ノアール(CIPhotoEffectNoir)
  • フェード(CIPhotoEffectFade)
  • クローム(CIPhotoEffectChrome)
  • プロセス(CIPhotoEffectProcess)
  • トランスファー(CIPhotoEffectTransfer)
  • インスタント(CIPhotoEffectInstant)

括弧内は CIFiltet を生成するときに必要なフィルタの名前です。

ライブラリスクリプトにフィルタに画像を渡し、フィルタを適用し、画像を出力するハンドラを追加します。

Script Editor で開く

on applyEffect(imageFile, photoFilterName)
    -- CIImage を生成
    set imageRef to my openImageFile(imageFile)

    -- CIFiter を生成
    set filter to my filterWithName(photoFilterName)

    -- フィルタの設定値を入力
    filter's setValue:imageRef forKey:"inputImage"

    -- フィルタが適用された画像を取得
    return filter's valueForKey:"outputImage"
end applyEffect

CIFilter には色々な入力値があります。これらの値を変更することで画像に色々な処理を施すことができます。今回利用するカメラ用のフィルタには画像以外の入力値がありません。

フィルタが適用された画像は CIFilter の outputImage メソッドで取り出すことができます。

最後にこの画像をファイルに保存します。

適用された画像を保存

スクリプトライブラリでは画面への描画が行えません。Xcode で ASOC アプリケーションを作れば話は別ですが、ウィンドウやビューがない AppleScript Editor で作る ASOC アプリケーションや AppleScript のライブラリでは直接ディスプレイ上に画像を描画できません(方法はないわけではないですが、それなら Xcode で ASOC アプリケーションを作った方が手っ取り早い)。

NSBitmapImageRep は CIImage から生成するメソッドがあるのでこれを利用し、ファイルに保存します。

Script Editor で開く

on saveAsJPEG(imageRef, theFile)
    -- NSBitmapImageRep を CIImage から生成
    set rep to NSBitmapImageRep's alloc()'s initWithCIImage:imageRef

    -- NSBitmapImageRep から JPEG データを取得
    set jpegData to rep's representationUsingType:(current application's NSJPEGFileType) |properties|:(missing value)

    -- ファイルに保存
    jpegData's writeToFile:theFile atomically:true
end saveAsJPEG

このハンドラをライブラリスクリプトに追加します。また property に NSBitmapImageRep も追加します。

(* 前略 *)
....
property NSString : class "NSString"
property NSURL : class "NSURL"
property NSBitmapImageRep : class "NSBitmapImageRep"
...
(* 後略 *)

JPEG 決めうちですが、NSBitmapImageRep は他の画像形式も扱えます。また、このハンドラではもとの画像が持っている情報(Exif や位置情報)などは失われます。元の画像に上書きするのではないので問題はないと思いますが。

これで必要なハンドラが揃いました。最後にこれを利用するスクリプトを作ります。

クライアントスクリプト

Script Editor で開く

on run
    set inputFile to choose file of type {"public.jpeg"} with prompt "フィルタを適用する画像を選んでください"
    set outputFile to choose file name with prompt "保存先:" default name "Output.jpg"

    tell script "CoreImage"
        set imageRef to applyEffect(POSIX path of inputFile, "CIPhotoEffectFade")
        saveAsJPEG(imageRef, POSIX path of outputFile)
    end tell
end run

選択した画像にフェードフィルタを適用しています。実行すると以下のようになります。

フィルタ適用前/適用後

ちょっと画像が小さくて分かりにくいですが...。

スクリプトを書くのが嫌だというナマケモノには、これらの機能を簡単に試せるようなスクリプトをご用意しました。

バンドル形式になっています。バンドルの中にライブラリを入れています。スクリプトメニューに登録、もしくはそのまま開いて AppleScript Editor で実行してみてください。

AppleScript の新機能 (7) - まとめ

Mavericks というか、AppleScript 2.3 の主な変更点をまとめてきました。

これら以外にも変更点はあります。それらについて、補足...のようなまとめ書き。

全く触れなかった変更点として、

  • iCloud のサポート
  • 通知センターが利用できるように
  • Speakable-Workflow

があります。

iCloud のサポート

これは、AppleScript の書類を複数の Mac 間で共有できるってことなんですが、まだ、使ったことがない...。使ったことがないので、不具合にも遭遇していない。あると便利かなと思うのですが、AppleScript Editor 起動時のファイルオープンダイアログの表示はやめてほしいな...。

通知センター

スクリプト内から処理の経過などを通知センターで表示できるようになりました。というか、そういう命令が新しく追加されました。世の中ではこれが一番話題になっているようですね。通知センターを切ってしまっている身にはそれほど関係がない...。

スクリプトのデバック時に役に立つかな。

使い方は専用の命令を呼び出すだけ。

Script Editor で開く

display notification "通知したい本文"

オプションがいくつかあってタイトルとサブタイトル、通知時の音を指定することができます。

Script Editor で開く

display notification "通知したい本文" with title "タイトル" subtitle "サブタイトル" sound name "Glass"

ここで指定する通知音は、システム環境設定の「サウンド」に表示される音声ファイルの名前です。

System_Prefs_Sounds.png

言い換えるなら /System/Library/Sounds 以下のファイル名です。もちろん、ユーザーが追加したものも利用できます。display notification で表示される通知の設定はシステム環境設定の「通知」で変更できます。

System_Prefs_Notifications.png

ここでは AppleScript Editor が表示されていますが、display notification を利用する(した)アプレットやドロプレットはここの「通知センターで表示する項目」に追加されるので、個別に通知に関する設定が行えます。

利用するときの注意点としては、通知をスクリプトの最後に出すときは『通知を出す前にスクリプトが終了してしまうので、スクリプトの終了前にウェイトをおいてください』ってことです。具体的には、

display notification "通知です"
delay 1 -- 通知を出す前にスクリプトが終了してしまうので、1 秒停止

このような感じです。アプレットやドロップレットとしてスクリプトを保存して利用するときに必要な処理です。通知を OS に登録する以前にアプレットが終了してしまうんですね。ちょっとかっこわるいぞ。

また通知で表示されるウィンドウ(?)はクリックすることで通知を出したアプリケーションを全面に表示させることができます。

これを利用し、

  1. なんらかの処理
  2. 通知を表示
  3. 通知をクリック
  4. 再度、処理を実行

と、連続してスクリプトを実行させることができます。同じ処理を何度も繰り返すスクリプトってどんな状況か思い浮かばないのですが、通知センターから起動できるってのはちょっと便利かも。

Speakable-Workflow

これは Automator の話なのですが、Automator で作ったワークフローで Speakable Item を作ることができるようです。いったいなんのことか分からない人の方が多いような気がしますが。自分で作ったスクリプトやワークフローを音声認識を使って起動させることができます。話しかけて Mac を操作できる...と。ただし、英語に限る。

もともと、Mac には音声認識で操作するための機能は入っていました。システム環境設定の「アクセシビリティ」にある「音声認識コマンド」がそれです。

System_Prefs_Accessibility_Speech_Recognition.png

「入」にすると小さいウィンドウが表示されます。

Speech_ Recognition.png

「esc」キーを押しながら話しかけることで処理が行われます。例えば、「Switch to iTunes」などと話しかけると iTunes が前面に表示されます。

話しかけるといってもなんでもいいわけではなく、内蔵されている「コマンド」を発話しなければいけません。このコマンドは「音声コマンドウィンドウ」を表示させることで調べることができます。

Show_Speech_Commands_Window.png

標準の状態でいくつかのコマンドが入っているわけですが、ここにはない処理を行いたいとき『Automator のワークフローでも作ることができますよ』というのが、今回の新機能になります(AppleScript のスクリプトでも可能。これはかなり昔からできるようになっていました)。

コマンドは ~/Library/Speech/Speakable Items に保管されています(音声認識コマンドを利用したことがなければ、このフォルダはありません)。コマンド自体はプロパティリスト形式のテキストファイルで作成することができます(いつのまにかそうなっていたんですね)。

Automator や AppleScript で作ったコマンドもここに保管しておけば音声認識コマンドで利用することができます。ファイル名は英語で...例えば、「Go to Home Folder.scpt」などと保存しておきます。このファイル名がコマンド名になりますので、話しかけるような英語にしておくと音声認識しやすくていいです。単語一つだけだとなかなか認識されにくかったりしますし、他の似たようなコマンドと間違われてしまいます。

使いこなせれば有用で便利には違いないのですが、

  • 英語しか認識できない
  • その英語もネイティブじゃないと認識されにくい
  • その姿は冷静に考えるとけっこう滑稽

といった欠点があるため、あまり陽の目をみない機能になっています。せっかく専用のキャラクターもいたのにね。

OS_9_Speech_Recognition.jpg

その他

あえて、触れなかった部分もいくつかあります。例えば、ライブラリの用語説明の作り方。これは時間がかかるのでまたいつか、暇があれば...。

Script Library のインストール場所は 4 カ所ありますが、これも触れていませんでした。

  1. ~/Library/Script Libraries
  2. /Library/Script Libraries
  3. アプレット内の Resouces/Script Libraries
  4. アプリケーション内の Resouces/Script Libraries

アプレットやアプリケーション内におさめておけば、別にインストールしてもらう必要がないので便利かもしれません。また、これらの中に入れておくとライブラリはそのアプレット(アプリケーション)実行時のみライブラリが有効になります(言い換えると、他のスクリプトなどからは利用できない)。

コード署名についてですが、触れなかったことの中に『コード署名されたアプレット(アプリケーション)の実行時にはインターネットの接続が必要』というものがあります。これは、アプレット(アプリケーション)が有効なものかどうかを Apple に問い合わせるためです。なんで、触れなかったかというと検証していないからです。

接続されていないと起動時にエラーになりますので、頭の片隅にでもおいときましょう。

今回のバージョンアップにより、AppleScript Language Guide もアップデートされています。主に Script Libraries 関連情報の追加です。

script クラスのリファレンスを見てもらうと分かるように、id、name、version 属性が追加されています。

AppleScript Langage Guide と合わせて AppleScript Release Notes: 10.9 も公開されています。

こんなところかな?

まぁ、色々な不具合については触れてませんがね。

AppleScript の新機能 (6) - GUI Scripting

もうそろそろ新機能の話題は飽きてきた...。これで最後にしたいものです。

AppleScript のいざという時の奥の手。GUI Scripting。ここにもセキュリティの魔の手がのびてきました...。

いや。悪いことではないんですけどね。

GUI Scripting の使用/不使用は、従来はシステム環境設定の「アクセシビリティ」で一括して管理されていました。

これが OS X Mavericks では「セキュリティとプライバシー」の「プライバシー」タブにある「アクセシビリティ」で管理されるようになりました。

このため Mavericks 以前では使用を許可すると全てのアプリケーションでの使用が可能でしたが、Mavericks 以後ではアプリケーションごとに個別に使用を許可するように変更されました。

...困ったものです。以前まではアプレットの起動時に GUI Scripting が許可されているかどうかを調べ、許可されていなかったらアプレットから許可を行うことができたのですが、今後はこの方法が使えないようになったのです。

具体的には次のような処理はできません。

Script Editor で開く

(* スクリプト初期化処理... *)

tell application "System Events"
    set bool to UI elements enabled
    if not bool then
        -- GUI Scripting が利用できないなら
        set UI elements enabled to true
    end if
end tell

(* 以降の処理 *)

System Events の System Events Suite の application クラスにあった UI Elements Enabled 属性は、Process Suite の application クラスの属性に変更されています。かといって、各アプリケーション毎の状況を調べられるわけではなく、読み出しのみの属性になっています。

現状、起動時に UI Elements enabled を設定するようなアプレットやアプリケーションはその処理の時点で、処理が終了します(UI Elements enabled 属性が読み出しのみになっているのでエラーになる)。

今後は、アプレットの起動時などにシステム環境設定を開いて、アプレットやアプリケーションごとに『手動』でアクセシビリティに追加してもらう必要があります。

AppleScript にかぎったことではなく、アクセシビリティにアクセスするようなアプリケーションは(許可しない限り)軒並み同じ目に遭っているので仕方はないのですが...。

また、アプリケーション単位での許可ですので、例えば、AppleScript Editor で作っていたスクリプトをスクリプトメニューから実行させると、実行できなかったりします。AppleScript Editor だけをアクセスビリティに追加してもだめなのです。

スクリプトを利用するアプリケーション、ここでは System UI Server も追加する必要があります。もし、Terminal から osascript でスクリプトを利用するなら、Terminal も追加する必要があります。これは、Automator で作ったアプリケーションでも同様です(もちろん、アクセシビリティに関連する操作をするものだけですが)。

で、問題は AppleScript で作られたアプレットやドロップレットです。これらは毎回利用の許可を求められることがあります。なぜかというと、ここに詳しいです(やっと、日本語訳されました)。

簡単にいうと『property の値が毎回変わるため、同一のアプリケーションとして認識されない』ためです。ここにはこの問題の解決法が書かれているのですが...、まぁ、書かれているようにセキュリティの脆弱性を招くため『自分で利用するもの』だけに適用するのがいいでしょうし、でも、なるべく使わない方がいいかもしれません。

最後に。アクセシビリティに関する Terminal コマンドに tccutil というものがあります。

これはアクセシビリティにアクセスするアプリケーションの一覧をリセットすることができます(要管理者権限)。例えば、以下のようにすると『連絡先』にアクセスするアプリケーションをリセットすることができます。

$ tccutil reset AddressBook

アクセスビリティなら、

$ tccutil reset Accessibility

となります。

現状ではリセットのみ利用できます。その他の操作はできないのですが、アップデートで機能が追加されたりするのかな?

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 に対応してくれるのが一番なんだけどね。