Archive for 5月, 2014

2014/05/30 Scriptingを楽にするツール「ASDiff」をリリース予定

自分用に作ってものすごく長い間使い続けてきたツールが、「ASDiff」です。

asdiff_icon_2.png
▲アイコンこんなかんじ?(Take 2)

これが何かといえば、ほぼ世界に1つしかない「AppleScriptのプロパティ値のdiff」を計算する専用ツールです。AppleScriptのプログラム同士の差分であれば、各種テキストエディタ(TextWranglerなど)を用いて手軽に求められますが、プロパティ値のどこが変化したのかを検出することは(行単位のdiffでは)困難です。

ASDiffは、プロパティ中の属性値の変化を検出してお知らせします。GUI上での機能とAppleScript用語辞書中の属性値がどう対応し、どう変化するのかを調べられます(Adobe InDesignとかPhotoshopとかの巨大な辞書を持つアプリに特に有効)。

asdif1.png
▲画面は開発中のものです(出力結果のフォーマットなど変更中)

たとえば、画面上で属性値を調べたいオブジェクトを選択しておいて、AppleScriptエディタ上でプロパティを求めます。

asdif2.png

この「結果」欄の内容をASDiffの「プロパティ値1(Before)」にコピー&ペースト。

次に、画面上で選択したオブジェクトの色やフォントなどを画面上で変更して、再度AppleScriptエディタ上でプロパティを求め、結果をASDiffの「プロパティ値2(After)」にコピー&ペースト。

BeforeとAfterのプロパティ値をASDiffに入力したら、「プロパティ値の差分(Diff)を計算」をクリックすると、「Diffの結果」欄に差分が表示されます。これによって、「画面上での操作をAppleScriptでどのように属性値を書き換えれば再現できるか、手軽に調査できます。

asdif3.png

必要に迫られ、最初のバージョンをAppleScript Studioで作成し、長年メンテナンスしてきました。仕事でも趣味でも役立つツールです。

AppleScriptObjCとAppleScriptで記述した(書き直した)このツール、Mac AppStoreでリリース予定です(新バージョンももう動作しており、知り合いにレビューしてもらっています)。

AppleScriptのほぼ全文法をコンテクストメニューから入力できるようにする(強力な記述支援ツール多数付属)「コンテクストメニューアシスタント」は、オンライン書籍に付ける予定です。

2014/05/27 App Napのコントロール

Mavericksの新機能「App Nap」、ほかのウィンドウで隠れている場合にアプリのCPU利用率を抑えるもので、これをオン/オフにするAppleScript(ほとんどshell script)です。

MacBook ProでApp Napをオフにしたら、ファンが回りまくって大変だったので自分はデフォルトに戻しました。

スクリプト名:App Napを無効にする
do shell script "defaults write NSGlobalDomain NSAppSleepDisabled -bool YES"

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

スクリプト名:App Napを有効にする
do shell script "defaults write NSGlobalDomain NSAppSleepDisabled -bool NO"

tell application "System Events"
  restart
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/26 文字列中の昇順、降順ソート

与えられた文字列内の文字をソートするAppleScriptです。

文字でグラフ的なもの(度数分布的なもの、種別つき)を表現した場合に、同じ文字同士が連続した方が見やすいので、ソートにより文字列中の文字の並べ替えを行うものです。

たとえば、年齢別のスタッフの人数の度数分布を文字グラフで作るような場合に……

10代:**
20代:*
30代:****
40代:**
50代:******
60代:*

などと表現することは多々あります。ただ、これに「*」が何を指し示すか、属性を反映させようとすると……

10代:栄栄
20代:栄
30代:調調調調
40代:調調
50代:調栄調調栄調
60代:調

などとなりますが、このグラフで、

50代:調栄調調栄調

などと表現されると、分りにくい印象を受けます。そこで、本ルーチンによって、

50代:調調調調栄栄

などと整形するわけです。ただしここで、「50代:」と「調栄調調栄調」を別々に処理し、後者をソート対象とする必要があります。

数百人分のExcelデータを渡されて、事業所別にすべての事業所について、職種、性別、雇用形態などの属性値を文字で示した度数分布のグラフをテキストで出力するAppleScriptを(奥方様から)作成させられ、そのときに作った部品です。

AppleScriptの実行速度も一昔前と比べると格段に速くなったので、数千件や数万件ぐらいなら手軽に処理できるようになってきた感があります。数十万件や数百万件になるとちょっと気合いが必要ですが……。

スクリプト名:文字列中の昇順、降順ソート
set aStr to “ああおおいいううえええあああ”

set aRes to sortStrDesc(aStr)
–> “おおえええうういいあああああ”

set aRes to sortStrAscend(aStr)
–> “あああああいいううえええおお”

–文字列中の文字の降順ソート
on sortStrDesc(aStr)
  set aList to characters of aStr
  
set bList to shellSortDescending(aList) of me
  
set bStr to bList as string
  
return bStr
end sortStrDesc

–文字列中の文字の降順ソート
on sortStrAscend(aStr)
  set aList to characters of aStr
  
set bList to shellSortAscending(aList) of me
  
set bStr to bList as string
  
return bStr
end sortStrAscend

–入れ子ではないリストの昇順ソート
on shellSortAscending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) > temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortAscending

–入れ子ではないリストの降順ソート
on shellSortDescending(aSortList)
  script oBj
    property list : aSortList
  end script
  
