Archive for 1月, 2013

2013/01/29 Xcode 4.6でAppleScript用語辞書の若干の間引きを

Xcode 4.6が一般向けにリリースされたので、AppleScript用語辞書の内容をAS DictionaryでHTMLに書き出して、FileMergeで前バージョンである4.5.2の用語辞書と差分をとってみました。

■Xcode 4.6で削除された用語

project roots
–> list of text
— A list of paths to all directories that contain files referenced in this project.

projectのactive build style属性
– (Obsolete) The active build style. Unless explicitly indicated in a build or clean command, this build style is used.

projectのbreakpoint属性
–by name, index, relative, range, test, id

projectのsymbolic breakpoint属性
–by name, index, relative, range, test, id

この用語辞書の修正にどのぐらいの意味があるのか不明です。あんまり意味があるようには見えません。いきなり大量に使えない用語辞書を付けるよりも、実用的な自動化が行えるレベルの小さくまとまった辞書に差し替えたほうがよさそうに思えます。

2013/01/24 Photoshopのsave optionをテキストで指定して反映させるテスト

Photoshopにかぎらない話なのですが、ある処理を実行するルーチンがあったとして、外部からテキストでパラメータを与えたいケースというのが多々あると思います。

しかし、オプションを指定しようにも、アプリケーション内のオブジェクトを指定しなければならないケースが多々あり……ここでは、noneなどという他愛もないキーワードが、実はPhotoshopのAppleScript用語辞書に用意されている予約語のnoneでなければなりません。文字(string)で”none”などと指定すると、エラーになってしまいます。

そこで、無理な処理でも無理矢理実現するrun scriptコマンドの登場。古くは、レコード型の変数を動的に作成するなどという、AppleScriptの文法上は不可能なことをさくっと実現。当時はファイルに一度書き出して読み込んでやりとりしていましたが、さらに「手口」が洗練されて手軽に使う方法が確立してきました(ファイルI/Oを経由する必要はなかったという)。

というわけで、外部からはパラメータを文字列で指定して、処理ルーチン内でrun scriptコマンドでオブジェクトを動的に生成し、コマンドのオプションに指定するサンプルとして紹介しています。

スクリプト名:Photoshopのsave optionをテキストで指定して反映させるテスト
set aFile to choose file name with prompt “書き出し先のファイルパスを選択”
set aFilePathOrigStr to aFile as Unicode text

set aCompStr to “JPEG” as Unicode text —指定可能な文字列は、”none”, “LZW”, “ZIP”, “JPEG”
set profileFlag to true

–ファイル保存を行う
tell application id “com.adobe.photoshop”
  
  
–TIFF save optionsの指定用オブジェクトをテキストから動的に生成
  
set aScriptText to (“tell application id \”com.adobe.photoshop\” to “ & aCompStr)
  
set aCompObj to run script aScriptText
  
  
–TIFFオプションを指定して指定パスに保存する
  
set anOption to {class:TIFF save options, byte order:Mac OS, image compression:aCompObj, interleave channels:false, JPEG quality:12, layer compression:ZIP, save annotations:false, save image pyramid:false, save alpha channels:false, save layers:false, transparency:false, save spot colors:false, embed color profile:profileFlag}
  
save document 1 in file aFilePathOrigStr as TIFF with options anOption appending lowercase extension with copying
  
–close document 1 saving no
  
end tell

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

2013/01/20 システムにインストールされているICC Profileのうち、指定キーワードに該当する名前を持つものでバージョン番号が最新のものを返す v2

システムにインストールされているICC Profileのうち、指定キーワードに該当する名前を持つもので、バージョン番号が最新のものをピックアップするAppleScriptです。

…………最初のものから、50行ぐらい短くなっています。

ICC Profileは/Library/ColorSync/Profilesとか、~/Library/ColorSync/Profilesなど複数箇所にインストール可能で、Mac OS Xが標準で用意しているものと、Adobeの各アプリがてんでバラバラにインストールしてお互いに互換性のないものなどが無造作に入っています。

