AppleScript でファイルを開いてみましょ

Finder でファイルをダブルクリックし、ファイルを開き、アプリケーションで処理をする。もしくは、Finder でファイルを選択し、それをアプリケーションアイコンにドラッグ & ドロップしてファイルを開く...。

たかだかファイルやフォルダを開くだけなのですが、日常的にやっている作業でもあります。それゆえに自動化の恩恵はそれなりにあるのではないでしょうか。

この作業を Applecrip で代替してみましょう。


例えば未対応なアプリケーションで

「アプリケーションの起動と終了」でも書いたようにアプリケーションは AppleScript に対応しているものとそうでないものがあります。アプリケーションが AppleScript に対応していてもどのていど対応しているかが異なります。

しかし、ファイルを開くということだけならほとんどのアプリケーションで利用することができます。アーカイブユーティリティ.app は OS X に標準でついてくる圧縮されたファイルを解凍するアプリケーションですが、これは AppleScript に対応していません。しかし、基本的な命令をうけつけるアプリケーションでもあります。

では、AppleScript からファイルを開くとどうなるか?

-- デスクトップにある Archive.zip を指定
set theFile to (path to desktop folder as text) & "Archive.zip"
tell application "Archive Utility"
    -- アーカイブユーティリティで開く
    open file theFile
end tell

予想通り、圧縮されたファイルが解凍されます。このようなアプリケーションは(AppleScript で操作できているように見えますが)「AppleScript 未対応」です。本来の AppleScript に対応しているアプリケーションは「用語説明を持っている」アプリケーションです。分かりにくいのですがここでは未対応に分類します。

と、こういうことを踏まえて、AppleScript でどこまでできるのか?

ファイルを開く基本は Finder です

AppleScript に対応していなくてもファイルを開くだけであれば、Finder を通せばどんなファイルでも開けます(そのファイルを開けるアプリケーションがあれば)。

tell application id "com.apple.finder"
    -- ウィンドウがなければ終了
    if not (front Finder window exists) then return
    tell front Finder window
        -- 前面のウィンドウにある拡張子が mp4 のファイルを取得
        set fileList to document files whose name extension is "mp4"
    end tell
    -- ファイルを開く
    open fileList
end tell

これは、Finder でファイルをダブルクッリクする動作と同じで、デフォルトのアプリケーションでファイルを開きます。なにも変更していないなら、QuickTime Player が起動してファイルが表示されます。

また、Finder は開くアプリケーションを指定してファイルを開くことができます。これも AppleScript で制御できます。open の using オプションを利用します。

tell application id "com.apple.finder"
    -- ウィンドウがなければ終了
    if not (front Finder window exists) then return
    tell front Finder window
        -- 前面のウィンドウにある拡張子が mp4 のファイルを取得
        set fileList to document files whose name extension is "mp4"
    end tell
    -- ファイルを VLC.app を使って開く
    open fileList using (path to application "VLC")
end tell

この方法を使えば、テキストファイルなら CotEditor で、画像ファイルなら Photoshop でといったこともできますね。

open と Finder の open の違い

AppleScript を書き始めたころ理解しにくかったのが、同じ命令がいくつもあることでした。

Finder の open は Finder で定義されているもので using なんていう便利なオプションが追加されています。

もう一方の open は AppleScript で定義されているもので using なんてオプションはありません。でもね、見た目は一緒なんです。では、それらをどうやって見分けるのか?

これらの見分け方は命令を送っている対象が誰か?で分かります。

QuickTime Player は AppleScript に対応しており、open 命令を持っています。

tell application "QuickTime Player"
    open fileList
end tell

だけど、using というオプションはないのでただ、ファイルを開くだけです。

命令を送っている対象というのは tell 構文で指定されています。tell で指定されていないなら、それは AppleScript に最終的に送られます。

tell 構文というのは命令を送る対象を指定する構文なので、対象はアプリケーションでなくてもかまいません。

set theList to {1, 2, 4, 10.1, "文字", 0.1, "abc"}
tell theList
    length
    --> 7
    strings
    --> {"文字", "abc"}
    numbers
    --> {1, 2, 4, 10.1, 0.1}
    reverse
    --> {"abc", 0.1, "文字", 10.1, 4, 2, 1}
end tell

どのアプリケーションが持っている命令か?どこに命令を送っているか?というのは結構重要なことでこれを見極めていないとスクリプトがエラーになる、動かないといった原因になります。

再びの URL Scheme

以前にも URL Scheme のことを書いたことがありますが、あれは iPhone アプリのことでした。現在では OS X のアプリケーションでも AppleScript には対応していないけど、URL Scheme は設定されているというアプリケーションが増えてきました。それらを使ってファイルを開いてみましょう。

Apple 純正のものだと辞書.app や FaceTime.app、App Store.app、連絡先.app などがわりと有名ですね。

URL を AppleScript で開くには open location 命令を使います。この命令は URL をデフォルトのアプリケーションで開く命令です。http で始まる URL なら Safari が起動しますし、itms で始まるなら iTunes が起動します。

ちょっと余談なんですが、open location は StandardAdditions.osax という AppleScript 独自の機能拡張で定義されています。特別に変な使い方をしていない限り、StandardAdditions.osax は必ずインストールされています。

普段は意識せずに使っているので気がつきませんが、StandardAdditions.osax は AppleScript に足りない機能を追加するかなり重要なものです。AppleScript 自体に定義されている命令なんて activate、copy、count、get、launch、run、set ぐらいです(いま delay の説明を見ていたら delay はビルトインコマンドだと書かれていた。なら、StandardAdditions.osax の用語説明にのせるなよ。っていうかいつのまに変わった?)。

