最終更新日:2013/11/06
説明は書きかけです。書くだけ書いて全く整理されていません。また、開発中の機能が書いてある場合があります。
MenuBarAppleScriptはメニューバー右側のステータスバーにメニュー項目を作れるAppleScriptです。自分はメニューバーアプリケーションは15インチのMacを使ってて画面が狭いこともあってあまり好きじゃないのですが、AppleScriptで自由に作成できたら面白いことができるかもしれないということで作ってみました。
WebView付きのウインドウはデスクトップに表示してクリックを無効にすれば、よくあるデスクトップに何か情報を表示するアプリケーションのように使えます。WebViewなので指定したURL、HTML、画像ファイル等を表示できるので何かを表示したい場合に使えると思います。
メニューを作るには何かシステムの情報が必要だろうということで、NSWorkspace、NSDistributedNotificationCenter、AXNotificationの通知を受け取って処理できるようにしました。具体的には、アプリケーションが起動した、切り替わった、iTunesの曲が変わった、ダウンロードが完了した、などの通知を受けて処理ができます。システムから通知が来るので、定期的に監視する等のCPUパワーの消費がありません。
MenuBarAppleScriptに正規表現やハッシュなどの独自の命令を追加してあるので、それらを使うとスクリプトを書くのが便利になると思います。
スクリプトが結構ややこしいので、タイプ別のサンプルを見て、それを変更しながら自作アプリケーションにして下さい。でも、AppleScriptObjCで同等のスクリプトを書くよりは簡単だと思います。
MenuBarAppleScriptで作ったアプリケーションにMenuBarAppleScriptで作ったものが置いてあります。とりあえず、どんなことができるか確認したい場合は下の長い説明より実際にできたものを見るのをお勧めします。
MenuBarAppleScriptはAppleScript StudioやAppleScriptObjCのようなAppleScriptを使っている新しい環境です。覚えることもたくさんありますが、同じことをAppleScriptObjCでやるよりはMenuBarAppleScriptの方が簡単ではないかと思います。
MenuBarAppleScriptを使う主な利点、欠点は下記のようになります。
通常のAppleScriptではGUIは簡単なダイアログしか作れませんが、MenuBarAppleScriptではメニューバーアイテムとWebView付きウインドウを作成できます。
メニューバーアイテムからメニューで選択してスクリプトの各種機能を実行したり、状態を表示したりできます。AppleScriptObjCで0からメニューバーアイテムを作るのよりはMenuBarAppleScriptの方が簡単だと思います。
WebView付きウインドウには画像を表示したり、フォームでダイアログを作って表示したりと、いろいろと応用できると思います。クリックを受け取らない半透明のウインドウなども作れるので、画面の隅っこに常時何か表示したい場合にも使えます。これも、AppleScriptObjCで0からWebView付きウインドウよりMenuBarAppleScriptの方が簡単だと思います。
この2つしかありませんがスクリプトから完全に制御できるのでいろいろとできるようになります。
OSにはアプリケーションが起動したとき、切り替わったとき、iTunesの曲が変わったとき、Safariでダウンロードが完了したとき、ネットワークの接続状況が変わったとき、などに通知が送られますが、その通知を受け取ることができます。通常のAppleScriptではこれらの通知は受け取れないのでiTunesの曲が変わった時にしても、一定間隔でチェックする必要がありますが、そういう無駄がなくなります。
AXNotificationの通知は、かなり奥が深くて面白いのですが、これをAppleScriptで使えるものはあまりないと思います。AXNotificationでは、ウインドウのスクロールバーをスクロールした、リストの選択した項目が変わった、選択したテキストが変わった、メニューを選択した、などの通知を受け取ることができます。これらを使うと、かなり特殊なことができるようになります。通常のGUIスクリプティングでは取得できない(と思う)、フォーカスが当たっているGUIパーツを取得できるので、応用範囲が格段に広がります。
MenuBarAppleScriptに自分がAppleScriptを書いていて欲しいなと思った命令をいくつか追加しました。よくありそうなところではハッシュ関係の命令があります。使い方はちょっと癖がありますが、今までMenuBarAppleScriptでいろいろとスクリプトを書いてきましたがこれがあるとスクリプトを書く効率が格段に上がります。
また、リスト操作、パス関係など、通常それを処理するハンドラを書いている処理も命令を追加して、MenuBarAppleScriptを書く時にそれらのハンドラをコピペしなくていいようにしました。
命令の数は結構多いですが、AppleScriptEditorHelperでメニューから選んで挿入できるので、うろ覚えでも大丈夫です。
最初ははシンプルだったのですが、自分がAppleScriptで欲しい機能をなんでも追加して行ったら、自分でも全機能を把握できないぐらいになってきました。
ただ、覚えたら通常のAppleScriptではできないことができますし、MenuBarAppleScriptの命令も開発効率が上がるものがあると思います。
ただし、MenuBarAppleScriptの命令、キーワードはAppleScriptEditorHelperで挿入できるのでうろ覚えでもだいたい対処できます。
メニューバーアイテムやウインドウを操作する場合は、特定の処理をした場合に特定のハンドラが実行されます。どのハンドラがどういう時に実行されるかの知識が必要になります。
メニューアイテムにアプリケーションの状態に応じてチェックを付ける等もちょっとこつが必要です。
アプリケーション実行に起きたスクリプトのエラーは原因がコンソールに表示されますが、エラーの行が表示されないので、どこが原因かわかりにくいです。
デバッグ方法を使用してAppleScriptエディタでスクリプトを実行することにより、特定のハンドラのイベントログを確認しながらデバッグすることはできます。
自分はMenuBarAppleScriptを作ってから書いたスクリプトはほとんどMenuBarAppleScriptを使っています。自分のAppleScript作成環境がMenuBarAppleScriptで一つ新しいレベルに移行した感じです。もちろん、これは自分が欲しい機能をひたすら追加していったからですが、他の人でも有用に使えるのではないかと思います。MenuBarAppleScriptで作ったアプリケーションを見て、興味が有るものがあったら使ってみて、スクリプトも見てみて、便利そうだと思ったら是非使ってみて下さい。
10.6.8で作ってます。
10.8でちょっとだけ動作確認しました。
MenuBarAppleScriptで作ったアプリケーションは自由に配布することができます。許可や連絡等は必要ありません。
同梱している物や、ここで公開しているアプリケーションを改造した物も自由に配布することができます。
通知の種類等でCocoaの知識が必要だったり、結構ややこしいです。アプリケーションのタイプ別にサンプルをいくつか用意したのでそれを参考にどうなってるか見て下さい。
基本的には処理したいイベントや通知を設定すると、そのイベントや通知を処理するハンドラが実行されるので、そのハンドラでアプリケーションに結果をレコードで返して、メニューを作成したり、ウインドウを作成したりします。doInitでどのイベントや通知を処理するか設定しますが設定しないとdoInit以外のハンドラは何も実行されないので注意して下さい。また、設定自体はいつでもできるのでユーザーの命令が来てから、特定の通知をオンにする等もできます。
作ってみたもの・サンプルをダウンロードして参考にして下さい。
実行するハンドラ名は下記イベント名や通知名の最初にdo
を付けたものになります。
.
など半角英数_以外の文字はすべて_
に置換されます。theActiveAppRecord, theThisAppRecordはすべての実行されるハンドラの最後の引数になります。
theActiveAppRecordのキーはアプリケーションの情報のtheUserInfoのキー / theActiveAppRecordを見て下さい。
すべてのハンドラで同じ書式のレコードを返します。このレコードに設定したい値を入れます。
下記のようにして返します。
return {|AutoAddQuitMenuItem|:1, |NSDistributedNotificationList|:{"com.apple.DownloadFileFinished"}, |EventList|:{"selectMenuItem", "menuDidClose"}}
下記スクリプトのように«event MBASHndl»
命令でレコードをアプリケーションに処理させることもできます。
«event MBASHndl» {|WebViewWindow|:{|Name|:"YT", |URL|:"http://youtube.com/"}}
«event DebugLog»
の出力をしなくなります。{ "*" }
ですべてのイベントのハンドラを実行します。ハンドラがスクリプト中に無い場合のエラーは表示されません。{ "*" }
です。"---title タイトル"
が追加されています。doIdle()
ハンドラを実行する。繰り返しはしないので連続して実行したい場合はdoIdleで毎回この値を返して下さい。{|Handler|:"Test", |Sec|:3, |UserInfo|:"hoge" }
でon DoAfterSecTest(theUserInfo,theActiveAppRecord, theThisAppRecord)
ハンドラを3秒後に実行する。|DoAtTime|:{|Handler|:"22Century", |Time|:{|Year|:2101, |Month|:1, |Day|:1, |Hour|:0, |Minute|:0, |Second|:0}}
|DoAtTime|:{|Handler|:"NewYear", |Time|:{|Month|:1, |Day|:1, |Hour|:0, |Minute|:0, |Second|:0}}
|DoAtTime|:{|Handler|:"NyaaNyaaDayTime", |Time|:{|Day|:2, |Hour|:2, |Minute|:2, |Second|:2}}
|DoAtTime|:{|Handler|:"WBS", |Time|:{|Hour|:23, |Minute|:0, |Second|:0}}
|DoAtTime|:{|Handler|:"Every30Min", |Time|:{|Minute|:30, |Second|:0}}
|DoAtTime|:{|Handler|:"Every15Sec", |Time|:{|Second|:15}}
|DoAtTime|:{|Handler|:"NewYear", |Time|:{|Month|:1, |Day|:1, |Hour|:0, |Minute|:0, |Second|:0}, |Repeat|:1, |DoOnlyFirstRun|:1}
|DoAtTime|:{|Handler|:"NyaaNyaaDayTime", |Time|:{|Day|:2, |Hour|:2, |Minute|:2, |Second|:2}, |Repeat|:1, |DoOnlyFirstRun|:1}
|DoAtTime|:{|Handler|:"WBS", |Time|:{|Hour|:23, |Minute|:0, |Second|:0}, |Repeat|:1, |DoOnlyFirstRun|:1}
|DoAtTime|:{|Handler|:"Every30Min", |Time|:{|Minute|:30, |Second|:0}, |Repeat|:1, |DoOnlyFirstRun|:1}
|DoAtTime|:{|Handler|:"Every15Sec", |Time|:{|Second|:15}, |Repeat|:1, |DoOnlyFirstRun|:1}
on doAtTimeTest(theFireTimeDifference, theIsWake, theIsFirstRun, theSettingRecord, theActiveAppRecord, theThisAppRecord)
|DoAtTime|
に渡したレコード(キーが小文字になっていますが登録に影響はありません)。Repeatで繰り返すように出来ますが、«event MBASHndl» {|DoAtTime|:theSettingRecord}
を実行することによって再度DoAtTimeを同じ設定で登録して手動で繰り返すことができます。{ |Time|:{|Minute|:30, |Second|:0} }
で時間指定すると、次の30分にハンドラを実行します。秒や時間指定も同じです。{ |Time|:{|Minute|:30, |Second|:0}, |Repeat| : 1 }
で時間指定すると、毎時30分にハンドラを実行します。秒や時間指定も同じです。|CancelDoAtTime| : theSettingRecord
{ "LeftMouseUp", "LeftMouseDown", "LeftMouseDragged", "Periodic" }
{|HotKeyList|:{{|Name|:"RateUp", |KeyChar|:"up", |ModifierKeyList|:{"shift", "option"}, |Enable|:1 }}}
doHotKeyRateUp( theActiveAppRecord, theThisAppRecord )
というハンドラが実行されます。{|HotKeyList|:{{|Name|:"RateUp", |KeyChar|:"up", |ModifierKeyList|:{"shift", "option"}, |Enable|:1 }}}
doAppHotKeyRateUp( theActiveAppRecord, theThisAppRecord )
というハンドラが実行されます。theEventPathList
は変更があったフォルダのリストです。サブフォルダで変更があるとサブフォルダのパスが入ります。どのファイルで変更があったかは自分で調べて下さい。on doFSEvent(theEventPathList, theActiveAppRecord, theThisAppRecord)
return {|WebViewNavigationAction|:{|URL|:theURL, |Action|:0}}
return { |VersionCheck| : { |URL|:theVersionJSONURL, |Interval|: days * 7 } }
on doKanaEisuuKeyUp(theKeyInfoRecord, theActiveAppRecord, theThisAppRecord)
on doKanaEisuuKeyDown(theKeyInfoRecord, theActiveAppRecord, theThisAppRecord)
return { |ASBMExecLoop| : {{theVal1, theVal2, theSleepSec}} }
return { |ASBMExecLoop| : {{1, 1, 1.4}} }
return { |ASBMExecLoop| : "stop" }
"stop"
を渡すとスレッドを終了して処理を終了するWebView付きのウインドウを作成できます。
WebViewではいろいろ表示できるので、何かを表示したい時に使えると思います。
WebViewそのままなのでSafariにある機能がすべて使える訳ではありません。デフォルト設定で表示できるだけです。
クローズボタンを押して閉じると、YouTubeの再生が止まりました。|Visible|:0
で隠した場合は止まりませんでした。
return {|WebViewWindow|:{|Name|:"Yahoo", |Visible|:1, |Width|:windowWidth, |Height|:windowHeight, |URL|:"http://www.yahoo.co.jp/"}}
return {|WebViewWindow|:{|HTML|:theHTML, |Name|:"YT", |Visible|:1, |Width|:windowWidth, |Height|:windowHeight, |Delete|:0, |AllowsScrolling|:0, |Title|:defaultTitle, |Activate|:1, |Alpha|:0.5, |Opaque|:0}}
"mouse"
でカーソルの位置"mouse"
でカーソルの位置|UserAgent|:"Version/6.0 Safari/536.25"
«event WebVUtil» {theWindowName, "getUserInfo"}
で取得mbas:HandlerName?key=value
の様なmbasから始まるURLを開いた時には、HandlerNameにdoURLを付けたon doURLHandlerName(theFormRecord, theActiveAppRecord, theThisAppRecord)
を実行します。theFormRecordは{|key|:{"value"}}
のレコードになります。
formで上記URLを作る場合は<form action='mbas:ChooseMovie' method=get>
の様にactionを設定すればOKです。
|rawURL| of theFormRecord
にURLがそのまま入っています。
rawmbasから始まるURLを開いた場合は、on doRawURL(mbasURL, theActiveAppRecord, theThisAppRecord)
が実行されます。
ハンドラをいっぱい作りたくない場合はこちらをつかってください。
次のJavaScriptで
var result = window.MenuBarAppleScript.runHandler("ハンドラ名","渡す値");
var result = window.MenuBarAppleScript.runHandler("bc",e.value);
var result = "";
if( window.MenuBarAppleScript )
{
result = window.MenuBarAppleScript.runHandler("bc", e.value);
}
else
{
result = "ブラウザでテスト実行した時の仮の値";
}
次のハンドラが実行されます。結果はレコードのキー |Result| に入れて返します。
on doHandlerFromJavaScriptbc(theValue, theActiveAppRecord, theThisAppRecord)
-- «event DebugLog» {"doHandlerFromJavaScriptbc"}
set theResult to do shell script "echo " & quoted form of theValue & " | bc "
set theResult to «event TextRegx» {"replace", "(\\d)(?=(\\d\\d\\d)+(?!\\d))", theResult, "$1,"}
return {|Result|:theResult}
end doHandlerFromJavaScriptbc
DebugJavaScriptが1の場合、通常のブラウザで使える下記命令を使うとをコンソールにログを出力します。
console.log("hoge");
通知以外のアプリケーションの動作のイベントです。登録したイベントのハンドラのみ実行されます。
例:
return {|EventList|:{"selectMenuItem","menuDidClose","clickMenuBarItem","doubleClickMenuBarItem"}}
on doRightClickMenuBarItem(theActiveAppRecord, theThisAppRecord)
return {|PopUpMenuItemList|:theMenuItemList}
end doRightClickMenuBarItem
on doButton3ClickMenuBarItem(theActiveAppRecord, theThisAppRecord)
on doScrollWheelMenuBarItem(theDeltaX, theDeltaY, theActiveAppRecord, theThisAppRecord)
on doClickMenuBarItem( theWindowName, theActiveAppRecord, theThisAppRecord)
on doClickMenuBarItem( theWindowName, theActiveAppRecord, theThisAppRecord)
on doClickMenuBarItem( theWindowName, theActiveAppRecord, theThisAppRecord)
on doOpenURL(theURL, theActiveAppRecord, theThisAppRecord)
on doOpenFile(thePathList, theActiveAppRecord, theThisAppRecord)
on doFileService(thePathList, theActiveAppRecord, theThisAppRecord)
on doTextService(theText, theActiveAppRecord, theThisAppRecord)
on doDidFinishLoadForFrame(theWindowName, theActiveAppRecord, theThisAppRecord)
on doJavaScriptResult(theWindowName, theJavaScriptResult, theActiveAppRecord, theThisAppRecord)
on doWebViewNavigationAction(theURL, theActiveAppRecord, theThisAppRecord)
return {|WebViewNavigationAction|:{|URL|:theURL, |Action|:0}}
on doNewWindowURL(theURL, theActiveAppRecord, theThisAppRecord)
return {|NewWindowURL|:{|URL|:theURL, |Action|:0}}
do shell script "open " & quoted form of theURL
on doFileURL(theURL,theActiveAppRecord, theThisAppRecord)
bundleIdentifierなど値が無いキーもあります。nameが使いやすいかなと思います。
screenList:{{x:0, height:900, y:0, visibleWidth:1436, width:1440, visibleY:22, visibleX:0, visibleHeight:878}, {x:262, height:1200, y:900, visibleWidth:1920, width:1920, visibleY:900, visibleX:262, visibleHeight:1200}}
パスなどは起動時の値で起動後に動かしても値は変わりません。
NSWorkspaceとNSDistributedNotificationCenterの通知を受けることができます。
(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
になります。
on doNSWorkspaceDidActivateApplicationNotification(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
on docom_apple_iTunes_playerInfo(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
NSWorkspaceの通知を受けて処理をすることができます。
たくさんあるのですが、一部を紹介します。
上記以外の通知は下記URLの「Notifications」の所を見て下さい。
下の方に「NSWorkspaceDidLaunchApplicationNotification」等がありますが、これが通知名です。
同梱の「NSDistributedNotificationCenterTest.app」でシステムで飛び交ってる通知を表示できますので、これで面白いことができる通知が無いか調べることができます。
{}
で囲まれているのがレコードで()
で囲まれているのがリストです。
com.apple.securityagent.InputPrefsChangedのAppleSelectedInputSourcesの"Input Mode"を取得するには次のようになります。
set AppleSelectedInputSourcesRecord to item 1 of AppleSelectedInputSources of theUserInfo
set theInputMode to |Input Mode| of AppleSelectedInputSourcesRecord
この通知は上の二つの通知とは違って、下記設定が必要です。
「ユニバーサルアクセス」環境設定で「補助装置にアクセスできるようにする」にチェックを入れます。
現在のバージョンでは設定した通知を削除することはできません。アプリケーションを終了したりUI Elementが無くなると指定した通知は無くなるのかは分かりませんが、実行されなくなり、影響は無くなります。
下記リンクを先に書いてあります。
kAXWindowCreatedNotificationの場合、下記のように書いてありますが、CFSTR内のAXWindowCreated
が通知で指定する値になりますので注意して下さい。
#define kAXWindowCreatedNotification CFSTR("AXWindowCreated")
AXNotificationではどのアプリケーションのどのElementを調べるか指定する必要があります。*
ですべてのアプリケーションやElementを対象にできますが、通知の種類によっては頻繁に実行されるものがありますので注意して下さい。
指定方法は下記のようになります。指定する値がリストなので括弧が多くなりますので注意して下さい。
{|AXNotificationList|:{{|AppName|:"Finder", |NotificationList|:{{|name|:"AXSelectedRowsChanged", target:"*"}, {|name|:"AXSelectedChildrenChanged", target:"*"}, {|name|:"AXValueChanged", targetList:{"AXWindows", "", "AXRole", "AXSplitGroup", "AXRole", "AXScrollArea", "AXOrientation", "AXVerticalOrientation"}}}}}}
AppName:対象のアプリケーション名。英語名で指定。*ですべて。"AppleScrit エディタ"の場合、"AppleScript Editor"となります。
Name:通知名
Target:対象のElement。*ですべて指定可能。
TargetList:対象のElementを指定する属性と値のリスト。上から順番に属性と値で指定して行きます。値は正規表現で指定可能です。AXApplicationから指定します。最初の属性を"CreatedWindow"にすると(値は"")作成されたウインドウを対象にElementを探すようになります。通知で実行されるハンドラ内で"AXEL"にすると(値は"")、通知の対象になったUI Elementが対象になります。«event UIElSetE»
で指定したUI Elementを対象とする"UIEl"もあります。
通知の種類によっては*ですべてのElementを対象にすると、対象が多くて通知が来る頻度が非常に高くなる場合がありますので注意して下さい。
on doAXValueChanged(theAXAppRecord, theActiveAppRecord, theThisAppRecord)
下記リンク先に書いてあるSystemConfigurationの機能を使ってネットワーク設定などに変化があったときにハンドラを実行できます。
正式にはSCNotificationとは言いませんが、MenuBarAppleScriptではSCNotificationとして設定します。
設定する通知名はSystemConfiguration.framework でネットワークの情報を得る | ishwt::trackingに書いてある方法で調べられます。
例:State:/Network/Global/IPv4
値が変わった時に実行されるハンドラは記号を_に置換した下記になります。
on doState__Network_Global_IPv4(theValue, theActiveAppRecord, theThisAppRecord)
変化があった時に実行されますが、アプリケーション起動直後に現在値を知っておきたい場合は、{ |RunSCNotificationHandler|:1 }
をSCNotificationListの設定と同時に返しておくと、上記ハンドラに現在値が渡されて実行されます。
注意:Macがルータまで繋がっているか確認できますが、ルータがインターネットに繋がっているかどうかはわかりません。
doInitで次のレコードを返します。
{ |SCNotificationList|:{"State:/Network/Global/IPv4"}, |RunSCNotificationHandler|:1 }
次のハンドラが実行されるのでtheValueをチェックします。ネットワークに接続していない場合はtheValueがmissing valueになります。
on doState__Network_Global_IPv4(theValue, theActiveAppRecord, theThisAppRecord)
-- ネットに繋がっているかチェックして記憶
-- «event DebugLog» {"doState__Network_Global_IPv4", theValue}
if theValue is not missing value then
set theNetworkStatus to true -- 繋がっている
else
set theNetworkStatus to false -- 繋がっていない
end if
end doState__Network_Global_IPv4
あったらいいな、と思ったのをいくつか入れてみました。命令はAppleEventのコードで指定します。
«event MBASHndl» theRecord
«event MBASRunH» {|Handler|:"MyHandlerName", |Key|:theValue}
on doHandlerMyHandlerName(theRecord, theActiveAppRecord, theThisAppRecord)
«event DebugLog» theValue_Text_Number_List_Record
return {|DebugLog|:0}
のように0を設定すると出力しなくなります。«event InfoAApp»
«event InfoTApp»
«event MBASHndl»
で処理をした後の値を取得したい場合や、疑似スレッドで取得したい場合等に«event PlaySnd_» { |Sound| : theSoundName }
«event ASBMExec»{theVal1, theVal2}
«event ChckRegi»{theLicenseName, theLicenseKey}
«event ChckRegi»
localizedName
とbundleIdentifier
をチェックして、変わっていたら«event ChckRegi»
は実行しない、アプリケーションを終了する、などが考えられます。MenuBarAppleScript,me@example.com
com.yourcompany.MenuBarAppleScript.MenuBarAppleScript,me@example.com
master,me@example.com
make_lincese
の$stringData
は上記値になるようにしておいてください。$stringData = $product_code.",".$name;
«event ChckRegi»
を実行した場合、前回チェックした時の名前とキーでチェックして値を返します。登録成功時に自動的に名前とキーを保存します。保存場所は辞書ではないのでスクリプトからは操作できません。me@example.com
GAWQE-FA2GB-UKA4Q-GUWGT-D39QH-2EEGN-KHUAH-FSMAC-CUAJT-A6M6D-ZKZNH-7CM7J-G6QFW-NHQJM-RWU5Z-A
«event WebVJS__» {theWindowName, "location.href"}
«event WebVFind» {theWindowName, theText}
«event SW2PsPDF» {theSavePath, "screen", "printbackgrounds", theWindowName}
«event SW2PsPDF» {"mbas:theName", "screen", "printbackgrounds", theWindowName}
set theResult to «event SW2PsPDF» {theSavePath, "screen", "printbackgrounds", theWindowName}
«event SW2PsPDF» {theSavePath, "screen", "printbackgrounds", |keyWindowName| of theThisAppRecord, "{{106, 11}, {400, 400}}"}
«event WebVUtil» {theWindowName, "doJavaScript", theScript }
«event WebVUtil» {theWindowName, "isLoading" }
«event WebVUtil» {theWindowName, "stopLoading" }
«event WebVUtil» {theWindowName, "reload" }
«event WebVUtil» {theWindowName, "findText", theText }
«event WebVUtil» {missing value, "clearSafeCookie"}}
«event WebVUtil» {theWindowName, "setUserInfo", hoge}
«event WebVUtil» {theWindowName, "getUserInfo"}
«event Cmd_Run_» {|CommandPath|:"/bin/ls", |ArgvList|:{"-la"}, |Handler|:"LS", |Name|:"LS" }
on doCommandResultLS(theOutputText, theErrorText, theTerminationStatus, theCommandRecord, theActiveAppRecord, theThisAppRecord)
«event Cmd_Run_» {|CommandPath|:"/usr/bin/tw", |ArgvList|:{"--stream:filter=" & theWord & ""}, |Handler|:"GetLiveTwitter", |Interval|:3.0, |OutputType|:"Standard", |Word|:theWord, |Index|:theTDIndex, |WindowName|:"RealTimeWindow", |Activate|:0, |TryCount|:0}
missing value
になり、終了している場合は実行したコマンドのTerminationStatusになります。«event Cmd_Run_»
に渡したレコードそのままなので、値を取得することができます。«event Cmd_Quit» theCommandRecord
«event Cmd_Quit» {|Name|:theName }
Growlと10.8の通知(UserNotification)に同じスクリプトの命令で通知を出すことができます。デフォルトでは、10.8で実行していてUserNotificationが使えるならUserNotificationを使い、10.6や10.7など使えない場合はGrowlで通知を出します。
NotificationPriorityで画像を表示したい場合等に常にGrowlで通知を出すようにもできます。
«event PostNoti» {|Title|:theTitle, |SubTitle|:theSubTitle, |InformativeText|:theInformativeText, |ImagePriority|:"Image", |Icon|:theIcon, |Image|:theArtworkURL, |LaunchApp|:"iTunes", |Handler|:"Click", |SoundName|:"", |ActionButtonTitle|:"iTunes", |OtherButtonTitle|:"Close"}
DistributedNotificationを受ける側でなく通知をする命令です。
これで通知したものもNSDistributedNotificationCenterの通知で受けられるのでMenuBarAppleScriptのアプリケーション同士で連携したい場合に使用したりできると思います。
«event PostDNti» { "Notification Name" , theObject, theUserInfoRecord }
«event PostDNti» { "com.apple.DownloadFileFinished" , "/Users/hoge/Downloads/MenuBarAppleScript1.4.5.zip" }
«event PostDNti» {"com.yourcompany.YoruNoQuake", "", {|url|:"http://www.yahoo.co.jp"}}
{ "Message" }
になります。my doNotificationHoge(theRecord, theActiveAppRecord, theThisAppRecord)
«event PostNoti»
で渡したレコード。処理用に独自の値を追加しておいてもOK。ただし、UserNotificationではレコード全体でデータサイズが1KByte以下になるようにするのがお勧めらしいです。«event Win_Visi» theWindowName
«event Win_Hide»
«event Win_Hide» theWindowName
«event Win_DelA»
«event Win_Show»
«event Win_Show» theWindowName
«event Win_Show» theAllWebViewWindowRecord
«event Win_Show» theAllWebViewWindowRecord
|allWebViewWindowRecord| of theThisAppRecord
のレコードを保存しておくと、«event Win_Hide»
で隠して、«event Win_Show» theAllWebViewWindowRecord
で戻せます。«event PostKeyE» {|app|:theAppNameOrBundleIdentifier, |keyChar|:"v", |modifierkeyList|:{"command"}}
«event PostSKey» {|key|:"SOUND_UP", |option|:"down", |shift|:"down" }
pressSpecialKey -key sound_up -option down -shift down
«event PostSKey» {|key|:"sound_up", |option|:"down", |shift|:"down" }
«event PostMove» { theX, theY }
«event PostClck» { |Button| : "left", |X| : theX, |Y| : theY, |clickCount| : 1 }
«event UIElSetE» {"AppleScript Editor", {"Attr", "AXFocusedUIElement"}}
«event UIElSetE» {"AppleScript Editor", {"Attr", "AXFocusedUIElement", "Attr1", "Value1", "Attr2", "Value2" }}
«event UIElSetE» {"AppleScript Editor", {"Attr", "AXFocusedUIElement"}, "SaveName" }
«event UIElSetE» {"AppleScript Editor", { 100, 80 } } -- 座標(x,y)で調べる(指定したアプリケーションのみ対象)
«event UIElSetE» { missing value , { 100, 80 } } -- 座標(x,y)で調べる(すべてのアプリケーションが対象)
«event UIElGetV»
、«event UIElSetV»
の処理対象のUI Elementを指定"属性", "値"
のペアを並べて書きます。対象のUI Elementの子(AXChildren)を探していきます。一番最初はAXApplicationが対象になっています。"^ねこ$"
にします。正規表現のメタ文字も考慮して安全に書くなら"^\\Qねこ\\E$"
にします。
{"Attr", "AXFocusedUIElement" }
と{"AXFocusedUIElement", "" }
は同じになります。古いスクリプトでは後者のスクリプトがある場合がありますが、前者の"Attr"を使う方を推奨します。missing value
にすると座標にあるUI Elementをアプリケーションを限定せず取得します。
set {theRole, theVisibleCharacterRange, theValue} to «event UIElGetV» {"AXRole", "AXVisibleCharacterRange", "AXValue"}
«event UIElSetE»
で指定したUI Elementの属性の値を取得。リストで指定で複数の属性を指定可能。«event UIElSetV» {"AXVisibleCharacterRange", "pos=" & pos & " len=1"}
«event UIElSetE»
で指定したUI Elementの属性の値を設定。リストで、属性、値を指定します。«event UIElSetV» {"AXSelectedTextMarkerRange", "Range", "SavedValue"}
«event UIElVal_»
で保存した名前で指定できます。«event AXElGetV» {"AXRole", "AXVisibleCharacterRange", "AXValue"}
event AXElSetV» {"AXVisibleCharacterRange", "pos=" & pos & " len=1"}
«event UIElIndx» { theIndex }
«event UIElIndx» { theIndex, theSaveName }
«event UIElSetE»
で保存した複数のUI Elementをインデックスで切り替える«event UIElSetE»
実行直後はは1。«event UIElSetE»
で指定した名前、もしくは属性値のリスト。未指定の時は最後にUIElSetEした時のリスト。«event UIElPAct» {"AXPress"}
«event AXElPAct» {"AXPress"}
«event UIElPAct» {"AXPress", {|modifierKeys|:"command option shift control"}}
«event AXElPAct» {"AXPress", {|modifierKeys|:"command option shift control"}}
«event UIElVal_» {"Attr Name", "parameter", "SaveName"}
«event AXElVal_» {"Attr Name", "parameter", "SaveName"}
«event UIElVal_» {"AXSelectedTextMarkerRange", "", "SelectedTextMarkerRange"} -- AXSelectedTextMarkerRangeをSelectedTextMarkerRangeに保存
set theSelectedText to «event UIElVal_» {"AXStringForTextMarkerRange", "SelectedTextMarkerRange", ""} -- AXStringForTextMarkerRangeを上で保存したSelectedTextMarkerRangeをパラメータにして取得
AXWebAreaで取得可能な属性@10.6.8:{"AXUIElementForTextMarker", "AXTextMarkerRangeForUIElement", "AXLineForTextMarker", "AXTextMarkerRangeForLine", "AXStringForTextMarkerRange", "AXTextMarkerForPosition", "AXBoundsForTextMarkerRange", "AXAttributedStringForTextMarkerRange", "AXTextMarkerRangeForUnorderedTextMarkers", "AXNextTextMarkerForTextMarker", "AXPreviousTextMarkerForTextMarker", "AXLeftWordTextMarkerRangeForTextMarker", "AXRightWordTextMarkerRangeForTextMarker", "AXLeftLineTextMarkerRangeForTextMarker", "AXRightLineTextMarkerRangeForTextMarker", "AXSentenceTextMarkerRangeForTextMarker", "AXParagraphTextMarkerRangeForTextMarker", "AXNextWordEndTextMarkerForTextMarker", "AXPreviousWordStartTextMarkerForTextMarker", "AXNextLineEndTextMarkerForTextMarker", "AXPreviousLineStartTextMarkerForTextMarker", "AXNextSentenceEndTextMarkerForTextMarker", "AXPreviousSentenceStartTextMarkerForTextMarker", "AXNextParagraphEndTextMarkerForTextMarker", "AXPreviousParagraphStartTextMarkerForTextMarker", "AXStyleTextMarkerRangeForTextMarker", "AXLengthForTextMarkerRange", "AXBoundsForRange", "AXStringForRange"}
{"AXLineForIndex", "AXRangeForLine", "AXStringForRange", "AXRangeForPosition", "AXRangeForIndex", "AXBoundsForRange", "AXRTFForRange", "AXStyleRangeForIndex", "AXAttributedStringForRange"}
«event UIElPAtr»
«event UIElVal_»
で指定可能なパラメータ付き属性の名前を取得します。«event UIElAppI»
«event UIElAXEl»
«event UIElSave» {"name", "UIEl"/"AXEl" }
«event UIElLoad» {"name"}
«event UIElSave»
で保存した値をロードする«event UIElFind» {"ParentAndSelf", {theAttr, theValue, theOpt}}
«event UIElFind» {"ParentAndSelf", {"AXRole", "(AXTextArea|AXTextField|AXStaticText)", "regex"}}
«event UIElFind» {"Parent", {theAttr, theValue, theOpt}}
MenuBarAppleScriptのアプリケーションがHTTPサーバーになることができます。
何故この機能を追加したかというと、次の理由からです。
«event HTTPSevr» { "start", {|Port|:21248, |DocumentRoot|:"~/Desktop", |User|:"", |Password|:""}}
«event HTTPSevr» { "stop" }
{"index.html", "index.htm"}
on doHTTPResponse(theRequestPath, theDocumentFilePath, theMethod, theActiveAppRecord, theThisAppRecord)
で行います。Dictionary Services Referenceの命令です。
ハッシュではないです。
«event DicSShow» { theText, theTextOffset, { theShowWindowX, theShowWindowY } }
«event DicSShow» { theText, theTextOffset, { theShowWindowX, theShowWindowY }, theSavedAttributedStringName }
«event DicSShow» { "りんご", 1, { 20, 20 } }
missing value
を指定すると、指定したテキストをそのまま調べます。missing value
にします。«event UIElVal_»
で保存したAttributedStringを指定する場合。«event DicSTerm» { theText, theTextOffset }
«event DicSTerm» {"海外旅行に行ってきたよ!", 1 }
{ missing value, missing value }
を返す。{"海外旅行", "pos=0 len=4"}
«event DicSDef_» { theText, theTextOffset, theDictName }
«event DicSDef_» {"海外", 1, "国語辞典"}
«event DicSDef_» {"apple", 1, "英和/和英辞典"}
«event DicSDef_» {"りんご", 1, "英和/和英辞典"}
«event DicSDef_» {"感じ", 1, "類語辞典"}
«event DicSDef_» {"Finder", 1, "Apple"}
«event DicSDef_» {"犬", 1, "Wikipedia"}
missing value
を指定すると、指定したテキストをそのまま調べます。詳しい説明はNSString Class Referenceを見て下さい。
«event TextUtil» {"lowercaseString", theText}
«event TextUtil» {"uppercaseString", theText}
«event TextUtil» {"capitalizedString", theText}
«event TextUtil» {"capitalizedString", theText}
«event TextUtil» {"printfInt", "%04d", 12}
«event TextUtil» {"printfFloat", "%.2f", 12.345678}
«event TextUtil» {"strtol", theText, theBase}
«event TextUtil» {"strtol", theText, { theBase1, theBase2, theBase3 } }
«event TextUtil» {"strtol", "AA", 16}
«event TextUtil» {"strtol", "AA 99 FF", { 16, 16, 16 } }
«event TextUtil» {"parseJSON", theURLorText}
«event TextUtil» {"parseXML", theURLorText}
«event TextUtil» {"writeJSON", theRecord }
«event TextUtil» {"MGTemplateEngine", theTemplate, theValueRecord }
«event TextUtil» {"MGTemplateEngine", theTemplate, theValueRecord, thePersistentValueRecord }
«event TextUtil» {"MGTemplateEngine", theTemplate, theValueRecord, thePersistentValueRecord, {|OpenHTML|:1, |Browser|:"Safari", |OpenText|:0, |Editor|:"mi", |Activate|:0, |Name|:"UseCount"} }
«event TextURL_» {"encodeURIComponent", theText [, mojiCode] }
«event TextURL_» {"decodeURIComponent", theText [, mojiCode] }
«event TextURL_» {"parseURL", theURL }
|queryRecord| |absoluteString| |absoluteURL| |baseURL| |fragment| |host| |lastPathComponent| |parameterString| |password| |path| |pathComponents| |pathExtension| |port| |query| |relativePath| |relativeString| |resourceSpecifier| |scheme| |user|
set theURLRecord to «event TextURL_» {"parseURL", "http://www.google.co.jp/search?q=3ds"}
«event DebugLog» (theURLRecord)
«event DebugLog» (item 1 of q of |queryRecord| of theURLRecord)
«event TextURL_» {"htmlspecialchars", theText }
«event TextURL_» {"decodeHtmlSpecialchars", theText }
TextRegx
は引数の順番がイマイチで気になるのでそれを直した新しい命令を作りました。
«event TextRE__» {"isMatched", "text", "pattern" }
«event TextRE__» {"capture", "abc 123 efg", "([0-9]+)", 1 }
«event TextRE__» {"capture", "abc 123 efg", "(\\w+) ([0-9]+)", {1,2} }
«event TextRE__» {"matchedList", "123 abc 456 efg", "[0-9]+" }
{ "123", "456" }
«event TextRE__» {"matchedList", theHTML, "(?<=<a href=\")(.*?)(?=\">)" }
«event TextRE__» {"split", "text", "pattern" }
«event TextRE__» {"replace", "text", "pattern", "replaceText" }
«event TextRegx» {"isMatched", "pattern", "text" }
«event TextRegx» {"capture", "([0-9]+)", "abc 123 efg", 1 }
«event TextRegx» {"capture", "(\\w+) ([0-9]+)", "abc 123 efg", {1,2} }
«event TextRegx» {"matchedList", "[0-9]+", "123 abc 456 efg" }
{ "123", "456" }
«event TextRegx» {"matchedList", "(?<=<a href=\")(.*?)(?=\">)", theHTML }
«event TextRegx» {"split", "pattern", "text" }
«event TextRegx» {"replace", "pattern", "text", "replaceText" }
詳しい説明はNSString Class Referenceを見て下さい。
Finderでできるのに何故あるのかというとFinderでファイル操作をしているとエラーになることがたまにあってFinderを信用してないからです。
コマンドでもできますが、命令を用意したほうが処理が軽いかなと思い(未確認)機能を付けました。
«event TextPath» {"url", thePath}
«event TextPath» {"exists", thePath}
«event TextPath» {"isDirectory", thePath}
«event TextPath» {"stringByExpandingTildeInPath", thePath}
«event TextPath» {"lastPathComponent", thePath}
«event TextPath» {"pathExtension", thePath}
«event TextPath» {"stringByDeletingLastPathComponent", thePath}
«event TextPath» {"stringByDeletingPathExtension", thePath}
«event TextPath» {"stringByAbbreviatingWithTildeInPath", thePath}
«event TextPath» {"stringByAppendingPathComponent", thePath, "inu.zip"}
«event TextPath» {"stringsByAppendingPaths", thePath, {"mac", "wii"}}
«event TextPath» {"isAbsolutePath", thePath}
«event TextPath» {"stringByResolvingSymlinksInPath", thePath}
«event TextPath» {"stringByStandardizingPath", thePath}
«event TextPath» {"move", theSrcPath, theDstPath }
«event TextPath» {"copy", theSrcPath, theDstPath }
«event TextPath» {"remove", thePath}
«event TextPath» {"createDirectory", thePath}
«event TextPath» {"contentsOfDirectory", thePath }
«event TextPath» {"contentsOfDirectory", thePath, { "SkipsHiddenFiles", "folderOnly", "fileOnly", "AddSlashToFolder" } }
«event TextPath» {"moveToTrash", thePath}
«event TextPath» {"duplicate", thePath}
«event TextPath» {"setIcon", thePath, theImagePathOrURL }
«event TextPath» {"getTag", thePath }
missing value
を返します«event TextPath» {"setTag", thePath, theTagStrOrTagList }
addTag
を使います。«event TextPath» {"setTag", thePath, "Apple" }
«event TextPath» {"setTag", thePath, { "Apple", "Store" } }
«event TextPath» {"addTag", thePath, theTagStrOrTagList }
«event TextPath» {"removeTag", thePath, theTagStrOrTagList }
«event DateUtil» {"SecondsUntilDate", {|Year|:2101, |Month|:1, |Day|:1, |Hour|:0, |Minute|:0, |Second|:0}}
«event DateUtil» {"SecondsUntilDate", {|Month|:1, |Day|:1, |Hour|:0, |Minute|:0, |Second|:0}}
«event DateUtil» {"SecondsUntilDate", {|Day|:2, |Hour|:2, |Minute|:2, |Second|:2}}
«event DateUtil» {"SecondsUntilDate", {|Hour|:23, |Minute|:0, |Second|:0}}
«event DateUtil» {"SecondsUntilDate", {|Minute|:30, |Second|:0}}
«event DateUtil» {"SecondsUntilDate", {|Second|:30}}
«event DateUtil» {"SecondsUntilDate", "2013-04-12 15:30:45 +0900"}
Cocoaで辞書、一般にはハッシュと呼ばれる、文字列をキーとして、データを参照する機能を実現する命令です。
あったら便利だと思うので付けてみました。
これを使う利点は辞書が便利だから以外に、ここに保存したデータはMenuBarAppleScriptのスクリプトを変更、保存してもMenuBarAppleScriptのアプリケーションが起動している限り保持されるので、ここに状態を保存すると、スクリプトを保存、更新しても、次の実行時に前回の状態のままスクリプトを実行できます。開発中のアプリケーションでスクリプトの一部分を変更した場合でも、property
に保存するとスクリプトを保存した時に初期化されてしまいますが、これを使うと前回の状態を保持したまま実行できます。
また、return {|SaveDict|:1}
を返すと、辞書の値をPreferenceのplistに保存するようになり、アプリケーションを終了、再起動しても値を保持できるようになります。デフォルト値も1です。なお、値の読み込みは起動時に自動的に行われます。
辞書のキーには文字列以外も指定できますが内部で文字列に変換されます。これはplistでは辞書のキーは文字列のみ有効だからです。その為、"1"と1は同じキーとなるので注意してください。
実行中のみの一時データは下記名前にすると保存ません。
«event DictSetV» {dictName, "theKey", "value" }
«event DictSetV» {dictName, "theKey", {"hoge", "hage" } }
"value"
、{"hoge", "hage" }
に設定。dictName[theKey] = theValue;
«event DictSetD» {dictName, "theKey", "defaultValue" }
«event DictGetV»
をした時に、«event DictSetV»
で値を設定していない場合にmissing valueでなく、これで設定した値を返します。«event DictGetV» {dictName, "theKey"}
«event DictGetV» {dictName, "theKey", theDefaultValue }
missing value
を返します。メモ:missing value
はFourCC "msng"でした。«event DictGetL» {dictName, "theKey"}
«event DictNotV» {dictName, "theKey"}
«event DictNotV» {dictName, "theKey", theDefaultValue }
«event DictDelV» {dictName, "theKey"}
«event DictDelA» {dictName}
«event DictAKey» {dictName,"sort function"}
«event DictAVal» {dictName,"sort function"}
«event DictInit»
«event DictList» {dictName,"listKey",actionName, "value1"}
リスト操作であれば便利そうなのを入れてみました。
この命令は対象のリストを変更します。
値を取得する物で、値を取得できなかった場合はmissing value
を返します。
直接リストを操作したい場合は下記のように«event HndlList»にして、theListで直接リストを指定します。命令の種類は下記«event DictList»と同じです。
返り値のtheListが処理済みのリストで、theResultは処理した時の値で無い場合はmissing valueになります。shiftやpopの場合等に値が入っています。
set {theList, theResult} to «event HndlList» {theList,"sort", "localizedStandardCompare" }
«event DictList» {dictName,listKey,"objectAtIndex", 1}
«event DictList» {dictName,listKey,"indexOfObject", "hoge"}
«event DictList» {dictName,listKey,"containsObject", "hoge"}
«event DictList» {dictName,listKey,"count"}
«event DictList» {dictName,listKey,"reverse"}
«event DictList» {dictName,listKey,"pop"}
«event DictList» {dictName,listKey,"shift"}
«event DictList» {dictName,listKey,"addObject", "hoge"}
«event DictList» {dictName,listKey,"push", "hoge"}
«event DictList» {dictName,listKey,"unshift", "hoge"}
«event DictList» {dictName,listKey,"addObjectsFromArray", { "hoge", "hage" } }
«event DictList» {dictName,listKey,"setArray", { "hoge", "hage" } }
«event DictList» {dictName,listKey,"trim", 10}
«event DictList» {dictName,listKey,"head", 10}
«event DictList» {dictName,listKey,"tail", 10}
«event DictList» {dictName,listKey,"removeLastObject"}
«event DictList» {dictName,listKey,"removeAllObjects"}
«event DictList» {dictName,listKey,"removeObject", "hoge"}
«event DictList» {dictName,listKey,"removeObjectsInArray", { "hoge", "hage" } }
«event DictList» {dictName,listKey,"sort", "sort function" }
«event DictList» {dictName,listKey,"isEqualToArray", theList }
«event DictList» {dictName,listKey,"uniq"}
«event DictList» {dictName,listKey,"grep", "pattern" }
«event DictList» {dictName,listKey,"notgrep", "pattern" }
«event DictList» {dictName,listKey,"insertAfterEveryItem", "hoge" }
{1,2,3}
が{1,"hoge",2,"hoge",3,"hoge"}
になります。«event DictList» {dictName,listKey,"insertBeforeEveryItem", "hoge" }
{1,2,3}
が{"hoge",1,"hoge",2,"hoge",3}
になります。«event DictList» {dictName,listKey,"insertAfterTheItem", "find", "insert" }
{1,"find",3}
が{1,"find","insert",3}
になります。«event DictList» {dictName,listKey,"insertBeforeTheItem", "find", "insert" }
{1,"find",3}
が{1,"insert","find",3}
になります。«event DictList» {dictName,listKey,"insertListAfterEveryItem", theInserList }
«event DictList» {dictName,listKey,"insertListAfterEveryItem", {8,9} }
{1,2,3}
が{1,8,9,2,8,9,3,8,9}
になります。«event DictList» {dictName,listKey,"insertListBeforeEveryItem", theInserList }
«event DictList» {dictName,listKey,"insertListBeforeEveryItem", {8,9} }
{1,2,3}
が{8,9,1,8,9,2,8,9,3}
になります。«event DictList» {dictName,listKey,"insertListAfterTheItem", "find", theInserList }
«event DictList» {dictName,listKey,"insertListAfterTheItem", "find", {8,9} }
{1,"find",3}
が{1,"find",8,9,3}
になります。«event DictList» {dictName,listKey,"insertListBeforeTheItem", "find", theInserList }
«event DictList» {dictName,listKey,"insertListBeforeTheItem", "find", {8,9} }
{1,"find",3}
が{1,8,9,"find",3}
になります。«event DictList» {dictName,listKey,"prependTextToEveryItem", "hoge" }
{"a","b"}
が{"hogea","hogeb"}
になります。«event DictList» {dictName,listKey,"appendTextToEveryItem", "hoge" }
{"a","b"}
が{"ahoge","bhoge"}
になります。«event DictList» {dictName,listKey,"replaceTextToEveryItem", "pattern", "replaceText" }
«event DictList» {dictName,listKey,"join", "text"}
辞書(レコード)操作であれば便利そうなのを入れてみました。
値を取得する物で、値を取得できなかった場合はmissing value
を返します。
直接レコードを操作したい場合は下記のように«event HndlDict»にして、theRecordで直接リストを指定します。命令は下記«event DictDict»と同じです。
返り値のtheRecordが処理済みのレコードで、theResultは処理した時の値で無い場合はmissing valueになります。
set {theRecord, theResult} to «event HndlDict» {theRecord,"setObjectForKey", theObject, theKey }
«event DictDict» {dictName,dictKey,"objectforkey", theKey }
«event DictDict» {dictName,dictKey,"setObjectForKey", theObject, theKey }
«event DictDict» {dictName,dictKey,"removeObjectForKey", theKey }
«event DictDict» {dictName,dictKey,"count" }
«event DictDict» {dictName,dictKey,"removeAllObjects" }
«event DictDict» {dictName,dictKey,"removeObjectsForKeys", { key1, key2 } }
«event DictDict» {dictName,dictKey,"allKeys", "sort function" }
«event DictDict» {dictName,dictKey,"allValues", "sort function" }
«event DictDict» {dictName,dictKey,"isEqualToDictionary", theRecord }
«event DictDict» {dictName,dictKey,"setDictionary", theRecord }
«event DictDict» {dictName,dictKey,"allKeysForObject", theObj }
辞書の指定したキーに値が無い場合は0を処理対象にする
«event DictNum_» {dictName,dictKey,"+", 1}
«event DictNum_» {dictName,dictKey,"not"}
辞書の指定したキーに値が無い場合は空文字列を処理対象にする
«event DictText» {dictName,dictKey,"append", "abc"}
«event DictText» {dictName,dictKey,"compare", "abc", "compare"/"caseInsensitiveCompare"/"localizedCaseInsensitiveCompare"/"localizedStandardCompare"}
初期設定でURLスキームを設定するとそのURLを開いた時に下記ハンドラが実行されます。
on doOpenURL(theURL, theActiveAppRecord, theThisAppRecord)
URLの分析するにはparseURLを使うと便利かもしれません。
他のアプリケーションからURLを開くと、MenuBarAppleScriptアプリケーションがアクティブになり、現在アクティブなアプリケーションがアクティブじゃなくなります。バックグラウンドで、URLを使って命令をやり取りする場合には、結構気になります。それを回避するには、初期設定のアプリケーションの実行タイプをLSBackgroundOnlyにします。ただし、デフォルトのLSUIElementとGUIを利用する場合の細かい動作が違ってきます。
UI ElementはUIElementInspectorで調べることができます。「Download Sample Code」をダウンロードするとアプリケーションが入っています。
属性名の最後に(W)と書いてある物が設定可能になります。Position、Rangeなどを設定するには取得した属性と同じ形式のテキストを作成して渡すことにより設定可能です。
値によってはGUI Scriptingでは取得、設定できない物もありますので、いろいろ楽しめるのではないかと思います。
property theMenuBarItemImage : "icon Finder" -- -- メニューバーの画像。無い場合はmissing value
property theMenuBarTitle : "タイトル" -- メニューバーのタイトル 。無い場合はmissing value
property theMenuItemList : {"メニュー1", "メニュー2"}
on run
-- デバッグ用
-- tellするアプリケーションを編集中のMenuBarAppleScriptアプリケーションにしておく必要があります
-- ハンドラをテスト実行
-- theActiveAppRecord は «event InfoAApp»
-- theThisAppRecord は «event InfoTApp»
-- になります
my doSelectMenuItem("メニュー1", 1, «event InfoAApp», «event InfoTApp»)
end run
on doInit(theActiveAppRecord, theThisAppRecord)
return {|SaveDict|:1, |LogAppleScriptError|:1, |DebugLog|:1, |DebugJavaScript|:0, |AutoAddQuitMenuItem|:1, |AutoAddLaunchOnLoginMenuItem|:1, |AutoAddVersionMenuItem|:1, |AutoSaveAndRestoreWindowFrameByName|:1, |MenuBarItemImage|:theMenuBarItemImage, |MenuBarItemTitle|:theMenuBarTitle, |MenuItemList|:my getMenuItemList(theActiveAppRecord, theThisAppRecord)}
end doInit
on doRun(theActiveAppRecord, theThisAppRecord)
-- 起動直後に処理をするならここに記述
end doRun
on getMenuItemList(theActiveAppRecord, theThisAppRecord)
set theWorkList to theMenuItemList
-- ここでメニューアイテムにチェックを付けたりする
set end of theWorkList to (current date) as text
set theWorkList to {|name| of theThisAppRecord, "---separatorItem"} & theMenuItemList
return theWorkList
end getMenuItemList
on setMenuItem(theActiveAppRecord, theThisAppRecord)
«event MBASHndl» {|MenuItemList|:my getMenuItemList(theActiveAppRecord, theThisAppRecord)}
end setMenuItem
on doSelectMenuItem(theTitle, theTag, theActiveAppRecord, theThisAppRecord)
-- メニュー選択時の処理
if theTitle is "メニュー1" then
beep
end if
end doSelectMenuItem
サービスから実行するアプリケーションのテンプレートです。テキストとファイル、ついでにURLが書いてありますが、必要無いものは消して下さい。
on run
-- デバッグ用
-- tellするアプリケーションを編集中のMenuBarAppleScriptアプリケーションにしておく必要があります
my doTextService("テキスト text TEXT", «event InfoAApp», «event InfoTApp»)
my doFileService({"/hoge.txt"}, «event InfoAApp», «event InfoTApp»)
my doOpenURL("http://memogakisouko.appspot.com/MenuBarAppleScript.html#download", «event InfoAApp», «event InfoTApp»)
end run
on doInit(theActiveAppRecord, theThisAppRecord)
return {|LogAppleScriptError|:1, |DebugLog|:1, |SaveDict|:1}
end doInit
on doTextService(theText, theActiveAppRecord, theThisAppRecord)
«event DebugLog» theText
-- 処理をする
set theText to «event TextUtil» {"lowercaseString", theText}
-- 置換する場合ReplaceStringを返す
return {|ReplaceString|:theText, |QuitAfterSec|:30.0}
end doTextService
on doFileService(thePathList, theActiveAppRecord, theThisAppRecord)
«event DebugLog» thePathList
repeat with thePath in thePathList
-- thePath に処理をする
end repeat
return {|QuitAfterSec|:30.0}
end doFileService
on doOpenFile(thePathList, theActiveAppRecord, theThisAppRecord)
my doFileService(thePathList, theActiveAppRecord, theThisAppRecord)
end doOpenFile
on doOpenURL(theURL, theActiveAppRecord, theThisAppRecord)
set theURLRecord to «event TextURL_» {"parseURL", theURL}
«event DebugLog» theURLRecord
-- URLの処理をする
return {|QuitAfterSec|:30.0}
end doOpenURL
property theMenuBarItemImage : "icon Evernote" -- -- メニューバーの画像。無い場合はmissing value
property theMenuBarTitle : "履歴" -- メニューバーのタイトル 。無い場合はmissing value
property theMenuBarItemDisplayAppNameList : {"Evernote", "AppleScript Editor"} -- 表示するアプリケーション名
|NSWorkspaceNotificationList|:{"NSWorkspaceDidActivateApplicationNotification"},
on doNSWorkspaceDidActivateApplicationNotification(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
set activeAppName to |name| of theUserInfo
if (theMenuBarItemDisplayAppNameList does not contain activeAppName) and (|name| of theThisAppRecord is not activeAppName) then
-- 隠す
return {RemoveMenuBarItem:1}
end if
-- 表示
return {|MenuBarItemImage|:theMenuBarItemImage, |MenuBarItemTitle|:theMenuBarTitle, |MenuItemList|:my getMenuItemList(theActiveAppRecord, theThisAppRecord)}
end doNSWorkspaceDidActivateApplicationNotification
on doNSWorkspaceDidActivateApplicationNotification(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
set activeAppName to |name| of theUserInfo
if (theMenuBarItemDisplayAppNameList does not contain activeAppName) and (|name| of theThisAppRecord is not activeAppName) then
-- 隠す
-- ウインドウのvisibleを保存して全部隠す
if 1 is |menuBarItemVisible| of theThisAppRecord then -- まだ隠してない時のみ隠す
«event DictSetV» {"Var", "allWebViewWindowRecord", |allWebViewWindowRecord| of theThisAppRecord}
«event Win_Hide»
return {RemoveMenuBarItem:1}
else
return
end if
end if
-- 表示
if 0 is |menuBarItemVisible| of theThisAppRecord then
«event Win_Show» («event DictGetV» {"Var", "allWebViewWindowRecord"}) -- 非表示から表示した時のみ表示する
end if
return {|MenuBarItemImage|:theMenuBarItemImage, |MenuBarItemTitle|:theMenuBarTitle, |MenuItemList|:my getMenuItemList(theActiveAppRecord, theThisAppRecord)}
end doNSWorkspaceDidActivateApplicationNotification
|MenuItemList|
で、メニューアイテムが設定されているとそのメニューが表示されdoClickMenuBarItem
は実行されず、動的にメニューを作れません。
他のレシピと併用する場合は|MenuItemList|
を削除して下さい。
|SendActionOn|:{"LeftMouseDown"},
on doClickMenuBarItem(theActiveAppRecord, theThisAppRecord)
return {|PopUpMenuItemList|:my getMenuItemList(theActiveAppRecord, theThisAppRecord)}
end doClickMenuBarItem
on doRightClickMenuBarItem(theActiveAppRecord, theThisAppRecord)
return {|PopUpMenuItemList|:theRightClickMenuItemList}
end doRightClickMenuBarItem
-- 処理対象のリスト
set theWorkList to |allWebViewWindowNameList| of theThisAppRecord
-- ソートするなら
set {theWorkList, theResult} to «event HndlList» {theWorkList, "sort", "localizedStandardCompare"}
-- タイトル用のprefixを付ける。付けなくてもほぼ大丈夫だけど---tagなどで始まるタイトルは表示されない
set {theWorkList, theResult} to «event HndlList» {theWorkList, "prependTextToEveryItem", "---title "}
-- メニューを選択した時に実行する処理の種類をtagで指定。メニューが複雑になってくると、tagでの判別が必要になってきます。
set {theWorkList, theResult} to «event HndlList» {theWorkList, "insertAfterEveryItem", "---tag " & theSelectWindowTag}
-- 特定の項目にチェックを付ける
set checkTitle to |keyWindowName| of theThisAppRecord -- チェックを付けるタイトル
set {theWorkList, theResult} to «event HndlList» {theWorkList, "insertAfterTheItem", "---title " & checkTitle, "---state on"}
-- 完成
スクリプトに追加するには、最初に下のスクリプトを全部コピペしてから、theHogeSetting
を自分の好きな名前に一括置換すると速いです。
property theSettingDictName : "Setting" -- 設定を保存する辞書の名前
property theHogeSettingTagAndKey : 1 -- 設定のタグとキー。数字で指定
property theHogeSettingDefaultValue : 1 -- 設定の初期値。1がオン、0がオフ
property theMenuItemList : { "設定", "---disable", "Hogeの設定", "---tag " & theHogeSettingTagAndKey, "---indentationLevel 1"}
-- 初期値を辞書に登録
«event DictSetD» {theSettingDictName, theHogeSettingTagAndKey, theHogeSettingDefaultValue}
on getMenuItemList(theActiveAppRecord, theThisAppRecord)
set theWorkList to theMenuItemList
-- メニューアイテムにオンの場合チェックを付ける
set {theWorkList, theResult} to «event HndlList» {theWorkList, "insertAfterTheItem", "---tag " & theHogeSettingTagAndKey, "---state " & («event DictGetV» {theSettingDictName, theHogeSettingTagAndKey})}
return theWorkList
end getMenuItemList
on doSelectMenuItem(theTitle, theTag, theActiveAppRecord, theThisAppRecord)
if theTag is theHogeSettingTagAndKey then
-- 設定のトルグ
«event DictNotV» {theSettingDictName, theHogeSettingTagAndKey }
my setMenuItem(theActiveAppRecord, theThisAppRecord) -- メニュー更新
end if
end doSelectMenuItem
if 1 is («event DictGetV» {theSettingDictName, theHogeSettingTagAndKey}) then
-- 設定がオンなので処理をする
end if
上のサンプルと似ていますが、これはある値のオンオフじゃなくて、数種類の値から選ぶメニューになります。
property theThemeMenuItemTag : 9 -- メニューアイテムに付けるタグ。重複しない任意の数値
property theDefaultThemeName : "Cosmo" -- デフォルト値
property theSettingDictName : "Setting" -- 設定を保存する辞書名
property theThemeNameKey : "ThemeName" -- 設定を保存する辞書のキー
property theMenuItemList : {"HTML テーマ", "---submenu", "Amelia", "---tag " & theThemeMenuItemTag, "Cerulean", "---tag " & theThemeMenuItemTag, "Cosmo", "---tag " & theThemeMenuItemTag, "Cyborg", "---tag " & theThemeMenuItemTag, "Journal Readable", "---tag " & theThemeMenuItemTag, "Simplex", "---tag " & theThemeMenuItemTag, "Slate", "---tag " & theThemeMenuItemTag, "Spacelab", "---tag " & theThemeMenuItemTag, "Spruce", "---tag " & theThemeMenuItemTag, "Superhero", "---tag " & theThemeMenuItemTag, "United", "---tag " & theThemeMenuItemTag, "---end submenu"}
-- 初期値を辞書に登録
«event DictSetD» {theSettingDictName, theThemeNameKey, theDefaultThemeName}
on getMenuItemList(theActiveAppRecord, theThisAppRecord)
set theWorkList to theMenuItemList
-- 現在の値のメニューアイテムにチェックを付ける
set theThemeName to «event DictGetV» {theSettingDictName, theThemeNameKey}
set {theWorkList, theResult} to «event HndlList» {theWorkList, "insertAfterTheItem", theThemeName, "---state 1"}
return theWorkList
end getMenuItemList
on doSelectMenuItem(theTitle, theTag, theActiveAppRecord, theThisAppRecord)
-- メニュー選択時の処理
if theTag is theThemeMenuItemTag then
-- メニューを選択したので値を保存して、メニューを更新
«event DictSetV» {theSettingDictName, theThemeNameKey, theTitle}
my setMenuItem(theActiveAppRecord, theThisAppRecord)
end if
end doSelectMenuItem
on setMenuItem(theActiveAppRecord, theThisAppRecord)
«event MBASHndl» {|MenuItemList|:my getMenuItemList(theActiveAppRecord, theThisAppRecord)}
end setMenuItem
set theHTMLTheme to «event DictGetV» {theSettingDictName, theThemeNameKey}
-- theHTMLThemeに選択した値が入っているので処理をする
曲が変わった通知を受け取ります。
|NSDistributedNotificationList|:{"com.apple.iTunes.playerInfo"},
on docom_apple_iTunes_playerInfo(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
-- NSDistributedNotificationのcom.apple.iTunes.playerInfoの通知を受けた時に実行されます
-- theUserInfoに曲の情報が入っています
-- 次のようにして取り出します
-- set theName to |Name| of theUserInfo
-- 停止中はメニューバーから取り除く
if |Player State| of theUserInfo is "Paused" then
-- 停止した時の処理
return {}
end if
-- Playing以外の場合はハンドラを終了
-- PausedとPlaying以外あるかどかはわかりません
if |Player State| of theUserInfo is not "Playing" then
-- 再生していない時の処理
return {}
end if
tell application "iTunes"
-- theUserInfoから通知で受けた曲を取り出すには次のようにpersistent IDを使います
-- current trackでもだいたいいいと思います
set thePlaylistList to every playlist whose persistent ID is |Playlist PersistentID| of theUserInfo
set thePlaylist to item 1 of thePlaylistList
set theTrackList to every track of thePlaylist whose persistent ID is |PersistentID| of theUserInfo
set theTrack to item 1 of theTrackList -- theTrackがtheUserInfoで受け取ったtrackになります
-- 処理をする
end tell
end docom_apple_iTunes_playerInfo
|NSDistributedNotificationList|:{"com.apple.DownloadFileFinished"},
on docom_apple_DownloadFileFinished(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
-- theObjectにダウンロードしたファイルのパスが入っています
set theDownloadFilePath to theObject
-- 処理をする
end docom_apple_DownloadFileFinished
|SCNotificationList|:{"State:/Network/Global/IPv4"}, |RunSCNotificationHandler|:1,
on doState__Network_Global_IPv4(theValue, theActiveAppRecord, theThisAppRecord)
-- ネットに繋がっているかチェックして記憶
if theValue is not missing value then
-- ネットに繋がっている
set theNetworkStatus to true
else
-- ネットに繋がっていない
set theNetworkStatus to false
end if
end doState__Network_Global_IPv4
|HotKeyList|:{{|Name|:"RateUp", |KeyChar|:"up", |ModifierKeyList|:{"shift", "option"}, |Enable|:1 }, {|Name|:"RateDown", |KeyChar|:"down", |ModifierKeyList|:{"shift", "option"}, |Enable|:1}},
-- ホットキー設定 変更した場合は要アプリケーションの再起動
property theEnableHotKey : 0 -- ホットキーを有効にするなら1 無効なら 0
property theHotKeyKey : "right" -- ホットキーのキー
property theHotKeyModifierKeyList : {"command", "control"} -- ホットキーの修飾キー
|HotKeyList|:{{|Name|:"RateUp", |KeyChar|:theHotKeyKey, |ModifierKeyList|:theHotKeyModifierKeyList, |Enable|:theEnableHotKey}},
on doHotKeyRateUp(theActiveAppRecord, theThisAppRecord)
-- ホットキーを押した時の処理
end doHotKeyRateUp
|AppHotKeyList|:{{|Name|:"RateUp", |KeyChar|:"up", |ModifierKeyList|:{"shift", "option"}, |Enable|:1 }, {|Name|:"RateDown", |KeyChar|:"down", |ModifierKeyList|:{"shift", "option"}, |Enable|:1}},
-- ホットキー設定 変更した場合は要アプリケーションの再起動
property theEnableHotKey : 0 -- ホットキーを有効にするなら1 無効なら 0
property theHotKeyKey : "right" -- ホットキーのキー
property theHotKeyModifierKeyList : {"command", "control"} -- ホットキーの修飾キー
|AppHotKeyList|:{{|Name|:"RateUp", |KeyChar|:theHotKeyKey, |ModifierKeyList|:theHotKeyModifierKeyList, |Enable|:theEnableHotKey}},
on doAppHotKeyRateUp(theActiveAppRecord, theThisAppRecord)
-- ホットキーを押した時の処理
end doAppHotKeyRateUp
可能ならデバッグ方法を使用したほうがイベントログが確認できます。
on run
set theApp to (path to me) as Unicode text
tell application theApp
«event MBASRunH» {|Handler|:"Test"}
end tell
end run
on doHandlerTest(theRecord, theActiveAppRecord, theThisAppRecord)
-- ここにテスト用スクリプトを書く
-- MenuBarAppleScriptで実行されるので各種MenuBarAppleScriptの命令が使えます
say "test"
end doHandlerTest
コマンドの非同期実行でosascriptを実行して、MenuBarAppleScriptのスクリプトのハンドラを実行して擬似的にスレッドを実現しているものです。
スレッド間での値の受け渡しは辞書(ハッシュ)関係の命令を使い、MenuBarAppleScriptを起点に行うことができます。MenuBarAppleScriptの命令はアプリケーションのメインスレッドで実行されるので、別のスレッドから同時に実行しても大丈夫です。
スレッドではpropertyは使えますが、初期値は保証されません。また、他のスレッド間でpropertyの値を共有することはできません。初期化してそのスクリプト中で使うことはできます。
MenuBarAppleScriptThread.scptは最新のMenuBarAppleScriptにはバンドルの中に入っています。
-- 新規スレッドで実行
my detachNewThread("Test", {"hoge", "neko"})
on detachNewThread(theThreadHandler, theUserInfo)
-- スレッド作成
set appInfoRecord to «event InfoTApp»
set theThreadNumber to «event DictNum_» {"ThreadUserInfo", "ThreadNumber", "+", 1}
set theThreadNumberStr to theThreadNumber as text
«event DictSetV» {"ThreadUserInfo", theThreadNumberStr, theUserInfo}
set theThreadScriptPath to (|scriptsFolderPath| of appInfoRecord) & "MenuBarAppleScriptThread.scpt"
«event Cmd_Run_» {|CommandPath|:"/usr/bin/osascript", |ArgvList|:{theThreadScriptPath, |bundleIdentifier| of appInfoRecord, theThreadHandler, theThreadNumberStr}, |Handler|:"FinishThread" & theThreadHandler, |ThreadNumber|:theThreadNumber, |UserInfo|:theUserInfo}
end detachNewThread
on doThreadTest(theBundleIdentifier, theThreadNumberStr)
try
-- スレッドで実行されるハンドラ
tell application id theBundleIdentifier
-- 元のアプリケーションにtell
set theUserInfo to «event DictGetV» {"ThreadUserInfo", theThreadNumberStr}
«event DebugLog» {"UserInfo", theUserInfo}
-- 時間がかかる処理など
«event DictDelV» {"ThreadUserInfo", theThreadNumberStr} -- UserInfoを削除しておく
end tell
return "abc"
on error msg
«event DebugLog» "Thread Error"
«event DebugLog» msg
end
end doThreadTest
on doCommandResultFinishThreadTest(theOutputText, theErrorText, theTerminationStatus, theCommandRecord, theActiveAppRecord, theThisAppRecord)
-- スレッド終了時に実行されるハンドラ
-- theOutputTextの末尾には改行が付加されているようです。
«event DebugLog» {"ThreadResult", theOutputText}
«event DebugLog» theCommandRecord
end doCommandResultFinishThreadTest
例:ファイルメニューの保存がCmd + Sやメニューアイテムを選択して実行された時にハンドラを実行します。
{|AXNotificationList|:{{|AppName|:"Evernote", |Enable|:1, |NotificationList|:{{|Name|:"AXMenuItemSelected", |Enable|:1, |TargetList|:{"AXMenuBar", "", "AXTitle", "ファイル", "AXRole", "AXMenu", "AXTitle", "保存"}}}}}}
on doAXMenuItemSelected(theAXAppRecord, theActiveAppRecord, theThisAppRecord)
-- メニューが選択された
end doAXMenuItemSelected
GUIが全く無いアプリケーションに書いておくと終了できるようになります。
on doReOpen(theActiveAppRecord, theThisAppRecord)
set theName to |name| of theThisAppRecord
activate
display dialog theName & "を終了します" buttons {"Cancel", "OK"} default button 2
if the button returned of the result is "OK" then
quit
end if
end doReOpen
メニューに終了とログイン時に自動的に起動する設定を表示しておけば、終了したり、その設定ができるようになります。
|AutoAddQuitMenuItem|:1, |AutoAddLaunchOnLoginMenuItem|:1, |AutoAddVersionMenuItem|:1,
on doReOpen(theActiveAppRecord, theThisAppRecord)
set theName to |name| of theThisAppRecord
«event MBASHndl» {|ContextMenuItemList|:{theName}}
end doReOpen
テキストの取得、選択しているテキストの取得、テキストの選択、選択しているテキストへスクロール、指定したテキストへスクロールができます。
on getFocusedUIElementText(theActiveAppRecord)
-- ver.2012.12.25.00
-- フォーカスが当たっているAXTextFieldかAXTextAreaのテキストを取得
«event UIElSetE» {|name| of theActiveAppRecord, {"AXFocusedUIElement", ""}}
set {theRole, theValue} to «event UIElGetV» {"AXRole", "AXValue"}
if {"AXTextField", "AXTextArea"} does not contain theRole then
return ""
end if
return theValue
end getFocusedUIElementText
on getFocusedUIElementSelectedText(theActiveAppRecord)
-- ver.2012.12.25.00
-- フォーカスが当たっているAXTextFieldかAXTextAreaの選択しているテキストを取得
«event UIElSetE» {|name| of theActiveAppRecord, {"AXFocusedUIElement", ""}}
set {theRole, theSelectedText} to «event UIElGetV» {"AXRole", "AXSelectedText"}
if {"AXTextField", "AXTextArea"} does not contain theRole then
return ""
end if
return theSelectedText
end getFocusedUIElementSelectedText
on focusedUIElementSelectText(theActiveAppRecord, theSelectText)
-- ver.2012.12.31.00
-- フォーカスが当たっているAXTextField、AXTextAreaの指定したテキストを選択する
«event UIElSetE» {|name| of theActiveAppRecord, {"AXFocusedUIElement", ""}}
set {theRole, theValue} to «event UIElGetV» {"AXRole", "AXValue"}
if {"AXTextField", "AXTextArea"} does not contain theRole then
return false
end if
set pos to offset of theSelectText in theValue
if pos is 0 then
return false
end if
set theLen to (count of theSelectText)
set pos to pos - 1
«event UIElSetV» {"AXSelectedTextRange", "pos=" & pos & " len=" & theLen}
«event UIElSetV» {"AXVisibleCharacterRange", "pos=" & pos & " len=" & theLen}
return true
end focusedUIElementSelectText
on focusedUIElementScrollToSelection(theActiveAppRecord)
-- ver.2012.12.25.00
-- フォーカスが当たっているAXTextAreaを選択しているテキストかカーソルがある位置にスクロールする
«event UIElSetE» {|name| of theActiveAppRecord, {"AXFocusedUIElement", ""}}
set {theRole, theRange} to «event UIElGetV» {"AXRole", "AXSelectedTextRange"}
if theRole is not "AXTextArea" then
return
end if
«event UIElSetV» {"AXVisibleCharacterRange", theRange}
end focusedUIElementScrollToSelection
on focusedUIElementScrollToText(theActiveAppRecord, theSelectText)
-- ver.2012.12.25.00
-- AXTextAreaで指定したテキストを表示するようにスクロールする。
-- テキストが複数ある場合は最初のものが対象になる
«event UIElSetE» {|name| of theActiveAppRecord, {"AXFocusedUIElement", ""}}
set {theRole, theVisibleCharacterRange, theValue} to «event UIElGetV» {"AXRole", "AXVisibleCharacterRange", "AXValue"}
if theRole is not "AXTextArea" then
return ""
end if
set pos to offset of theSelectText in theValue
if pos is 0 then
return "no text"
end if
set {theVPos, theVLen} to my rangeTextToList(theVisibleCharacterRange)
if theVPos ≤ pos and pos ≤ (theVPos + theVLen) then
-- すでに画面に表示されている場合はスクロールしない
return "visible"
end if
«event UIElSetV» {"AXVisibleCharacterRange", "pos=" & pos & " len=1"}
return "scroll"
end focusedUIElementScrollToText
on rangeTextToList(theRange)
-- ver.2012.12.25.00
set {thePos, theLen} to «event TextRegx» {"capture", "pos=([0-9]+) len=([0-9]+)", theRange, {1, 2}}
set thePos to thePos + 1 -- AppleScriptは1ベースなので
return {thePos, theLen}
end rangeTextToList
AXTextField,AXTextArea,AXWebAreaなどが取得できます。
クリップボード、サービスを使わずにかなりの状況でテキストを取得できると思います。
-- 指定したアプリケーションのフォーカスが当たっているUI Elementのテキストを取得
if 1 is «event UIElSetE» {"Evernote", {"Attr", "AXFocusedUIElement"}} then
set {theRole, theAllText, theSelectedText, theSelectedRangeList, theInsertionWord, theInsertionLine, theTerm} to my getUIElementText("Evernote", "InsertionWord")
end
on getUIElementText(theGetOption)
-- ver.2013.01.30.00
-- UIElの選択しているテキストなどを取得する
-- "AXTextField", "AXTextArea", "AXWebArea" などから取得できる
-- theAppName : 対象のアプリケーション名 英語名で指定
-- theGetOption :
-- "InsertionWord"で挿入部分の単語(同じ種類の文字の連続)と行を取得。対象が"GetOption"と繋がっている場合は"GetOption"を返します。
-- "Term"で辞書サービスで単語を取得 。単語を取得します。対象が"GetOption"と繋がっていても"Get"や"Option"を返します。
-- 複数指定する場合ははスペースで繋げる
-- ""で未指定
-- 注意
-- AXWebAreaはテキストを選択している場合、theSelectedRangeListがちゃんと取得できない場合がある。
-- その為、 theInsertionWord, theInsertionLineもちゃんと取得できない場合がある
-- 使い方
-- set {theRole, theAllText, theSelectedText, theSelectedRangeList, theInsertionWord, theInsertionLine, theTerm} to my getUIElementText("InsertionWord Term")
-- theInsertionWordはカーソル位置の文字の英数字、ひらがな、カタカナ、漢字の連続した部分です。
tell me
try
set theRole to missing value
set theAllText to missing value
set theSelectedText to missing value
set theSelectedRangeList to missing value
set theInsertionWord to ""
set theInsertionLine to ""
set theTerm to missing value
set {theRole} to «event UIElGetV» {"AXRole"}
if theRole is "AXWebArea" then
«event UIElVal_» {"AXTextMarkerRangeForUIElement", "UIEl", "AllTextMarkerRange"}
set theAllText to «event UIElVal_» {"AXStringForTextMarkerRange", "AllTextMarkerRange", ""}
«event UIElVal_» {"AXSelectedTextMarkerRange", "", "SelectedTextMarkerRange"}
set theSelectedText to «event UIElVal_» {"AXStringForTextMarkerRange", "SelectedTextMarkerRange", ""}
set theBounds to «event UIElVal_» {"AXBoundsForTextMarkerRange", "SelectedTextMarkerRange", ""} -- ,
if theBounds is not missing value then
set {theX, theY, theWidth, theHeight} to «event TextRE__» {"capture", theBounds, "x=(-?\\d+) y=(-?\\d+) w=(-?\\d+) h=(=?\\d+)", {1, 2, 3, 4}}
«event UIElVal_» {"AXTextMarkerForPosition", "x=" & theX & " y=" & theY, "StartSelectionTextMarker"}
«event UIElVal_» {"AXStartTextMarker", "", "StartTextMarker"}
«event UIElVal_» {"AXTextMarkerRangeForUnorderedTextMarkers", {"StartTextMarker", "StartSelectionTextMarker"}, "StartToStartSelectionMarkerRange"} -- ,
set StartToStartSelectionText to «event UIElVal_» {"AXStringForTextMarkerRange", "StartToStartSelectionMarkerRange", ""}
set theSelectedRangeList to {1 + (count of StartToStartSelectionText), count of theSelectedText}
else
set theSelectedRangeList to missing value
end if
else
set {theAllText, theSelectedText, theSelectedTextRange} to «event UIElGetV» {"AXValue", "AXSelectedText", "AXSelectedTextRange"}
if theSelectedTextRange is not missing value then
set {thePos, theLen} to «event TextRE__» {"capture", theSelectedTextRange, "pos=(-?\\d+) len=(-?\\d+)", {1, 2}}
set theSelectedRangeList to {thePos + 1, theLen}
end if
end if
if ((theGetOption contains "Insertionword") or (theGetOption contains "Term")) and (theAllText is not missing value) and (theAllText is not "") and (theSelectedRangeList is not missing value) then
set theAllTextLen to count of theAllText
set theSelPos to (item 1 of theSelectedRangeList)
if (item 2 of theSelectedRangeList) > 0 then
set theSelPos to theSelPos + (item 2 of theSelectedRangeList) - 1
end if
if theSelPos < 1 then
set theSelPos to 1
end if
if theSelPos > theAllTextLen then
set theSelPos to theAllTextLen
end if
if (theGetOption contains "Term") then
set {theTerm, theRange} to «event DicSTerm» {theAllText, theSelPos}
end if
if (theGetOption contains "insertionword") then
set theInsertionWord to text theSelPos of theAllText
set theGetLineRegex to "([^\\n\\r]+)"
set theRegexList to {theGetLineRegex, "([-_A-Za-z0-9]+)", "(\\p{Hiragana}+)", "([\\p{Katakana}ー]+)", "(\\p{Han}+)"}
repeat with theRegexItem in theRegexList
set theRegex to contents of theRegexItem
if (1 is («event TextRE__» {"isMatched", theInsertionWord, theRegex})) then
if theSelPos > 1 then
set theText to text 1 thru (theSelPos - 1) of theAllText
set theText to «event TextRE__» {"capture", theText, theRegex & "\\z", 1}
if theText is not missing value then
set theInsertionWord to theText & theInsertionWord
end if
end if
if theSelPos < theAllTextLen then
set theText to text (theSelPos + 1) thru -1 of theAllText
set theText to «event TextRE__» {"capture", theText, "^" & theRegex, 1}
if theText is not missing value then
set theInsertionWord to theInsertionWord & theText
end if
end if
if theRegex is theGetLineRegex then
set theInsertionLine to theInsertionWord
set theInsertionWord to text theSelPos of theAllText
else
exit repeat
end if
else if theRegex is theGetLineRegex then
-- 前の行を取得
if theSelPos > 1 then
set theText to text 1 thru (theSelPos - 1) of theAllText
set theText to «event TextRE__» {"capture", theText, theRegex & "\\z", 1}
if theText is not missing value then
set theInsertionLine to theText
end if
end if
end if
end repeat
end if
end if
on error
set theRole to missing value
set theAllText to missing value
set theSelectedText to missing value
set theSelectedRangeList to missing value
set theInsertionWord to ""
set theInsertionLine to ""
set theTerm to missing value
end try
return {theRole, theAllText, theSelectedText, theSelectedRangeList, theInsertionWord, theInsertionLine, theTerm}
end tell
end getUIElementText
選択しているものが変わったことはわかりますが、何を選択しているかは通知ではわからないのでselectionで調べて下さい。
|AXNotificationList|:{{|AppName|:"Finder", |NotificationList|:{{|Name|:"AXSelectedRowsChanged", |Target|:"*"}, {|Name|:"AXSelectedChildrenChanged", |Target|:"*"}}}}
on doAXSelectedChildrenChanged(theAXAppRecord, theActiveAppRecord, theThisAppRecord)
-- アイコン表示で選択した項目が変わった。選択したファイルはFinderのselectionで取得します。
-- ファイル以外のものを選択した場合も実行されると思うので、選択したファイルが変わっているか調べる等して下さい
my FinderSelectionChanged()
end doAXSelectedChildrenChanged
on doAXSelectedRowsChanged(theAXAppRecord, theActiveAppRecord, theThisAppRecord)
-- リスト表示で選択した項目が変わった。選択したファイルはFinderのselectionで取得します。
-- ファイル以外のものを選択した場合も実行されると思うので、選択したファイルが変わっているか調べる等して下さい
my FinderSelectionChanged()
end doAXSelectedRowsChanged
on FinderSelectionChanged()
try
tell application "Finder"
set theSelectionAliasList to selection as alias list
end tell
on error
return
end try
repeat with theAlias in theSelectionAliasList
-- 処理をする
end repeat
end FinderSelectionChanged
doInit以外でもOK
|FSEventFolderList|:{"/path/to/check/folder/"}
on doFSEvent(theEventPathList, theActiveAppRecord, theThisAppRecord)
-- 変更があった
end
"CalSearch"を好きな名前に変えて下さい。
次のJavaScriptでブラウザで動作確認できます。
var result = "";
if( window.MenuBarAppleScript )
{
result = window.MenuBarAppleScript.runHandler("CalSearch","MenuBarAppleScriptに渡す値");
// 文字列、数値、リストを渡すことができます
//result = window.MenuBarAppleScript.runHandler("Sample",[ 1, 2, [ "a", "b" ] ] );
// AppleScript側ではtheValueが { 1, 2, { "a", "b" } } になります。
}
else
{
result = "hoge"; // ブラウザで動作確認用のresult
}
// HTMLの状態はLocal Storageでセーブ、ロードできます。
var settingA =localStorage.getItem('settingA'); -- ロード
localStorage.setItem('settingA', $(this).val()); -- セーブ
on doHandlerFromJavaScriptCalSearch(theValue, theActiveAppRecord, theThisAppRecord)
-- «event DebugLog» {"doHandlerFromJavaScriptCalSearch"}
-- 処理をする
set theResult to "結果"
return {|Result|:theResult} -- JavaScriptに返す値
return {|Result|: { theResult, 1, 2 } } -- リストを返すとJavaScriptでは配列になります
end doHandlerFromJavaScriptCalSearch
property theWindowName : "ウインドウ名"
on showWindow(theActiveAppRecord, theThisAppRecord)
-- ウインドウが無ければ作成して表示、あればそれを表示
if (|allWebViewWindowNameList| of theThisAppRecord) does not contain theWindowName then
my makeWindow(theActiveAppRecord, theThisAppRecord)
end if
«event Win_Show» theWindowName
end showWindow
on hideWindow(theActiveAppRecord, theThisAppRecord)
«event Win_Hide» theWindowName
end hideWindow
on makeWindow(theActiveAppRecord, theThisAppRecord)
set theHTMLURL to (|resourcesFolderURL| of theThisAppRecord) & "datepicker/index.html"
«event MBASHndl» {|WebViewWindow|:{|URL|:theHTMLURL, |Name|:theWindowName, |Title|:theWindowName, |Visible|:0, |Activate|:0, |Width|:228, |Height|:400, |Delete|:0, |AllowsScrolling|:0, |Alpha|:1.0, |Opaque|:0, |Level|:"FloatingWindowLevel", |StyleList|:{"Titled", "Utility", "Closable", "HUD"}, |CanHide|:1, |HasShadow|:1, |AcceptsFirstMouse|:1}}
end makeWindow
doDidFinishLoadForFrame
でページの読み込み完了時に処理をできますが、この場合処理が分離されるので、JavaScriptでページの値を取得してそれを処理をする場合などはすごい面倒になります。
そこで、ページの読み込み完了まで待ってJavaScriptを実行するサンプルです。
set theTmpWindowName to "tmp"
set theURL to "http://tv.yahoo.co.jp/listings/"
-- 画像やプラグインをオフにしておくと読み込みがちょっと早いと思う
-- ただし、一度オフにするとずっとオフになるので次にウインドウを作る時は
-- |LoadsImagesAutomatically|:1, |PlugInsEnabled|:1, |JavaEnabled|:1
-- を必要に応じてウインドウ作成時に指定して下さい。
«event MBASHndl» {|WebViewWindow|:{|Name|:theTmpWindowName, |URL|:theURL, |Width|:512, |Height|:512, |Visible|:0, |LoadsImagesAutomatically|:0, |PlugInsEnabled|:0, |JavaEnabled|:0}}
repeat 100 times
delay 0.2
if 0 is («event WebVUtil» {theTmpWindowName, "isLoading"}) then
exit repeat
end if
end repeat
set theJavaScript to "
var text = '';
var result = document.evaluate('id(\"ListingsHeader\")//td[@class=\"station\"]//a//text()', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for(var i=0;i<result.snapshotLength;i++){
var node = result.snapshotItem(i);
text += node.textContent + '<>';
}
return text;
"
-- return で値を返せるように囲っておく
set theJavaScript to "(function(){" & theJavaScript & ";})();"
set theChannel to «event WebVUtil» {theTmpWindowName, "doJavaScript", theJavaScript}
-- XPath 例2
set theJavaScript to "
var text = '';
var result = document.evaluate('id(\"ListingsMargin\")/following-sibling::*[1][local-name()=\"table\"]/tbody/tr[1]//td', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for(var i=0;i<result.snapshotLength;i++){
var node = result.snapshotItem(i);
text += node.innerText + '<>';
}
return text;
"
set theJavaScript to "(function(){" & theJavaScript & ";})();"
set theInfo to «event WebVUtil» {theTmpWindowName, "doJavaScript", theJavaScript}
«event MBASHndl» {|WebViewWindow|:{|Name|:theTmpWindowName, |Delete|:1 } }
«event UIElSetE»で処理対象を指定するサンプルです。
«event UIElSetE» {"AppleScript Editor", {"Attr", "AXFocusedUIElement"}}
«event UIElSetE» {"Safari", {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXRole", "AXWebArea"}}
«event UIElSetE» {"Evernote", {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXRole AXTitle", "^AXTable \\Qノートリスト\\E$"}}
«event UIElSetE» {"Evernote", {"UIEl", "", "AXRole", "AXRow", "AXRole AXValue", "^AXTextField \\QAppleScript\\E$", "Attr", "AXParent"}}
«event UIElSetE» {"Evernote", {"UIEl", "", "AXRole", "AXRow", "AXRole", "AXGroup", "AXRole AXValue", "AXDisclosureTriangle 0", "PerformAction", "AXPress"}}
«event UIElSetE»
でGUIを操作するハンドラです。どのAXGroupやAXScrollAreaに入っているかなどは指定する必要がなくて、タイトルなどそのUI Elementの属性で指定できます。
set theAppName to "System Preferences"
my setSliderValue(theAppName, "AXDescription", "Dock のサイズつまみ", 0.1)
my clickMenuItem(theAppName, "アーカイブ")
my selectPopupButton(theAppName, "AXValue", "(スケール|ジニー)", "ジニーエフェクト")
my clickButton(theAppName, "プロファイルを開く")
my setCheckAndRadioButtonValue(System, "拡大:", 1)
my selectTab("System Preferences", "AXValue", ".*(設定|コマンド).*", "設定")
on selectTab(theAppName, theTargetTabAttr, theTargetTabValue, theSelectTabTitle)
-- ver.2013.01.06.00
tell me
if 0 is not («event UIElSetE» {theAppName, {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXRole " & theTargetTabAttr, "AXTabGroup " & theTargetTabValue & "", "Attr", "AXTabs", "FindSelf", "", "AXTitle", "^\\Q" & theSelectTabTitle & "\\E$", "PerformAction", "AXPress"}}) then
return true
end if
return false
end tell
end selectTab
on setSliderValue(theAppName, theTargetSliderAttr, theTargetSliderValue, theValue)
-- ver.2013.01.09.00
tell me
if 0 is not («event UIElSetE» {theAppName, {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXRole " & theTargetSliderAttr, "^AXSlider \\Q" & theTargetSliderValue & "\\E$"}}) then
«event UIElSetV» {"AXValue", theValue}
return true
end if
return false
end tell
end setSliderValue
on setCheckAndRadioButtonValue(theAppName, theButtonTitle, theValue)
-- ver.2013.01.06.00
-- 1でオン、0でオフ
tell me
if theValue is 1 then
set theCurrentValue to "0"
else
set theCurrentValue to "1"
end if
if 0 is not («event UIElSetE» {theAppName, {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXRole AXValue AXTitle", "^(AXCheckBox|AXRadioButton) " & theCurrentValue & " \\Q" & theButtonTitle & "\\E$", "PerformAction", "AXPress"}}) then
return true
end if
return false
end tell
end setCheckAndRadioButtonValue
on clickButton(theAppName, theButtonTitle)
if 0 is not («event UIElSetE» {theAppName, {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXTitle", "^\\Q" & theButtonTitle & "\\E$", "PerformAction", "AXPress"}}) then
return true
end if
return false
end clickButton
on selectPopupButton(theAppName, theTargetPopupAttr, theTargetPopupValue, theSelectTitle)
-- ver.2013.01.06.00
tell me
if 0 is not («event UIElSetE» {theAppName, {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXRole " & theTargetPopupAttr, "AXPopUpButton " & theTargetPopupValue & "", "PerformAction", "AXPress"}}) then
if 0 is not («event UIElSetE» {theAppName, {"UIEl", "", "AnyRole", "(AXMenu|AXMenuItem)", "AXTitle", "^\\Q" & theSelectTitle & "\\E$", "PerformAction", "AXPress"}}) then
return true
end if
end if
return false
end tell
end selectPopupButton
-- 下のより «event UIElPAct» を使ったほうが動作が安定するようです。
on clickMenuItem(theAppName, theMenuItemTitle)
-- ver.2013.07.28.00
tell application theAppName to activate
tell me
if 0 is not («event UIElSetE» {theAppName, {"Attr", "AXMenuBar", "AnyRole", "(AXMenu|AXMenuItem)", "AXRole AXEnabled AXTitle", "^AXMenuItem 1 \\Q" & theMenuItemTitle & "\\E$"}}) then
«event UIElPAct» {"AXPress"}
return true
end if
return false
end tell
end clickMenuItem
-- «event UIElSetE» 1行で書く場合。指定したメニュー実行後にメニューが表示されるケースが有りました。
on clickMenuItem_2(theAppName, theMenuItemTitle)
-- ver.2013.04.19.00
tell application theAppName to activate
tell me
if 0 is not («event UIElSetE» {theAppName, {"Attr", "AXMenuBar", "AnyRole", "(AXMenu|AXMenuItem)", "AXRole AXEnabled AXTitle", "^AXMenuItem 1 \\Q" & theMenuItemTitle & "\\E$", "PerformAction", "AXPress"}}) then
return true
end if
return false
end tell
end clickMenuItem
JavaScriptの途中でreturnで値を返すとその結果を取得できない場合があります。それの対策にはスクリプトを次のようにします。
-- 実行したいJavaScript
set theJavaScript to "hoge(); if( ! hage ){ return 'fusafusa';} katsura();"
-- これでスクリプトを囲む
set theJavaScript to "(function(){" & theJavaScript & ";})();"
-- 実行する
set theResult to «event WebVJS__» {theWindowName, theJavaScript}
DoAtTimeのサンプルです。
スリープから復帰時がややこしいですが、下記サンプルで細かいところまで対処できていると思います。あまり動作テストはしてませんが…。
スリープからの復帰時に気をつけることは、予定時間を過ぎていた、複数指定して複数の予定時間を過ぎていたです。下の例では、複数の予定時間を過ぎていた場合でも1回だけ処理を実行します。すべての予定時間分の処理を行う場合はDoOnlyFirstRun
を0にする必要があります。
-- 好きな時間を登録する。いくつでもOK
«event MBASHndl» {|DoAtTime|:{|Handler|:"Hoge", |Time|:{|Hour|:8, |Minute|:0, |Second|:0}, |Repeat|:1, |DoOnlyFirstRun|:1}}
«event MBASHndl» {|DoAtTime|:{|Handler|:"Hoge", |Time|:{|Hour|:13, |Minute|:30, |Second|:0}, |Repeat|:1, |DoOnlyFirstRun|:1}}
«event MBASHndl» {|DoAtTime|:{|Handler|:"Hoge", |Time|:{|Hour|:19, |Minute|:0, |Second|:0}, |Repeat|:1, |DoOnlyFirstRun|:1}}
on doAtTimeHoge(theFireTimeDifference, theIsWake, theIsFirstRun, theSettingRecord, theActiveAppRecord, theThisAppRecord)
-- «event DebugLog» {"doAtTimeHoge", theFireTimeDifference, theIsWake, theIsFirstRun}
-- 処理でネットワークに接続する場合、スリープ解除直後は
-- ネットワークに繋がっていない場合があるので
-- 下記のようにして10秒程度後に実行させると確実に繋がると思います。
if theIsWake is 1
-- 起きた直後は10秒後に実行
«event MBASHndl» {|DoAfterSec|:{|Handler|:"Hoge", |Sec|:10}}
else
-- 上の10秒待ちの間に指定時間が更に来た場合にハンドラを
-- 直接実行しないでDoAfterSecですぐに実行するようにしておくと
-- 10秒のはキャンセルされて0.01秒後に実行される。
«event MBASHndl» {|DoAfterSec|:{|Handler|:"Hoge", |Sec|:0.01}}
end if
-- ネットに繋がない場合は上のようなめんどくさいことはしなくて
-- したいことを直接書いてOK
-- my hoge()
end doAtTimeHoge
on doAfterSecHoge(theUserInfo, theActiveAppRecord, theThisAppRecord)
-- スリープから復帰時の遅延実行用
my hoge() -- したいこと
end doAfterSecHoge
DoAtTimeのサンプルです。
スリープから復帰時がややこしいですが、下記サンプルで細かいところまで対処できていると思います。動作テストはしてませんが…。
-- 好きな時間を登録する。いくつでもOK
«event MBASHndl» {|DoAtTime|:{|Handler|:"Hoge", |Time|:{|Hour|:8, |Minute|:0, |Second|:0}}}
«event MBASHndl» {|DoAtTime|:{|Handler|:"Hoge", |Time|:{|Hour|:13, |Minute|:30, |Second|:0}}}
«event MBASHndl» {|DoAtTime|:{|Handler|:"Hoge", |Time|:{|Hour|:19, |Minute|:0, |Second|:0}}}
on doAtTimeHoge(theFireTimeDifference, theIsWake, theIsFirstRun, theSettingRecord, theActiveAppRecord, theThisAppRecord)
-- «event DebugLog» {"doAtTimeHoge", theFireTimeDifference, theIsWake, theIsFirstRun}
-- 繰り返すので再登録
«event MBASHndl» {|DoAtTime|:theSettingRecord}
-- 同じハンドラでDoAtTimeをいくつか登録した時にスリープして、
-- 指定時間をいくつか過ぎた場合、
-- スリープから起きた時に全部ハンドラが実行されるが1回だけ処理したい場合は
-- 下記のように調べる。
-- 起きている時に実行される時もtheIsFirstRunは1なので実行される。
if theIsFirstRun is 1 then
-- 処理でネットワークに接続する場合、スリープ解除直後は
-- ネットワークに繋がっていない場合があるので
-- 下記のようにして10秒程度後に実行させると確実に繋がると思います。
if theIsWake is 1
-- 起きた時10秒後に実行
«event MBASHndl» {|DoAfterSec|:{|Handler|:"Hoge", |Sec|:10}}
else
-- 上の10秒待ちの間に指定時間が更に来た場合にハンドラを
-- 直接実行しないでDoAfterSecですぐに実行するようにしておくと
-- 10秒のはキャンセルされて0.01秒後に実行される。
«event MBASHndl» {|DoAfterSec|:{|Handler|:"Hoge", |Sec|:0.01}}
end if
-- ネットに繋がない場合は上のようなめんどくさいことはしなくて
-- したいことを直接書いてOK
-- my hoge()
end if
end doAtTimeHoge
on doAfterSecHoge(theUserInfo, theActiveAppRecord, theThisAppRecord)
-- スリープから復帰時の遅延実行用
my hoge() -- したいこと
end doAfterSecHoge
アプリケーションアイコンの上に文字を重ねた画像をメニューバーアイコンにしたいと思ったのでやってみました。
下で使っているIconMaker.htmlの例:IconMaker.html
IconMaker.htmlを好きなように変更してMenuBarAppleScriptアプリケーション内部のResourcesフォルダに入れて下さい。
property theMenuBarItemImage : "mbas:icon" -- MenuBarAppleScript内部に保存するアイコン画像の名前。"mbas:"から始める必要があります。
-- "IconMaker.html"で表示した内容をアイコンにする
set theHTMLPath to (|resourcesFolderPath| of theThisAppRecord) & "IconMaker.html"
«event MBASHndl» {|WebViewWindow|:{|Name|:"IconMaker", |URL|:theHTMLPath, |Visible|:0, |DrawsBackground|:0}}
画像のみを表示する場合に必要
{|PriorityToMenuBarItemImage|:1}
on doDidFinishLoadForFrame(theWindowName, theActiveAppRecord, theThisAppRecord)
if theWindowName is "IconMaker" then
-- 画像でMenuBarAppleScript内部に保存
«event SW2PsPDF» {theMenuBarItemImage, "screen", "", theWindowName}
-- 保存した画像をメニューバーアイコンに設定して、ウインドウを削除
«event MBASHndl» {|RemoveMenuBarItem|:1, |MenuBarItemImage|:theMenuBarItemImage, |WebViewWindow|:{|Name|:theWindowName, |Delete|:1}}
end if
end doDidFinishLoadForFrame
on doInit(theActiveAppRecord, theThisAppRecord)
-- キーは小文字で指定する。shift + キーの場合、大文字で指定する。
-- 修飾キー無しもできます
return {|AppHotKeyList|:{{|Name|:"Save", |KeyChar|:"s", |ModifierKeyList|:{"command"}, |Enable|:1}, {|Name|:"Close", |KeyChar|:"w", |ModifierKeyList|:{"command"}, |Enable|:1}}}
end doInit
on doAppHotKeySave(theActiveAppRecord, theThisAppRecord)
-- 何か保存する処理
end doAppHotKeySave
on doAppHotKeyClose(theActiveAppRecord, theThisAppRecord)
my hideWindow(theActiveAppRecord, theThisAppRecord)
end doAppHotKeyClose
この機能は1.3.1で無効になっています。
on doScrollWheelMenuBarItem(theDeltaX, theDeltaY, theActiveAppRecord, theThisAppRecord)
-- トラックパッドでスクロールした時にはイベントがたくさん来る
-- イベントが終わったと思われるときに一回だけ実行するにはDoAfterSecで遅延実行を使うと簡単にできる
--«event DebugLog» {"doScrollWheelMenuBarItem", theDeltaX, theDeltaY}
-- Deltaは上、左が正の数、下、右が負の数
-- 遅延時間が多いと反応が遅くなるので適切な値に調整して下さい
return {|DoAfterSec|:{|Handler|:"ScrollWheel", |Sec|:0.35, |UserInfo|:{theDeltaX, theDeltaY}}}
end doScrollWheelMenuBarItem
on doAfterSecScrollWheel(theUserInfo, theActiveAppRecord, theThisAppRecord)
set theDeltaX to item 1 of theUserInfo
set theDeltaY to item 2 of theUserInfo
--«event DebugLog» {"doAfterSecScrollWheel", theDeltaX, theDeltaY}
if theDeltaY ≥ 1 then
-- 上
say "up"
else if theDeltaY ≤ -1 then
-- 下
say "down"
else if theDeltaX ≥ 1 then
-- 左
say "left"
else if theDeltaX ≤ -1 then
-- 右
say "right"
end if
end doAfterSecScrollWheel
フォルダ内のファイル一覧を返す例
on startHTTPServer(theActiveAppRecord, theThisAppRecord)
set theResult to «event HTTPSevr» {"start", {|Port|:50208, |DocumentRoot|:"~/", |DirectoryIndexFileNames|:{}, |User|:"", |Password|:""}}
if theResult is not 1 then
activate
beep
display alert "サーバーの起動に失敗しました。アプリケーションを終了します。" & return & theResult
«event MBASHndl» {|Quit|:1}
end if
-- set theThisAppRecord to «event InfoTApp»
-- do shell script "open " & quoted form of HTTPServerURL of theThisAppRecord
end startHTTPServer
on doHTTPResponse(theRequestPath, theDocumentFilePath, theMethod, theActiveAppRecord, theThisAppRecord)
«event DebugLog» {theRequestPath, theDocumentFilePath, theMethod}
if theDocumentFilePath ends with "/" then
if theRequestPath does not end with "/" then
set theRequestPath to theRequestPath & "/"
end if
-- パスに '' が含まれていると駄目です
set theTemplate to "
<html>
<head>
<title>{{ folder | html }}</title>
</head>
<body>
<h1>{{ folder | html }}</h1>
<a href='{{ parentFolder | html }}'>UP</a>
<ol>
{% for path in paths %}
<li><a href='{{ parentPath }}{{ path }}'>{{ path | html }}</a></li>
{% /for %}
</ol>
</body>
</html>
"
set theParentFolderPath to «event TextPath» {"stringByDeletingLastPathComponent", theRequestPath}
set theList to «event TextPath» {"contentsOfDirectory", theDocumentFilePath, {"SkipsHiddenFiles", "AddSlashToFolder"}}
if theRequestPath is "/" then
set theParentPath to ""
else
set theParentPath to theRequestPath
end if
set theValue to {|folder|:theDocumentFilePath, |parentPath|:theParentPath, paths:theList, |parentFolder|:theParentFolderPath}
set theHTML to «event TextUtil» {"MGTemplateEngine", theTemplate, theValue}
return {|HTTPResponse|:{|HTML|:theHTML, cache:0}}
else
return {|HTTPResponse|:{|File|:theDocumentFilePath}}
end if
end doHTTPResponse
-- リスト
set theTemplate to "
<h1>{{ title | html }}</h1>
<p>{{ note | html }}</p>
<ol>
{% for item in items %}
<li>{{ item | html }}</li>
{% /for %}
</ol>
"
set theTitle to "<リストのサンプル>"
set theNote to "リストをHTMLのリストに変換"
set theItemList to {}
set end of theItemList to "黒"
set end of theItemList to "猫"
set end of theItemList to "白"
set end of theItemList to "犬"
set theValueRecord to {|items|:theItemList, title:theTitle, |note|:theNote}
set theHTML to «event TextUtil» {"MGTemplateEngine", theTemplate, theValueRecord, {}, {|OpenHTML|:0, |Browser|:"Safari", |OpenText|:0, |Editor|:"mi", |Activate|:0, |Name|:"Test"}}
-- レコード
set theTemplate to "
<h1>{{ title | html }}</h1>
<p>{{ note | html }}</p>
<table>
<tr>
<th>{{ label.Name | html }}</th>
<th>{{ label.Kind | html }}</th>
<th>{{ label.Age }}</th>
</tr>
{% for item in items %}
<tr>
<td>{{ item.Name | html }}</td>
<td>{{ item.Kind | html }}</td>
<td>{{ item.Age }}</td>
</tr>
{% /for %}
</table>
"
set theTitle to "<テーブルのサンプル>"
set theNote to "レコードのリストをテーブルに変換。キーに'name'を使用したらMGTemplateEngineのキーワードのようで表示されませんでした。その為'Name'にしました。"
set theLabel to {|Name|:"名前", |Kind|:"種類", Age:"年齢"}
set theItemList to {}
set end of theItemList to {|Name|:"ミケ", |Kind|:"ねこ", Age:10}
set end of theItemList to {|Name|:"ポチ", |Kind|:"イヌ", Age:2}
set end of theItemList to {|Name|:"たまちゃん", |Kind|:"あざらし", Age:7}
set end of theItemList to {|Name|:"マイケル", |Kind|:"ねこ", Age:15}
set theValueRecord to {|items|:theItemList, label:theLabel, title:theTitle, |note|:theNote}
set theHTML to «event TextUtil» {"MGTemplateEngine", theTemplate, theValueRecord, {}, {|OpenHTML|:0, |Browser|:"Safari", |OpenText|:0, |Editor|:"mi", |Activate|:0, |Name|:"Test"}}
-- バージョンチェックの間隔
property theAppVersionCheckIntervalSec : days * 7
-- バージョン情報が記載されたJSONのURL
property theAppVersionJSONURL : "https://googledrive.com/host/0B7jmDxmCrfK0UUJub1p6NnAxcGc/version.js"
|VersionCheck|:{|URL|:theAppVersionJSONURL, |Interval|:theAppVersionCheckIntervalSec}},
theAppVersionJSONURLで参照するファイルを作るAppleScriptです。
MenuBarAppleScriptの機能で作っています。
ファイルの書き出しにdo shell script
を使ったら、エスケープされた改行が解除されて動かなくなったので、AppleScriptの命令を使用しています。
comment
はdisplay dialog
でそのまま表示されます。改行したい場合はreturn
でつないで下さい。
property theHistoryJSPath : "/Users/hage/Sites/foo/version.js"
tell application "MenuBarAppleScript"
«event DictDelV» {"tmp", "version"}
-- ここにアプリケーション名とバージョン、URL、コメントを書いていく
«event DictDict» {"tmp", "version", "setObjectForKey", {|version|:"1.4.1", |url|:"http://memogakisouko.appspot.com/MenuBarAppleScript.html", comment:"・いろいろ機能を追加"}, "MenuBarAppleScript"}
«event DictDict» {"tmp", "version", "setObjectForKey", {|version|:"1.4.1", |url|:"http://memogakisouko.appspot.com/MenuBarAppleScript.html#AppleScriptEditorHelper", comment:"・MenuBarAppleScript1.4.1の用語に対応"}, "AppleScriptEditorHelper"}
set theValue to «event DictGetV» {"tmp", "version"}
set theJSONText to «event TextUtil» {"writeJSON", theValue}
«event TextUtil» {"writeToFile", theHistoryJSPath, theJSONText}
quit
end tell
-- 指定時間、スリープ復帰時、一定時間間隔に処理をする
-- いくつかハンドラがありますが処理自体はdoIdleで行いますのでdoIdleにしたい処理を書きます
property theUpdateIntervalSec : hours * 2.0 -- 更新間隔。この秒数間隔で処理を定期的に実行
on doInit(theActiveAppRecord, theThisAppRecord)
-- 例:0時5分に日が変わった時の処理をする
«event MBASHndl» {|DoAtTime|:{|Handler|:"NextDay", |Time|:{|Hour|:0, |Minute|:5, |Second|:0}, |Repeat|:1, |DoOnlyFirstRun|:0}}
-- idleとdidWakeを登録
return {|IdleSec|:theUpdateIntervalSec, |NSWorkspaceNotificationList|:{"NSWorkspaceDidWakeNotification"}}
end doInit
on doNSWorkspaceDidWakeNotification(theNotificationName, theObject, theUserInfo, theActiveAppRecord, theThisAppRecord)
-- «event DebugLog» "NSWorkspaceDidWakeNotification"
-- スリープから復帰後に更新したいけどwifi再接続に時間がかかる場合あるのでちょっと後に更新
-- IdleSecは上書き可能なのでこれでOK
return {|IdleSec|:20}
end doNSWorkspaceDidWakeNotification
on doAtTimeNextDay(theFireTimeDifference, theIsWake, theIsFirstRun, theSettingRecord, theActiveAppRecord, theThisAppRecord)
«event DebugLog» {"doAtTimeHoge", theFireTimeDifference, theIsWake, theIsFirstRun}
if theIsWake is 1 then
-- wake時はdidWakeで処理しているので何もしない
else
-- 直接実行しないでこれでdoIdleを呼ぶようにすると、
-- 通常のdoIdleやNSWorkspaceDidWakeNotificationがほぼ同時に実行される場合に
-- それらをキャンセルして一回だけ実行されるようにできる。
return {|IdleSec|:20} -- チャイムを時間ピッタリに鳴らすなどの場合はここで直接実行するか0.01を返すなどにして下さい
end if
end doAtTimeNextDay
on doIdle(theActiveAppRecord, theThisAppRecord)
-- ここで一定間隔&ほぼ指定時間&スリープ復帰時にしたい処理を実行
my hoge()
return {|IdleSec|:theUpdateIntervalSec}
end doIdle
{ |DebugLog|:1, |LogAppleScriptError|:1 }
を返します。«event DebugLog» theValue
を実行するとコンソールに表示されます。注意点は表示できるのは数値とテキストのみでエイリアスやアプリケーション独自の値等は表示できません。通常のスクリプト同じようにイベントログを確認しながらデバッグするには次のようにします。
|key|
と書くようにした方が安定します。スペースが無い場合はkey
でも書けるのですが、大文字小文字を最初に間違った時に、修正しても自動的に前の値に戻されたりする場合があり、なんで動かないんだろうと、はまることがあります。コピー&ペーストした時に|key|
が| key |
のようにスペースが入っていないも注意して下さい。最新版ではそういうことが多かったので大文字小文字を区別しないのでここではまることはなくなっています。true
、false
は使えません。true,false
に該当する値の場合は1,0
を返して下さい。アプリケーション独自の値なども駄目です。repeat with theStr in theStrList
-- ☆要注意
-- この参照の theStr を含むリスト { theStr }をMenuBarAppleScriptに渡すと処理が行われない場合があります。
-- エラーも何もでないのでバグに気がつきにくいです
-- そのようなケースにであったら contents of theStr か theStr as text などして下さい。
end repeat
on touchAXWebArea()
if 0 is not («event UIElSetE» {"Evernote", {"Attr", "AXFocusedWindow", "AnyRole", "(AXSplitGroup|AXGroup|AXScrollArea|AXTabGroup)", "AXRole", "AXWebArea"}}) then
«event UIElGetV» {"AXValue"} -- 10.8.2では一度AXWebAreaを触らないとdoAXSelectedTextChangedがなかなか来ない
--«event DebugLog» "ax test"
end if
end touchAXWebArea
«event MBASHndl» {|WebViewWindow|:{|Name|:theWillCloseWindowName, |Visible|:0}}
!!! _NSLayoutTreeLineFragmentUsedRectForGlyphAtIndex invalid glyph index 0
ってエラーがコンソールに出る場合がありますが、原因は不明です。tell app "MenuBarAppleScript" to every window
を実行してもWebView Windowが取得できない(まれに取得できることもある)。System Eventでも同じ。何故、他のスクリプト言語やアプリケーションやOSAX、そしてAppleScript自体でできる命令を追加しているのかというと、主に次の2つの理由です。
一つは配布する時に、他のソフトのインストール無しでMenuBarAppleScriptのアプリケーションをダブルクリックするだけでアプリケーションが動作してすぐに試せる方がいいんじゃないか、というのがあります。その為、OS標準のコマンドやアプリケーション以外はできるだけ使わなくて済むように命令を追加してます。
もう一つは複数行のAppleScriptのハンドラやスクリプト言語で書くことができるけど、それらを書くのが面倒くさいので1行で書ける命令を追加してしまおう、ってのがあります。正規表現を数百回実行する場合などは、スクリプト言語を数百回起動させるよりMenuBarAppleScriptの命令のほうが実行速度も早かったので、そういう利点もあるかな、と思ってます。
長年謎だった、AXWebAreaのAXSelectedTextMarkerRangeの使い方がついに分かったので、MenuBarAppleScriptで使えるようにしてみました。これで、クリップボードやサービスを使わずにSafari、Evernote、MailなどAXWebAreaを使ったアプリケーションの選択しているテキストを同じスクリプトで取得できます!。
細かい機能だけどあればスクリプトの作成が楽になるものをいくつか追加。
AXNotificationは奥が深いけど、Cocoaだとちょっと試すのも結構めんどくさいのが、AppleScriptで手軽にいろいろ挑戦できるのがいい感じです。非常にとっつきにくいけど、通常できなさそうなことができたりする場合があるのでお勧めです。
アプリケーションを作るのにあたって欲しいと思った機能を追加しています。しかし、そろそろ完成が近づいているのではないでしょうか。メニューバーアイテムを二つ以上作れないのが気になってますが、結構大きな変更になりそうだし、メニューバーのスペースの関係もあり、なかなか手を出せないでいます。
以前から気になっていたEventListの指定とdoInitでエラーが表示されないのを改善してみました。これで結構作りやすくなったと思います。
YouTubePlaylistPlayerを作ってて思いましたが、独自の命令がいっぱいでAppleScriptだけど通常のAppleScriptとは全く違う物になってしまった感じです。しかし、ハッシュ、リスト、正規表現の命令があると開発効率がいいですね。
非同期でUnixコマンドを実行する命令を付けました。コマンドが実行中でも出力を取得して処理をできるので、curlやffmpegの途中経過を表示するのに使えると思います。 osascriptでスクリプトを非同期で実行させて擬似的にスレッドを実現できそうですが、いろいろ考えても必要な場面が思いつかなかったので、使ったことはありません。
その他いろいろと欲しい機能を追加しました。
今まで、Property List Editorで設定していた、URLスキームを初期設定で指定できるようにしました。ブラウザのUserScriptからURLスキームを使ってMenuBarAppleScriptに命令を飛ばして、いろいろ処理したり、結果をAppleScriptでブラウザに反映させる、ってのをやってますが、結構面白いです。
正規表現とパス関係とURLエンコードなど、自分がスクリプト書く時にdo shell scriptのお世話になっている機能をいくつか入れてみました。正規表現でテストしたところではdo shell scriptでperlより«event TextRegx» の方が速かったです。
AppleScriptEditorHelperを作っててハッシュ機能が欲しくなったので、似たようなことができるようにしましたが、有る無しで開発効率が全然違いますね。
AXNotificationをとりあえず、アプリケーション全体で設定できるようにしてみました。なかなか面白いネタは思いつきませんが、サンプルで作ったmdlsViewerはいい感じで今後mdls、mediainfoで調べる時はこれを使うと思います。サービスメニューを選択したりドラッグドロップでファイルをアプリケーションに渡す手間がないのは楽でいいですね。
他の人が作ったMenuBarAppleScriptのスクリプトを見て、スクリプトのオーナというのか実行主がアプリケーション本体だというのを今頃知りました。それで«event MBASHndl»
命令を追加してみました。何かと使えるのではないでしょうか。
いろいろとMenuBarAppleScriptのネタが思いついてAppleScript魂が久しぶりに燃えています!。作った後に実用するかどうかは分からないけど・・・。
思ったよりややこしいので作り方で分からないことがあったらご連絡ください。
AppleScript エディタでAppleScriptを書くのを補助するアプリケーションです。$200のエディタを使ってる人には必要無いと思いますが、AppleScript エディタを使ってる自分が欲しい機能をいくつか入れてみました。
MenuBarAppleScriptの命令、キーワードを挿入できるのでMenuBarAppleScriptのスクリプトを書くには必須アイテムです。
「ユニバーサルアクセス」環境設定で「補助装置にアクセスできるようにする」にチェックを入れる必要があります。[設定のスクリーンショット]
UI Element関係の命令を使ったサンプルで何かネタが無いかと思っていたのですが、前から思っていたAppleScriptエディタの不便なところを結構解消できるアプリケーションができました。
使い方:メニューバーのAppleScript エディタのアイコンのメニューから操作します。ToolbarAppleScriptと同梱のスクリプトを使用するとウインドウのツールバーから手軽に操作できるようになります。
ASEverHelperとあわせて自分のAppleScript制作環境がかなり良くなった感じです。
tell app "hoge"
、tell process "hage"
をメニューから挿入できる。on hage( foo, bar )
だけです。MenuBarAppleScriptで作ったアプリケーションに移動しました。
新しくメモした順
«event RemvEvnt» {"TextPath", "TextUtil", "Cmd_Run_"}
を追加。MenuBarAppleScriptの命令を実行できなくできます«event PostKeyE»
のバグを修正。«event HndlList» {theList,"insertListAfterEveryItem", theInserList }
を追加«event HndlList» {theList,"insertListBeforeEveryItem", theInserList }
を追加«event HndlList» {theList,"insertListAfterTheItem", "find", theInserList }
を追加«event HndlList» {theList,"insertListBeforeTheItem", "find", theInserList }
を追加«event DictList» {dictName,listKey,"insertListAfterEveryItem", theInserList }
を追加«event DictList» {dictName,listKey,"insertListBeforeEveryItem", theInserList }
を追加«event DictList» {dictName,listKey,"insertListAfterTheItem", "find", theInserList }
を追加«event DictList» {dictName,listKey,"insertListBeforeTheItem", "find", theInserList }
を追加«event TextPath» {"getTag", thePath }
以降を参照。window.MenuBarAppleScript.beep();
を追加«event UIElSetE»
で座標で対象を指定できるようにした。«event InfoUApp»
を追加«event WebVUtil» {missing value, "clearSafeCookie"}
を追加«event TextUtil» {"writeToFile", thePath, theText }
を追加«event TextUtil» {"readFile", thePath }
を追加«event WebVUtil» {theWindowName, "setUserInfo", hoge}
を追加«event WebVUtil» {theWindowName, "getUserInfo"}
を追加on doSelectMenuItemHandlerSuffix(theTitle, theTag, theUserInfo, theActiveAppRecord, theThisAppRecord)
«event UIElLoad»
が成功したかどうか分かるように返り値を返すようになったconsole.log('hoge');
がdidFinishLoadForFrameの実行前まではログ出力できていなかったのを修正。DebugJavaScript
が1の場合、JavaScriptの構文エラーと実行時エラーがコンソールに出力されるようになった。デフォルトは0です。ただし、詳しいエラー状況はわかりません。しかし、エラーが起きていて、それが構文エラーか実行時エラーかがわかるだけでも参考になると思います。console.log('hoge');
でコンソールにログを出力するようになった。«event TextPath» {"url", thePath}
を追加|resourcesFolderURL|
を追加«event Win_Hide»
、«event Win_Show»
で指定したウインドウの表示を変更できるようにした。missing value
を指定すると指定した文字列をそのまま調べるようにした。«event TextRE__»
を追加«event TextPath»
に命令exists、isDirectoryを追加。«event TextURL_»
にdecodeHtmlSpecialcharsを追加。«event Win_Hide»
、«event Win_Show»
を追加。«event DictText»
にcompareを追加。|Enable| : 0
を追加するとその通知は有効にならなくなった。スクリプト作成中等に、一時的にオフにしたい場合等に。アプリケーション実行中に一度オンにしたものを実行中にオフにすることはできません。«event TextURL_»
に新しい命令を追加«event TextUtil»
に新しい命令を追加«event MBASRunH»
を追加«event TextPath»
を追加«event TextURL_»
を追加«event TextUtil»
を追加«event TextRegx»
を追加«event DictDict»
を追加«event DictList»
の命令を追加«event DictGetV»
で初期値を指定できるようにしたreturn {|SaveDict|:1}
で保存できなかったのを修正。doService
からdoTextService
等のように、値によって変えるようにした。«event MBASHndl» {|WebViewWindow|:{|Name|:"YT", |URL|:"http://youtube.com/"}}
«event MBASHndl»
でスクリプト中で命令を随時処理できるようになった。theModifierKeyList
をtheActiveAppRecord
に入れました。modifierKeyList of theActiveAppRecord
で参照して下さい。また、引数からtheModifierKeyListを削除して下さい。|name| of theThisAppRecord
に動作しているMenuBarAppleScriptの名前が入っています。また、AppRecordにマウスの位置とスクリーンのサイズを追加。theThisAppRecordの情報はアプリケーション起動直後の値になります。現在値はtheActiveAppRecordを参照して下さい。WebViewWindowの位置やサイズ調整が必要な場合の情報として。|LogAppleScriptError|:1
を返すとでエラーをコンソールに出力できるようになった。デバッグが楽になると思います。