Mac OS X 10.6でColorSync Scriptingが廃止になったときに、ICC Profileの管理機能はImage Eventsに移管されました(少し前からそうだったか、、、)。このImage Eventsに対してICC Profileの所在を問い合わせると、複数のインストール先にインストールされた同一名称のプロファイルが取得できたりするので、ユニーク化処理を行って重複分の消し込みを行っています。

スクリプト名:システムにインストールされているICC Profileのうち、指定キーワードに該当する名前を持つものでバージョン番号が最新のものを返す v2
–システムにインストールされているICC Profileのうち、指定キーワードに該当する名前を持つものでバージョン番号が最新のものを返す

set nList to getAProfile_(“Piyomaru Piyopiyo”)
set b to retLatestVersionICCprofileNameFromList_delExt_(nList)
–> “Piyomaru Piyopiyo V5.0.icc”

–キーワードを含む名称を持つICC Profileを取得する
on getAProfile_(aKeyword)
  
  
set unique_profile_names to {}
  
  
tell application “Image Events”
    
    
set all_profile_names to name of every profile whose name contains aKeyword
    
    
repeat with i from 1 to (count all_profile_names)
      set cur_profile_name to item i of all_profile_names
      
      
if cur_profile_name is not in unique_profile_names then
        set end of unique_profile_names to cur_profile_name
      end if
      
    end repeat
    
  end tell
  
  
return unique_profile_names
  
end getAProfile_

–与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す
on retLatestVersionICCprofileNameFromList_delExt_(a)
  
  
–取り出した数値部分だけをキーにして降順ソート(大きな数→小さな数)
  
considering numeric strings
    set resList to shellSortDecending(a)
  end considering
  
  
return item 1 of resList
  
end retLatestVersionICCprofileNameFromList_delExt_

–シェルソートでリストを昇順ソート(入れ子ではないリスト)
on shellSortAscending(a)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item (j - h + 1) of a) > v)
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortAscending

–シェルソートでリストを降順ソート(入れ子ではないリスト)
on shellSortDecending(a)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item (j - h + 1) of a) < v)
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortDecending

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

2013/01/20 与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す v2

同一の名称でバージョン番号のみ異なるICC Profile名称リストから、もっともバージョンの新しい(バージョン番号の数字が大きい)ものを抽出して返すAppleScriptです。

コメント欄でs_z_k_3さんからご指摘いただいた「considering numeric strings」(使ってなかったので忘れてた!)を利用したところ、なんと50行ぐらい不要になってしまいまして……

実に…………あっさりとしたものになりました。削減前と比較して、まったく同様の処理結果が得られます。

別に、ICC Profileの名称ソートに限定しなくても「バージョン番号を含む(複数の小数点文字から構成される)数値文字列を考慮したソートルーチン」のような別ジャンルにしてもよかったのかもしれません。

きっと、しばらくの間「considering numeric strings」を忘れることはないでしょう。

スクリプト名:与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す v2
set a to {"Piyomaru Piyopiyo V4.0.icc", "Piyomaru Piyopiyo V10.0.2.icc", "Piyomaru Piyopiyo V10.0.1.icc", "Piyomaru Piyopiyo V5.0.icc"}
set b to retLatestVersionICCprofileNameFromList(a, ".icc")

–> "Piyomaru Piyopiyo V10.0.2.icc"

–与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す
on retLatestVersionICCprofileNameFromList(a, aExt)
  
  
–取り出した数値部分だけをキーにして降順ソート(大きな数→小さな数)
  
considering numeric strings
    set resList to shellSortDecending(a)
  end considering
  
  
return item 1 of resList
  
end retLatestVersionICCprofileNameFromList

–シェルソートでリストを昇順ソート(入れ子ではないリスト)
on shellSortAscending(a)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item (j - h + 1) of a) > v)
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortAscending

–シェルソートでリストを降順ソート(入れ子ではないリスト)
on shellSortDecending(a)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item (j - h + 1) of a) < v)
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortDecending

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

2013/01/19 与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す

同一の名称でバージョン番号のみ異なるICC Profile名称リストから、もっともバージョンの新しい(バージョン番号の数字が大きい)ものを抽出して返すAppleScriptです。

なお、バージョン番号数字が登場する箇所は1ブロックのみと仮定しており、複数ブロックに数字が出現するようなパターンは想定していません。