set len to count oBj’s list’s items
  
set gap to 1
  
repeat while (gap len)
    set gap to ((gap * 3) + 1)
  end repeat
  
repeat while (gap > 0)
    set gap to (gap div 3)
    
if (gap < len) then
      repeat with i from gap to (len - 1)
        set temp to oBj’s list’s item (i + 1)
        
set j to i
        
repeat while ((j gap) and (oBj’s list’s item (j - gap + 1) < temp))
          set oBj’s list’s item (j + 1) to oBj’s list’s item (j - gap + 1)
          
set j to j - gap
        end repeat
        
set oBj’s list’s item (j + 1) to temp
      end repeat
    end if
  end repeat
  
return oBj’s list
end shellSortDescending

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/26 生年月日と基準日から年齢を求める

YYYYMMDD形式で与えられた誕生日の文字列と、算出基準となる日付のdateオブジェクトから、その算出基準日における年齢を求めるAppleScriptです。

ごくありふれたデータ処理用に作ったものですが、これまでに作りためサブルーチン(monthDiff)にちょっとだけ手を加えただけです。

スクリプト名:基準日から年齢を求める
set bDateStr to “19660914″ –”YYYYMMDD” 誕生日
set curData to date “2014年4月1日火曜日 0:00:00″ –計算基準となる日付(date object)

set anAge to getAge(bDateStr, curData)

– 基準日の日付(date Object)と誕生日のYYYYMMDDの文字列から年齢を取得する
on getAge(bDateStr, baseDate)
  
  
if length of bDateStr is not equal to 8 then return 0 –パラメータの文字数が足りない場合にはエラー
  
  
set aYear to (text 1 thru 4 of bDateStr) & “/” & (text 5 thru 6 of bDateStr) & “/” & (text 7 thru 8 of bDateStr)
  
set aY to date aYear
  
  
set dRes to monthDiff(aY, baseDate) of me
  
set ageNum to yearDiff of dRes
  
  
return ageNum
  
end getAge

–2つのdateの差を月単位で取得する
on monthDiff(aDateObj, bDateObj)
  
  
set aYear to year of aDateObj
  
set aMonth to month of aDateObj as number
  
  
set bYear to year of bDateObj
  
set bMonth to month of bDateObj as number
  
  
if aMonth > bMonth then
    set dMonth to (12 - aMonth) + bMonth
    
set dYear to bYear - aYear - 1
  else
    set dMonth to (bMonth - aMonth)
    
set dYear to bYear - aYear
  end if
  
  
return {yearDiff:dYear, monthDiff:dMonth}
  
end monthDiff

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/26 Finderのフィルタ参照にファイル名の0を無視するバグ

US AppleがホスティングしているAppleScript Users MLで話題になっていた件で、あとからMLの話題に気付いて自分でも確認してみました。

フォルダ中に、

  Test_1.png
  Test_01.png
  Test_001.png
  Test_0001.png
  Test_00001.png

などのファイルが存在している状態で、

スクリプト名:ファイルの存在確認 10.9
set aFol to choose folder
set aFolStr to aFol as string

tell application “Finder”
  tell folder aFolStr
    set aList to name of every file whose name is “Test_1.png”
  end tell
