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

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

ちょっと思いついただけでも activatelaunchopenreopenrun...と、さまざま。これらすべて挙動が異なっているのですが、どうも違いが分かりにくい。だからかどうかは分からないのですが、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 ではなにが実行されるかアプリケーションの設定により異なります。この辺りが activatelaunch と異なるところです。アプリケーション側が 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 を使うと効率的です。

注意しなければいけないのは runlaunch を起動しているアプリケーションに使ったときにどういう処理をするかはアプリケーションに依存する...というところでしょうか。実際のところこれら以外の命令であってもどのように処理するかはアプリケーション依存なのだけど。だから 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 terminologyaccepts 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 が真なら activatequit といった命令は受け取れます(...と教科書通りにいけばいいのですが、実際はそうじゃないこともあります。結局、最終的には手作業で調べなきゃいけないはめになる。それが AppleScript)。

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

activateopenquit が使えればなんとかなる...と個人的には思っているのですが、世の中の大半の人はそうではないようで、この 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 にしておきましょう。

0 件のコメント :

コメントを投稿