想定している : XXXXXX1.0.3xxxx.icc
想定していない: X3X4XX1.0.3xxxx.icc

ただ、このようにバージョン番号以外の場所に数字の文字があったとしても、実用上は問題ないものと判断しています。

X3X4XX1.0.3xxxx.icc
X3X4XX10.0.2xxxx.icc
X3X4XX5.0.1xxxx.icc

スクリプト名:与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す
set a to {“Piyomaru Piyopiyo V4.0.icc”, “Piyomaru Piyopiyo V10.0.2.icc”, “Piyomaru Piyopiyo V10.0.1.icc”, “Piyomaru Piyopiyo V5.0.icc”}
set b to retLatestVersionICCprofileNameFromList(a, “.icc”)

–> “Piyomaru Piyopiyo V10.0.2.icc”

–与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す
on retLatestVersionICCprofileNameFromList(a, aExt)
  
  
set allList to {}
  
  
–与えられたリストの各要素に対して、所定の拡張子を外したのちに数字部分だけを抽出
  
set allCount to 1
  
repeat with ii in a
    
    
set jj to contents of ii
    
set b to repChar(jj, aExt, “”)
    
    
set bList to characters of b
    
set nList to {}
    
    
repeat with i in bList
      set j to contents of i
      
if j is in {“0″, “1″, “2″, “3″, “4″, “5″, “6″, “7″, “8″, “9″, “.”} then
        set the end of nList to j
      end if
    end repeat
    
    
–小数点が複数個登場する(かもしれない)文字列を評価して、普通の数値に変換する
    
set nStr to nList as string
    
set nStrNum to interpretNumStrAsNum(nStr)
    
    
    
–ソート用リストに、抽出した数値とアイテム番号を追加する
    
set the end of allList to {nStrNum, allCount}
    
    
set allCount to allCount + 1
  end repeat
  
  
  
–取り出した数値部分だけをキーにして降順ソート(大きな数→小さな数)
  
set resList to shellSortListDecending(allList, 1)
  
set {fData, fOrder} to first item of resList
  
  
set newestData to contents of item fOrder of a
  
  
return newestData
  
end retLatestVersionICCprofileNameFromList

–「10.7.3」などの複数のピリオドが入る数字の文字列を数値として解釈して返す
on interpretNumStrAsNum(a)
  
  
set {strCount, posList} to countAcharInStrAndDetectPos(a, “.”)
  
  
–ターゲット文字が複数回出現した場合
  
if strCount > 1 then
    set firstPosNum to first item of posList
    
    
if firstPosNum > 1 then
      –通常パターン
      
set aStr to text 1 thru firstPosNum of a
      
set bStr to text (firstPosNum + 1) thru -1 of a
      
set b2Str to repChar(bStr, “.”, “”)
      
      
set c to aStr & b2Str
    else
      –先頭に小数点が入っている場合
      
set aStr to text 2 thru -1 of a
      
set b2Str to repChar(aStr, “.”, “”)
      
      
set c to “0.” & b2Str –小数点が冒頭にあったので先頭に「0」を足した
    end if
    
  else
    –ターゲット文字(小数点)が1回のみ出現した場合
    
set c to a
  end if
  
  
set cNum to c as number
  
  
return cNum
  
end interpretNumStrAsNum

–文字置換ルーチン
on repChar(origText, targStr, repStr)
  set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
  
set temp to text items of origText
  
set AppleScript’s text item delimiters to repStr
  
set res to temp as text
  
set AppleScript’s text item delimiters to txdl
  
return res
end repChar

–文字列中に指定文字が何個入っているかカウントし、登場位置をリストで返す
on countAcharInStrAndDetectPos(aStr, aTargChar)
  set aList to {}
  
set posC to 1
  
  
considering case
    set aHit to offset of aTargChar in aStr
    
    
if aHit is not equal to 0 then
      set aaList to characters of aStr
      
set aCount to 0
      
      
repeat with i in aaList
        
        
set j to contents of i
        
        
if j = aTargChar then
          set aCount to aCount + 1
          
set the end of aList to posC
        end if
        
        
set posC to posC + 1
        
      end repeat
      
      
return {aCount, aList}
    else
      return {0, {}}
    end if
  end considering