たかだかこれだけの命令しかないので StandardAdditions.osax がないとなにもできなかったりします。なんでこのことを長々と書いているかというと、どこで定義されている命令かということが重要なのと、OS X 10.8 以降では use を使って StandardAddition.osax の命令を使うよって宣言が必要になるからです(まぁ、面倒)。

閑話休題。

URL Scheme は基本的にはそのまま開いてしまえば、アプリケーションが起動します。

-- 連絡先.app を起動
open location "addressbook://"
-- カレンダー.app を起動
open location "ical://"
-- 辞書.app を起動
open location "dict://"
-- FaceTime.app を起動
open location "facetime://"
-- App Store.app をセキュアに起動
open location "macappstores://"

しかし、URL Scheme には便利なオプションがあります。例えば、連絡先.app なら登録している人の ID を指定して編集を行う事ができます。

tell application id "com.apple.AddressBook"
    set theList to selection
    if theList is {} then return
    set personID to id of item 1 of theList
end tell
open location "addressbook://" & personID & "?edit"

ほとんど意味のないサンプルですが、URL Scheme を使うとこういうことが出来ます。が、問題なのは URL Scheme に渡すことができるオプションや値の説明がほとんどない、ということです。

マップ.app の URL Scheme は Map Links で説明がありますが、こういう説明はほとんどの場合ありません。なので自分で検索して使い方を見つけるしかないのが現状です。これが、URL Scheme の問題点。

Apple のアプリケーションでさえまともな説明がないのですから、その他のアプリケーションなんかほとんど使い方が分かりません。使えると便利そうなのだけど、使い勝手があまり考慮されていないという...。

自分の環境で使える URL Scheme を調べるには「自分のMacで使えるURLスキームをリスト表示するコマンド - Macの手書き説明書」を参考にしてみてください。

OS X 上での URL Scheme はアプリケーションで設定されているものを使うより、自分で好きな URL Scheme を作ってそれを使うというのが便利な気がします。

この方法を使えば、AppleScript でなんでもかんでも処理できてしまいますね。

open ハンドラを使ってみる

最後に話を戻して...open についてもう一度。

これまで書いてきたように open をそのまま使ってファイルをアプリケーションに渡す...というのは AppleScript ではどちらかというとあまり利用されていない使い方だと思います。

では、どういう使い方をするのかというと open ハンドラを使ってドロップレットを作る...というのがよく見かけるものです。

on open theseItems
    -- ドラッグ & ドロップされた項目を処理します
    repeat with thisItem in theseItems
        -- ここのファイル処理を記述します
    end repeat
end open

これを Script Editor.app でアプリケーションとして保存すると、ドラッグ & ドロップを受け入れるアプリケーション(ドロップレットと呼ばれたりします)になります。

そうなんです。スクリプトはアプリケーションとして保存することができるのです。

on run
    -- ここに起動したときの処理
    display dialog "run イベントが呼び出されました"
end run
on open theseItems
    -- ドラッグ & ドロップされた項目を処理します
    display dialog "open イベントが呼び出されました"
    repeat with thisItem in theseItems
        -- ここのファイル処理を記述します
    end repeat
end open
on reopen
    -- ここに reopen イベントを受け取ったときの処理
    display dialog "reopen イベントが呼び出されました"
end reopen
on quit
    -- ここに終了時の処理
    display dialog "quit イベントが呼び出されました"
    continue quit
end quit
on idle
    -- ここに一定時間ごとに行う処理
    display dialog "idle イベントの定期処理です"
    return 60
end idle

このスクリプトを「実行後、自動的に終了しない」アプリケーションとして保存します。起動させると最初に run と idle が呼び出されます。idle はこの後、60 秒後に再度呼び出されます。

Dock にあるアイコンをクリックすると reopen が呼び出されます。Finder からなんらかの項目をドラッグ & ドロップすると open が呼び出されます。終了させると quit が呼び出されます。

完全にアプリケーションですね。で、このスクリプトをみてなにか思いつかないでしょうか?

そうです。先に書いた「基本的な命令を受けつけるアプリケーション」そのものです。実のところ、このサンプルを通しての方が「基本的な命令を受け付けるアプリケーション」っていうものが理解しやすいと思います。それらのアプリケーションはこういう中身をしているんだ、と(ごくごく乱暴にまとめてしまっていますが、動作原理を知る分にはいいでしょう...?)。

起動中のアプリケーションに run を送ったとき、なにをするかはアプリケーション次第、と書きました。run は基本的には起動時に一回処理されるだけなのですが、このサンプルをみるともう一回ダイアログが表示されますね。

open ハンドラがあれば外部からの AppleScript でファイルを開くことができます。reopen ハンドラがあれば、Dock のアプリケーションアイコンのクリックで動作します。で、どんな処理をするかはアプリケーション次第です。

こういう AppleScript アプリケーションを作って外部から操作したりするとより AppleScript の理解ははやまると思います。

では、実際に open ハンドラを使ったサンプルを作ってみます。

Finder でファイルをダブルクリックすると通常ならそのまま設定されているアプリケーションでファイルが開かれます。例えば、拡張子 md の Markdown 書類だと Xcode で開かれたりします(Xcode がインストールされていなるなら)。しかし、Atom で開きたい、もしくは CotEditor で開きたいなんて場合もあります。

Finder の「情報をみる」でファイルを開くアプリケーションを変更することはできますが、それも面倒だったりします。

特定のファイルだったら、全部 CotEditor で開いてしまえ、というものを作りましょう。が、なるべく手間をかけずにやってしまいたい。


これをアプリケーションとして保存します。拡張子 md、html、json、py のファイルをドラッグ & ドロップすることで CotEditor でファイルを開くことができます。これら以外のファイルだった場合は Finder に処理を任せてしまいます。拡張子を追加すれば、他のファイルにも対応できます。

