2018年1月にDBをふっとばしてくれたホスティング業者「Xserver」をまだ使っているわけですが、日本標準時(JST)で2020年1月21日 AM2:00〜AM8:00ごろまで、データセンター内の回線増強にともない10分から60分程度、本サイトにアクセスできなくなる見通しです。
その間は、本サイトを通じて配布している各種AppleScriptライブラリのsdefに入れている画像やムービーなどが見えなくなります。
2018年1月にDBをふっとばしてくれたホスティング業者「Xserver」をまだ使っているわけですが、日本標準時(JST)で2020年1月21日 AM2:00〜AM8:00ごろまで、データセンター内の回線増強にともない10分から60分程度、本サイトにアクセスできなくなる見通しです。
その間は、本サイトを通じて配布している各種AppleScriptライブラリのsdefに入れている画像やムービーなどが見えなくなります。
Finderで選択中のファイルすべてに対して、親フォルダ名、親+1階層のフォルダ名を反映させたファイル名をつけるAppleScriptです。
Blogアーカイブ本を作るのに、こうした道具を作って使っています。一度書いておけば、2度目からは大幅に時間を短縮できます。最初のファイルから拡張子を取得してそれを後続のファイルすべてに適用するため、同一種類のファイルである必要があります。自分用のツールならではの手抜きといったところでしょうか。
| AppleScript名:BlogアーカイブのMarkdown書類をリネーム。親、親+1階層フォルダをMM, YYYYとみなして反映 .scptd |
| — Created 2020-01-18 by Takaaki Naganoya — 2020 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" –選択ファイルを取得 tell application "Finder" set aSel to selection as alias list –Select documents to Rename end tell set aCount to 10 –start set aStep to 10 set aDigit to 3 –パス情報を取得 set aFirst to first item of aSel set aPOSIX to POSIX path of aFirst set aPathStr to (current application’s NSString’s stringWithString:aPOSIX) set aDateComList to aPathStr’s pathComponents() –ディレクトリごとにパス情報を分割してリスト化 set aExt to aPathStr’s pathExtension() –拡張子 set aYear to (item -3 of aDateComList) as string –親の親フォルダ名 set aMonth to (item -2 of aDateComList) as string –親フォルダ名 –リネーム(数百項目を超える場合にはFinderでは遅すぎるのでNSFileManagerで処理) tell application "Finder" repeat with i in aSel set aTEXT to numToZeroPaddingStr(aCount, aDigit, "0") of me –3 digit set aName to aYear & aMonth & aTEXT & "." & aExt set name of i to aName set aCount to aCount + aStep end repeat end tell –整数の値に指定桁数ゼロパディングして文字列で返す on numToZeroPaddingStr(aNum as integer, aDigit as integer, paddingChar as text) set aNumForm to current application’s NSNumberFormatter’s alloc()’s init() aNumForm’s setPaddingPosition:(current application’s NSNumberFormatterPadBeforePrefix) aNumForm’s setPaddingCharacter:paddingChar aNumForm’s setMinimumIntegerDigits:aDigit set bNum to current application’s NSNumber’s numberWithInt:aNum set aStr to aNumForm’s stringFromNumber:bNum return aStr as text end numToZeroPaddingStr |
AppleScriptのランタイム環境が何であるかを区別できるようになりました。
それらのうち、/usr/bin/osascriptを呼び出しているランタイム環境について、さらに細分化して個別に識別するために、ランタイム環境そのものと親プロセスが何であるかをAppleScriptで調査してみました。
結論からいえば、親プロセスが「bash」ならTerminalからosascriptコマンドで起動されたと判断してよさそう。その他は、実行されたScriptの置かれているパスを求めて、macOS標準装備のScript Menuかアプリケーション内部のScript Menuかを見分けるぐらいでしょうか。
これを判定したプログラム自体は、別に技術的にすごいとか機密の塊とかいうことはなくて、「清書してなくて、きたない。無駄に長い。掲載されているプログラムを見ても何も感じない」ことから掲載していません。実験用のテストプログラムでも、ちょっと近年稀に見るぐらいひどい出来です。内容は、単にランタイムプログラムのプロセスIDを取得して、そのプロセスIDをもとにpsコマンドで親プロセスを求めて、コマンドからいろいろ情報を抜いてくるというだけのものです。
ランタイムのパラメータ:/usr/bin/osascript -P /Users/me/Library/Scripts/Finder/pTEST.scptd 親プロセス:/System/Library/Frameworks/Foundation.framework/Versions/C/XPCServices/com.apple.foundation.UserScriptService.xpc/Contents/MacOS/com.apple.foundation.UserScriptService
与えられているScriptが特定フォルダ(/Users/me/Library/Scripts/)以下にあるものかどうか、という識別は可能。ちょっと、根拠が弱そうです。
ランタイムのパラメータ:/usr/bin/osascript -sd -T 14534 -P /Users/me/Library/Application Scripts/com.coteditor.CotEditor/010)M-pM^_M^MM^NM-pM^_M^SM^\AppleScriptM-cM^AM-(M-cM^AM^WM-cM^AM-&M-hM-'M-#M-iM^GM^H/0020)M-pM^_M^SM^\M-fM-'M^KM-fM^VM^GM-gM-"M-:M-hM-*M^MM-cM^AM^WM-cM^AM-&M-eM-.M^_M-hM-!M^LM-cM^AM^WM-cM^@M^AM-pM^_M^SM^DM-fM^VM-0M-hM-&M^OM-fM^[M-8M-iM-!M^^M-cM^AM-+M-gM-5M^PM-fM^^M^\M-cM^BM^RM-eM^GM-:M-eM^JM^[.@r.scpt 親プロセス:/System/Library/Frameworks/Foundation.framework/Versions/C/XPCServices/com.apple.foundation.UserScriptService.xpc/Contents/MacOS/com.apple.foundation.UserScriptService
親プロセスはScript Menuと同じですが、こちらも実行しているScriptのパスが/Users/me/Library/Application Scripts/com.coteditor.CotEditor/以下であること。これも、識別のための根拠が弱いです。
ランタイムのパラメータ:osascript /Users/me/Desktop/pTEST.scptd 親プロセス:-bash
これは、親プロセスが-bashであることから、明確に区別できます。Terminal.appではなくbashなんですね。
ランタイムのパラメータ:/usr/bin/osascript /Users/me/Desktop/pTEST.scptd 親プロセス:-bash
フルパスで指定すると何か変わってくるかとも思いましたが、とくに変化はありませんでした。
| Program | AS Style | runtime name | Parent Process Name | Script Path |
| Script Editor | Script/Scriptd | Script Editor | ||
| Script Editor | Cocoa-AppleScript Applet | CocoaApplet | ||
| Script Editor | Applet | applet | ||
| Script Debugger | Script/Scriptd | Script Debugger | ||
| Script Debugger | Applet (Enhanced) | FancyDroplet | ||
| ASOBjC Explorer 4 | Script/Scriptd | ASOBjC Explorer 4 | ||
| Automator | Workflow | Automator | ||
| Automator | Applet | Application Stub | ||
| Script Menu | Script/Scriptd | osascript | …com.apple.foundation.UserScriptService | /Users/me/Library/Scripts/ ほか |
| CotEditor | Script | osascript | …com.apple.foundation.UserScriptService | /Users/me/Library/Application Scripts/com.coteditor.CotEditor/ |
| Terminal.app (osascript) | Script/Scriptd | osascript | bash |
AppleScriptのランタイムプログラム名(ランタイム名)を取得するAppleScriptです。AppleScript自身が「何によって」実行されているか、その実行プログラム名を取得するものです。
AppleScriptには何種類かランタイム環境が存在し、ランタイム環境ごとに若干の動作が変わってくることが知られています。
ランタイム環境ごとにどこが違うといえば、Finderからのselectionを取得できるとかできないとか(AppleScript Studioがこれに該当。もうありませんけれども)、GUI Scriptingの権限の認証を得られるとか得られないとか。ウィンドウを動的に生成して表示したときに最前面に表示できるとかできないとか(Script Menuがこれに該当)。明示的にメインスレッドで実行する機能がないとか(Script Debuggerがこれに該当)。そういうところです(ほかにもあるかもしれない)。
過去に作ったAppleScriptを確認していたところ、プロセス名を取得するだけの使えないプログラムだと思っていたものが、実は「ランタイムプログラムのプログラム名」を取得できるというスゲーものであることを再発見しました。
ながらく、ランタイムプログラム名をAppleScript側から取得する必要性を感じていたため、この機会に調べてみることに。プログラム自体は些細な(↓)ものです。
| AppleScript名:ランタイム環境名の表示 |
| — Created 2015-09-08 by Takaaki Naganoya — 2015 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" set procInfo to current application’s NSProcessInfo’s processInfo() set aName to procInfo’s processName() as string display dialog aName |

–> “Automator”
これには驚きました。特別のランタイムプログラムが使われているのか、それとも単に親プロセスとしてのAutomatorが返ってきているのかは不明ですが、識別できるということには意義がありそうです。

–> “osascript”
これは、よく知られていることなのでとくに驚きはありません。テストを実施したのはmacOS 10.14.6上で、Script Menuは「スクリプトメニュー」という別アプリケーション(/System/Library/CoreServices/ にある)に変更になったOSバージョンですが、ランタイムにosascriptを使い続けていることを確認することになりました(それ以前のOSと挙動が同じなのでそうだと思っていましたけれども)。



–> “osascript”
Folder Actionは、macOS標準搭載のフォルダ監視機能です。監視対象のフォルダにファイルが追加されたり、移動されたり、フォルダそのものがオープンしたりするとその対象ファイル/フォルダで指定のAppleScriptを実行します。よく、ドラッグ&ドロップで処理を受け付けたり、ネットワーク経由でファイルを受信した場合にAppleScriptを実行するような使われ方をします。
Folder Action Disptcherが実行しているとばかり思っていたのですが、実際に確認したらosascriptでした。ちなみに、Folder ActionはmacOS 10.11でフルに書き換えられてそれ以前とは別物になっています。以前は数秒に一度対象フォルダをチェックする方式でしたが、10.11以降はFSEventsを利用して随時監視対象フォルダへの変更を受け付けます。


–> “osascript”
障害者向けの機能としてmacOSに標準装備されている、フローティングパレットからAppleScriptを呼び出せる機能である「Switch Control」。手を使わずに操作したり、他のコントローラで操作するような標準的ではない使い方をサポートするための機構ですが、普通に普通の人が使っても役立ちます。
Switch ControlでAppleScriptを実行する場合のランタイムプログラムはosascriptです。

–> “osascript”
これは、CotEditorのソースを読んで確認してありました。ここだけ割と手抜き実装ですが、それでも複数のOSA言語に対応できたりと機能的には悪くはありません。むしろ、osascript側のランタイム環境が他の環境よりも一段落ちることに問題が、、、、GUI Scriptingの権限を取得できないこととか、このCotEditorのメニューから実行するとREST APIが呼び出せないとか。同じosascript系でもScript Menuのほうが制約が少ないのは、おそらくアプリケーション自体に許可されている条件の違いによるものでしょう。

–> “FileMaker Pro”
FileMaker Proはランタイムアプリケーションが廃止され、FileMaker Pro AdvancedもFileMaker Proに一本化されたので、v19以降はFileMaker Proは「FileMaker Pro」というランタイムのみでしょう。v19でもFileMaker Pro自体はSandbox化されていないため、微妙にセキュリティ上の制約が少ない=自由度の高いランタイム環境として残っていくことでしょう。

–> “applet”
取得できたらいいなぐらいの気持ちで試してみたものの、これが識別できるのはうれしい誤算です。書き出したAppleScript Applet名は「Appletでランタイム名を取得」であったため、この「applet」というものとは異なります(ねんのため)。

–> “Application Stub”
見たことのない名前が、、、、やっぱり、これも別ランタイムなんですね、、、、

–> “FancyDroplet”
Appleの標準ランタイムとはあきらかに別物(Enhanced)なので、たぶん別の名前がついているだろうとは思っていましたが、そういう名前でしたか。名前が予想外だったので驚かされましたが、識別できることに意義があります。

–> “CocoaApplet”
Script Editor上で作成できる、通常のAppleScriptとXcode上で作成するCocoa-Applicationの中間的な性格を持つ「Cocoa-AppleScript Applet」でランタイムプログラム名を取得したらこうなりました。
もちろん、実行プログラム名はまったく別の「ランタイム名を表示するだけのCocoa-AppleScript Applet」というものです。
macOS 12で搭載されたショートカット.app(Shortcuts.app)および不可視プロセスのAppleScriptの補助専用アプリケーション「Shortcuts Events.app」上で、アクション「AppleScriptを実行」でAppleScriptを実行するときのランタイム名は、「MacHelper」です。意外なところで、ユーザーディレクトリ以下にインストールされたAppleScriptライブラリをこのMacHelper環境は認識します。
→ macOS 13上では、ショートカット.app(Shortcuts.app)およびShortcuts Events.app上のランタイム名が「ShortcutsMacHelper」に変更されました。
red sweater softwareによるメニュー常駐型Script Menuソフトウェア「FastScripts」から実行したときのランタイム名は「FascScripts Script Runner」です。
Knurling Groupによるコンテクストメニューのカスタマイズ・ソフトウェア「Service Station」から実行したときのランタイム名は「osascript」です。
これらのほか、各アプリケーション内でAppleScript呼び出し機能を有するもの(ファイルメーカー、Mail.appなど)でランタイムプログラム名を取得すると有益な情報が得られることでしょう。
これは、地球上にいる人類が観測衛星を打ち上げて「ここは銀河系だ」と観測できるぐらいすごいことなので、割と意義深いものです。実行中のAppleScriptが、「いま、何のプログラムによって自分自身が実行されている」かという情報を取得できます。
ランタイムプログラムの名称取得については、いくつかのAppleScript実行方法を試してみましたが、得られる名前に違いがないことを確認しています。
直接AppleScriptを動かす方法に加え、間接的にAppleScriptを動かす方法も試してみましたが、同じ結果が得られました。
つまり、動的にOSAScriptViewを生成して実行しようが、NSAppleScriptで実行しようが、AppleScriptの「run script」コマンドで実行しようが、取得されるランタイム名には差がありません。
これで、ランタイム環境のプロセスの親プロセスの情報が取得できると、Terminal.app上から起動したosascriptコマンドで呼び出したのか、Script Menu上から呼び出したのかという状況をAppleScript側で認識できることになることでしょう。
ランタイム環境を識別した上で、各環境で実行できない処理を行わないとか、ランタイム環境ごとに処理を分岐できるようになることでしょう。
ちょうど、Edama2さんと「ランタイム環境ごとに若干の挙動の違いが見られるし、利用できるCocoaの機能にも違いがあるから、ランタイム環境ごとに認識コードでも振ってみようか」などと相談していたので、渡りに船でした。
指定UTIのファイルのFinderからのドラッグ&ドロップを受け付けるAppleScript Libraries「display drop dialog」をリリース、フリー配布いたします。
# 本ライブラリはEdama2さんのScriptに若干の改変を行い、sdefをつけてパラメータのエラー処理を行い、コードサインしてライブラリ化したものです
–> Download display drop dialog Script Library (To ~/Library/Script Libraries/)
本ライブラリは、指定UTIの書類のドラッグ&ドロップを受け付けるダイアログをAppleScriptから手軽に使えるようにするものです。macOS 10.14以降で動作します。
現在のAppleのTim Cook体制は、セキュリティ強化の美名のもとに「動かないコンピュータ」を目指し、各種機能を阻害する方向に進んでいます。
AppleScriptの世界はその中でも自由度が比較的高い状態にありますが、それでもmacOS 10.12以降では、AppleScriptドロップレットにFinderからドラッグ&ドロップしても、Finderの拡張属性(Xattr)がついたファイルは無視されたりと、なかなか困ります。
そこで、Finderからのドラッグ&ドロップ操作を通常の(Applet書き出ししていない)AppleScript、とくにScript Menuから呼び出す形式のAppleScriptで使えるようにすることを目的として本ライブラリを作成しました。
本ライブラリを用いることで、ドロップレットを作らなくても、Script Menuから呼び出した普通のAppleScriptにファイルのドラッグ&ドロップの受信機能を追加できます。セキュリティと機能性(ドラッグ&ドロップ)を両立させるため、すべてのScripterに欠かすことのできないライブラリになるものと期待しています。
本ライブラリのAppleScript用語辞書には実行イメージ(画面キャプチャ)およびサンプルScriptを入れてあり、実行時の様子やサンプルスクリプト(本Blogと同様のワンクリックで内容が転送されるリンクつき)を確認できるようになっています。
| AppleScript名:accept AppleScript documents |
| use dropLib : script "display drop dialog"
set aMainMes to "Drop AppleScript" set aSubMes to "Drag and Drop AppleScript files here" set aUTI to "com.apple.applescript.script" set execButtonTitle to "Execute" set aRes to (display drop dialog aUTI main message aMainMes sub message aSubMes with initial folder "" OK button title execButtonTitle) –> {alias "Machintosh HD:Users:me:Documents:display drop dialog:accept PNG images.scpt", alias "Machintosh HD:Users: me:Documentsaccept Markdown documents.scpt"} |
| AppleScript名:accept Markdown documents |
| use dropLib : script "display drop dialog"
set aMainMes to "Drop Markdown Documents" set aSubMes to "Drag and Drop Markdown documents here" set aUTI to "net.daringfireball.markdown" set execButtonTitle to "Execute" set aRes to (display drop dialog aUTI main message aMainMes sub message aSubMes with initial folder "" OK button title execButtonTitle) –> {alias "Machintosh HD:Users:me:Documents::–Book 11 「Blog Archives vol5 2013-2014」:2013:01:201301013.md", alias "Machintosh HD:Users:me:Documents::–Book 11 「Blog Archives vol5 2013-2014」:2013:01:201301012.md"} |
| AppleScript名:accept PNG images with initial folder contents |
| use scripting additions use dropLib : script "display drop dialog" set aMainMes to "Drop PNG" set aSubMes to "Drag and Drop png images here" set aUTI to "public.png" set execButtonTitle to "Execute" set iFolder to path to pictures folder –At first, PNG files in this folder is displayed set aRes to (display drop dialog aUTI main message aMainMes sub message aSubMes with initial folder iFolder OK button title execButtonTitle) –> {alias "Machintosh HD:Users:me:Pictures:1015sedhelp2.png", alias "Machintosh HD:Users:me:Pictures:574G01.png", alias "Machintosh HD:Users:me:Pictures:macDown1.png", alias "Machintosh HD:Users:me:Pictures: 2017-09-27 19.33.53.png", alias "Machintosh HD:Users:me:Pictures:2018-11-01 10.54.06.png" |
MicrosoftによるGoogle Chrome(オープンソース版のChromium)をベースにした(そのまま)Webブラウザ「Microsoft Edge」の正式版がリリースされました。
ChromiumはAppleScriptに対応しており、ベータ版である「Edge Canary」もChromiumと同様のAppleScript用語辞書を備えていました(A=B)。
正式版「Edge」のAppleScript用語辞書と「Edge Canary」の用語辞書とdiffをとってみたところ、内容が同じであることを確認できました(B=C)。
このため、Google Chromium用のAppleScriptとtell文のアプリケーション名だけ書き換えればEdge用のAppleScriptとして動かせることが期待できます(メニューやボタンを直接動かすGUI Sciriptingをのぞく)。
発売したばかりの「BlogアーカイブVol.5」ですが、画像のリンク抜けがあるとのご報告をいただき、すぐさま修正版をリリースしました。すでにお買い上げの方々におかれましても、再ダウンロードでダウンロードできるはずなので、おためしください。
本件については、画像リンクチェッカーAppleScriptの機能を強化。これまでも、Markdownタグでリンクした画像のリンク抜けについてはAppleScriptで自動チェックを行なっていたのですが、HTMLタグで入れた画像リンクについてはチェック対象外となっていました。
そこで、画像リンクチェッカー「指定ディレクトリ以下のMarkdown書類のリモート画像URLのリンク切れをチェック(Markdown、HTMLタグ両方)」を作成し、自動でMarkdown/HTMLタグで記述された画像リンクチェックを実施。
5か所ほど画像リンク抜けが見つかったので、その点を修正したRev. 1.1をアップロードしました。
表紙と奥付のリビジョン表記が1.0から1.1になっています。
原稿(Pages&Markdown書類)→PDF書き出し→PDF結合→TOC作成 と、ワークフローをAppleScriptで自動化しており、こうしたツールを作ってきたからこそ、たった1人で執筆、編集、発行(販売)を行えるようになっている次第です。
なお、画像リンク抜け以外の箇所はほとんど変わっていませんので、現時点でお手もとにあるPDFで不満のない場合にはそのままでもけっこうです。
Numbersでオープン中の最前面の書類のすべてのシートの表1の行数の合計を計算するAppleScriptです。
Blogアーカイブ本作成のために書いたものです。
この記事数をカウントするために使いました。
| AppleScript名:Numbersでオープン中の最前面の書類のすべてのシートの表1の行数合計を計算する |
| set aCount to 0
tell application "Numbers" tell front document set sList to every sheet repeat with i in sList tell i try set tmpC to count every row of table 1 set aCount to aCount + tmpC end try end tell end repeat end tell end tell return aCount |
Edama2さんからの投稿Scriptです。アラートダイアログ中に各種GUI部品を詰め込む「箱庭インタフェース」シリーズ。テーブルビューを表示して、Finderからのアプリケーションのドラッグ&ドロップを受け付けるAppleScriptです。
–> Download Editable & Executable Script Bundle Document
カスタムクラスの作り方がわかったので、懲りずにTable Viewネタです。 Thanks Shane Stanley! アプリケーションフォルダ直下のアプリを、NSValueTransformerを使ってコラムにローカライズ名とアイコンを表示するのと、finderからのドラッグ&ドロップでアイテムの追加です。 追加時に重複チェックをしているので試す時はユーティリティフォルダか別の場所にあるアプリで試してください。
本Scriptは自分も作りたいと思いつつも調査が進んでいなかったものですが、TableViewでFinderからのドラッグ&ドロップを受け付けます。
これを鍛えて再利用しやすい部品に育てることで、ドロップレットの代替品にできると思っています。macOS 10.12以降、Finder上のファイルをAppleScriptドロップレットにドラッグ&ドロップで処理させても、OS側がダウンロードファイルにつけた拡張属性(Xattr)「com.apple.quarantine」がついているとドロップレット側で処理できなかったりします(回避方法はありますけれども)。
このドロップレットという仕組みを使わずに、同様にドラッグ&ドロップによる処理を手軽にできる代替インタフェースとしてこのようなものを考えていた次第です。自分で作らなくてもEdama2さんが作ってくださったので助かりました、、、、
| AppleScript名:アラートダイアログ上にTable Viewを表示 v9_アイコンを表示、Finderからのドラッグ&ドロップ |
| use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppleScriptObjC" use scripting additions on run my main() end run on main() # Script Bundle内のResourcesフォルダを求める set resourcePath to POSIX path of (path to me) & "Contents/Resources/" set theBundle to current application’s NSBundle’s bundleWithPath:resourcePath theBundle’s loadAppleScriptObjectiveCScripts() # set aFolder to path to applications folder set aPath to aFolder’s POSIX path set aURL to current application’s NSURL’s fileURLWithPath:aPath # 指定フォルダの直下のファイルを取得 set filePaths to current application’s NSFileManager’s defaultManager’s ¬ contentsOfDirectoryAtURL:aURL ¬ includingPropertiesForKeys:{current application’s NSURLNameKey} ¬ options:(current application’s NSDirectoryEnumerationSkipsHiddenFiles) ¬ |error|:(missing value) set filePaths to filePaths as list set thisFileType to "com.apple.application-bundle" set fileURLs to {} repeat with anItem in filePaths set aPath to anItem’s contents’s POSIX path set aURL to (current application’s NSURL’s fileURLWithPath:aPath) set {aResult, aUTI, aError} to (aURL’s getResourceValue:(reference) ¬ forKey:(current application’s NSURLTypeIdentifierKey) ¬ |error|:(reference)) if (aUTI as text) is thisFileType then set fileURLs’s end to aURL end if end repeat set optionRec to {fileURLs:fileURLs, fileType:thisFileType} set aMainMes to "アプリケーションの選択" set aSubMes to "適切なものを以下からえらんでください" set dateObj to my chooseItemByTableView(aMainMes, aSubMes, optionRec) end main # アラートダイアログでtableviewを表示 on chooseItemByTableView(aMainMes, aSubMes, optionRec) ### set up view set {theView, makeObj} to my makeContentView(optionRec) set paramObj to {myMessage:aMainMes, mySubMessage:aSubMes, setView:theView, myOption:makeObj} my performSelectorOnMainThread:"raizeAlert:" withObject:paramObj waitUntilDone:true if (my _retrieve_data) is missing value then error number -128 return (my _retrieve_data) end chooseItemByTableView ## retrieve date on raizeAlert:paramObj set mesText to paramObj’s myMessage set infoText to paramObj’s mySubMessage set theView to paramObj’s setView set aTableObj to paramObj’s myOption global _retrieve_data set _retrieve_data to missing value ### set up alert tell current application’s NSAlert’s new() setMessageText_(mesText) setInformativeText_(infoText) addButtonWithTitle_("OK") addButtonWithTitle_("Cancel") setAccessoryView_(theView) tell |window|() setInitialFirstResponder_(theView) end tell #### show alert in modal loop if runModal() is (current application’s NSAlertSecondButtonReturn) then return end tell ### retrieve date set aIndexSet to aTableObj’s selectedRowIndexes() set chooseItems to ((aTableObj’s dataSource())’s arrangedObjects()’s objectsAtIndexes:aIndexSet) as list set _retrieve_data to {} repeat with anItem in chooseItems set _retrieve_data’s end to anItem’s fileURL end repeat end raizeAlert: ## ContentView を作成 on makeContentView(paramObj) ## 準備 set fileURLs to (paramObj’s fileURLs) as list set thisFileType to (paramObj’s fileType) as text set keyrec to {column1:"Name"} set keyDict to (current application’s NSDictionary’s dictionaryWithDictionary:keyrec) set theDataSource to current application’s YKZArrayController’s new() ## NSTableView tell current application’s NSTableView’s alloc() tell initWithFrame_(current application’s CGRectZero) setAllowsEmptySelection_(false) setAllowsMultipleSelection_(true) setDataSource_(theDataSource) setDelegate_(theDataSource) setDoubleAction_("doubleAction:") setGridStyleMask_(current application’s NSTableViewSolidVerticalGridLineMask) setSelectionHighlightStyle_(current application’s NSTableViewSelectionHighlightStyleRegular) setTarget_(theDataSource) setUsesAlternatingRowBackgroundColors_(true) set thisRowHeight to rowHeight() as integer set theDataSource’s _table_view to it end tell end tell # data sourceに追加 tell (theDataSource) awakeFromNib() set its _file_type to thisFileType repeat with aURL in fileURLs addObject_({fileURL:aURL}) end repeat setSelectionIndex_(0) end tell ## NSTableColumn ### Columnの並び順を指定する set viewWidth to 320 set columnsCount to keyDict’s |count|() repeat with colNum from 1 to columnsCount set keyName to "column" & colNum as text set aTitle to (keyDict’s objectForKey:keyName) tell (current application’s NSTableColumn’s alloc()’s initWithIdentifier:(colNum as text)) tell headerCell() setStringValue_(aTitle) set thisHeaderHeight to cellSize()’s height end tell ### Columnの横幅を指定 setWidth_(viewWidth) ### バインディングのオプション — ソートを無効にする set anObj to (current application’s NSNumber’s numberWithBool:false) set aKey to current application’s NSCreatesSortDescriptorBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) ### 表示内容をNSArrayControllerにバインディング bind_toObject_withKeyPath_options_(current application’s NSValueBinding, theDataSource, "arrangedObjects", bOptions) (theDataSource’s _table_view’s addTableColumn:it) end tell end repeat ## NSScrollView ### Viewの高さを計算 set rowCount to 12 –> 行数を固定 set viewHeight to (thisRowHeight + 2) * rowCount + thisHeaderHeight –> 2を足さないと高さが合わない set vSize to current application’s NSMakeRect(0, 0, viewWidth, viewHeight) ### Viewを作成 tell current application’s NSScrollView’s alloc() tell initWithFrame_(vSize) setBorderType_(current application’s NSBezelBorder) setDocumentView_(theDataSource’s _table_view) –setHasHorizontalScroller_(true) setHasVerticalScroller_(true) set aScroll to it end tell end tell return {aScroll, theDataSource’s _table_view} end makeContentView |
| AppleScript名:subClasses |
| #MARK: – NSValueTransformer script YKZURLToIcon property parent : class "NSValueTransformer" property allowsReverseTransformation : false –>逆変換 property transformedValueClass : a reference to current application’s NSImage –>クラス #変換処理 on transformedValue:fileURL if fileURL is missing value then return set appPath to fileURL’s |path|() set iconImage to current application’s NSWorkspace’s sharedWorkspace’s iconForFile:appPath return iconImage end transformedValue: end script script YKZURLToDisplayedName property parent : class "NSValueTransformer" property allowsReverseTransformation : false –>逆変換 property transformedValueClass : a reference to current application’s NSString –>クラス #変換処理 on transformedValue:fileURL if fileURL is missing value then return set appPath to fileURL’s |path|() set displayedName to current application’s NSFileManager’s defaultManager’s displayNameAtPath:appPath return displayedName end transformedValue: end script #MARK: – Array Controller script YKZArrayController property parent : class "NSArrayController" #MARK: IBOutlets property _table_view : missing value #MARK: property _data_type : do shell script "uuidgen" property _file_type : "" on awakeFromNib() –log "YKZArrayController – awakeFromNib" tell _table_view registerForDraggedTypes_({current application’s NSFilenamesPboardType, _data_type}) setDraggingSourceOperationMask_forLocal_(current application’s NSDragOperationCopy, false) end tell #Transformerの登録 set tNames to {} set tNames’s end to "YKZURLToIcon" set tNames’s end to "YKZURLToDisplayedName" repeat with aTransformer in tNames set theTransformer to current application’s class aTransformer’s new() (current application’s NSValueTransformer’s setValueTransformer:theTransformer forName:aTransformer) end repeat end awakeFromNib #MARK: Data Source Overrides on numberOfRowsInTableView:aTableView return my content()’s |count|() end numberOfRowsInTableView: on tableView:aTableView viewForTableColumn:aColumn row:aRow –log "viewForTableColumn" set aCellView to aTableView’s makeViewWithIdentifier:"YKZTableCellView" owner:me –set isNull to aCellView’s isEqual:(current application’s NSNull’s |null|()) –log isNull as text if aCellView is missing value then set frameRect to current application’s NSMakeRect(0, 0, aColumn’s width, aTableView’s rowHeight()) set aCellView to current application’s YKZTableCellView’s alloc’s initWithFrame:frameRect set anObj to "YKZURLToIcon" set aKey to current application’s NSValueTransformerNameBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) (aCellView’s imageView())’s bind:(current application’s NSValueBinding) toObject:aCellView withKeyPath:"objectValue.fileURL" options:bOptions set anObj to "YKZURLToDisplayedName" set aKey to current application’s NSValueTransformerNameBindingOption set bOptions to (current application’s NSDictionary’s dictionaryWithObject:anObj forKey:aKey) (aCellView’s textField())’s bind:(current application’s NSValueBinding) toObject:aCellView withKeyPath:"objectValue.fileURL" options:bOptions end if return aCellView end tableView:viewForTableColumn:row: # テーブル内のセルが編集できるか on tableView:aTableView shouldEditTableColumn:aColumn row:aRow return false end tableView:shouldEditTableColumn:row: # テーブル内をダブルクリックしたらOKボタンを押す on doubleAction:sender log "doubleAction" ## ヘッダをクリックした時は何もしない if (sender’s clickedRow()) is -1 then return set theEvent to current application’s NSEvent’s ¬ keyEventWithType:(current application’s NSEventTypeKeyDown) ¬ location:(current application’s NSZeroPoint) ¬ modifierFlags:0 ¬ timestamp:0.0 ¬ windowNumber:(sender’s |window|()’s windowNumber()) ¬ context:(current application’s NSGraphicsContext’s currentContext()) ¬ |characters|:return ¬ charactersIgnoringModifiers:(missing value) ¬ isARepeat:false ¬ keyCode:0 current application’s NSApp’s postEvent:theEvent atStart:(not false) end doubleAction: #MARK: Drag Operation Method #ドラッグを開始(ペーストボードに書き込む) on tableView:aTableView writeRowsWithIndexes:rowIndexes toPasteboard:pboard –log "writeRowsWithIndexes" set aData to current application’s NSKeyedArchiver’s archivedDataWithRootObject:rowIndexes pboard’s declareTypes:{_data_type} owner:(missing value) pboard’s setData:aData forType:_data_type return true end tableView:writeRowsWithIndexes:toPasteboard: #ドラッグ途中 on tableView:aTableView validateDrop:info proposedRow:row proposedDropOperation:operation –log "validateDrop" #列の間にドラッグ if (operation is current application’s NSTableViewDropAbove) then return current application’s NSDragOperationMove end if #項目の上にドラッグ set aTypes to info’s draggingPasteboard’s types() if (aTypes’s containsObject:_data_type) as boolean then return current application’s NSDragOperationNone end if return current application’s NSDragOperationNone end tableView:validateDrop:proposedRow:proposedDropOperation: #ドラッグ終了 on tableView:aTableView acceptDrop:info row:row dropOperation:operation –log "acceptDrop" # set pboard to info’s draggingPasteboard() set aTypes to pboard’s types() if (aTypes’s containsObject:_data_type) as boolean then set rowData to pboard’s dataForType:_data_type set rowIndexes to current application’s NSKeyedUnarchiver’s unarchiveObjectWithData:rowData if (rowIndexes’s firstIndex()) < row then set row to row – (rowIndexes’s |count|()) end if set aRange to current application’s NSMakeRange(row, rowIndexes’s |count|()) set aIndexSet to current application’s NSIndexSet’s indexSetWithIndexesInRange:aRange set anObj to ((my content())’s objectsAtIndexes:rowIndexes) my removeObjects:anObj my insertObjects:anObj atArrangedObjectIndexes:aIndexSet my rearrangeObjects() else set pathList to pboard’s propertyListForType:(current application’s NSFilenamesPboardType) –log result as list repeat with aPath in pathList set isAdd to addItem_({filePath:aPath, atIndex:row}) if isAdd then beep end repeat end if return true end tableView:acceptDrop:row:dropOperation: #追加する on addItem:sender –> {filePath:aPath, atIndex:aIndex} –log "addItem:" set isAdd to false #missing valueの時は最後に追加 set {filePath:aPath, atIndex:aIndex} to sender if aIndex is missing value then set aIndex to my content()’s |count|() as number end if #URLに変換 set appURL to (current application’s NSURL’s fileURLWithPath:aPath) #アプリケーション以外は追加しない set {aResult, aUTI, aError} to (appURL’s getResourceValue:(reference) ¬ forKey:(current application’s NSURLTypeIdentifierKey) ¬ |error|:(reference)) log aUTI if (aUTI as text) is not (my _file_type as text) then return isAdd #すでにあるか確認 set bList to my content() as list repeat with aRecord in bList if ((appURL’s isEqual:(aRecord’s fileURL)) as boolean) then set isAdd to true exit repeat end if end repeat #なければ追加 if not isAdd then set anObj to {fileURL:appURL} (my insertObject:anObj atArrangedObjectIndex:aIndex) end if return isAdd end addItem: end script #MARK: – NSTableCellView script YKZTableCellView property parent : class "NSTableCellView" on initWithFrame:rect –log "initWithFrame" continue initWithFrame:rect setAutoresizingMask_(current application’s NSViewWidthSizable) tell current application’s NSImageView’s alloc tell initWithFrame_(current application’s NSMakeRect(0, 0, 16, 16)) setImageScaling_(current application’s NSImageScaleProportionallyUpOrDown) setImageAlignment_(current application’s NSImageAlignCenter) my setImageView:it my addSubview:it end tell end tell tell current application’s NSTextField’s alloc tell initWithFrame_(current application’s NSMakeRect(20, 2, 200, 14)) setBordered_(false) setDrawsBackground_(false) setEditable_(false) my setTextField:it my addSubview:it end tell end tell return me end initWithFrame: end script |
Numbersでオープン中の最前面の書類中にあるすべてのシートの名前を置換するAppleScriptです。
内容は簡単ですが、Numbersにそうした機能が存在しないので、作っておくと便利です。アーカイブ本を作るのに、こうした(↓)資料を作る必要があるわけですが、そのためにこうしたこまごまとしたScriptをその場で作って作業の効率化を図っています。

▲上の画像あくまでは処理イメージなので、厳密に同一データの処理前・処理後の画面スナップショットではありません。各シートの名称の一部の文字列(YYYY部分)のみ置換されていることをご確認ください
| AppleScript名:Numbersで各シート名称を置換 |
| tell application "Numbers" tell front document set tList to every sheet repeat with i in tList tell i set aName to name set bName to replaceText(aName, "2011_", "2013_") of me set name to bName end tell end repeat end tell end tell –任意のデータから特定の文字列を置換 on replaceText(origData, origText, repText) set curDelim to AppleScript’s text item delimiters set AppleScript’s text item delimiters to {origText} set origData to text items of origData set AppleScript’s text item delimiters to {repText} set origData to origData as text set AppleScript’s text item delimiters to curDelim return origData end replaceText |
一部の本Blog愛読者の方々から「アーカイブ本まだ?」と言っていただいて気にはしておりました。ここに、Blogアーカイブ本のVol.5(2013〜2014年)の発売をお知らせいたします。
本Blog「AppleScriptの穴」は、開設10年目を迎える直前の2018年1月末に、格安ホスティングサービス「Xserver」との間の手違いでDBがシャットダウンされ、そのままの形で公開し続けることができなくなってしまいました。
そこで、AppleScriptのプログラムによるFinder上のAppleScriptの自動HTML化、AppleScriptによるXML-RPC経由でのWordPressへの自動記事投入といったさまざまなソリューションを投下して、現在の登録記事数に復旧。自動記事投入による復旧を行なったために、当時の作業環境でオープンできない(OSバージョン依存、アプリケーション依存)Scriptについては掲載を見送っています。
旧AppleScriptの穴:2008〜2018年 Mac OS X 10.4〜10.5の時代に開設
現AppleScriptの穴:2018年〜 Cocoa APIを呼びまくる現在のスタイルが定着
自動投入可能なコンテンツを優先して掲載したため、旧Blogと現Blogでは掲載内容が大幅に変わっています。別物といってよいでしょう。
そのため、Cocoa APIを使用しない「オールドスタイルのAppleScript」(とくに昔のバージョンのアプリケーション依存)については再掲載が難しく、このBlogアーカイブ本シリーズにしか情報をまとめていません。
旧Blogアーカイブ本シリーズも、1年ごとに区切って記事をPDF化して5冊目。この頃の記事投稿数が少なかったので、Vol.5は2013年1〜2014年12月の2年分の内容をまとめています。全429ページ、2,000円。
ファイル形式はPDFで、キーワードによる全文検索や印刷が行えます。本文からリンクしているXcodeプロジェクトのダウンロードリンクも新たに現行Blogにアップロードしたアーカイブにリンクし直しています。
もちろん、各プログラムリスト末尾にURLリンクをそなえ、ワンクリックでプログラム内容をスクリプトエディタに転送可能です。
ほとんどの掲載プログラムについてコメントを行い、現時点(2020年時点)で評価してどうか、ということを追記しています。
AppleScriptObjCでViewを印刷
AppleScriptObjCで透明ウィンドウを表示
Bundle IDで指定したアプリの非同期起動
指定プロセスの死活判定
最前面のアプリケーションをいったん終了させてから起動
指定プロセスの死活判定 v2
複数のピリオドが入る数字の文字列を数値として解釈して返す
与えられたICC Profileの名称リストからもっともバージョンの新しい(大きい)数字を持つものを返す
システムにインストールされているICC Profileのうち、指定キーワードに該当する名前を持つものでバージョン番号が最新のものを返す v2
Photoshopのsave optionをテキストで指定して反映させるテスト
AppleScriptObjCでボタンの文字色を変更
Xcode 4.6でAppleScript用語辞書の若干の間引きを
USBメモリやネットワーク上のサーバーなどをアンマウント
AppleScriptObjCでボタンを動的に生成
AppleScriptObjCでボタンを動的に生成(横に複数作成)
miで文字置換
AppleScriptObjCでMyriad Helpersの三角関数を使ってWindowを円運動
AppleScriptObjCとメモリ管理
AppleScriptObjCでPDFViewを使ってPDFをプレビュー(3)
ログファイルから時間帯ごとの度数分布を計算
指定のコード体系の全パターンのコードを生成 v1
指定のコード体系の全パターンのコードを生成 v2
指定のコード体系の全パターンのコードを生成 v3
指定のコード体系の全パターンのコードを生成 v4
指定のコード体系の全パターンのコードを生成 v5
リストを任意のデリミタ付きでテキストに v2
特定の語句を含むMail.appのフォルダ(mailbox)を抽出してフルパスを文字列化
現在表示中のCanvasに存在しているラインのうち青いものに影を付ける
miで選択中の内容をファイルに書き出してperlのプログラムとしてterminalで実行
miで選択中の内容をファイルに書き出してperlのプログラムとしてterminalで実行 v2
文字入力モードを制御
iTunesのMobileアプリをコピーしてすべて展開する
Find same file name with different extension
AppleのAppleScript関連ドキュメントの個人的な翻訳サイト
インストールされているアプリのAS辞書を書き出すv2
Mailで選択中のmessageの親フォルダのフルパスを文字で取得する
指定フォルダ中のファイル名が拡張子を外すと衝突するかどうかチェック
AppleのiBookstoreが日本国内向けにコンテンツ販売を開始
Safariで指定可能なユーザーエージェントのリストを返す
Safari 6で指定のURLをオープンする
Safariで指定User Agentで指定URLをオープン
共通項目をキーにしてリスト内の項目を統合する v2
アプリケーションのクラッシュレポートダイアログの表示切り換え
入れ子のリストの昇順、降順ソート(超高速版)
IPアドレスがプライベートIPアドレスかどうかチェック
OS X 10.8のdateに強烈なバグ
1Dリスト中のシーケンシャルサーチ
1Dリストのスイープ
自然言語による相対日付指定v14
CSVデータを読み込んでリスト化
SafariでRadiko選局を行う 10.6.8+5.1.8版
SafariでRadikoの選局を行う v2
開始日と終了日の間を、指定日数単位で切った{開始日,終了日}のリストの日付文字列リストを返す
手作りのノンブルが作ってあるPowerPoint書類に対して、座標位置(一番左側に寄せてある)を手がかりにフレームを取得してリナンバリング
0〜255の8ビットの値から構成されるRGB値のリストの色をプレビューする
Photoshop CS6でRGB→LAB、LAB→RGB変換
0〜255の8ビットの値から構成されるRGB値のリストの色をプレビューする
PowerPointで、オブジェクトの外側の線の色を、水色から青に変更
Photoshop CS6でRGB→LAB、LAB→RGB変換
Photoshop CS6でRGB→HSB、HSB→RGB変換
Photoshop CS6でRGB→RGB Hex、RGB Hex→RGB変換
Photoshopでオープン中の画像をグレースケール→白黒2値の画像にモードを変換する
Safariでオープン中のファイルを別途ダウンロードv2
Photoshop上で選択範囲を指定色(RGB)で塗りつぶす
AppleScriptの処理中断
PDFのページ数を数える
EPSファイルの破損チェック(高速版)
プレビュー.appをAppleScriptから操作
国民の祝日を求める v4
AppleriptObjCベースのCocoaアプリケーションのSandbox化は可能か?
入り組んだリストの中に指定要素が存在するかどうかをチェック
Mail.appのメールボックスオブジェクトを渡すと、テキストのフルパスに変換 v1
Mail.appの指定メールボックス内に任意のメールボックスを新規作成 v2
OmniOutlinerで選択中の行の内容のうち指定列のデータをすべて取得
リストをテキストに
指定の文字エンコーディングでテキストをファイルに保存
ASObjCExtrasでファイルの情報を取得
ASObjCExtrasで1Dリスト中の合計値、最大値、最小値を求める
ASObjCExtrasで1D List中のヌル要素を削除
ASObjCExtrasで1D List中のmissing valueを置換
ASObjCExtrasで2DのListをフラット化(1D List化)
OS X 10.10 Yosemite のAppleScript関連バグまとめ
2Dリストで最長の要素に満たない個数の要素は後ろに埋め草を追加
2D Listの各要素に指定の1Dリストの内容をインサートする
指定ファイルからサイズ情報を取得
ドロップレットのデバッグ方法
Finder Windowを円運動 v2
list同士のdiffをとる(asoc)
listの共通項を返す(asoc)
1D Listのユニーク化(asoc) 処理内容比較
asocで文字置換
2D Listを1D Listに変換
レコードの操作
レコードのリストをソート(asoc)
レコードのリストから抽出(asoc)
リストから抽出(asoc)
レコードのリストから抽出(別リストに該当するもののみ)
Keynote 6.5で各スライドのタイトル、マスタースライド名を取得してデータ化
1D Listを文字列長でソート
2D Listを文字列長でソート
1D Listを文字列長でソート v2
10.10でプリンタを選択して印刷
10.10でプリンタの情報を取得する
無限次元リストのフラット化
2D Listを文字列長でソート
1D Listの内容すべてに指定数値を加算
画像の特定のピクセルの色を取得
画像中の色数をカウント
指定アプリケーションがフルスクリーン状態かどうか調べる
NSNumberFormatterのテスト
as integerの落とし穴
数値の桁数を求める
同一パターンの連番文字列の作成
1Dリスト中の複数指定アイテムの出現位置をリストで返す
CoreImageでフィルタしまくり
Bluetoothに接続中のデバイス名を取得するv2
NSSoundで音声を再生
バージョン番号文字列からメジャーバージョンを求める
バージョン番号文字列からメジャーバージョンを求める v2v3
指定AppleScriptをしらべたり実行したりする
指定ロケールの月名、曜日名を取得する
AppleScriptでJavaScriptを実行する
SafariのWebViewのGUI Scripting的な参照を取得する
アプリケーションのローカライズ分布を取得する
アプリケーションのローカライズ分布を取得するv2
アプリケーションのローカライズ分布を取得する v4
アプリケーションのローカライズ分布を取得する v5
アプリケーションごとのローカライズ言語数を求める
オーディオファイルのチャンネル数と再生時間を取得する
aListのうち、bListに入っていない項目を返す
PDFをページごとに分解する
文字エンコーディングを自動判別してテキストファイル読み込み v1
文字エンコーディングを自動判別してテキストファイル読み込み v2
GUI Scripting、それは画面上のGUI部品に直接メッセージを送って強引にアプリケーションを動かす必要悪。AppleScript非対応機能を強引に動かすことが目的の機能です。
MarkdownエディタのMacDownも、PDF書き出しについてはAppleScript用語辞書にコマンドが掲載されていないので、Markdown書類のPDF書き出しはGUI Scriptingで行なっています。
メイン環境をmacOS 10.12.6から10.14.6に移行して、はじめて動かした重量級のAppleScriptがあります。指定フォルダ以下のPages、Markdown、Wordなどの書類をすべてデスクトップ上にPDFで書き出して、ファイル名順にならべかえて1つのPDFにまとめるAppleScriptです。
つまり、電子書籍の書き出し&連結作業を1本でこなすScriptなわけで、自分にとっては命綱的に重要なAppleScriptです。
で、こいつがmacOS 10.14.6上でまともに動かないことが判明して、顔色が変わりました。
システム環境設定のセキュリティ系の妨害を受けているのかと思って確認してみると、必要な設定はすべて行なってある状態。MacDownからのPDF書き出しだけが効いていません。
–注意!! ここでGUI Scriptingを使用。バージョンが変わったときにメニュー階層などの変更があったら書き換え
on macDownForceExport()
activate application "MacDown"
tell application "System Events"
tell process "MacDown"
— File > Export > PDF
–click menu item 2 of menu 1 of menu item 14 of menu 1 of menu bar item 3 of menu bar 1
click menuItemRef1
–Go to Desktop Folder
keystroke "d" using {command down}
–Save Button on Sheet
click button 1 of sheet 1 of window 1
end tell
end tell
end macDownForceExport
★Click Here to Open This Script
ためしに、SIPを解除して実行してみたものの、それでも問題は解決しません。
動きを観察していると、書き出し時に「保存」ではなく「キャンセル」ボタンをクリックしている模様。そこで、ボタンをIndexではなくTitleで指し示してみたら、問題なく書き出しできました。
–注意!! ここでGUI Scriptingを使用。バージョンが変わったときにメニュー階層などの変更があったら書き換え
on macDownForceExport()
activate application "MacDown"
tell application "System Events"
tell process "MacDown"
— File > Export > PDF
–click menu item 2 of menu 1 of menu item 14 of menu 1 of menu bar item 3 of menu bar 1
click menuItemRef1
–Go to Desktop Folder
keystroke "d" using {command down}
–Save Button on Sheet
click button "保存" of sheet 1 of window 1 –保存ボタンのIndexが変わっていた
end tell
end tell
end macDownForceExport
★Click Here to Open This Script
# 久しぶりに怪奇現象っぽい挙動で震えました。だいたい、怪奇現象とか心霊現象っぽい挙動というのは、技術や経験が足りない場合に「そう見える」だけであって、知っていたり経験していれば「当然」という現象に見えます
いま、ちょうどGUI Scriptingは端境期で、手で書かなくてもアプリケーションの状態を検知してオブジェクト階層を動的に走査して動かすようなScriptが一部で使われている一方で、古いタイプのIDやTitleを直接指定するような書き方が残っていたりします。
OSがアップデートされても、IDで書いておけば同じGUI部品を指定できるだろう、という思い込みがありましたが、Titleで指定していたほうがIDの数え方が変わっても影響がない、という現象だったのでしょうか。
久しぶりにハマりそうになりました。あと、MacDownのソースに手を入れて、PDF書き出し命令ぐらいは自前で実装したい気がします。
Edama2さんと「無理だよねー」「そうそう、絶対無理〜」などとメールで言っていたら、Shane Stanleyから届いた「できるよ」という衝撃のメール。
スクリプトエディタ上で記述する通常のAppleScriptで、CocoaのCustom Class宣言と呼び出しができるとのこと。
自分で動作確認してみるまで、半信半疑でしたが、、、、できますねこれは、、、
–> Download Editable and Executable Script Bundle
冗談半分で思いついたことを試してみたらできてしまったり、冗談半分でできるわけないよねと念のために書いておいたことが世界の誰かの目に止まったりと、「冗談半分」ってけっこう重要なことだと思えてきました。
以下、Shane Stanleyの説明による、その書き換え手順です。
こういう(↑)スタイルですね。かならずscript宣言しつつ、parent属性を宣言しておくところがXcode上のAppleScriptアプリケーションのスタイルです。あとで動作確認して、アプリケーションの起動や終了に関するイベントハンドラを書いておいたのは無駄(実行されない)ではないかとも思われました。
普通、AppleScriptのファイルが入る/Contents/Resources/Scripts/でも、ライブラリを入れておく/Contents/Resources/Script Libraries/でもなく、/Contents/Resources/の直下に入れます。ファイル名はオリジナルのEdama2さんのものをそのまま採用していますが、割となんでもいいようです。Custom Classファイルは分割してもいいし、このサンプルのようにまとめてもいいんでしょう。
# 追加実験してみたところ、Resourcesフォルダ以下の/Scriptsや/Script Libraries/フォルダと重複しない名称の別フォルダ(例:/Classes/)に入れておいても大丈夫でした
見たことのない光景ですが、書くことについてはとくに障害はありません。AppKit.Frameworkもuse宣言しておいたほうがよかったかもしれません。
set theBundle to current application’s NSBundle’s bundleWithPath:pathToFolderWithScripts
theBundle’s loadAppleScriptObjectiveCScripts()
★Click Here to Open This Script
試行錯誤して、上記の「pathToFolderWithScripts」にバンドル内の/Contents/Resources/を入れて実行すればよいことが理解できました。
以上の変更を加えて、ためしにスクリプトエディタ&Script Debugger上で実行してみたところ、改変前と変わりなく実行できてしまいました(冒頭のスクリーンショット)。
いや、これはめちゃくちゃすごいですよ!! 何がすごいって、CocoaのCustom Classをスクリプトライブラリ中に入れて呼び出せるということで、けっこう無茶な箱庭インタフェースが作れてしまう予感が、、、、。
そして、AppleScriptObjC(AppleScriptObjC.frameworkより)でスクリプトエディタの「.scpt」形式のファイルを読み込んで実行できてしまったということは、Xcode上のAppleScriptアプリケーション内のScriptもテキスト形式だけでなく、スクリプトエディタで編集できる.scpt形式のファイルを突っ込んで編集できる可能性が見えてきました。
ただ、テキスト形式になっていないと、Interface Builderとの連携のあたりで問題になりそうな気もします。
| AppleScript名:customClassTest.scptd |
| — – Created by: Edama2 2020/01/10 – Adviced by: Shane Stanley 2020/01/11 – Modified by: Takaaki Naganoya 2020/01/12 — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use framework "AppleScriptObjC" use scripting additions property _clock_text_view : missing value –> 時計用の文字列 property _clock_timer : missing value –> 時計用のNSTimer –Script Bundle内のResourcesフォルダを求める set resourcePath to POSIX path of (path to me) & "Contents/Resources/" set theBundle to current application’s NSBundle’s bundleWithPath:resourcePath theBundle’s loadAppleScriptObjectiveCScripts() my performSelectorOnMainThread:"raizeWindow:" withObject:(missing value) waitUntilDone:true # ウィンドウを作成 on raizeWindow:aParam # 時計用の文字を作成 tell current application’s NSTextView’s alloc() tell initWithFrame_(current application’s NSMakeRect(35, 120, 300, 40)) setRichText_(true) useAllLigatures_(true) setTextColor_(current application’s NSColor’s whiteColor()) setFont_(current application’s NSFont’s fontWithName:"Arial-Black" |size|:48) setBackgroundColor_(current application’s NSColor’s colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0) setAlphaValue_(1.0) setEditable_(false) –setString_("00:00:00") set my _clock_text_view to it end tell end tell # 時計を更新するNSTimerを作成 set my _clock_timer to current application’s NSTimer’s scheduledTimerWithTimeInterval:1 target:me selector:"idleHandler:" userInfo:(missing value) repeats:true # 丸いViewを作成 set aFrame to current application’s NSMakeRect(0, 0, 300, 300) tell current application’s RoundView’s alloc() tell initWithFrame_(aFrame) setNeedsDisplay_(true) setSubviews_({my _clock_text_view}) set customView to it end tell end tell #スクリーンのサイズを調べる set aScreen to current application’s NSScreen’s mainScreen() # Window set aBacking to current application’s NSWindowStyleMaskBorderless –set aBacking to current application’s NSBorderlessWindowMask set aDefer to current application’s NSBackingStoreBuffered tell current application’s CustomWindow’s alloc() tell initWithContentRect_styleMask_backing_defer_screen_(aFrame, aBacking, aDefer, false, aScreen) –setTitle_(uniqueName) –>タイトル setBackgroundColor_(current application’s NSColor’s clearColor()) — Grammar Police –>背景色 setContentView_(customView) –>内容ビューのセット setDelegate_(me) –>デリゲート setDisplaysWhenScreenProfileChanges_(true) –>スクリーンプロファイルが変更されたときウインドウの内容をアップデートするか setHasShadow_(true) –>ウインドウに影があるか setIgnoresMouseEvents_(false) –>マウスイベントを無視するか –setLevel_((current application’s NSScreenSaverWindowLevel) + 1) –>ウインドウの前後関係の位置 setOpaque_(false) –>ウインドウを不透明にするか setReleasedWhenClosed_(true) –>閉じたときにメモリを解放するか # |center|() makeKeyAndOrderFront_(me) –>キーウインドウにして前面に持ってくる –setFrame_display_(aFrame, true) –>表示 end tell end tell end raizeWindow: #タイマー割り込み on idleHandler:aSender set mesStr to time string of (current date) (my _clock_text_view)’s setString:mesStr end idleHandler: |
| AppleScript名:CocoaAppletAppDelegate.scpt |
| script CocoaAppletAppDelegate property parent : class "NSObject" on applicationWillFinishLaunching:aNotification — end applicationWillFinishLaunching: on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: end script script CustomWindow property parent : class "NSWindow" property canBecomeKeyWindow : true property _initial_location : missing value on mouseDown:theEvent set my _initial_location to theEvent’s locationInWindow() end mouseDown: on mouseDragged:theEvent –set res to display dialog "mouseDragged" buttons {"OK"} default button "OK" try set screenVisibleFrame to current application’s NSScreen’s mainScreen()’s visibleFrame() set screenVisibleFrame to my myHandler(screenVisibleFrame) set windowFrame to my frame() set windowFrame to my myHandler(windowFrame) set newOrigin to windowFrame’s origin set currentLocation to theEvent’s locationInWindow() set newOrigin’s x to (newOrigin’s x) + ((currentLocation’s x) – (_initial_location’s x)) set newOrigin’s y to (newOrigin’s y) + ((currentLocation’s y) – (_initial_location’s y)) set tmpY to ((newOrigin’s y) + (windowFrame’s |size|’s height)) set screenY to (screenVisibleFrame’s origin’s y) + (screenVisibleFrame’s |size|’s height) if tmpY > screenY then set newOrigin’s y to (screenVisibleFrame’s origin’s y) + ((screenVisibleFrame’s |size|’s height) – (windowFrame’s |size|’s height)) end if my setFrameOrigin:newOrigin on error error_message number error_number set error_text to "Error: " & error_number & ". " & error_message display dialog error_text buttons {"OK"} default button 1 return error_text end try end mouseDragged: on myHandler(aFrame) if class of aFrame is list then set {{theX, theY}, {theWidth, theHeight}} to aFrame set aFrame to {origin:{x:theX, y:theY}, |size|:{width:theWidth, height:theHeight}} –set aFrame to current application’s NSMakeRect(theX, theY, theWidth, theHeight) end if return aFrame end myHandler end script script RoundView property parent : class "NSView" on drawRect:rect set aFrame to my frame() set myColor to current application’s NSColor’s redColor() tell current application’s NSBezierPath tell bezierPathWithOvalInRect_(aFrame) myColor’s |set|() fill() end tell end tell end drawRect: end script |
本Scriptは、Edama2さんから投稿していただいたものです。Cocoa AppleScript AppletでCustom Classを宣言して作られた、丸いウィンドウ(透明ウィンドウの上に丸いグラフィックを描画)を表示して、タイマー割り込みで時計を表示するAppleScriptです。
–> Download Editable and Executable Applet
AppleScriptのランタイム環境はいくつかあり、それぞれに「できること」と「できないこと」、「手軽さ」などが異なります。
(1)スクリプトエディタ上で記述、実行する環境
一番セキュリティ上の制約が緩く、できることの多い環境です。
(2)Script Debugger上で記述、実行する環境
Cocoaのイベントやオブジェクトのログ表示などができる環境です。
(3)Applet環境
AppleScriptの実行ファイルです。
(4)Script Menu環境
macOS標準装備の、メニューからAppleScriptを実行できる環境です。
さらに、Cocoa Scriptingの機能に着目してみると、見え方が変わります。
本Scriptを記述している「Cocoa AppleScript Applet」環境(上の図の赤い部分)は、スクリプトエディタ上で記述する通常のAppleScriptと、Xcode上で記述するCocoaアプリケーションの中間的な性格を持つものです。スクリプトエディタ上で直接は動かせず、アプレット形式で動作させることになりますが、スクリプトエディタ上で動かすよりも、よりCocoaの機能が利用できます。
Cocoa AppleScript Appletでは、アプリケーション(Applet)起動や終了の最中で発生するイベントを利用できますし、本ScriptのようにCocoaのCustom Classを宣言できます。これは、普通のスクリプトエディタ上で記述する(本Blogの大部分のScriptのような)Scriptではできない真似です。
→ Shane Stanleyからツッコミが入って、手の込んだ作業を行うとできるとかで(テンプレートそのままでは無理)、後で実際に試してみます
タイトルは「丸いウィンドウと時計の表示」 NSWindowのカスタムクラスを使ったタイトルバーなしのドラッグで移動できる丸いウィンドウとオマケに時計を表示したものです。 初心者受けしそうなやつです。 問題はそこではなく、 XcodeやCocoa applescript appletから実行するASOCだとカスタムクラスが作れるけど、 ノーマルのapplescriptから実行するASOCではカスタムクラスが作れないということです。 表現がややこしいですが...。 ノーマルのapplescriptからカスタムクラスを作ると、ただのスクリプトオブジェクトにしかなりません。 誰かうまい解決方法を知っている人がいたら教えてください。
ちょうど、こういう資料をまとめていたので補足説明に役立ってしまいました。スクリプトエディタ上で記述する通常のAppleScriptでもCustom Cocoa Classが宣言できると便利そうですが、どんなもんでしょうか?
Custom Classは便利なので使いたくなる一方、AppleScriptのインタプリタ上で実行するため、Objective-Cなどで書くのと同じような感覚で使うと「遅くて使えない」という話になると思いますが、このEdama2さんのサンプルぐらいの使いかたであれば、ちょうどいいんじゃないかというところです。
歴史的にみると、Cocoa-AppleScript Appletは、Xcode上で記述するCocoa-Applicationを簡略化してスクリプトエディタ上でCocoa Scriptingを手軽に使えるように手直しした「途中」の環境といえます。
Cocoa-AppleScript Appletは、GUIが手軽に作れるわけでもなく、スクリプトエディタ上で直接実行やログ表示ができるわけでもなありません。マスターしたところで最終到達点がCocoaアプリケーションほど高くなく、編集や習熟もしづらいことから「中途半端」「使えない」という評価になっていました(自分も使っていませんでした)。
その後、Cocoa-AppleScript Appletの機能要素をさらにダウンサイジングして、スクリプトエディタ上で手軽に記述・実行ができるように進化したのが現在・広くつかわれているCocoa Scripting環境です。
ただ、使いやすくなって広く使われるようになったはいいものの、「Xcodeを使うまでもないが、もうちょっとCocoaの機能が利用できないか?」という意見も出るようになり、Cocoa-AppleScript Appletを再評価してもいいんじゃないかと考えるようになってきてはいます。
ちなみに、本Cocoa-AppleScript AppletでCustom Classを宣言しているのと同じような書き方で、通常のCocoa Scriptingの環境で動かすような変更を加えたScriptもEdama2さんが試していますが、それは「動かない」ということで結論が出ています。
| AppleScript名:CocoaAppletAppDelegate.scpt |
| script CocoaAppletAppDelegate property parent : class "NSObject" property _clock_text_view : missing value –> 時計用の文字列 property _clock_timer : missing value –> 時計用のNSTimer on applicationWillFinishLaunching:aNotification my raizeWindow() end applicationWillFinishLaunching: on applicationShouldTerminate:sender my _clock_timer’s invalidate() return current application’s NSTerminateNow end applicationShouldTerminate: # ウィンドウを作成 on raizeWindow() # 時計用の文字を作成 tell current application’s NSTextView’s alloc() tell initWithFrame_(current application’s NSMakeRect(35, 120, 300, 40)) setRichText_(true) useAllLigatures_(true) setTextColor_(current application’s NSColor’s whiteColor()) setFont_(current application’s NSFont’s fontWithName:"Arial-Black" |size|:48) setBackgroundColor_(current application’s NSColor’s colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.0) setAlphaValue_(1.0) setEditable_(false) –setString_("00:00:00") set my _clock_text_view to it end tell end tell # 時計を更新するNSTimerを作成 set my _clock_timer to current application’s NSTimer’s scheduledTimerWithTimeInterval:1 target:me selector:"idleHandler:" userInfo:(missing value) repeats:true # 丸いViewを作成 set aFrame to current application’s NSMakeRect(0, 0, 300, 300) tell current application’s RoundView’s alloc() tell initWithFrame_(aFrame) setNeedsDisplay_(true) setSubviews_({my _clock_text_view}) set customView to it end tell end tell #スクリーンのサイズを調べる set aScreen to current application’s NSScreen’s mainScreen() # Window set aBacking to current application’s NSWindowStyleMaskBorderless –set aBacking to current application’s NSBorderlessWindowMask set aDefer to current application’s NSBackingStoreBuffered tell current application’s CustomWindow’s alloc() tell initWithContentRect_styleMask_backing_defer_screen_(aFrame, aBacking, aDefer, false, aScreen) –setTitle_(uniqueName) –>タイトル setBackgroundColor_(current application’s NSColor’s clearColor()) –>背景色 setContentView_(customView) –>内容ビューのセット setDelegate_(me) –>デリゲート setDisplaysWhenScreenProfileChanges_(true) –>スクリーンプロファイルが変更されたときウインドウの内容をアップデートするか setHasShadow_(true) –>ウインドウに影があるか setIgnoresMouseEvents_(false) –>マウスイベントを無視するか –setLevel_((current application’s NSScreenSaverWindowLevel) + 1) –>ウインドウの前後関係の位置 setOpaque_(false) –>ウインドウを不透明にするか setReleasedWhenClosed_(true) –>閉じたときにメモリを解放するか # |center|() makeKeyAndOrderFront_(me) –>キーウインドウにして前面に持ってくる –setFrame_display_(aFrame, true) –>表示 end tell end tell end raizeWindow #タイマー割り込み on idleHandler:aSender set mesStr to time string of (current date) (my _clock_text_view)’s setString:mesStr end idleHandler: end script script CustomWindow property parent : class "NSWindow" property canBecomeKeyWindow : true property _initial_location : missing value on mouseDown:theEvent set my _initial_location to theEvent’s locationInWindow() end mouseDown: on mouseDragged:theEvent –set res to display dialog "mouseDragged" buttons {"OK"} default button "OK" try set screenVisibleFrame to current application’s NSScreen’s mainScreen()’s visibleFrame() set screenVisibleFrame to my myHandler(screenVisibleFrame) set windowFrame to my frame() set windowFrame to my myHandler(windowFrame) set newOrigin to windowFrame’s origin set currentLocation to theEvent’s locationInWindow() set newOrigin’s x to (newOrigin’s x) + ((currentLocation’s x) – (_initial_location’s x)) set newOrigin’s y to (newOrigin’s y) + ((currentLocation’s y) – (_initial_location’s y)) set tmpY to ((newOrigin’s y) + (windowFrame’s |size|’s height)) set screenY to (screenVisibleFrame’s origin’s y) + (screenVisibleFrame’s |size|’s height) if tmpY > screenY then set newOrigin’s y to (screenVisibleFrame’s origin’s y) + ((screenVisibleFrame’s |size|’s height) – (windowFrame’s |size|’s height)) end if my setFrameOrigin:newOrigin on error error_message number error_number set error_text to "Error: " & error_number & ". " & error_message display dialog error_text buttons {"OK"} default button 1 return error_text end try end mouseDragged: on myHandler(aFrame) if class of aFrame is list then set {{theX, theY}, {theWidth, theHeight}} to aFrame set aFrame to {origin:{x:theX, y:theY}, |size|:{width:theWidth, height:theHeight}} –set aFrame to current application’s NSMakeRect(theX, theY, theWidth, theHeight) end if return aFrame end myHandler end script script RoundView property parent : class "NSView" on drawRect:rect set aFrame to my frame() set myColor to current application’s NSColor’s redColor() tell current application’s NSBezierPath tell bezierPathWithOvalInRect_(aFrame) myColor’s |set|() fill() end tell end tell end drawRect: end script |
Script Object関連の調べ物をしていて、AppleのWebサイト掲載のReferenceを見ていたら、サンプルをまじえて説明されていたので、理解が深まりました。
AppleScript:トップレベルObject。不変
my(me):現在のScript Object。可変
it:現在のアプリケーションObject。可変
AppleScript > my(me) ≧ it
というレベルになっていることもよく理解できました。
ただ、サンプルが自分的にいまひとつわかりやすくなかったので、自分用に書き換えてみました。バージョン取得ではなく、「名前を取得」しないといまひとつ分からないんじゃないでしょうか。
| AppleScript名:testScript |
| me –> «script»–実行中のAppleScript書類(Script Object) AppleScript –> «script AppleScript» it –> «script»–current target object tell application "Finder" it –> application "Finder"–current target object –testMe() –> error "Finderでエラーが起きました: testMeを続けることができません。" number -1708 testMe() of me testMe() of aTEST of me testMe() of bTEST of aTEST of me end tell on testMe() set aName to name of me –> "testScript" display dialog aName –Name of this AppleScript document file end testMe script aTEST on testMe() set aName to name of me –> "aTEST" display dialog aName –Name of this script object (aTEST) end testMe script bTEST on testMe() set aName to name of me –> "bTEST" display dialog aName –Name of this script object (bTEST) end testMe end script end script |
最前面のアプリケーションのAppleScript用語辞書をオープンするAppleScriptです。
macOS標準装備のScript Menuに入れて呼び出すことを前提に作りました。はるかかなた昔に作って、OSバージョンが上がるごとに細かい改修を行なって使い続けているものです。
この手のScriptは日常的にAppleScriptを書いている人間なら、たいてい書いてみたことがあるはずです。しかし、あまり生真面目なアプローチでアプリケーションバンドル中のsdefのパスを求めてオープンといった処理を行っていると、「例外」にブチ当たって困惑します。
sdefファイルを直接持っていないAdobe Creative Cloud製品です。InDesignあたりがそうなんですが、直接sdefを保持しておらず、どうやら実行時に動的に組み立てるようで、絶体パス(absolute path)で指定してもsdefをオープンできません。
また、Microsoft Office系アプリケーションのsdefも、外部の(OS側のsdef)テンプレートをincludeするようなので、生真面目に対象アプリケーションのInfo.plistの情報をもとにアプリケーションバンドル中のsdefファイルをオープンするように処理すると、sdefの一部のみを表示するようになってしまって、sdef全体を表示することができません。
そうしたもろもろの問題に当たって、結局アプリケーションそのものをScript Editorでオープンするように指定しています。
| AppleScript名:–このアプリケーションの用語辞書を表示する v4 |
| — Created 2017-07-23 by Takaaki Naganoya — 2017 Piyomaru Software use AppleScript version "2.4" use scripting additions use framework "Foundation" –最前面のプロセスのファイルパスを取得する set aFile to path to frontmost application set aRec to (getAppPropertyFromInfoPlist(aFile, "NSAppleScriptEnabled") of me) as boolean –スクリプト用語辞書をScript Editorでオープンする手順 if aRec = true then –OS X 10.10でScript Editor側からアプリケーションをオープンしてもダメだったので、Finder側からScript Editorで指定アプリをオープンすることに try tell application "Finder" set apFile to (application file id "com.apple.scripteditor2") as alias open aFile using application file apFile end tell on error tell application id "com.apple.scripteditor2" open aFile end tell end try tell application id "com.apple.scripteditor2" to activate else display dialog "本アプリケーションは、各種OSA言語によるコントロールに対応していません。" buttons {"OK"} default button 1 with icon 2 with title "Scripting非対応" end if on getAppPropertyFromInfoPlist(aP, aPropertyLabel) set aURL to current application’s |NSURL|’s fileURLWithPath:(POSIX path of aP) set aBundle to current application’s NSBundle’s bundleWithURL:aURL set aDict to aBundle’s infoDictionary() set aRes to aDict’s valueForKey:aPropertyLabel if aRes is not equal to missing value then set aRes to aRes as anything end if return aRes end getAppPropertyFromInfoPlist |
AppleScript users ML上で2016/10/16〜18に行われていた議論の内容を自分でも再確認しました(Thanks Yvan!)。一時フォルダの場所を求める「path to temporary items」の値が、macOS 10.12から変更になっています。

▲AppleScript Users ML上の議論のフロー(Mail Flow Visualizerによりバッチ出力。つまり、Mail.app上で出力フローを選択してAppleScriptでフロー図出力しています)
AppleScript標準装備の「path to」コマンドは、Classic MacOS環境からMac OS X環境に移行した後に重要度が上がったコマンドです。OS側が用意している特別な意味を持つフォルダの場所を返してくれます。
とくに、(いまさらですが)Mac OS X環境はマルチユーザー環境なので、とくにPicturesとかDocumentsとかDesktopとかの特別なフォルダのパスを求める機能は基礎的ではあるものの、とても重要です。このあたりをめんどくさがってなんでもかんでも固定パスで書きたがる人を見かけたことがありますが、その人が書いたプログラムは他のユーザー環境で動かなくて苦労していました(本人ではなく周囲の人々が)。
AdobeのCS/CCアプリケーションのサンプルAppleScriptでも固定パスで書かれたものを多数見かけましたが、あれはなんだったんでしょう。
もともと、path toコマンドにより求められる各種パスには、
system domain:/System network domain:/Network user domain:~
などのほか、
local domain:/Library Classic domain: Classic Mac OSのシステムフォルダ(Classic環境をサポートするOS&ハードウェアの組み合わせ上でのみ有効)
などの「ドメイン」が指定できることになっていました。system domainはOSのSystemが利用、User domainはユーザーのホームフォルダ以下。network domainはmacOSのNetBootが廃止/他の機能への移行対象となっている(すでに、iMac ProではNetBoot非サポート)ほか、そのような環境下にないと有意な結果が得られません。
ドメインの区分けが存在するものの、これらの区分のうちClassic domainははるかかなた昔に消滅、network domainも使ったことのあるユーザーは稀(まれ)でしょう。
そんな中、作業用の一時フォルダの場所を求める「path to temporary items」の仕様がmacOS 10.12で変更になっていました。
テンポラリフォルダの特性ゆえに、細かいディレクトリ名はユーザー環境ごと、実行時ごとに微妙に異なりますが(赤字部分が変化する部分)、上の図では(↑)どのあたりに作られるのかという点に着目して色分けしてみました。
macOS 10.12以降では、user domainでもsystem domainでも同じような場所が指定されることになることがわかります。
ちなみに、Cocoaの機能を用いて求められる一時フォルダはまた別の場所が示されています。
use framework "Foundation"
current application’s NSTemporaryDirectory() as text
–> "/var/folders/h4/jfhlwst88xl9z0001s7k9vk00000gr/T/"
ディスプレイの輝度変更を行うAppleScriptです。
AppleScriptにディスプレイの輝度変更を行う機能はないので、GUI Scripting経由で(画面上のGUI部品を野蛮に操作して)変更するか、あるいは「他の方法」を採用することになります。
本Scriptは「他の方法」をとってみたものです。画面の輝度変更については、そのようなニーズがあり、割とまともにOSに機能が実装されていることもあり、IOKitを呼び出して変更している例がいろいろ見つかります(Objective-Cとかで)。そういったものをFramework化してAppleScriptから呼び出すか、あるいはコマンドライン上で動作するプログラムをAppleScript内に入れて呼び出すか、といったところになります。
Github上で探し物をしてみたところ、nriley氏の「brightness」というツールがコマンドライン上から呼び出せるようになっており、AppleScriptのバンドル内に突っ込んで呼び出しやすそうでした。
そこで、実際にAppleScriptバンドル書類中に突っ込んで、いい感じに呼び出せるように最低限の機能を実装してみました。ディスプレイの接続数の確認(getDisplayNumber)、指定ディスプレイの現在の輝度確認(getBrightNess)、そして指定ディスプレイの輝度設定(setBrightNess)です。
–> Download displayBrightness.scptd (Script Bundle with brightness binary in its bundle)
ディスプレイ番号は1から始まるものとして扱っています。輝度は0.0〜1.0(1.0が一番明るい)までの値を指定します。
一応、brightnessユーティリティでは複数のディスプレイが接続された状態を前提に処理しているのですが、どうせ輝度変更ができるのはMacBook ProやiMacなどの内蔵ディスプレイだけです。外部接続したディスプレイの輝度は変更できません。
本Scriptのようなお気楽実装だと問題になってくるのが、
(1)デスクトップマシン(MacPro、Mac miniなど)で外部ディスプレイを接続していない(ヘッドレス運用)の場合への対処
(2)ノート機+iMac/iMac Proで、メインディスプレイを本体内蔵ディスプレイ「以外のもの」に指定している場合への対処
(3)ノート機で、Lid Closed Mode運用している(本体を閉じて外部ディスプレイをつないで使っている)状態への対処
などです。本Scriptはそこまで厳密な対応を行っていません。ただ、実用上はこのぐらいで問題はなさそうだ、という「割り切り」を行って手を抜いています。
各条件でテストを行って「使える」という確証が得られたら、sdef(AppleScript用語辞書)をつけたライブラリにでもするとよさそうです。
また、本Script中に同梱しているbrightnessツールの実行ファイルはNotarizationを通していないため、2020年2月3日を過ぎると10.15.2以降のmacOSで何らかのダイアログが出て実行をキャンセルされる可能性がありますが、正直なところmacOS 10.15.xは「バグが多すぎ」「実用性なし」と判断。macOS 10.15.xは自分としては「パス」するつもりなので、現時点において対処する必要性を感じません(仕事で納品するバイナリについては全力で対処しますが)。
| AppleScript名:displayBrightness.scptd |
| — – Created by: Takaaki Naganoya – Created on: 2020/01/04 — – Copyright © 2020 Piyomaru Software, All Rights Reserved — use AppleScript version "2.4" — Yosemite (10.10) or later use framework "Foundation" use scripting additions set dRes to getDisplayNumber() of me –> 1 set bRes to getBrightNess(1) of me –> 0.763672 set sRes to setBrightNess(1, 0.7) of me on getDisplayNumber() set myPath to POSIX path of (path to me) set cmdPath to myPath & "Contents/Resources/brightness" set aRes to do shell script quoted form of cmdPath & " -l" set pList to paragraphs of aRes return (length of pList) div 2 end getDisplayNumber on getBrightNess(displayNumber as integer) set dRes to getDisplayNumber() of me if dRes < displayNumber then error "Your parameter is too large (" & "there is " & (dRes as string) & " display only)." set targDispNumber to displayNumber – 1 set targString to "display " & (targDispNumber as string) & ": brightness " set myPath to POSIX path of (path to me) set cmdPath to myPath & "Contents/Resources/brightness" set aRes to do shell script quoted form of cmdPath & " -l" repeat with i in (paragraphs of aRes) set j to contents of i if j begins with targString then set aOffset to offset of targString in j set resStr to text (aOffset + (length of targString)) thru -1 of j return resStr end if end repeat error "Display’s brightness information is not present" end getBrightNess on setBrightNess(displayNumber as integer, targBrightnewss as real) set dRes to getDisplayNumber() of me if dRes < displayNumber then error "Your parameter is too large (" & "there is " & (dRes as string) & " display only)." set targDispNumber to displayNumber – 1 set targString to "display " & (targDispNumber as string) & ": brightness " set myPath to POSIX path of (path to me) set cmdPath to myPath & "Contents/Resources/brightness" set aRes to do shell script quoted form of cmdPath & " -l" set hitF to false repeat with i in (paragraphs of aRes) set j to contents of i if j begins with targString then set hitF to true exit repeat end if end repeat if hitF = false then error "There is no display (" & (displayNumber as string) & ")" set bRes to do shell script quoted form of cmdPath & " " & (targBrightnewss as string) set curBright to getBrightNess(displayNumber) of me –結果を評価する if (targBrightnewss as real) is not equal to (curBright as real) then return false else return true end if end setBrightNess |
Mac App Storeで、Piyomaru SoftwareによるMac用アプリケーション「Double PDF」の新バージョンv2.0をリリースしました。100% AppleScriptで記述しています。
本バージョンは、macOS 10.13においてScripting Bridge経由でPDFViewを正しくアクセスできないバグが発生している症状を回避したものです。表示中のページ管理などをすべてアプリケーション側で行うことにより、機能の回復を実現しました(書いていて情けない)。
v1ではGPUImage.frameworkを利用して画像処理していましたが、これを取り外してすべてAppleScriptで処理するように変更しました。
■v2.0の追加機能
・ダークモード対応
・画像比較時のカラーモード追加(従来はグレースケールのみ)
・30言語に対応(English, Catalan, Chinese, Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Malay, Norwegian Bokmål, Polish, Portuguese, Romanian, Russian, Simplified Chinese, Slovak, Spanish, Swedish, Thai, Traditional Chinese, Turkish, Ukrainian, Vietnamese)
・Unicode文字の「Zero Width Space」削除機能を追加
■v2.0の修正機能
・順次Diffチェック時に、アプリケーションアイコン上に描画するプログレスバーの進捗度が間違っていたのを修正