end countAcharInStrAndDetectPos

–シェルソートで入れ子のリストを昇順ソート
on shellSortListAscending(a, keyItem)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of a) > (item keyItem of v))
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortListAscending

–シェルソートで入れ子のリストを降順ソート
on shellSortListDecending(a, keyItem)
  set n to length of a
  
set cols to {1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1}
  
repeat with h in cols
    if (h (n - 1)) then
      repeat with i from h to (n - 1)
        set v to item (i + 1) of a
        
set j to i
        
repeat while (j h) and ((contents of item keyItem of item (j - h + 1) of a) < (item keyItem of v))
          set (item (j + 1) of a) to (item (j - h + 1) of a)
          
set j to j - h
        end repeat
        
set item (j + 1) of a to v
      end repeat
    end if
  end repeat
  
return a
end shellSortListDecending

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

2013/01/19 複数のピリオドが入る数字の文字列を数値として解釈して返す

“10.8.3″などのピリオドが複数入った数字の文字列を数値(10.83)として評価して返すAppleScriptです。

システムにインストールされた特定のモジュールの、各名称につけられたバージョン番号を考慮し、同一名称でバージョン番号が大きい(=新しい)ものをピックアップするときに作成しました。

元の文字列をそのままソートすると、10.8よりも5.1のほうが「大きい」ものとして評価されてしまい、都合がよろしくありません。

なにげなく「こういうケースが発生したらまずい」と気付いて作り出したら……予想外に巨大なプログラムになってしまいました。もっと簡単な方法がありそうではありますが、とりあえず期待どおりの動作を行ってくれています。

スクリプト名:複数のピリオドが入る数字の文字列を数値として解釈して返す
set a to ".2.1.3"
set b to interpretNumStrAsNum(a)
–> 0.213

set a to "23"
set b to interpretNumStrAsNum(a)
–> 23

set a to "10.0.1"
set b to interpretNumStrAsNum(a)
–> 10.01

set a to "10.0.1.2.3.4"
set b to interpretNumStrAsNum(a)
–> 10.01234

–「10.7.3」などの複数のピリオドが入る数字の文字列を数値として解釈して返す
on interpretNumStrAsNum(a)
  
  
set {strCount, posList} to countAcharInStrAndDetectPos(a, ".")
  
  
–ターゲット文字が複数回出現した場合
  
if strCount > 1 then
    set firstPosNum to first item of posList
    
    
if firstPosNum > 1 then
      –通常パターン
      
set aStr to text 1 thru firstPosNum of a
      
set bStr to text (firstPosNum + 1) thru -1 of a
      
set b2Str to repChar(bStr, ".", "")
      
      
set c to aStr & b2Str
    else
      –先頭に小数点が入っている場合
      
set aStr to text 2 thru -1 of a
      
set b2Str to repChar(aStr, ".", "")
      
      
set c to "0." & b2Str –小数点が冒頭にあったので先頭に「0」を足した
    end if
    
  else
    –ターゲット文字(小数点)が1回のみ出現した場合
    
set c to a
  end if
  
  
set cNum to c as number
  
  
return cNum
  
end interpretNumStrAsNum

–文字置換ルーチン
on repChar(origText, targStr, repStr)
  set {txdl, AppleScript’s text item delimiters} to {AppleScript’s text item delimiters, targStr}
  
set temp to text items of origText
  
set AppleScript’s text item delimiters to repStr
  
set res to temp as text
  
set AppleScript’s text item delimiters to txdl
  
return res
end repChar

–文字列中に指定文字が何個入っているかカウントし、登場位置をリストで返す
on countAcharInStrAndDetectPos(aStr, aTargChar)
  set aList to {}
  
set posC to 1
  
  
considering case
    set aHit to offset of aTargChar in aStr
    
    
if aHit is not equal to 0 then
      set aaList to characters of aStr
      
set aCount to 0
      
      
repeat with i in aaList
        
        
set j to contents of i
        
        
if j = aTargChar then
          set aCount to aCount + 1
          
set the end of aList to posC
        end if
        
        
set posC to posC + 1
        
      end repeat
      
      
return {aCount, aList}
    else
      return {0, {}}
    end if
  end considering