これでいちいちデフォルトのアプリケーションを変更する必要もないですし、Finder のウィンドウのツールバーにでも置いておけば、どこからでも利用できます。また、デフォルトのアプリケーションで開きたいのなら Finder でダブルクリックすれば事足ります。

ファイルを開くってことだけで結構いろいろできるでしょう?

...と、これで話が終わればいいんですが、初心者向きのスクリプトばかりで面白みがないなぁという人(誰だ、それは)向けに。

以下は完全に蛇足です。


本当は最初に書いたスクリプトはこれなんですけどね。分かりにくいだろうし、説明も面倒なことになりそうだし...ということで採用を見送りました。

こっちの方がカスタマイズしやすいし、シンプルなんだけどね(自画自賛)。

あと、通常なら Finder でファイルをダブルクリックして開くとデフォルトのアプリケーションが起動します。

が、これを Finder のダブルクリックで AppleScript で作ったアプリケーションを起動させてしまえば、いいんじゃねと思ったのでそんなこともやってみました。例えば、JPEG ファイルをダブルクリック、AppleScript でリサイズ、Mail.app で開くみたいな感じです。

まぁ、作ってからドロップレットでいいじゃんと反省したのですが、単純に Finder のダブルクリックに反応させるにはどうしたらいいのだろう、と思ったのです。

作り方は Info.plist を編集するだけでできます。

まぁ、本当の蛇足ですね。これは。

AppleScript でアプリケーションの起動と終了

Pseudo TotalTerminal(偽の TotalTerminal)を書いていて思ったのですが、アプリケーションを起動させるってことだけでも
AppleScript ではいろいろ書き方があります。

ちょっと思いついただけでも activate、launch、open、reopen、run...と、さまざま。これらすべて挙動が異なっているのですが、どうも違いが分かりにくい。だからかどうかは分からないのですが、System Events を使ったプロセスの制御を行うスクリプトをよく見かけます。

どうもその手のスクリプトをみていると...修正したくなります。いやいやそんな面倒なことをしなくても...なんて。

たぶん日本語で読める AppleScript の説明が少ないのが一番の問題なのでしょうが。そういうことを踏まえてちょっと AppleScript の基本的なことをまとめてみます。

まずはアプリケーションの制御...起動と終了についてです。


アプリケーションの起動

activate はアプリケーションが起動していないなら起動させ、最前面に表示します。Finder でアプリケーションアイコンをダブルクリックしたときの挙動ですね。

アプリケーション起動の最も一般的な命令です。

-- アプリケーションを必要なら起動し、最前面に表示
tell application id "com.apple.Terminal" to activate
activate application id "com.apple.Terminal"

並べて書いていますが、どちらも効果は同じです(意味はちょっと違うけど、そこを深く追求しだすと分かりにくくなるので)。

activate は表示されているアプリケーションに対して使う命令です。つまり、バックグラウンドアプリケーションに対しては本来使いません(そもそもバックグラウンドアプリケーションは最前面に表示されない)。

launch もアプリケーションを起動させる命令ですが、すでにアプリケーションが起動しているならなにも効果はありません。activate のように最前面にしたりはしません。

また、アプリケーションが起動していないなら起動を行いますが、初期化処理は行いません。Terminal は通常なら、起動時にウィンドウを表示させますが、launch で起動させた場合はウィンドウ表示が行われません。

-- アプリケーションを起動させるが、初期化処理は行わない
tell application id "com.apple.Terminal" to launch
launch application id "com.apple.Terminal"

run 命令でもアプリケーションを起動させます。しかし、アプリケーションは非表示で起動されます。

-- アプリケーションを起動させるが、非表示
tell application id "com.apple.Terminal" to run
run application id "com.apple.Terminal"

すでにアプリケーションが起動している場合、run ではなにが実行されるかアプリケーションの設定により異なります。この辺りが activate や launch と異なるところです。アプリケーション側が run 命令を受け取った時にこうしなさい、というプログラムが組まれているならそれを行います。

多くの場合はなにも起こりませんが、再度、初期化処理が行われる場合もあります。

open 命令は直接アプリケーションを起動する命令ではありません。

分かりやすくいうと「ファイルをアプリケーションアイコンにドラッグ & ドロップする動作」と同じです。結果としてアプリケーションを起動させてファイルを開かせることになります。

今までの命令と異なるのは引数として開かせる対象のファイルやフォルダが必要になることです。

-- 画像ファイル
set imageFile to (path to pictures folder as text) & "2016.jpg"
-- テキストファイル
set textFile to (path to desktop as text) & "目次.md"
-- フォルダ
set theFolder to path to documents folder
-- 全てをリストでまとめる
set fileList to {imageFile, textFile, theFolder}
-- Finder の open は Finder でファイルをダブルクリックした時と同じ効果
-- 結果的に目的のアプリケーションを起動しファイル表示を行う
tell application id "com.apple.finder" to open fileList
-- 画像ファイルを他のアプリケーションで開きたいならそのアプリケーションで open
-- Safari に先のリストを渡すと...
-- 画像とテキストファイルは開く
tell application "Safari" to open fileList
-- AppleScript に対応していない Day One なら?
-- 起動はするけどなにも行われない
-- ただ、Day One が起動している時に実行すると画像ファイルを添付した新規エントリ作成
tell application "Day One" to open fileList

open 命令は AppleScript で定義されている命令ですが、多くのアプリケーションで利用できます。

もう少し詳しく説明すると、AppleScript からみてアプリケーションは以下の 3 つのタイプに分類できます。

  1. AppleScript に対応している
  2. AppleScript に対応していないが、基本的な命令を受け付ける
  3. AppleScript に全く対応していない