end tell
–> {”Test_00001.png”, “Test_0001.png”, “Test_001.png”, “Test_01.png”, “Test_1.png”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

といったAppleScriptを実行すると…………すべてのファイルが取得できてしまいます。

数字の後ろに「0」が付いた場合は同一視しないのですが(例:「Test_10.png」はきちんと区別する)、正確にいえばプレフィックスの「0」を無視するという症状のようです(逆に、高度かつ余計な処理を行ってしまっているような……)。

実戦レベルのAppleScriptでは、たいてい1つのフォルダだけの内容を取得することは少なくて……指定フォルダ以下のすべての階層のフォルダからファイルを取得して抽出、といった処理が多いので、逆に単純な処理すぎて見過ごしていました。

なお、本症状はMac OS X 10.6.8、10.7.5、10.8.5、10.9.3で確認されています(それ以下のOSバージョンはさすがに実行環境が……)。

迂回方法

いくつか考えられます。do shell script経由でファイル名を取得して文字列比較を行えば、とくに問題はありません。フォルダ中に入っているファイル数が数千とか数万のオーダーにのぼる場合には、do shell script経由で取得して判断しています。

ただ……そこまで大げさではない場合には、ちょっと書き換えれば大丈夫です。

スクリプト名:ファイルの存在確認 10.9_recover
set aFol to choose folder
set aFolStr to aFol as string

tell application “Finder”
  tell folder aFolStr
    set aList to name of every file whose name is “Test_1.png” and name ends with “Test_1.png”
  end tell
end tell
–> {”Test_1.png”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/21 AppleScript Libraries中で自分のパスを求める

AppleScript Librariesを実戦でまともに使えるか実験中です。その中で「ライブラリに何か実行ファイルをまぜて使えないと辛い」という話が(1人会議中に)出てきました。

この、いろいろと手の込んだことをやろうと思うと必須になる「パスの取得」を、実際にためしてみました。

まず、ダミーのAppleScriptライブラリを作成。

スクリプト名:soundIO Lib
on testMe()
  
  
set aPath to path to me
  
return aPath
  
end testMe

on test()
  return “test”
end test

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

この「soundIO Lib」というAppleScriptライブラリは、AppleScript Librariesなので保存時に「スクリプトバンドル」として保存しておく必要があります。

saveas.png

バンドルで保存して、~/Library/Script Libraries/に移動。

こんな簡単なテストScriptを新規作成して実行。

スクリプト名:pathTest
use soundIO : script “soundIO Lib” version “1.0″

set a to soundIO’s testMe()
–> alias “Macintosh HD:Users:me:Library:Script Libraries:soundIO Lib.scptd:”

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

これで、AppleScriptライブラリ中で自分(ライブラリ)自身のパスを取得できることが確認できました。

AppleScriptライブラリ(バンドル)の中に実行ファイルを入れておいて、ライブラリ中で呼び出すことができるというわけです。

# do shell scriptで呼び出すことを想定しています

実際に、バンドル内に同梱した実行ファイルをdo shell scriptコマンドで呼び出して、オーディオ入出力デバイスの確認や設定がAppleScriptライブラリを経由して行えるようになりました(システム環境設定を起動する必要なし)。

aslib10.png

2014/05/21 AppleScript Librariesを指定する際のuseコマンドにversion指定が可能

「(現状の)AppleScript Librariesがなぜまともに使えないか?」という問いがありますが、

 (1)それを好きこのんで提供しても開発者側にメリットがない(金銭面の問題)
 (2)新バージョンのライブラリを作っても自動的にアップデートする仕組みがない
 (3)ライブラリのバージョン管理の仕組みがない

といったあたりが問題でしょうか。仕事で大規模なシステムを組むさいに、部品の管理用には使えるかもしれない(バンドル内にライブラリを入れて非公開使用)という程度の認識です。

本当は、(4)として相互依存性の解消について(方法がないことを)挙げておきたかったのですが、そもそもそういう面倒なことに目をつぶる考えの能天気システムっぽいのでやめておきました。あくまでOSAX(Scripting Addition)の代替としてありがたく運用しろよ、というAppleScript Engineeringチーム側の意図がヒシヒシと感じられます。

(1)はそういう「仕組み」を作る必要があるし、(2)もないと困りますが……けっこう(3)が問題です。

古いライブラリを前提にAppleScriptを書いてしまい、実行環境にWebからダウンロードしたライブラリはバージョンの新しいものだった(バージョンの不一致)……ということはひじょーにありえます。困りますよね?

useコマンドには、

 (f1) 特定アプリケーションへのtellブロックのデフォルト化
 (f2) AppleScriptライブラリの使用指定
 (f3) AppleScript実行環境(AppleScriptラインタイム)のバージョン指定
 (f4) Scripting Additionの使用の有無の指定
 (f5) AppleScriptライブラリ中における、Frameworkのinclude指定(AppleScriptObjC)

といったはたらき(fはfunctionのf)があります。ここで、(f3)と(f2)に注目。

「AppleScriptのバージョンが指定できるんだから、AppleScript Librariesのバージョンも指定できるんじゃねーの?」

という疑問が、当然のように湧いてくるわけです。そこで、実際に試してみました。

aslib1.png

Charanさんのところで公開されていたJSON⇄record変換のライブラリを使ってみましょう(実体参照の置換/変換やエラー時の対処がないのは自分で書けということで)。

aslib2.png

バージョンが”1.0″になっていることを確認。このライブラリは~/Library/Script Libraries/フォルダに入れてあります。

おもむろに、AppleScript Librariesを指定するuseコマンドにバージョンを表記して構文確認(コンパイル)すると……何も問題なく構文確認が通ります。

このバージョン表記を”2.0″とかの「存在しないバージョンの値(大きいほうの)」にしてAppleScriptエディタ上で構文確認してみると……

aslib3.png

(日本語的に意味不明な)エラーメッセージが表示されます。Appleのリリースノートにも掲載されていないし、Sal Soghoian+Shane Stanleyが更新しているmacosautomation.com上の記事にも書かれてはいませんが、どーーもAppleScript Librariesもuseコマンドにバージョン表記を行うことでバージョン管理が行えるようです。

ちなみに、AppleScript LibrariesのバージョンチェックはAppleScriptエディタの起動時に行っているようで、ライブラリ側でバージョンをいじくっても、AppleScriptエディタをいちど終了させないとバージョンの更新が認識されないようでした(いったん終了させればOK)。

スクリプト名:recordをJSONに変換 2
–http://ashplanning.blogspot.jp/2014/03/applescript-json.html
–「AppleScript で JSON を利用する」より

use jsonLib : script “JSON Lib” version “1.0″

set theList to {“a&b”, “b”, {10, 20}, {age:20}}
set json to jsonLib’s deserialize(theList)

–>

(*
“[
\”a\”,
\”b\”,
[
10,
20
],
{
\”age\” : 20
}
]”
*)

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/17 /Usersが消えなくな「らない」iTunes 11.2.1リリース

「Macを探す」をオンにしてあると/Usersディレクトリを不可視にするという謎のiTunes 11.2のリリースから1日、異例の早さでiTunes 11.2.1がリリースされました。

iTunes 11.2からAppleScript用語辞書の変更はありません。

OS X 10.9.3関連では……GUI Scriptingのclick at命令が(構文確認時に)エラーにならなくなりましたね。まだ、実行がきちんとできているのかは確認が必要ですが、構文確認時に「エラーにならなくなった」ということは確認できています。

2014/05/16 AppleScriptエディタ上で選択部分を伏せ字に(記号だけ元のまま残す)

AppleScriptエディタ上の選択部分を伏せ字にするAppleScriptです。数字とアルファベットは伏せ字にしますが、記号などその他の文字は元のまま残します。

アルファベット大文字と数字は「X」に、アルファベット小文字を「x」に置き換え、他の記号をそのまま残しておきます。このほうが雰囲気が(全部Xに置き換えるよりも)分りやすいので、既存のScriptに手を加えてみました。

本AppleScriptは、/Library/Scripts/Script Editor Scripts/フォルダに入れて、AppleScriptエディタのコンテクストメニューから呼び出して使うことを想定しています。

サンプルをBlogに掲載するにしても、そのままでは掲載できない情報を含んでいるような場合には、本Scriptのようなツールを用いて機械的に伏せ字にしています。いちいち手で打ち直すのは面倒だし、間違いも起こるので……こうしたツールの併用は欠かせません。

ase10.png
▲AppleScriptエディタ上で伏せ字にしたい範囲のテキストを選択(本iAd identifierの内容はダミーです)

ase11.png
▲AppleScriptエディタのコンテクストメニューから……

ase12.png
▲本Scriptを呼び出す

ase13.png
▲選択範囲が伏せ字に

スクリプト名:選択部分を伏せ字に(記号だけ元のまま残す)
tell application “AppleScript Editor”
  try
    tell document 1
      set ab to contents of selection
      
set abList to characters of ab
      
      
set outStr to “”
      
repeat with i in abList
        set j to contents of i
        
        
set anID to id of j
        
        
if anID 65 and anID 90 then
          –アルファベット大文字
          
set outStr to outStr & “X”
        else if anID 97 and anID 122 then
          –アルファベット小文字
          
set outStr to outStr & “x”
        else if anID 48 and anID 57 then
          –数字
          
set outStr to outStr & “X”
        else
          set outStr to outStr & j
        end if
        
      end repeat
      
      
set contents of selection to outStr
    end tell
  end try
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/16 あまり知られていないOS標準装備のAppleScriptランチャー

本来、OS Xには「Script Menu」というメニューバーからAppleScriptを呼び出して起動できる仕組みが存在しており、一般的にはScript Menuを使うのが普通です。Script MenuはAppleScriptエディタの「環境設定」でオン/オフにできます。メニューを階層化させたり、最前面のアプリを検出してそのアプリ用だけのScriptをメニューに出すなどの機能を持っています。

scriptmenu1.png

Script Menuとは別に……OS X 10.9で標準装備された新機能があり、これがAppleScriptの起動用ランチャーとして使えます。本来は「別の用途」のために作られたものですが、ちょっとした工夫でランチャーに。

それは、システム環境設定の「アクセシビリティ」の「スイッチコントロール」です。

→ OS X Mavericks:スイッチコントロールを利用してMacをナビゲートする

sw0.png

sw1.png

パネルをユーザー定義してAppleScriptを割り当てる

スイッチコントロールから「”パネルエディタ”を開く…」ボタンで、「スイッチコントロール・パネルエディタ」を呼び出せます。

sw2.png

sw3.png

同エディタは、/System/Library/Input Methods/Switch Control.app/Contents/Resources/Switch Control Panel Editor.app に位置しており、SwitchControlというインプットメソッドの中に同梱されている補助アプリであることが分ります。

新規パネルを作成して、パネル上に自由にボタンを配置。ボタンにAppleScriptを登録して呼び出せるという寸法です。

sw4.png

sw5.png

スイッチコントロールのホットキー迂回設定

sw6.png

ただ、本来は障碍者向けの機能であり、口や足などを用いてコンピュータを操作するデバイスのサポートのために使うものなので、ちょっとした「迂回手段」を併用する必要があります。

デフォルトでは「スペースキー」がアクセシビリティ機能の呼び出しホットキーに割り当てられているためで、日本語を入力しつつ文字変換のためにスペースキーを押すと、とたんにアクセシビリティ機能(本来の障碍者向け機能)が起動されて、日本語変換ができなくなってしまいます。

そこで、USBにゲーム用コントローラ(昔、PlayStationエミュレータ「Connectix Virtual Game Station」と一緒に買ったもの)を接続して、アクセシビリティ用機能の呼び出しをゲームコントローラのボタンに差し替えました。

r0016328.JPG

スイッチコントロールでAppleScriptを呼び出す

「アクセシビリティ」で「スイッチコントロールを有効にする」にチェックを入れるとスイッチコントロールが有効になり、パレットが表示されます。

sw0.png

そこで、さきほど作成した「ASパネル」を表示させ、必要に応じてクリックすることでAppleScriptを呼び出そうという寸法です。

sw8.png

sw7.png

本当は、iPadをMac/PCの外部ディスプレイにするAirDisplayを起動して、iPad上にこのパネルを移動させてタップでAppleScriptを起動させようと試していたのですが、AirDisplayが書きかけのテキストを巻き込んでド派手にクラッシュしたのでした。

r0016327.JPG
▲iPad 3 with AirDisplay. Launch AppleScript by touching screen

2014/05/16 iTunes 11.2で用語辞書に微妙な変更

OS X 10.9.3とiTunes 11.2のアップデートがリリースされ、ひととおり(ざっくりレベルで)調べています。

「システムの時計が狂いまくる」という致命的かつ巨大な問題点が直ったのか直っていないのか、なかなか判断が難しいところですが、ntpへの問い合わせ自体はうまく動作しているように見えますし、いまのところNICTのホームページで調べても時間は合っているように見えます。

カレンダー.app(旧称iCal)とリマインダー.appの間でカレンダーが両方とも見境なく見えてしまうのは、もはや仕様ということなんだか……直す気も説明する気もないようです。

iTunes 11.2では、AppleScript用語辞書に不思議な属性値が新設されました。Applicationの属性値として、「iAd identifier」というread onlyの属性値が。

複数台のMacで同じiTunes Storeのアカウントでログインした状態でこの「iAd identifier」を比較してみたところ、同じ内容にはなりませんでした。個別のマシンを追跡するための属性値なのか、いまひとつ存在意義が分らないというか、エンドユーザーがそれを知ったところでメリットがなさそうなので、何かの「手違い」で用語辞書に載ったのではないかと推測しています。不思議ですね。

10.9.3については、/Usersディレクトリが不可視に設定されるという意味不明なバグがあったりと……βの時には起こっていなかったことがなぜリリース時に起こるのか、理解に苦しみます。

スクリプト名:iTunes 11.2でiAd Identifierが追加された
tell application “iTunes”
  properties
  
–> {class:application, name:”iTunes”, player state:playing, version:”11.2″, frontmost:false, sound volume:84, mute:false, visuals enabled:false, full screen:false, visual size:large, EQ enabled:false, fixed indexing:false, player position:547.338012695312, converting:false, current stream title:missing value, current stream URL:missing value, AirPlay enabled:false, current AirPlay devices:{AirPlay device id 26 of application “iTunes”}, iAd identifier:”XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX”}
end tell

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/15 与えられた管理権限を持つユーザーアカウントのパスワードを試す

与えられた管理者ユーザーの名前とパスワードが妥当かどうかテストを行うAppleScriptです。

毒にも薬にもならないコマンド(pwd)を指定のユーザー名とパスワードで管理者権限実行してみて、エラーが発生したらパスワードが間違っている、というロジックです。妥当性評価コマンド自体が意外と存在していないので作っておきました。

本当は、このルーチンを含む超高機能ルーチンを作ったのですが、内容が長過ぎてWordPressに投稿したら記事が見えなくなってしまいました。残念。

スクリプト名:与えられた管理権限を持つユーザーアカウントのパスワードを試す
set rootUser to “hiyoko”
set rootPass to “xxxxx”

set pRes to verifyAdminUserPassword(rootUser, rootPass) of me
–> false

–与えられた管理権限を持つユーザーアカウントのパスワードを試す
on verifyAdminUserPassword(rootUser, rootPass)
  try
    do shell script “/bin/pwd” user name rootUser password rootPass with administrator privileges
    
return true
  on error
    return false
  end try
end verifyAdminUserPassword

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/14 ASOCのアプリをSandbox対応に(Sandbox初心者編)

AppleScriptのアプリケーションをMac App Storeで販売するためには、Xcode上でAppleScriptObjCでプログラムを開発する必要があります。

AppleScriptObjCのプログラムを組むだけであれば、徐々にノウハウもたまってきていますが、2つほど関門があります。

1つは、Mac App Storeのアカウントを取得し、Code Signすること。2つ目は、アプリケーションをSandbox対応にすることです。

前者は単なる作業なので、Apple相手に担当者といろいろモメたとしても解決できないほど難しい問題ではありません。日本のAppleの担当者には非常によくしていただいた(感謝!)のですが、USの担当者とはなぜかケンカをしないと物事が前に進みません。これが、いわゆる開拓者精神というやつなのでしょうか?

sand4.png
▲開発者アカウントを取得してプロファイルをダウンロードするとシステム環境設定で「プロファイル」が有効になる

脱線しましたが……問題は、後者の「Sandbox対応」です。

sand0.png

XcodeプロジェクトのSandbox化設定

AppleScriptObjCのプロジェクトをSandbox化する「だけ」なら、それほど大それた設定が必要なわけではありません。Xcode 5.1.1上だと……Xcode上でプロジェクトの「TARGETS」を選択して、「App Sandbox」をオンにするだけです。

この作業を行うと、「○○.entitlements」ファイルがプロジェクトに追加されるので、必要に応じてこのファイルを編集するだけです。主に、entitlementsファイルの編集が難関というか、この先苦労するんだろうなぁというポイントです(まだ、Sandbox化については自前で調査し始めたところなので)。

コントロール対象のアプリを登録する2つの方法

AppleScriptObjCで作ったアプリをSandbox対応させるのは、前述のとおりXcode上の設定だけで済みます。

ただ、AppleScriptで作ったアプリケーションで「他のアプリケーションをコントロールしない」というケースは少ないものと思われます。そこは、いろんなアプリケーションをこづき回して叩きまくって仕事をさせることでしょう。それこそ、AppleScriptの醍醐味(サディスト味)。

脱線しますが……たとえば、InDesignをコントロールするにしても、JavaScriptからコントロールした場合にはInDesign自体のクラッシュを検知して立ち上げ直すとか、クラッシュからのリカバリを行うこと自体が無理です。AppleScriptなら、そこもすべて対応して夜間回しっぱなしの無人運転まで対応できます。複数のアプリをまたがってのコントロールも重要なポイントです。

話を元に戻しましょう。

WWDC2012のSessionの資料を確認するに、App Sandbox対応したアプリ中で他のアプリをコントロールする場合には、entitlementsファイル中にその旨を宣言する2つの方法があると説明されています。

(1)「com.apple.security.scripting-targets」エントリをentitlementsファイルの中に作成し、操作対象のアプリのオブジェクトとか命令とか属性値を細かく記述(できればやってね、と記載あり)

(2)「さもなければ」「com.apple.security.temporary-exception.apple-events」エントリをentitlementsファイル中に作成し、その中にコントロール対象のアプリケーションのBundle IDを記述

Mac App Storeで購入したアプリやらApple純正アプリやらをこじ開けて、参考になるようなentitlementsファイルがないか探してみたものの……見つかりません。つまり、「ありものを参考にする」という方法が(どういうわけか)とれないので、「とりあえず」ここは簡単な(2)でやってみるしかないでしょう。

ただ……将来的に(2)はすべて禁止で(1)しか受け付けない、という方向に移行したいんだろうなぁ、という意向はひしひしと感じます。

コントロール対象のアプリのBundle IDを登録

とりあえず、(1)のことは忘れて、「com.apple.security.temporary-exception.apple-events」エントリをentitlementsファイル中に作成することにしてみましょう。

sand2.png

なにげに、SafariのBundle IDが「com.apple.safari」ではなく「com.apple.Safari」だったりして引っかけ要素がないでもないですが、とりあえず登録しておけば大丈夫です。

entitlementsに登録していないアプリケーションにASOCプログラム中からコントロールしようとすると、「起動していない」とログにエラーが出力されます。

run scriptでテキストから生成・実行したScriptも監視対象?

Sandbox環境下のAppleScriptの挙動については素朴な疑問がいくつかあるわけですが、「動的にテキストから生成したAppleScriptを実行しても、監視対象になるのか?」というのが最初の疑問。

実際に試してみたら、「監視対象になる」ことが分りました。テキストから生成・実行するAppleScript内でコントロールするアプリケーションのbundle idをあらかじめ登録しておけば大丈夫です。ただ、事前に対象アプリケーションが分らないとか(choose applicationで選択)いう場合には……ダメだと思います。Bundle ID指定にワイルドカードは効かないでしょうから(後述)。

choose fileとかできるの?

ただ単にSandbox化しただけでは禁止されます。「App Sandbox」の「File Access:」で「User Selected File」を「Read Only」ないし「Read/Write」に設定すればchoose fileやchoose folderが有効になります。

Bundle内のAppleScriptをload scriptできるの?

AppleScriptObjCのプログラム内で外部のプログラムをコントロールする場合には、コントロール部分を別ファイルに分離して、AppleScriptObjCのプログラム内からload scriptで呼び出すのが安全です。AppleScriptObjCプログラム内では通常のAppleScriptの挙動が変わったり、エラーになるケースが多々あるためです(このあたりは、AppleScript Studioの時もそうでした)。

2013年10月の段階(OS X 10.9のリリースから間もない時期)では、サンドボックス化したAppleScriptObjCプログラム内でload scriptを行うとエラーになっていたようです(MacScripter.netの議論にそのような痕跡が残っています)。現在、OS X 10.9.2上でload scriptを行ってもエラーにはなりませんでした。

ただし、一応念のためにバンドル内に同梱する(Xcode Projectに追加する)AppleScriptについては、(AppleScriptエディタで)保存時にも念のためにCode Signし、保存形式も「実行専用」を指定し、保存後にFinder上で「読み出しのみ」にパーミッションを設定しておきました。

アクティビティモニタで見てもサンドボックス有効になっていないみたいだけど?

Sandboxの配下で動いているはずなのに、アクティビティモニタでプロセスを確認して見ても(画面上では)サンドボックス内で動作しているように見えない、というケースに多々遭遇しました。

sand1.png

かなり不思議に思っていたのですが、sandboxd(デーモン)(なりアクティビティモニタ)がアプリケーションの判定結果をキャッシュしている(普通、初回起動後にsandbox対応状態が変わるアプリなどないはず)ものと見て、entitlementsの内容をGUI側からいじくって項目を増やしてみた(無意味に「ピクチャ」フォルダへのアクセス権を付加したり)ところ、AppleScriptObjCプログラムをビルドして実行すると「サンドボックス=はい」に表示が変わりました。

追記1:choose applicationによる事前指定のない任意アプリの制御

テストのために、

  –choose applicationによるアプリの任意選択制御(多分だめ)
  tell current application
    set anApp to choose application
  end tell

  tell anApp to activate

というコードを書いてSandbox対応したAppleScriptObjCアプリの中で実行してみたら……

sandtest[31876:303] *** -[AppDelegate clicked:]: Adobe Photoshop CS3 got an error: Application isn’t running. (error -600)

と、コンソールにエラーは出るものの、操作対象として指定したアプリケーションが起動することはしますね。

追記2:Finder経由でBundle IDを指定してアプリケーションを起動

パスやアプリケーション名称を指定した場合には、Sandboxに阻まれて起動ができなかったりしますが、FinderにはBundle IDを指定して特定のデータを指定のアプリケーションでオープンするという(AppleScript向けの)機能があります。

つまり、事前に宣言していなくても……Finder(com.apple.finder)の制御についてentitlementsに記述しておけば、指定ファイルを任意のアプリケーションでオープンさせるとか……Finder経由でアプリケーションの起動だけならできてしまうのでは? という疑問を持って実験してみました。

  –Finder経由でアプリケーションのオープン
  tell application “Finder”
    open application file id “com.apple.addressbook”–「コンタクト」を起動
  end tell

……できます(^ー^; 予想どおり、できてしまいます。ただ、sandboxdが許してもMac App Storeへの申請が通るかどうかは定かではありません。

まだまだ苦難は続く

entitlementsファイルの記述については、前述のとおり、(2)の操作対象アプリケーションのBundle IDを書く方法とは別に、(1)の「com.apple.security.scripting-targets」エントリを作成して、アプリケーション内のオブジェクトやら命令やらを書く、という(よりまっとうな)やり方があり、もしかしたらMac App Storeへの申請時に(1)を求められる可能性もあります。

ただ、いろいろ調べてみた上で書きますが、「ちょっと無理じゃないの?」というのが(1)についての正直な感想です。

最初に、WWDCのビデオを見たときに感じたのは「じゃあ、GUI Scriptingで動的にGUI elementを検出してコントロールするような場合には全滅じゃないか!」(事前にすべて調査して書いておくなんて無理?)ということでした。

昨今の動向としては明らかに「有用性」よりも「セキュリティ」にAppleの重視するポイントが変わりつつあり、1デベロッパーがUS Appleの担当者に噛み付いたり吼えたりしても影響力が及ばないところでしょうか。ぜひ、USのデベロッパーのUS Apple担当者への噛み付きや乱闘に期待したいところです。

2014/05/13 選択中のフォルダ内のすべてのファイルの作成日、変更日をcurrent dateに変更 v2

Finder上で選択中のフォルダ内のすべてのファイル/フォルダの作成日、変更日を現在時刻に変更するAppleScriptの、OS X 10.9対応版です。

実行のためには、Xcodeのインストールが必要です(/usr/bin/SetFileコマンド)。

currentdater.png

Script Menuに入れて、サンプルのXcodeプロジェクトの作成日/修正日の日付をまとめて変更するために作ったものです。

スクリプト名:選択中のフォルダ内のすべてのファイルの作成日、変更日をcurrent dateに変更 v2
set aCurDate to current date
set aDateStr to makeMMDDYYYYhhmmssStr(aCurDate) of me

tell application “Finder”
  set aSel to selection as alias list
  
if aSel = {} or aSel is equal to missing value then
    display dialog “何も選択されていません。” buttons {“OK”} default button 1
    
return
  end if
end tell

repeat with i in aSel
  
  
set j to contents of i
  
set aKind to detectFolder(j) of me
  
  
–選択中のアイテムがフォルダの場合にはその中のファイル一覧を取得
  
set fList to {}
  
  
if aKind = true then
    tell application “Finder”
      tell folder (j as string)
        set fList to entire contents as alias list
      end tell
    end tell
  else
    –選択中のアイテムがファイルの場合にはそのままリストに入れる
    
set fList to {j}
  end if
  
  

  
repeat with i in fList
    set j to contents of i
    
set aFres to detectFolder(i) of me
    
changeCreationDate(j, aDateStr) of me
  end repeat
  
end repeat

–aliasがFolderかどうか判定
on detectFolder(aSelFol)
  set aInfo to info for aSelFol as alias
  
tell application “Finder”
    try
      set aFol to kind of aInfo
    on error
      return false
    end try
  end tell
  
  
return (aFol = “フォルダ”) –”Folder” in Japanese, change here to each localized string
end detectFolder

–作成日と修正日を変更する
on changeCreationDate(aFile, aDateStr)
  set bP to POSIX path of aFile
  
  
try
    do shell script “/usr/bin/SetFile -d “ & quoted form of aDateStr & ” “ & quoted form of bP –作成日
    
do shell script “/usr/bin/SetFile -m “ & quoted form of aDateStr & ” “ & quoted form of bP –修正日
  end try
  
end changeCreationDate

–DateオブジェクトからMM/DD/YYYY hh:mm:ssの形式の文字列を返す
on makeMMDDYYYYhhmmssStr(aDate)
  –Dateオブジェクトから各要素を取り出す
  
set yStr to (year of aDate) as string
  
set mStr to (month of aDate as number) as string
  
set dStr to (day of aDate) as string
  
set hhStr to (hours of aDate) as string
  
set mmStr to (minutes of aDate) as string
  
set ssStr to (seconds of aDate) as string
  
  
–桁数を合わせる
  
set y2Str to retZeroPaddingText(yStr, 4) of me
  
set m2Str to retZeroPaddingText(mStr, 2) of me
  
set d2Str to retZeroPaddingText(dStr, 2) of me
  
set hh2Str to retZeroPaddingText(hhStr, 2) of me
  
set mm2Str to retZeroPaddingText(mmStr, 2) of me
  
set ss2Str to retZeroPaddingText(ssStr, 2) of me
  
  
return (m2Str & “/” & d2Str & “/” & y2Str & ” “ & hh2Str & “:” & mm2Str & “:” & ss2Str)
end makeMMDDYYYYhhmmssStr

–数値にゼロパディングしたテキストを返す
on retZeroPaddingText(aNum, aLen)
  set tText to (“0000000000″ & aNum as text)
  
set tCount to length of tText
  
set resText to text (tCount - aLen + 1) thru tCount of tText
  
return resText
end retZeroPaddingText

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/13 ASOCでウィンドウのオープン、クローズを行う

AppleScriptObjCでウィンドウのオープン、クローズを行うサンプルです。

ずいぶんと前に作っておいたものです。

win1.png
▲プロジェクトをビルドして実行するとメインウィンドウを表示

win2.png
▲メインウィンドウのボタンをクリックするとサブウィンドウを表示

win1.png
▲サブウィンドウのボタンをクリックするとサブウィンドウをクローズ

→ Xcodeプロジェクトのダウンロード(38K)

AppleScriptObjCファイル名:windowCloseAppDelegate.applescript

– windowCloseAppDelegate.applescript
– windowClose

– Created by Takaaki Naganoya on 10/11/17.
– Copyright 2010 Takaaki Naganoya. All rights reserved.


script windowCloseAppDelegate
  property parent : class “NSObject”
  
property aWindow : missing value –main window
  
property bWindow : missing value –sub window
  
  on applicationWillFinishLaunching:aNotification
    – Insert code here to initialize your application before any files are opened
  end applicationWillFinishLaunching:
  
  on applicationShouldTerminate:sender
    – Insert code here to do any housekeeping before your application quits
    
return current application’s NSTerminateNow
  end applicationShouldTerminate:
  
  –main windowのopenボタンを押した
  
on clicked1:sender
    bWindow’s makeKeyAndOrderFront:sender –Windowを表示状態に
  end clicked1:
  
  –sub windowのcloseボタンを押した
  
on clicked2:sender
    bWindow’s performClose:sender
  end clicked2:
  
end script

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/12 Deskop Pictureのパスを取得する

AppleScriptで現在設定中のデスクトップピクチャを取得するAppleScriptです。

画面(=デスクトップ)が1つの場合だけとは限りません。複数のモニターを接続している場合には、それぞれについて調査する必要があります。

r0016321.JPG

Finderに問い合わせると、メインディスプレイ(プライマリ)のデスクトップピクチャのファイルパスを取得できます。

スクリプト名:デスクトップピクチャを取得1
tell application “Finder”
  set aDskP to (desktop picture) as alias
end tell

–> alias “Macintosh HD:Library:Desktop Pictures:Galaxy.jpg”

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

複数のモニタが接続されている場合の調べ方です。(単数か複数かで)いちいち場合分けするよりも、こちらの方法で取得すべきです。Finderに問い合わせるやり方は、「盲腸」みたいなものと考えるべきでしょう。

スクリプト名:すべてのデスクトップのデスクトップピクチャを取得
set pictList to {}

tell application “System Events”
  set dCount to count every desktop
  
repeat with i from 1 to dCount
    tell desktop i
      set aPic to (picture as POSIX file) as alias
      
set end of pictList to aPic
    end tell
  end repeat
end tell

pictList
–> {alias “Macintosh HD:Library:Desktop Pictures:Galaxy.jpg”, alias “Macintosh HD:Users:me:Pictures:Desktop Picrures HD:01543_falls_1920×1200.jpg”, alias “Macintosh HD:Library:Desktop Pictures:Wave.jpg”}

▼新規書類に ▼カーソル位置に ▼ドキュメント末尾に

2014/05/03 AppleScriptエディタのクラッシュが頻発するときには

たまたまiTunesをコントロールして大量にiTunesからデータを受け取るようなAppleScriptを書いていて、こんなシンプルなものが不具合を起こすはずがない……というぐらいシンプルなものでした。

数回実行して問題がないことを確認していたものの、あるタイミングを境に、AppleScriptエディタがクラッシュするようになりました。でも、AppleScriptの内容はただフィルタ参照でiTunesのライブラリから特定の条件に合うものを抽出するだけの、ひじょうに単純な内容。

こんな時、一番よい解決策は……あきらめて寝てしまうことです。一晩寝れば、案外よいアイデアが浮かんでくることもあります。

翌日、ちょっと思いついたことがあったので試してみたところ……大正解! 問題はウソのように解決しました。

自分が書いたAppleScriptのバグでもなく、AppleScriptの処理系に問題があるわけでもなく、iTunesにバグがあるわけでもない……

問題の発生源は、「AppleScriptエディタの環境設定」にあったのです。

ase1.png

ふだん、AppleScriptエディタはアプリケーションとのやりとりの履歴、「イベントログ」を記録するようになっています。

ase2.png

デフォルトでは、この履歴を記録するようになっているのですが……どうやら、大量のイベントログが発生すると記録し切れないようで、AppleScriptエディタごとクラッシュするようです。

そこで、このイベント履歴の記録を行わないようにしたら、ウソのようにAppleScriptエディタのクラッシュは起こらなくなりました。

ase3.png

もちろん、この「イベントログの履歴」は、アプリケーションに命令を送ってプログラムを書く場合には有用な情報です。今回のような「非常事態」にはオフにすることはあっても、普段はオンにしておいたほうが役に立つことでしょう。

ase4.png

追記:

冒頭のAppleScriptに記述を加えたところ、再度AppleScriptエディタがクラッシュするようになったのですが、アプリケーションとして書き出して実行したらとくに問題なく実行できました。AppleScriptエディタに問題がある、と言えるかもしれません。