end countAcharInStrAndDetectPos

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

2013/01/16 指定プロセスの死活判定 v2

指定プロセス(アプリケーション)がハングアップしているかどうかをUNIX的な見地と、AppleScript的な見地から判定するAppleScriptです。

以前掲載したバージョンでは、shellのpsコマンドだけで「クラッシュした」状態を検出していましたが、それだけでは不十分という話を書いていました。

そこで、2フェーズに分けて……

 フェーズ1:psコマンドでゾンビプロセスの確認
 フェーズ2:AppleScriptから指定アプリに名称確認

という確認をするようにしてみました。

これだけ丁寧にきめ細かく確認を行えば、たいていのクラッシュやハングアップ状態は検出できることでしょう(あいにく、Mac OS X上ではそんなに頻繁にそういう状態に遭遇していないので確認が難しいのですが)。ぜひ、いろいろなケースで試してみたいところです。

スクリプト名:指定プロセスの死活判定(Bundle IDで判定) v2
set aRes to detectAppAliveByID_(“com.apple.itunes”)
–> true (起動中、ゾンビ化していない)
–> false(起動していない)
–> “killed”(ゾンビ化していたので、killした)

–指定プロセスの死活判定(Bundle IDで判定)
on detectAppAliveByID_(aProcBundleID)
  set aProcBundleID to aProcBundleID as string
  
  
–Phase 1 psコマンド経由で状態を確認してみる
  
set aRes to false
  
try
    tell application “System Events”
      set aList to bundle identifier of every process
      
if aProcBundleID is in aList then
        
        
set tmpList to name of every process whose bundle identifier = aProcBundleID
        
set aProcName to contents of first item of tmpList
        
        
set aRes to true
        
tell process aProcName
          set processID to unix id –プロセスIDを取得
        end tell
        
        
set processID to processID as string
        
        
–指定プロセスがゾンビプロセス化しているかどうかを判定
        
set procState to (words of (do shell script “/bin/ps “ & processID & ” | cut -d ‘ ‘ -f 6″)) as string
        
        
if procState contains “Z” then
          –ゾンビプロセスを殺す
          
do shell script “/bin/kill -9 “ & processID
          
return “killed”
        end if
      else
        –指定したBundle IDのプロセスが存在しない場合falseでリターン
        
return false
      end if
    end tell
  on error
    –異常発生時にはさっさとリターン
    
return false
  end try
  
  
  
–Phase 2 指定アプリに直接コンタクトして反応を見る
  
try
    with timeout of 3 seconds
      tell application id aProcBundleID
        set aTmpvar to name –これができない(Scriptableな)アプリはほとんどないだろう(多分)
      end tell
    end timeout
  on error
    –名称を取得できなかった場合にはコケていると見なしてプロセスを殺す
    
do shell script “/bin/kill -9 “ & processID
    
return “killed”
    
  end try
  
  
  
return aRes
end detectAppAliveByID_

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

スクリプト名:指定プロセスの死活判定(アプリ名で判定) v2
set psRes to detectAppAliveByName_(“Safari”)
–> true (起動中、ゾンビ化していない)
–> false(起動していない)
–> “killed”(ゾンビ化していたので、killした)

–指定プロセスの死活判定(アプリ名で判定)
on detectAppAliveByName_(aProcName)
  set aProcName to aProcName as string
  
  
–Phase 1 psコマンド経由で状態を確認してみる
  
set aRes to false
  
try
    tell application “System Events”
      set aList to name of every process
      
if aProcName is in aList then
        set aRes to true
        
tell process aProcName
          set processID to unix id –プロセスIDを取得
        end tell
        
        
set processID to processID as string
        
        
–指定プロセスがゾンビプロセス化しているかどうかを判定
        
set procState to (words of (do shell script “/bin/ps “ & processID & ” | cut -d ‘ ‘ -f 6″)) as string
        
        
if procState contains “Z” then
          –ゾンビプロセスを殺す
          
do shell script “/bin/kill -9 “ & processID
          
return “killed”
        end if
      else
        –指定した名称のプロセスが存在しない場合falseでリターン
        
return false
      end if
    end tell
  on error
    –異常発生時にはさっさとリターン
    