Day One は 2 番の「AppleScript に対応していないが、基本的な命令を受け付ける」アプリケーションです。この手のアプリケーションは多く、AppleScript でちょっとした処理が出来たりします。

最後の reopen もアプリケーションの起動として利用できます。

すでに起動しているアプリケーションの Dock にあるアイコンをクリックした時と同じ挙動を行わせる命令...といえば分かりやすいでしょうか。

run 命令に似ているのですが、reopen の場合必ずなんらかの処理が行われます。多くの場合は起動時と同じ処理...例えば、新規ウィンドウを作る等。Terminal の場合、ウィンドウがない場合は新しいウィンドウを表示します。すでにウィンドウがあるならなにもしません。

まとめましょう。

単純にアプリケーションを最前面に持ってきたいなら activate を使います。

静的にアプリケーションを起動させたいだけなら launch を使います。アプリケーションを静的に起動させ、かつ、起動時の処理も行わせたいなら run と組み合わせます。

起動しているアプリケーションに起動時の処理を再度行わせたいなら reopen を使います。

アプリケーションを起動し、ついでになんらかのファイルを開きたいなら open を使うと効率的です。

注意しなければいけないのは run や launch を起動しているアプリケーションに使ったときにどういう処理をするかはアプリケーションに依存する...というところでしょうか。実際のところこれら以外の命令であってもどのように処理するかはアプリケーション依存なのだけど。だから AppleScript でアプリケーションを操作する場合はテストが必須です。

配布されているようなスクリプトならなおさら。長いこと AppleScript を使っているけど、いまだに人が書いたスクリプトが読めないことがありますし...(これはこれで問題だ)。

アプリケーションの終了

起動したアプリケーションは終了しなければいけません(そんなことはない)。

終了は起動に比べてシンプルで quit 命令しかありません。この命令は通常の終了であり、強制終了ではありません。

quit application id "com.apple.Terminal"

ただ、アプリケーションによってはファイルの保存が必要になることがあります。ファイルが未保存の場合、多くの場合アプリケーションが保存されていないが本当に終了してもいいか?と問いかけてくることでしょう。

ときどき保存が必要なのにその確認を行わないアプリケーションもあります。こういった命令を利用するときは事前に動作確認をしておく必要があります。

また、保存は行わずに直ちに終了させたい場合があります。そういうときは quit のオプションを使います。

-- 保存されていなくても直ちに終了する
quit application id "com.apple.TextEdit" saving no
-- 保存をしてから終了する
quit application id "com.apple.TextEdit" saving yes
-- 保存するかどうか尋ねる
quit application id "com.apple.TextEdit" saving ask

状況によってこれらのオプションを使い分けます。

この命令は通常なら終了できないようなアプリケーションでも終了させることができます。分かりやすいのが Finder でしょうか。Finder は通常なら終了メニューがないので終了できませんが、quit で終了させることができます。

-- Finder の再起動
quit application id "com.apple.finder"
-- 終了処理が終わらないうちに起動命令が伝わるのでちょっと待つ
delay 1
activate application id "com.apple.finder"

Finder なら別にいいのですが、logwinwindow や SystemUIServer などという背後で動いている重要なアプリケーションでも quit を送ることができます。おそらくそれらが終了することはないのですが、なにが起きるかは分からないのでやめておきましょう。

quit 命令でよくあるのがアプリケーションの一括終了。


コメントはいつもより多めにしています。

AppleScript に対応していないアプリケーションについて

アプリケーションの起動と終了に関して見てきましたが、以上の命令は特別に AppleScript に対応していなくても使えるものです。

先にも書きましたが「AppleScript に対応していないが、基本的な命令を受け付けるアプリケーション」は AppleScript から操作できます。

では、基本的な命令とはなんでしょうか?

  1. アプリケーションを開く
  2. 書類を開く
  3. 書類を印刷する
  4. アプリケーションを終了する

これらの処理です。では、これらの処理ができるのかどうかをどうやって調べるのかというと System Events を使います。

System Events というのは OS やシステムに関する設定などを行ってくれるバックグラウンドアプリケーションです。このアプリケーションは process というクラスを持っていて、これを使うとプロセスに関する情報を調べることができます。

tell application id "com.apple.SystemEvents"
    -- Terminal が起動していないなら終了
    if not (process "Terminal" exists) then return
    tell process "Terminal"
        -- バンドル ID
        bundle identifier
        --> "com.apple.Terminal"
        -- 表示されているか隠されているか?
        visible
        --> true
        -- 最前面かどうか?
        frontmost
        --> false
        -- Unix でのプロセス番号
        unix id
        --> 16448
        -- kill してみる
        do shell script "kill 16448"
    end tell
end tell

AppleScript に関連する情報は process クラスの has scripting terminology と accepts high level events 属性を調べます。AppleScript に対応しているなら has scripting terminology が真になり、対応していなくても accepts high level events が真なら基本的な命令を受け付けることができます。

tell application id "com.apple.SystemEvents"
    -- Terminal が起動していないなら終了
    if not (process "Terminal" exists) then return
    tell process "Terminal"
        -- 基本的な命令を受け付けるか?
        accepts high level events
        --> true
        -- AppleScript 対応か?
        has scripting terminology
        --> true
    end tell
end tell

accepts high level events が真なら activate や quit といった命令は受け取れます(...と教科書通りにいけばいいのですが、実際はそうじゃないこともあります。結局、最終的には手作業で調べなきゃいけないはめになる。それが AppleScript)。

System Events にはこの他に UI Element というクラスが定義されていて、(個人的には)最終手段ともいえるプロセス自身の泥臭い操作もできます。

activate、open、quit が使えればなんとかなる...と個人的には思っているのですが、世の中の大半の人はそうではないようで、この UI Element を使ってナニか面倒なことをよくやっている人が多いです。もっと楽をしようよ。

