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 にしておきましょう。

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

frontmosttrue だと最前面にいるということなので最後の「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 で変更できるのでそれを用いています。

最後にショートカット割り当てですが ApptivateBetterTouchTool(有料になりましたね)、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 のタイプ。statictextcombopopupclassbuildSetting 等がある
  • NotPersisted(真偽値)
    • 入力値が今後も利用されるかどうか
  • Values(配列)
    • Type で combopopupclass を指定した時に表示される値
  • 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 がクリップボードにコピーされる

ってことができます。

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

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

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

  • NSTextCheckingTypeDate
  • NSTextCheckingTypeAddress
  • NSTextCheckingTypeLink
  • NSTextCheckingTypePhoneNumber
  • NSTextCheckingTypeTransitInformation.

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

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

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

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

ブログの整理

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

見にくいですよね。

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

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

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

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

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

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

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

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

画面共有を開始する一番簡単な方法

近くて遠い隣の部屋。そこに Mac はあるのに...。

そういう時の画面共有。便利ですね。

しかし、画面共有.app って /System/Library/CoreServices にあってアクセスするのが面倒だったりする。Dock にでも登録しておけばいいんでしょうけど、もう少し手軽にこいつを利用する方法はないものか、と。

実は AppleScript に対応していたりするんですね。画面共有.app って。

なら、話は簡単です。

まず、共有したい Mac で画面共有を有効にします。

「システム環境設定」の「共有」にある「画面共有」にチェックを入れ、共有を開始します。

このとき、コンピューターの名前を覚えてきます。これで画面共有が利用できるようになりました。

Screen_Sharing_Preferences

次に、呼び出し側の Mac で次のような Automator サービスを作成します。

Automator_Service

スクリプトは次のようなものです。

Script Editor で開く

tell application id "com.apple.ScreenSharing"
    activate
    -- 先ほど「共有」で覚えておいた相手側 Mac の名前を指定する
    GetURL "vnc://MacBook-late-2007.local" -- 適宜、書き換えてね
end tell

実行すると画面共有.app が起動し、無事に隣の部屋にある MacBook を操作することができるようになります。

最後に Automator のサービスを保存し、「システム環境設定」の「キーボード」にある「ショートカット」でサービスにショートカットキーを割り当てます。

Keyboard_Shortcut_Preferences

ここでは F5 のショートカットキーを割り当てています。

これでいつでも簡単に画面共有を起動させることができますね。