return false
  end try
  
  
  
–Phase 2 指定アプリに直接コンタクトして反応を見る
  
try
    with timeout of 3 seconds
      tell application aProcName
        set aTmpvar to name –これができない(Scriptableな)アプリはほとんどないだろう(多分)
      end tell
    end timeout
  on error
    –名称を取得できなかった場合にはコケていると見なしてプロセスを殺す
    
do shell script “/bin/kill -9 “ & processID
    
return “killed”
    
  end try
  
  
return aRes
end detectAppAliveByName_

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

2013/01/12 最前面のアプリケーションをいったん終了させてから起動

最前面のアプリケーションをいったん終了させて、ふたたび起動させるAppleScriptです。

これは、コケかけているような怪しい状態のアプリケーションを対象としているものではなく、明確に動いているものを操作するためのものです。

画面の解像度を(狭い方に)変更したときに、フローティングパレットやフローティングウィンドウが隠れてしまうことがあります。その現象を解決するためにいったん終了させてふたたび起動するものです。とくに、アクティビティモニタのCPUの負荷メーターが見えなくなってしまう問題に対処するために作りました。

Script Menuに入れて呼び出して使っています。

スクリプト名:最前面のアプリケーションをいったん終了させてから起動
tell application “System Events”
  set aProc to every process whose frontmost is true
  
set aaProc to contents of first item of aProc
  
  
set aProp to properties of aaProc
  
  
set aBundleID to bundle identifier of aProp
  
end tell

tell application id aBundleID to quit

delay 1

tell application id aBundleID to launch

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

2013/01/12 指定プロセスの死活判定

指定プロセス(アプリケーション)がハングアップしているかどうかをUNIX的な見地から判定するAppleScriptです。

アプリ名称で指定して調べるものと、バンドルIDで指定して調べるものを用意してみました。

System Eventsで指定プロセス(アプリケーション)のプロパティを調べると、unix id(process id)を取得できます。このunix idで指定したプロセスの状態をpsコマンドで取得し、ゾンビプロセスになっているかどうかを判定します。

ただ……psコマンドで調べてゾンビプロセスに「なっていない」ものでも、GUI側から操作を試みても7色のビーチボールが回りっぱなしだったり、AppleScriptからの命令を受け付けないといった状態になっていることもままあります。

プロセス(アプリケーション)の死活判定はなかなか奥深いものがあります。実際には、psコマンドで調べるだけでなく、テストデータをオープンさせてみるとか、アプリケーションの名称を取得してみるとか、実行しても意味はないものの結果が得られるかどうかを調べてみるとよいでしょう。

スクリプト名:指定プロセスの死活判定(アプリ名で判定)
set psRes to detectAppAliveByName_(“Safari”)
–> true (起動中、ゾンビ化していない)
–> false(起動していない)
–> “killed”(ゾンビ化していたので、killした)

–指定プロセスの死活判定(アプリ名で判定)
on detectAppAliveByName_(aProcName)
  set aProcName to aProcName as string
  
  
set aRes to false
  
try
    tell application “System Events”
      set aList to name of every process
      
if aProcName is in aList then
        set aRes to true
        
tell process aProcName
          set processID to unix id –プロセスIDを取得
        end tell
        
        
set processID to processID as string
        
        
–指定プロセスがゾンビプロセス化しているかどうかを判定
        
set procState to (words of (do shell script “/bin/ps “ & processID & ” | cut -d ‘ ‘ -f 6″)) as string
        
        
if procState contains “Z” then
          –ゾンビプロセスを殺す
          
do shell script “/bin/kill -9 “ & processID
          
set aRes to “killed”
        end if
      else
        set aRes to false
      end if
    end tell
  on error
    set aRes to false
  end try
  
  
return aRes
end detectAppAliveByName_

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

スクリプト名:指定プロセスの死活判定(Bundle IDで判定)
set aRes to detectAppAliveByID_(“com.apple.safari”)
–> true (起動中、ゾンビ化していない)
–> false(起動していない)
–> “killed”(ゾンビ化していたので、killした)

–指定プロセスの死活判定(Bundle IDで判定)
on detectAppAliveByID_(aProcBundleID)
  set aProcBundleID to aProcBundleID as string
  
  