AppleScript を使う場合、そのアプリケーションに精通している必要があります(そもそもそのアプリケーションでなにができるが分からないのに操作したいなんてナンセンス)。AppleScript を使うまでもなく、アプリケーション自体に自動化できる装置がついていたりすることもあります。

Day One のようなアプリケーションは AppleScript に対応していませんが、シェルで操作できるツールが提供されていたりします(ところで、Day One 2 が出るんですね。AppleScript 対応...とかないかな)。このツールでできるのは新規作成だけですが、Day One はユーザーの Library フォルダに記事をファイルで保存しています。これは SQLite で SQLite ならシェルで操作できます。

また、有名どころの Web サービスなんかだと API が提供されていたりします。do shell script で curl 使えばなんとかなるでしょう。

Mac 版の Kindle だと AppleScript に対応していないし、そもそも API もない。それでも URL Scheme があったりするので特定のページや書籍を開くことはできます。

こういうふうに方法はいろいろあります。もちろん状況次第ですが、なるべくなら UI Elements の利用は控えておく方がいいです。すぐに使えなくなるから。

tell application id "com.apple.SystemEvents"
    tell process "Terminal"
        -- 「ターミナル」メニュー
        tell menu bar item 2 of menu bar 1
            -- 「環境設定」を表示
            click menu item 3 of menu 1
        end tell
    end tell
end tell

メンテナンスが大変なスクリプトの一例。こういうのやウィンドウのボタンをクリックするのとか。こういうのはメニューやボタンがその場にないと動かなくなるから。で、メニューやボタンなんてバージョンアップでわりと簡単に移動するから。そもそもコメントがないとなにをやっているか分からない。

最後の方はアプリケーションの起動や終了と関係ない話でしたね。といってもこのメニューを操作してアプリケーションを終了させるなんてスクリプトをわりと見かけたりします。もう、みんな大丈夫だよね。そう、こういうときは

quit を使え

最低でも kill にしておきましょう。

Pseudo TotalTerminal(偽の TotalTerminal)

今年に入ってやっと El Capitan にアップグレードしました。今までは新しい OS は必ずクリーンインストールしていたんですが、今回は Yosemite から直接 El Capitan に。不具合が出るかどうか分かりませんが、今のところ順調。

El Capitan といえば、ちょっと前に TotalTerminal が動かない、という話題を拝見しました。

最終的には Alfred のワークフローで問題は解決したようなのですが、問題としてオモシロイと思ったので作ってみました。


TotalTerminal の挙動をすべて再現できるわけではないのですが、以下の部分を作ってみます。

ひとまずこのスクリプトを実行することで、

  • Terminalが動いていなければ起動して最前面に
  • Terminalが裏にいるなら最前面に
  • Terminalが最前面にいるなら裏に回す

ことはできるようになった。


El CapitanでTotalTerminalが動かないならAppleScriptで代用すればいいじゃない? - TOKOROM BLOG

アプリケーションの状態を調べる方法はいろいろありますが、起動しているかどうかを確認したいなら、AppleScript が定義している application クラスの running 属性を調べてみればいい。

running of application id "com.apple.Terminal"
--> false

結果は真偽値で返ってきます。true なら起動中。これで最初の「Terminal が動いていなければ起動して最前面に」は以下のようにかけます。

if not (running of application id "com.apple.Terminal") then
    -- 起動していないなら起動して最前面に
    activate application id "com.apple.Terminal"
end if

次の「Terminal が裏にいるなら最前面に」も application クラスの frontmost 属性を使って調べることができます。

if not (frontmost of application id "com.apple.Terminal") then
    -- 最前面にいないなら前面に持ってくる
    activate application id "com.apple.Terminal"
end if

frontmost が true だと最前面にいるということなので最後の「Terminal が最前面にいるなら裏に回す」は次のようになります。

tell application "Finder" to set visible of process "com.apple.Terminal" to false

ここだけ application クラスではどうにもできないので Finder で処理しています。全部を組み合わせると次のようになります。


以上をスクリプトファイルとして保存すれば完成。これでも一応動作するのですが、課題も書かれています。

現在の課題としましては、

  • Terminalが起動しているがウィンドウがないときはウィンドウがないままアクティブになる(困ってはないけど勝手にウィンドウ作ってくれればスマート)
  • かっこよくない(冗長かもだが、TotalTerminalみたいにかっこよく動くとより良い)
  • プロセスの名前が常に Terminal でよいのかがよくわからん
  • Terminalが既にカレントのときにCommand+Tabキーを送って裏に回してるけど、もっとスマートな方法あるんじゃない?

などがありそうです。


El CapitanでTotalTerminalが動かないならAppleScriptで代用すればいいじゃない? - TOKOROM BLOG

いくつかは解決できるけど、面倒なものもありますね。

Terminal が最前面に来た時にウィンドウがなければ新しく作るというのは reopen 命令を使えば解決します。

-- 最前面にいないなら前面に持ってくる
activate application id appID
-- ウィンドウがないなら新しく作る
reopen application id appID

次の「かっこよくない」というのは...ウィンドウを上からスライドして表示、非表示のことでしょうね。できなくもないのですが...ちょっと手間が必要ですね。また、いつかということで(結局、やらない)。

「プロセスの名前が常に Terminal でよいのかがよくわからん」というのは、確かに AppleScript ではいつでも問題です。

ちょっと前まではアプリケーション名がバージョンアップにより変わりましたなんてことはよくあり、その度にスクリプトの修正を繰り返していました。

それはさすがに面倒なのでアプリケーションのバンドル ID(bundle identifier)から名前を取得するようにしています。この ID が変更になることはそうそうないので...。ちなみにこの ID を書き換えるだけで他のアプリケーションにも対応できます(あら、不思議)。