set aRes to false
  
try
    tell application “System Events”
      set aList to bundle identifier of every process
      
if aProcBundleID is in aList then
        
        
set tmpList to name of every process whose bundle identifier = aProcBundleID
        
set aProcName to contents of first item of tmpList
        
        
set aRes to true
        
tell process aProcName
          set processID to unix id –プロセスIDを取得
        end tell
        
        
set processID to processID as string
        
        
–指定プロセスがゾンビプロセス化しているかどうかを判定
        
set procState to (words of (do shell script “/bin/ps “ & processID & ” | cut -d ‘ ‘ -f 6″)) as string
        
        
if procState contains “Z” then
          –ゾンビプロセスを殺す
          
do shell script “/bin/kill -9 “ & processID
          
set aRes to “killed”
        end if
      else
        set aRes to false
      end if
    end tell
  on error
    set aRes to false
  end try
  
  
return aRes
end detectAppAliveByID_

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

2013/01/12 Bundle IDで指定したアプリの非同期起動

AppleScriptでは「ignoring application responses」節によってアプリケーションに対して非同期の命令実行を行えるようになっています。

非同期実行のサンプルとして、バンドルID(com.apple.icalなど)の指定により、該当のアプリケーションを非同期で起動するものを作ってみました。

アプリケーションに対してコマンドを非同期で投げると、結果を待たなくてよいので処理の高速化が図れる場合もありますが、処理結果は取得できないですしエラーが発生した場合にも対処できません。非同期実行は、動作が確実に行える内容で返り値を必要としない場合にのみ利用するべきです(そういうケースはあまりないので、活用するチャンスもそれほどないのですが)。

スクリプト名:Bundle IDで指定したアプリの非同期起動
set aRes to launchAsyncByID_("com.apple.ical") –iCal

–Bundle IDで指定したアプリの非同期起動
on launchAsyncByID_(aBundleID)
  set aBundleID to aBundleID as string
  
  
try
    ignoring application responses
      tell application id aBundleID
        launch
      end tell
    end ignoring
  on error
    –Bundle IDで指定したアプリが存在しない場合にはfalseを返す
    
return false
  end try
  
  
return true
  
end launchAsyncByID_

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

2013/01/03 PowerPoint 2011の書類内の各ページ(slide)内の各オブジェクト(shape)のアニメーション設定を取得

お正月でテレビを見ながら、お酒が入りつつちょっと調べものを。PowerPointをAppleScriptからこづき回しつつ、各オブジェクトに設定されているアニメーション設定情報を取得してみました。

まずは、メーカーが出しているScriptingの資料がないか、Googleで「AppleScript PowerPoint」などのキーワードで検索してみると、トップから2番目にその情報がヒットします。マイクロソフトが出しているPowerPoint 2004のAppleScript資料(PDF)。このPDF資料内を「animation」で検索すれば、該当箇所が出てきます。

ppt0.png

PowerPointはあまりScriptから動かしたりしないので、オブジェクト階層などもすっかり忘れている状態(Keynoteしか使っていないので)。初見に近い状態です。

最初に調べるのは、「書類」に該当するオブジェクト名が何であるか。一般的には、「document」ですが、プレゼン系のアプリケーション(Keynoteとか)ではdocumentではないオブジェクト名が定義されていることが多く……PDF資料を調べてみると、「presentation」だと分りました。

次に、presentation(書類)内の各ページにアクセスするオブジェクトが「slide」、各ページ(slide)内のオブジェクトにアクセスするのが「shape」だと分りました。

ppt1.png

オブジェクト階層を指定しつつ、ふつーにアクセスすれば、ふつーに情報にアクセスできました。何も問題はなさそうです。

書いている最中で行き当たった問題といえば、AdobeやMicrosoftなど大規模なAppleScript用語辞書を抱えたアプリの場合だと、(とくに、マイクロソフトのアプリ全般でその傾向があるのですが)オブジェクト階層の記述を1行の中にまとめると動くのに、tell文で複数行に分けて書くとアクセスできないとかエラーが出るという現象には直面しました。覚えておいて損はないと思います。

ppt2.png