最後の Command+Tab キーでの Terminal の非表示はプロセスの属性 visible で変更できるのでそれを用いています。

最後にショートカット割り当てですが Apptivate、BetterTouchTool(有料になりましたね)、Karabiner などがあります。

もちろん AppleScript を実行できるランチャーならなんでもいいのですが、個人的には割当が簡単な BetterTouchTool を使っています。


初期設定ファイルが初期化されて困る

ちょっと前からおかしかったんですよね。

Script Editor の環境設定でフォーマットを設定しても、いつの間にか初期設定に戻ってしまう。Xcode と Script Editor を使っているとそういうことがちょくちょく起きることがあった。

Script_Editor_Format_Prefs

なので、自分好みの設定をした初期設定ファイルを保管しておき、不具合があればファイルを差し替えるというスクリプトを使っていました。不便は不便だけど、すぐに自分の設定を再現できるので重宝していました。

ところが、初期設定を置き換えても設定が反映されないという不具合が起きるようになりました。

いつの頃からか初期設定の扱いが変わっていたのですね。知りませんでした。もう、てっきり Script Editor のバグだと思っていました。

OS X 10.8?それとも 10.9 辺りから初期設定がキャッシュされるようになり、設定ファイルの変更や削除を行ってもキャッシュされている設定で上書きしてしまうのですね。

セキュリティ?安全性のためなのでしょうが、アプリケーションを作成している時に初期設定をクリアしても新しい設定が反映されないのでちょっと迷惑。

設定のキャッシュは cfprefsd というプロセスが行っているようで、バックグラウンドでずっと起動しています。こいつが動いている限り、設定ファイルを置き換えても元に戻ってしまうようです。

好みの初期設定ファイルで置き換えるには次のような手順でできます。

  1. アプリケーションを一旦終了する
  2. 現在の初期設定ファイルを削除
  3. 保管している初期設定ファイルを ~/Library/Preferences に
  4. cfprefsd を kill する

cfprefsd は kill しても勝手に再起動します。これで自分好みの設定をアプリケーションに反映できます。これらの手順を AppleScript でまとめます。

このような感じですね。仕組みが分かれば、あとは応用ですね。defaults コマンドの初期設定一括削除は覚えておくと便利です。

初期設定を削除したり管理者権限を要求したりしているので、ご利用はご自身の責任で。

結局、Script Editor の初期設定が初期化されてしまうのはナゼだかわからないので根本的な解決になっていません。なにが原因か分からないんですよね...。

Xcode 6 でファイルテンプレートを自作する

Xcode 3 までは、AppleScriptObjC にもファイルテンプレートがあったんですよね...。

Xcode 3 ぐらいまでなら Xcode のカスタマイズの知識もあったのに、今ではその知識も通用しない。Xcode 6 の使い勝手を良くするために日夜努力をしています(Xcode 3 を久しぶりに起動したら日本語化されていてちょっと感動しました)。

では、 Xcode 6 にファイルテンプレートを追加するための方法を。

追加するのは AppleScriptObjC のテンプレートですが、方法や考え方は他の言語のテンプレートを作るときでも同じです。プログラミング言語用と限定すると使い勝手が悪いですが、書式の決まったテキストファイルの生成などにも応用できます。

Swift でも Objective-C でも ReadMe でも BSD ライセンスでも請求書でもなんでもござれ。

では、本題。テンプレートの作り方自体は、Xcode 4 の頃から変わりはありません。

Xcode に最初から入っているテンプレートは Xcode 内にあります。

/Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/

一方、自分で追加するテンプレートは次の場所になります。

~/Library/Developer/Xcode/Templates/

テンプレートは 2 種類あります。プロジェクトテンプレートとファイルテンプレートです。取り上げるのはファイルテンプレートです。ファイルテンプレートは Templates に File Templates をいうフォルダを作り、そこにテンプレートを配置します。

~/Library/Developer/Xcode/Templates/File Templates/

さらに File Templates/ 以下にフォルダを作ると、Xcode の新規テンプレート作成ウィンドウでカテゴリとして表示されます。例えば、ASOC というフォルダを作ると Xcode でも ASOC というカテゴリが追加されます。

Xcode_File_Template_Categories

以上、テンプレートの保存場所と分類方法です。次にテンプレートの構成要素について。

Xcode のテンプレートは必要なファイルをまとめたバンドル形式になっています。最低でも必要なファイルは以下の通りです。

  • ___FILEBASENAME___.applescript
    • テンプレートの雛形。拡張子が applescript になっているけど、swift や m など、目的に応じたもので可
  • TemplateIcon.png
    • Xcode 上で表示されるテンプレートのアイコン(48 x 48)
  • TemplateInfo.plist
    • テンプレートの詳細を記した plist ファイル

これらのファイルを拡張子 xctemplate を持ったバンドル(実際はただのフォルダ)にまとめ、Templates/ に置くと Xcode でテンプレートとして認識されるようになります(テンプレートの雛形も plist ファイルも中身が空でもいい)。

ここまでで Templates/ は次のようになっています。

Templates_Directory

実際に Xcode で新規ファイルを作成すると、テンプレート選択画面に表示されます。テンプレートの構成要素は以上です。

最後に一番大事な TemplateInfo.plist について。このファイルに記述されている内容に従って Xcode はテンプレート選択画面にオプションを追加したり、テンプレートに記述されている変数を展開していきます。

テンプレートに記述されている変数というのは、アンダースコア 3 つで囲まれたアルファベット大文字の文字列のことです。主な変数は次の通り。

  • ___FILENAME___
    • ファイル名(拡張子を含む)
  • ___FILEBASENAMEASIDENTIFIER___
    • C 言語のスタイルに変換したファイル名(拡張子を含まない)
  • ___PROJECTNAME___
    • プロジェクト名
  • ___PROJECTNAMEASIDENTIFIER___
    • C 言語のスタイルに変換したプロジェクト名
  • ___USERNAME___
    • ユーザーアカウントの名前(短縮)
  • ___FULLUSERNAME___
    • ユーザーアカウントのフルネーム
  • ___ORGANIZATIONNAME___
    • プロジェクトで設定した組織名
  • ___DATE___
    • 今日の日付
  • ___TIME___
    • 現在時間
  • ___YEAR___
    • 年を表す 4 桁の数字
  • ___COPYRIGHT___
    • コピーライト文字列
  • ___FILEEXTENSION___
    • ファイル拡張子

では、実際にテンプレートファイルに上記の変数を追加してみましょう。___FILEBASENAME___.applescript というファイルをテキストエディタで作成し以下のように記述。

--
--  ___FILENAME___
--  ___PROJECTNAME___
--
--  Created by ___FULLUSERNAME___ on ___DATE___.
--___COPYRIGHT___
--

script ___FILEBASENAMEASIDENTIFIER___
    property parent : class "NSObject"
end script

ここ、重要なのですが、このテンプレートファイルはテキストエディタで作成してください。決して Script Editor で作ってはいけません。Script Editor で作成し、保存するとファイルのエンコーディングが us-ascii になります。このエンコーディングだと Xcode で利用できなくなります。

テンプレートファイルは UTF-8、改行コード LF で保存してください。

次に TemplateInfo.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>DefaultCompletionName</key>
    <string>Empty AppleScript File</string>
    <key>Description</key>
    <string>An empty AppleScript File</string>
    <key>Kind</key>
    <string>Xcode.IDEKit.TextSubstitutionFileTemplateKind</string>
    <key>MainTemplateFile</key>
    <string>___FILEBASENAME___.applescript</string>
    <key>Name</key>
    <string>Empty AppleScript File</string>
    <key>SortOrder</key>
    <integer>1</integer>
    <key>Summary</key>
    <string>An empty AppleScript File</string>
    <key>Platforms</key>
    <array>
        <string>com.apple.platform.macosx</string>
    </array>
</dict>
</plist>

それぞれのキーは適切な値で設定され、意味は以下の通りです。

  • DefaultCompletionName(文字列)
    • ファイル保存時のデフォルトのファイル名
  • Description(文字列)
    • テンプレートの説明
  • Kind(文字列)
    • テンプレートの種類。現在、Xcode.IDEKit.TextSubstitutionFileTemplateKind しかない
  • MainTemplateFile(文字列)
    • テンプレートとして利用されるファイル名(拡張子含む)
  • Name(文字列)
    • テンプレート選択画面に表示される名前
  • SortOrder(整数値)
    • テンプレート選択画面に表示される順番
  • Summary(文字列)
    • テンプレート選択画面に表示されるテンプレートの説明
  • Platforms(配列)
    • 利用される OS。com.apple.platform.macosx なら、OS X。com.apple.platform.iphoneos なら iOS。指定がない場合はテンプレート選択画面の iOS、OS X の両方に表示される

これらを記述し、Xcode で新規ファイルを選択すると次のように表示されます。

Template_Dialog_Keys

テンプレートを選択し、進んでいくと保存できます。

Save_Dialog

以上でシンプルなテンプレートなら作れると思います。次に Xcode でオプションを入力するファイルテンプレートの作り方。

Xcode で Cocoa Class といったファイルテンプレートを選択すると、次のような画面が表示されます。

Cocoa_Source_Template_Options

新規ファイルを作成するときのオプションですね。これらのオプションは TemplateInfo.plist に Options キーとして記述することで追加できます。

Options で利用される主なキーは次の通りです。

  • Default(文字列)
    • UI のデフォルト値
  • Description(文字列)
    • UI の説明(ツールチップとして表示される)
  • Identifier(文字列)
    • UI の識別子。この識別子を使ってテンプレート内の変数を展開する
  • Name(文字列)
    • UI のラベル
  • Required(真偽値)
    • UI が必要かどうか
  • Type(文字列)
    • UI のタイプ。static、text、combo、popup、class、buildSetting 等がある
  • NotPersisted(真偽値)
    • 入力値が今後も利用されるかどうか
  • Values(配列)
    • Type で combo、popup、class を指定した時に表示される値
  • RequiredOptions(配列)
    • 他の入力値に対応して使用するか否かを選択する

これら以外にもキーはありますが、割愛。

例えば、Options の Type に text と記述しておくとテキスト入力欄が追加されます。TemplateInfo.plist に以下を追加します。

<key>Options</key>
<array>
    <dict>
        <key>Description</key>
        <string>Description of input value</string>
        <key>Default</key>
        <string>MyClass</string>
        <key>Identifier</key>
        <string>productName</string>
        <key>Name</key>
        <string>Class:</string>
        <key>NotPersisted</key>
        <true/>
        <key>Required</key>
        <true/>
        <key>Type</key>
        <string>text</string>
    </dict>
</array>

保存し、Xcode で新規ファイルを選択し、進んでいくと次のような画面が表示されます。

ASOC_Custom_Template_Options

今までは最後のファイル保存でファイル名をつける必要がありましたが、今後はここで入力した値がファイル名として利用されるようになります。

もう少し複雑なオプションをつけてみます。現在は NSObject を継承した単純なスクリプトを作るだけですが、今度は NSView を継承したスクリプトも選べるようにします。この方法を理解するために Xcode で新規の Cocoa クラスを作ってみます。

Xcode で新規ファイルを選択し、Source の Cocoa Class を選び、次に進みます。これらのオプションにはそれぞれ Identifier が TemplateInfo.plist で設定されています。

Cocoa_Source_Template_Options_Flow

Xcode はここで入力された値をつなぎ、その文字列をもとにテンプレートを選びます。この例なら NSViewControllerXIBSwift にある ___FILEBASENAME___.swift と ___FILEBASENAME___.xib が選ばれます。

Cocoa_Template_Directory

オプションから利用されるテンプレートが入っているフォルダを探し、そのフォルダ内のテンプレートファイルを開く...このような感じですね。

では、実際にやってみましょう。

TemplateInfo.plist の Options を編集します。

<dict>
    <key>Default</key>
    <string>NSObject</string>
    <key>Description</key>
    <string>What class to subclass in the new file</string>
    <key>Identifier</key>
    <string>cocoaSubClass</string>
    <key>Name</key>
    <string>Subclass of:</string>
    <key>Required</key>
    <true/>
    <key>Type</key>
    <string>popup</string>
    <key>Values</key>
    <array>
        <string>NSObject</string>
        <string>NSView</string>
    </array>
</dict>

これでポップアップメニューから NSObject か NSView を選択できるようになります。NSObject を選択すると NSObject というフォルダ内にある ___FILEBASENAME___.applescript を使ってファイルを作成します。

ASOC_Custom_Templates

以上をまとめたものを GitHub で公開しています。

触れていない部分も多々あったりします。Options の Identifier を使ったテンプレートの変数展開とか Options の RequiredOptions キーのこととか...。

Options で Identifier に fileName などと設定します。テンプレートファイル内に ___VARIABLE_fileName___ と記述しておけば、入力された値に置き換えられます。

RequiredOptions キーは、あるオプションが選択されたら利用できるようになるオプションです。NSWindowController が選択されたら、XIB も同時に作るチェックボックスが利用可能になる...といった感じです。

これらのオプションを利用すれば、そこそこ凝ったテンプレートも作れるのではないでしょうか。駆け足でしたが、Xcode 6 でカスタムテンプレートを作る方法でした。

テキストから特定のデータを抜き出す

さて。AppleSceript から Cocoa フレームワークの各機能を利用するこの企画(?)も今回で 5 回目となりました。

...なったのですが、AppleScript に関連する話題が WWDC で発表されましたね。OS X 10.10 から JavaScript でも OS X の自動化を行うことができる、と。

まぁ、長く AppleScript を触っている人にとって特別に目を引くような話題でもなんでもありません。JavaScript OSA ってものは、古くからありましたし(今はもうないのかな?)。それよりも気になるのはリリースノートを読んでいる限りでは PyObjC や RubyCocoa と同等の機能を持っているっぽいことです。

現状の AppleScript では Cocoa の機能呼び出しがスクリプトライブラリのみという制限付き。この制限が JavaScript にないのなら、JavaScript を積極的に利用する。AppleScript でも同じように機能が拡張されるといいのにな。

さらに JavaScript と AppleScript の混在ができればもっといいのにな。

Swift?

なにそれ?

おいしいの?

まぁ、ベータの Yosemite を入れればすぐに分かるんですけどね。

閑話休題。

しかし、ねえ...。ここに書いていることが Yosemite 以降、使えるのかどうか、知識が役に立つのかどうか、といったことが甚だ気になり、ブログ更新するのが億劫になるんだけど、AppleScript 自体はまだまだ現役で使えるのでがんばって更新します。

っていうか、楽になるのならなんでもウェルカムな姿勢なので、JavaScript という選択肢が増えることは大歓迎です。

おっと。また話が逸れてます。

文字列から特定のデータを抜き出す...。URL やメールアドレス、日付や電話番号などですね。この手のことがしたいなら、Automator を使えば簡単にできます。

Detect_URL_Service

このようなサービスを作っておけば、

  1. 文字列を選択
  2. コンテクストメニューでサービスを呼び出す
  3. URL がクリップボードにコピーされる

ってことができます。

こういった情報を抜き出すのに利用されているのが、NSDataDetector。 NSRegularExpression のサブクラスです。

NSDataDetector なんていまさら...な感が否めませんが、使ってみます。

NSDataDetector は抜き出したいデータを複数指定することができます。抜き出せるのは、日付、住所、URL、電話番号、飛行機などの運航情報。

  • NSTextCheckingTypeDate
  • NSTextCheckingTypeAddress
  • NSTextCheckingTypeLink
  • NSTextCheckingTypePhoneNumber
  • NSTextCheckingTypeTransitInformation.

Objective-C ではビット演算子で複数を指定することになるのですが、AppleScript にはビットを操作するような演算子はありません。

こういうときどうするかというと...、単純に足してしまいます。なんだか乱暴な感じがしますが、これでいいのです。以上をふまえてライブラリを作成します。

このライブラリを次のようにして利用します。抜き出すデータの種類はリストで渡します。

一定の書式に従っていないデータは抜き出せないという制限はありますが、十分に利用できますね。

ブログの整理

ブログの見た目を変えてみたのだけど、色々とチェックしている際に気になったのが記事内にある(主に AppleScript の)ソースコード。

見にくいですよね。

もう少し見やすくならないものかと色々と探してみました。

コードの整形やカラーリングを行うには SyntaxHighlighter なるものが主流のようですね。

なんだけど、いまいちピンとこない。

他にないかなと思いつつ探していたら、Gist のコードをそのまま貼付けることができるらしい。試しに貼ってみる。

見やすいと思うのですが、どうでしょう?

導入も簡単だし、AppleScript Editor から直接 Gist に投稿、ソースコードのリンクを作成し取得するという一連の流れがすべて AppleScript で制御できる。

問題はそれなりのスクリプトならともかく、断片のようなスクリプトをどうするかって事だけど。

とりあえずは Gist との連携でいってみようか...。