ライブラリのインストール方法やらスクリプトエディタの使い方やらがわかっていないとcommon elements Libを実際に使って試すことができないので、実際に機能を評価できるよう最低限のGUIをつけてみました。
–> Download GUI App Executable(Zip archive, 57KB)
macOS 10.13,10.14, 10.15上で動作します。実行にはインターネット接続を必要とします。
ライブラリのインストール方法やらスクリプトエディタの使い方やらがわかっていないとcommon elements Libを実際に使って試すことができないので、実際に機能を評価できるよう最低限のGUIをつけてみました。
–> Download GUI App Executable(Zip archive, 57KB)
macOS 10.13,10.14, 10.15上で動作します。実行にはインターネット接続を必要とします。
Xcode上で作成するAppleScript Cocoa Applicationで、キースキャンを試してみました。
ふだん作っているものだと、各種パラメータをGUI上で設定する程度のもので、キースキャンを行う必要などこれっぽっちもないのですが、いま作っているアプリケーションでキースキャンが必要になってしまったので、昔作ったものを引っ張り出してきました。
AppleScriptのプログラムでキースキャンを行うといえば、AppleScript Appletの起動時に何らかのModifier Keys(ShiftとかOptionとかCommandとかControlとか)が押されていることを検出して動作を変更するといった処理が一般的です。ループ処理中でも、これらのキー入力を定期的に監視することはよく行なっています(処理中に停止したいという要求はあるので)。
–> Download Xcode Project Archive
本プログラムでは、Modifier Keysにかぎらずキーボード入力全般を受け付けています。ただし、キースキャン可能なのは本プログラムが最前面にある場合のみです。
掲載しているコードからではわかりませんが、キー入力の受け付けをNSWindowで行なっています。FirstResponderまわりを一切いじくらずにほぼプログラミングなしでキー受け付けを行おうとした結果NSWindowで行うことになったというわけで、これがベストとも思いません。
とりあえず「こうすればできた」というレベルをおさえておいて、そこから自分の好きな方向に機能を変更していけばよいと思います。
| AppleScript名:AppDelegate.applescript |
| — — AppDelegate.applescript — keyEvents — — Created by Takaaki Naganoya on 2014/05/09. — Copyright (c) 2014年 Takaaki Naganoya. All rights reserved. — script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value property aButton : missing value property xMax : 500 property yMax : 500 property aStep : 50 on applicationWillFinishLaunching:aNotification — end applicationWillFinishLaunching: on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: on buttonMove:(aCode as integer) set curFrame to aButton’s frame() copy curFrame to {{x, y}, {xWidth, yHeight}} if aCode = 123 then –Left if x > 0 then set x to x – aStep end if else if aCode = 124 then –Right if x < xMax then set x to x + aStep end if else if aCode = 126 then –Up if y < yMax then set y to y + aStep end if else if aCode = 125 then –Down if y > 10 then set y to y – aStep end if else if aCode = 125 then end if set newRect to {{x, y}, {xWidth, yHeight}} aButton’s setFrame:newRect aButton’s setNeedsDisplay() end buttonMove: end script |
| AppleScript名:keyEventWin.applescript |
| script keyEvWin property parent : class "NSWindow" property aButton : missing value on canBecomeKeyWindow:sender return true end canBecomeKeyWindow: on canBecomeMainWindow:sender return true end canBecomeMainWindow: on keyDown:theEvent set aCode to (theEvent’s keyCode) as integer if aCode = 123 then –左 current application’s NSApp’s delegate()’s performSelector:"buttonMove:" withObject:(aCode) else if aCode = 124 then –右 current application’s NSApp’s delegate()’s performSelector:"buttonMove:" withObject:(aCode) else if aCode = 126 then –上 current application’s NSApp’s delegate()’s performSelector:"buttonMove:" withObject:(aCode) else if aCode = 125 then –下 current application’s NSApp’s delegate()’s performSelector:"buttonMove:" withObject:(aCode) end if end keyDown: end script |
Xcode上で作成するCocoa AppleScript Applicationにおいて、CoreAnimationを利用するサンプルProjectです。
–> Download Xcode Project Test with Xcode 11.3.1 + macOS 10.14.6
ひととおり(このぐらい)CoreAnimationでGUI部品をアニメーションするテストを行なっていました。あまり使いすぎるのは下品に見えるとの判断から、実際のアプリケーションでは最低限の地味な利用にとどめていました。
–> Watch Demo (2) This Project
Mac App Storeで販売している100% AppleScriptで記述したアプリケーション「Double PDF 2.0」においても、コマンド実行後にメニューを更新する際、更新したメニューを点滅表示するぐらいの「節度あるお付き合い」にとどめていました。
それが、ここ最近組んでいるアプリケーションではド派手に利用する必要があるようで、再度こうした試作品を引っ張り出してテストしだしています。
| AppleScript名:AppDelegate.applescript |
| — — AppDelegate.applescript — GUI Animation — — Created by Takaaki Naganoya on 2017/02/13. — Copyright 2017 Takaaki Naganoya. All rights reserved. — — http://liu044100.blogspot.jp/2013/07/cabasicanimation.html script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value property aPopup : missing value property gui1 : missing value property gui2 : missing value property gui3 : missing value property gui4 : missing value property gui5 : missing value property gui6 : missing value property gui7 : missing value property gui8 : missing value property gui9 : missing value property guiA : missing value property guiB : missing value property guiC : missing value on applicationWillFinishLaunching:aNotification — Insert code here to initialize your application before any files are opened end applicationWillFinishLaunching: on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: on clicked:sender set aInd to aPopup’s indexOfSelectedItem() set guiList to {gui1, gui2, gui3, gui4, gui5, gui6, gui7, gui8, gui9, guiA, guiB, guiC} repeat with i in guiList if aInd = 0 then (my blinkObject:i) else if aInd = 1 then (my scaleObject:i) else if aInd = 2 then (my rotateObject:i forAxis:"x") else if aInd = 3 then (my rotateObject:i forAxis:"y") else if aInd = 4 then (my rotateObject:i forAxis:"z") else if aInd = 5 then (my moveObject:i) else if aInd = 6 then –my mixtureAnimeObject:i end if delay 0.1 end repeat end clicked: on blinkObject:aObject set animation to current application’s CABasicAnimation’s animationWithKeyPath:"opacity" animation’s setDuration:0.1 animation’s setAutoreverses:true animation’s setRepeatCount:4 animation’s setFromValue:(current application’s NSNumber’s numberWithFloat:1.0) animation’s setToValue:(current application’s NSNumber’s numberWithFloat:0.0) aObject’s layer()’s addAnimation:animation forKey:"blink" end blinkObject: on scaleObject:aObject set animation to current application’s CABasicAnimation’s animationWithKeyPath:"transform.scale" animation’s setDuration:0.1 animation’s setAutoreverses:true animation’s setRepeatCount:2 animation’s setFromValue:(current application’s NSNumber’s numberWithFloat:1.0) animation’s setToValue:(current application’s NSNumber’s numberWithFloat:2.0) aObject’s layer()’s addAnimation:animation forKey:"scale-layer" end scaleObject: on rotateObject:aObject forAxis:anAxis set animation to current application’s CABasicAnimation’s animationWithKeyPath:("transform.rotation." & anAxis) animation’s setDuration:2.0 –animation’s setAutoreverses:false animation’s setRepeatCount:1 animation’s setFromValue:(current application’s NSNumber’s numberWithFloat:0.0) animation’s setToValue:(current application’s NSNumber’s numberWithFloat:4.0 * 3.1415926) aObject’s layer()’s addAnimation:animation forKey:"rotate-layer" end rotateObject:forAxis: on moveObject:aObject set aFrame to aObject’s frame() copy aFrame to {{fx1, fy1}, {fx2, fy2}} set animation to current application’s CABasicAnimation’s animationWithKeyPath:"position" animation’s setDuration:0.4 animation’s setAutoreverses:false animation’s setRepeatCount:1 animation’s setFromValue:(current application’s NSValue’s valueWithCGRect:(aObject’s frame())) animation’s setToValue:(current application’s NSValue’s valueWithCGRect:(current application’s CGRectMake(100, 100, fx2, fy2))) aObject’s layer()’s addAnimation:animation forKey:"move-layer" end moveObject: –Not Work Yet….. (* on mixtureAnimeObject:aObject set animation1 to current application’s CABasicAnimation’s animationWithKeyPath:"transform.translation.x" animation1’s setToValue:(current application’s NSNumber’s numberWithFloat:80.0) animation1’s setDuration:3.0 set animation2 to current application’s CABasicAnimation’s animationWithKeyPath:"transform.rotation.z" animation2’s setFromValue:(current application’s NSNumber’s numberWithFloat:0.0) animation2’s setToValue:(current application’s NSNumber’s numberWithFloat:4.0 * 3.1415926) animation2’s setDuration:3.0 set aGroup to current application’s CAAnimationGroup’s animation() aGroup’s setDuration:3.0 aGroup’s setRepeatCount:1.0 aGroup’s setAnimations:(current application’s NSArray’s arrayWithObjects:{animation1, animation2, missing value}) aObject’s layer()’s addAnimation:aGroup forKey:"move-rotate-layer" end mixtureAnimeObject: *) end script |
Xcode上でAppleScriptのアプリケーションを作って(Double PDF)、各国語にローカライズしていたときに、最難関言語として名高いアラビア語(Arabic)を試してみたことがありました。
ローカライズ作業自体にもAppleScriptを投入し、テキストエディタ上でオープンしているstringsファイルを読み取ってGoogle Translate APIを呼び出して指定言語に数秒で翻訳。記号類だけから構成されるフィールドは翻訳しないなど、その場でScriptを調整しながら大幅な翻訳作業の省力化を実現(ほかの方法もあるようなので、これがベストとは言いませんが、、、)。このScriptなしに他の言語への翻訳などというめんどくさい作業はやろうとは思いませんでした。
この翻訳用AppleScriptの威力で30言語ぐらい翻訳。主要言語のうち、最後に残ったのが、文字が右から左に流れるアラビア語とヘブライ語でした。
まずは、挙動がどのぐらい違うのか確認。Xcodeでローカライズ言語にアラビア語を追加。stringファイルの内容自体はオリジナルの英語のままの状態で、実行時のアプリケーションの言語環境に「アラビア語」を指定して起動してみました。


起動してびっくり!

▲左:日本語環境、右:アラビア語環境

▲日本語環境を指定して起動したところ@macOS 10.14.6

▲アラビア語環境を指定して起動したところ@macOS 10.14.6

▲アラビア語環境で表示したメニュー@macOS 10.14.6

▲アラビア語環境で表示したAbout Box@macOS 10.14.6。スクロールバーが右側ではなく左側に
ツールバーアイコンはデフォルトの英語環境や日本語環境では、左→右の順で並びますが、アラビア語環境ではウィンドウ上部右→左と並びました。メニューの内容も右側にタイトルが来て、左側にキーボードショートカットが並ぶなど、予想外の配置です。
これは、バグとかいった種類の話ではなく、はじめてアラビア語環境で自作のアプリケーションを起動して、その挙動の違いに驚かされたという話であります。
2つのPDFViewを並べてあり、それぞれ右とか左とか言わなくてもわかるよう、左側には青色のラベル、右側には赤色のラベルを貼っておいたのですが、ツールバーアイコンの左右も入れ替わってしまい、自分の意図したとおりのレイアウトからはかけ離れたものとなってしまいました。
アプリケーション内部で、アプリケーション起動環境から言語がアラビア語やヘブライ語のように右→左の表示を行う言語だった場合には何か処理を行なってツールバーボタンの入れ替えなどを行う必要があることでしょうか。ちょっとノーアイデアで分からないところ。ひたすら驚かされました。他の言語は軒並みGoogle Translation APIをAppleScriptから呼び出して翻訳できたのですが、アラビア語とヘブライ語は難敵でした。攻略できていないので、そこんとこなかなか辛いです。
Double PDF v2.0自体はMac App Storeで絶賛レビュー中です。In Reviewの状態に入ってからはや10日が経過し、さすがに不安になってDeveloper Connectionに電話して聞いてしまいましたが、わかったのは本当にまだレビュー作業中で、レビューにさらなる時間がかかるということだけです。
そろそろ、実際にアップデート作業にかけた時間よりもレビューにかかっている時間のほうが長くなりつつあり、そして確実に数倍の時間がかかりそうでもあり……各国語のPDFでも作って(表示をローカライズした30言語分)、それらのPDFで差分検出チェックでもしてるんじゃないかと、、、
Xcode上でCocoa AppleScriptアプリケーションを作成し、macOS 10.14から搭載された「Continuity Camera」の機能を利用して、iOSデバイス上で撮影した写真をその場でMacに転送・保存してみました。
–> ContinityCameraAS(Xcode 11 Project)

Continuity Cameraをサポートする部分のObjective-CのコードはThomas Zoechling氏のBlog上のものを利用させていただいております。
本当は通常のスクリプトでdisplay dialog的なダイアログを表示して、その上に作成したNSImageViewでContinuity Cameraを呼び出せるとよかったのですが、なかなかそこまで噛み砕いて解釈できなかったので、Xcode上のアプリそのままです。

Continuity CameraのプロジェクトをXcode上でビルド&実行すると、なにもないっぽいWindowが表示されますが、下地にNSImageViewを敷いてあるような気がします。このウィンドウの真ん中の方でControl-クリックあるいはマウスの右ボタンをクリックすると、コンテクストメニューが表示され、そこでカメラから画像取り込みを行うデバイスを選択し、それらの周辺デバイスで撮影した写真をそのまま取り込めます。

Continuity Camera機能は、同じiCloud IDで関連づけたiOSデバイスのカメラを無線LANのネットワークごしにMacから利用するものと理解しています(Bluetoothもオンにする必要があるかもしれない)。

ウィンドウ上の「Save Image」ボタンをクリックすると、その内容をデスクトップにPNG画像で保存します。

AppleScriptアプリケーションでもContinuity Cameraを利用できることがわかったので、このプログラムにAppleScript用語辞書(sdef)をつけてScriptableなアプリケーションに仕立て上げれば、通常のAppleScriptからcapture cameraといったコマンドで取り込めてよいのではないでしょうか。
コンテクストメニューの先にある機能に直接アクセスするためには、もう少し調べる必要がありそうではあります。
| AppleScript名:AppDelegate.applescript |
| — — AppDelegate.applescript — continityCameraAS — — Created by Takaaki Naganoya on 2019/10/21. — Copyright © 2019 Takaaki Naganoya. All rights reserved. — script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value property theImageV : missing value on applicationWillFinishLaunching:aNotification — Insert code here to initialize your application before any files are opened end applicationWillFinishLaunching: on applicationShouldTerminate:sender — Insert code here to do any housekeeping before your application quits return current application’s NSTerminateNow end applicationShouldTerminate: on clicked:aSender set imgRes to theImageV’s image() set dtPath to POSIX path of (path to desktop) log {"dtPath", dtPath} set fRes to retUUIDfilePath(dtPath, "png") of me log {"fRes", fRes} set sRes to saveNSImageAtPathAsPNG(imgRes, fRes) of me end clicked: –NSImageを指定パスにPNG形式で保存 on saveNSImageAtPathAsPNG(anImage, outPath) set imageRep to anImage’s TIFFRepresentation() set aRawimg to current application’s NSBitmapImageRep’s imageRepWithData:imageRep set pathString to current application’s NSString’s stringWithString:outPath set newPath to pathString’s stringByExpandingTildeInPath() set myNewImageData to (aRawimg’s representationUsingType:(current application’s NSPNGFileType) |properties|:(missing value)) set aRes to (myNewImageData’s writeToFile:newPath atomically:true) as boolean return aRes –true/false end saveNSImageAtPathAsPNG on retUUIDfilePath(aPath, aEXT) set aUUIDstr to (current application’s NSUUID’s UUID()’s UUIDString()) as string set aPath to ((current application’s NSString’s stringWithString:aPath)’s stringByAppendingPathComponent:aUUIDstr)’s stringByAppendingPathExtension:aEXT return aPath end retUUIDfilePath end script |
Mac App Storeで販売したはいいものの、途中(macOS 10.13)でド派手なバグをOS(PDFView)に作られ、AppleのDevelopper Supportに正式に連絡してもなしのつぶてでそのままMac App Storeに置かれている「Double PDF」。さすがに現状のままだとまずすぎるので、macOS 10.14/10.15上で動作するように改修作業を行なっている今日このごろ(macOS 10.13.xでは、動くことは動くんですがOS自体の未修正バグが多すぎて動作保証いたしかねます)。

# OSをアップデートすると機能が落ちるとは何事だろう?
PDFViewのド派手なバグ(currentPageを取得できない)を回避するために、自前でAppleScriptでいろいろ回避コードを走らせているので、ページめくりのスピードが落ちています(個人的にこれのために作ったので、テンションが落ちるところです)。
初版では画像処理にGPUImage.frameworkを同梱して利用していましたが、GPUImage1だとOpenGL経由でGPUの機能にアクセスしており、OpenGL自体が最新のmacOS 10.15では非推奨となっており、Mac App Storeでリジェクトされる危険性があります(より新しい代替APIであるMetalを使わないと因縁を付けられる気が、、、)。
そこで、GPUImageに依存しているコードをすべて書き換え、GPUImageを完全に取り外しました。これには、よくよく調べたら、PDFの各ページをグレースケール画像に変換する箇所でしか利用していなかったことが大きいです。これで、名実ともに「すべてAppleScriptで記述したアプリケーション」に。

GPUImageの除去は使い勝手には一切影響しません。若干の前向きな機能追加も行なっておきましょう。
これまでに意見として寄せられていないものの、どうもグレースケール画像で比較を行うことに不満を持っていたユーザーが一定数いるものとにらんでいたので、カラーのまま比較する機能を追加しました。

正直なところ、「カラー比較モード」は個人的には不要と思っている(文字主体の書籍の校正用に作った)のですが、念のためです。
Double PDFはデータ上のささいな違いを「見逃す」ように作ってあります。これは、Adobe AcrobatのPDF比較機能が、見た目には影響を及ぼさないデータ上の些細な違いばかりピックアップして実用性がまったくないことから思いついたものです。見た目に影響のない差異を見逃して、見た目や文字で差が発生している箇所を指摘するツールという味付けになっています。
画像サイズを小さくした上でグレースケール化して比較するのはそのための重要な機能です。ただ、間違って色が変わったことを検出したいような用途もあることでしょう。
あとは、PDFからテキスト抽出をしたときに、PDF書き出し時の環境(OSバージョン)によってはNo Width Spaceが検出されるものがあるため、No Width Spaceの削除機能を追加しました。
細かい箇所でまだ不具合点(右側のビューワーのノンブルが表示されないことがある)がありますが、とりあえず出してみてもよさそうな気配はしています。
地味なところで画像素材をDark Mode対応させました。Credits.rtfのダークモード対応など、地味に工数が増えるのは勘弁してほしいです。あとは、外部アプリケーション操作要求を行うためのInfo.plistのエントリ追加などもあります。これを追加しないと外部アプリケーションへの操作要求のダイアログ自体が出ない=一切操作できないので要注意点です。

ほかにも、現行のXcodeでCocoa AppleScriptアプリケーションのプロジェクトを作ると、デフォルトではソースコード開示状態のビルドセッティングになってしまうので、これも要チェック点でしょう。まさかと思ってこの設定を再確認してみたら、execute onlyになっていませんでした。怖すぎ、、、
PDFViewからcurrentPage()が取れないという、最低最悪レベルのバグが放置されたOSがリリースされ続けて2年。Devlopper Supportに報告しても返事の1つもない今日このごろです。
Apple Developper Connectionのインシデントを消費して質問しても返答がないので、本当に頭にきています。

この、macOS 10.13のBetaでは存在しなかったのにRelease版で作られた画期的なバグを回避するために、いろいろ工夫をしてみました。

テストしてみたところ、Objective-CやSwiftでは発生していないため、Scripting Bridgeのみで発生しているらしきバグのようです。こんな基本的な箇所でバグを作って直さない連中の気が知れません。
AppleのDevelopper Supportが役立たずで仕事をしないのは今日にはじまった話ではないので(Mailing Listが落ちた状態で、報告しても1か月放置した事件など)、仕方なく回避策を検討してみました。
(1)PDFViewまわりのWrapping ClassをObjective-Cで書いて使う
(2)Objective-Cでユーティリティを書いて、パラメータとして与えたPDFViewからObjective-CでcurrentPageを取得する
(3)今後もAppleがバグを作り続けることが予想されるため、サードパーティのPSPDFKitを導入する
といった対策を検討していたのですが、最も手短なものとして、
(4)表示中のページを自前で管理して、PDFViewに指定ページの内容を随時切り出して表示する
という対処を行ってみました。
PDFをオープンしてPDFViewで表示させ、左右の矢印キーでページをめくることができます。なお、macOS 10.14.5beta+Xcode 10.2上でビルドと確認を行いましたが、内容的にはXcodeのバージョンは関係ありません。
–> Download Xcode Project Archive (pdfTestZ)
| AppleScript名:AppDelegate.applescript |
| — — AppDelegate.applescript — pdfTestZ — — Created by Takaaki Naganoya on 2019/04/09. — Copyright © 2019 Piyomaru Software. All rights reserved. — script AppDelegate property parent : class "NSObject" — IBOutlets property theWindow : missing value property aView : missing value property maxPageNum : 0 property curPageIndex : 0 property aPDFDoc : missing value property pLabelField : missing value on applicationWillFinishLaunching:aNotification end applicationWillFinishLaunching: on applicationShouldTerminate:sender return current application’s NSTerminateNow end applicationShouldTerminate: on clicked:aSender set aTag to (tag of aSender) as integer if aTag = 100 then set aPath to choose file of type {"com.adobe.pdf"} set pPath to POSIX path of aPath set aURL to current application’s |NSURL|’s fileURLWithPath:pPath set my aPDFDoc to current application’s PDFDocument’s alloc()’s initWithURL:aURL set myFileName to (current application’s NSString’s stringWithString:pPath)’s lastPathComponent() theWindow’s setTitle:myFileName set maxPageNum to ((aPDFDoc’s pageCount()) as integer) – 1 set curPageIndex to 0 log {"maxPageNum", maxPageNum} aView’s setAutoScales:true aView’s setDisplaysPageBreaks:true my dispCurPageDoc() set tmpStr to ((maxPageNum + 1) as string) & "/" & ((curPageIndex + 1) as string) pLabelField’s setStringValue:tmpStr else if aTag = 200 then set tmpPage to my (aPDFDoc’s pageAtIndex:(curPageIndex)) set curPageLabel to tmpPage’s label() tell current application display dialog (curPageLabel as string) end tell else if aTag = 1000 then my decPage() my dispCurPageDoc() else if aTag = 1010 then my incPage() my dispCurPageDoc() end if end clicked: on dispCurPageDoc() set tmpPage to my (aPDFDoc’s pageAtIndex:(curPageIndex)) set tmpDoc to (current application’s PDFDocument’s alloc()’s initWithData:(tmpPage’s dataRepresentation())) aView’s setDocument:tmpDoc set tmpStr to ((maxPageNum + 1) as string) & "/" & ((curPageIndex + 1) as string) pLabelField’s setStringValue:tmpStr end dispCurPageDoc on incPage() if curPageIndex = maxPageNum then –何もしない else if curPageIndex < maxPageNum then set curPageIndex to curPageIndex + 1 end if end incPage on decPage() if curPageIndex = 0 then –何もしない else if curPageIndex > 0 then set curPageIndex to curPageIndex – 1 end if end decPage end script |
macOS 10.14.4Betaを使っていて遭遇したトラブルというかバグのような挙動なのですが、「システム環境設定」の「セキュリティーとプライバシー」>「プライバシー」>「アクセシビリティ」の項目。これは、Scripterにはおなじみの「GUI Scriptingの許可」を行う設定項目です。
# 本件については、最新のmacOS 10.14.6+同OSで利用可能な最新のXcode 11.3.1の組み合わせで、後述のように回避できるようになったことを確認しています

ここに、同一名称で異なるバージョン(バージョン番号の大きいもの=新バージョン)のアプリケーションが登録されない、という不具合です。
たとえば、v1.0のappletを登録しておいて、v2.0のappletを登録しようとしても、すでにv1.0が登録されているので追加登録できないし、いったん登録したものを削除できなかったという状況でした。
この問題のどこが困るかといえば、Xcode上でAppleScriptによるアプリケーションを開発していて、その中でGUI Scriptingを利用しているような場合です。Xcode上でAppleScriptアプリケーションをビルドすると、まず最初に動作確認用にdebugビルドを行うことになりますが、これで1つのバイナリができます。テスト実行時にアクセシビリティ認証を取得して、実行。
次いで、実際に単体で実行する(Xcodeのログ表示にlogコマンドによる表示が出ない)Relaseビルドのバイナリをビルドして実行。debugビルドとは別のバイナリができて、実行してみるとOS側からアクセシビリティ認証が許可されず、実行できないという状況でした。いったんこうなると、debugビルドのバイナリのアクセシビリティ認証を解除してもReleaseビルドを登録して認証することもできず、お手上げの状態でした。
以前、macOS 10.10あたりでこの項目で不可解な挙動が見られたことがありましたが、それが復活したような不安定さを感じます。ただ、テスト機はHDDで運用しているため、設定項目の変更がなかなか反映されないといった独特の挙動が発生することもあり、継続調査は必要だと感じていました。まだ、他のユーザーの環境でも同様の問題が発生するか「裏取り」ができていない状況でもありました。
この問題がmacOS 10.14 Beta Relaseの最中に突然発症したので、当時仕事で作成途中のプログラムがGUI Scriptingを一部で利用しており、現状のままでは10.14対応ができないと顧客に報告する必要が出てきました。
本BlogにアクセスしているクライアントのOSバージョンについてWebサーバーのアクセスlogから調査してみたところ、(自分の当時のメイン環境が10.12ということもありますが)10.12が最多。10.9から10.12にほぼ同じぐらいのクライアントが存在しているもようです。鬼っ子10.13や鬼っ子改の10.14については、アクセスlogに形跡が残っていない(Unknown Version?)としか言いようがありません。10.14は多い可能性もあります。
AppleにはmacOS 10.13でバギーなまま使い物にならない状態のOSをリリースしたという「前科」があり、10.14もその延長線上にあるということから、つねに疑問を持って接しています。
# かくして、macOS 10.14が正式リリースされ、開発には利用していましたが、メイン環境は長らく移行しませんでした。macOS 10.14.6が出た段階で再評価を行い、この問題が解決されていたので移行。当時macOS 10.15が出ていたものの、メールが消えるといった致命的な障害報告があったために、移行できない危険なOSだと判断しました。
macOS 10.14.x+Xcodeのプロジェクト内でGUI Scriptingを使ったプログラムの認証や開発が行えたなかった件については、Xcode側の対応が改善されたためか、OS側の対処が改善されたためか、現在では解決されています。
OSバージョン:macOS 10.14.6 Xcodeバージョン:11.3.1
この環境で、「File」>「New」>「Project」>「AppleScript App」のプロジェクトを作成し、中で他のアプリケーションをGUI Scripting経由でコントロールする処理を含むプログラムを書いて、アプリケーションとしてビルドし、OS側のセキュリティ機能と折り合いをつけて動作するようにできています。
Xcode Project側で設定すべき点は2つ。
(2)Xcode Projectの「TARGETS」でビルドターゲットを選択し、「Signning & Capabilities」>「All」で「Runtime Exceptions」の「Apple Events」にチェックを入れる
これらの設定を行って、Code Signしてあればビルドして実行すると、初回時にOSのセキュリティダイアログが表示され、そののちにGUI Scriptingの認証ダイアログが表示され、認証後、ビルドしたアプリケーションをいったん終了。
再度ビルド&実行するとOS側の「セキュリティ」認証とGUI Scriptingの(アクセシビリティの)認証が通ってアプリケーションの実行ができました。
Xcode上で記述し、他のアプリケーションを操作するAppleScript Cocoaアプリケーションで、セキュリティダイアログに表示する内容(Info.plist内のエントリ)をローカライズする方法について紹介します。
まず、macOS 10.14, Mojaveではセキュリティ機能が強化され、アプリケーションから他のアプリケーションをAppleEventで操作しようとすると、ユーザーに認証を求めます。この動作は、同一アプリケーションでは初回時のみ行われます。アプリケーションのバージョン・アップ時に再度問い合わされることはありません。

このセキュリティ認証をユーザーに求める動作は、Code Signしてあるアプリケーションであれば初回起動時の初回AppleEvents発行時のみ行われます。最新版のXcode上でアプリケーションを開発する場合にはCode Signできること(=Apple Developper Account年間契約を行っていること)が前提条件となるため、Code Signしない場合については言及しません。
# セキュリティダイアログで「OK」しなかった場合でも、あとからシステム環境設定>セキュリティとプライバシー>プライバシー>オートメーション の項目でチェックを入れれば認証したのと同じことになります。この項目、1年使い続けると項目数がヤバいぐらい増加しそうですが、そのあたりの膨大な数になったときの使い勝手が一切考えられていないバカっぷりが素敵です(項目のしぼりこみぐらいできないとまずい)


セキュリティダイアログを表示してユーザーに認証を得るためには、
(1)Info.plistに「Privacy – AppleEvents Sending Usage Description」(NSAppleEventsUsageDescription)のエントリを作成する必要がある
(2)AppleScriptアプリケーション内にて、セキュリティダイアログの認証が必要な動作(新規ドキュメントを作成するなど)を、アプリケーション起動中あるいは起動直後のタイミングで行い、セキュリティダイアログでユーザーの認証を得ておく(ことが望ましい)
という状態です。
昨日の段階では、このセキュリティダイアログに表示させる内容がローカライズできないと思っていました。
その後、冗談半分で「Info.plistの内容のローカライズ方法」などと検索エンジンで調べてみたら……さがしてみるもので、すぐに方法が見つかりました。
# 冗談半分で探してみる、というのはけっこう強力なソリューションです
Info.plistの内容のローカライズを行う場合には、InfoPlist.stringsというファイルを作成して、そこに(おなじみの)ローカライズ文字ファイルを、
"key" = "value";
のように記述しておき、InfoPlist.stringsファイルをXcode上でローカライズするのだとか。
Info.plistに「Privacy – AppleEvents Sending Usage Description」のエントリを作ってある状態から話をすすめます。

このままでは、このエントリのキー名称がよくわからないので、Info.plistをテキスト形式でオープンし直します。

すると、このエントリのキー名称が「NSAppleEventsUsageDescription」だということがわかります。

そこで、Xcodeのプロジェクトに「空のファイル」としてInfoPlist.stringsファイルを追加し、ローカライズします。ここでは、デフォルトで用意されるEnglishのほかにJapaneseを追加しました。


この状態でXcode上でビルド+実行(Command-R)を行うと、

のように、日本語環境にあわせてローカライズしておいたメッセージが表示されることが確認できました。
Apple Developper Accountの契約を行い、Xcode上で(他のアプリケーションをコントロールする)AppleScriptアプリケーションを作れるようにしたときに、macOS 10.14+Xcode最新版の10.1だと実際どうなのかをまとめてみました。
Sandbox対応させると制約事項が増えるため、Sandbox非対応のアプリケーションをビルドしてどこまで行けるかは興味深いところです。
ですが、とりあえず(安全のために)、Code Sign+Sandboxの設定を行ってXcode上でAppleScriptアプリケーションを作成。Keynoteに新規ドキュメントを作成させる程度のお気軽なコードを書き、ビルドして実行。すると、
2019-01-15 99:99:99.999999+0900 1014test[13673:1813575] *** -[AppDelegate applicationWillFinishLaunching:]: Not authorized to send Apple events to Keynote. (error -1743)
などとエラーが出るだけで、AppleScriptからの操作対象アプリケーション(ここではNumbers)は、動かせませんでした。Automationのセキュリティダイアログ自体が表示されない状態です。

▲セキュリティダイアログはこんなやつ
こういう場合には海外のサイトを調べてみるのが一番手っ取り早いところです。いろいろ調べてみたところ、Info.plistに対して、
Privacy - AppleEvents Sending Usage Description
というエントリを追加する必要があるとのこと。

このInfo.plist上のエントリ、例のMojaveから出るようになったSecurityダイアログにアプリケーション側から出力するカスタムメッセージを定義するエントリのようです。エントリさえ存在すれば、とくに何か気の利いたメッセージを入れる必要はないようです。

英語で書いても、日本語で書いてもそのままダイアログに出力されます。Info.plistの内容自体はローカライズできないので、本エントリの内容は広域に配布するものであれば英語で、特定の日本国内の顧客に向けて納品するものであれば日本語で書けばいいんじゃないでしょうか(一般的なメッセージ同様にローカライズできることが望ましい)。このあたり、ローカライズできないのは仕様的におかしいので、そのうち仕様が変わりそうです、、、、
Securityダイアログを表示させるためには、AppleScriptアプリケーション側から対象のアプリケーションに対して命令を発行する必要があるのですが、これが、activateさせるとかバージョン番号を調べるぐらいのコマンドではそのまま実行されてしまって、セキュリティダイアログは表示されません。
せめて新規ドキュメントを作成(make new document)して破棄(close without saving)するぐらいのことを行わないと、ダメなようです。このあたり、実際に試してみるしかないようです。
■セキュリティダイアログを表示させる程度の意味のない(ウォームアップのための)Scriptサンプル
tell application "Microsoft Excel"
set aDoc to (make new document)
close active workbook without saving
end tell
★Click Here to Open This Script
tell application "Keynote"
set aDoc to (make new document)
close aDoc without saving
end tell
★Click Here to Open This Script
tell application "Finder"
make new Finder window
close Finder window 1
end tell
★Click Here to Open This Script
–Touch Barの有無の検出
on detectTouchBar()
tell application "System Events"
set frontApp to first application process whose frontmost is true
try
set touchBar to first UI element of frontApp whose role is "AXFunctionRowTopLevelElement"
on error errMsg number errNum
return false
end try
set touchBarItems to value of attribute "AXChildren" of touchBar
return (touchBarItems is not equal to {})
end tell
end detectTouchBar
★Click Here to Open This Script
対象アプリケーションに対してコマンドを実行し、Securityダイアログが表示され、ユーザーによってきちんと認証されれば、tccKitを用いて認証状況を確認して、すべてのアプリケーションの認証完了状態をAppleScriptから知ることができます。
AppleScriptアプリケーションの冒頭(applicationWillFinishLaunchingイベントハンドラなど)でtccKitによって認証状態をチェックし、未認証の状態であれば操作対象のアプリケーションに対して試験的にコマンドを実行するようにすればよいでしょう。
AppleScriptアプリケーションがCode Signされていれば、セキュリティ認証ダイアログでユーザー認証する必要があるのは初回のみです。現在、Xcode上でGUIアプリケーションをビルドするためにはApple Developper Accountが必須となっているため、この点は必然的にクリアされることになります。
なお、この認証フローにおいてアプリケーション自体がSandbox化されている必要はありません(確認ずみ)。
Xcode上でGUIアプリケーションをAppleScriptObjCで記述できますが、Xcodeの「pragma mark」の機能をAppleScriptで利用する方法についてまとめておきます。
pragma markはXcode上のテキストエディタで現在編集中のソーステキスト内の移動を行いやすくするもので、基本的には「ただのコメント文」です。
Xcodeに編集中のソースの各プロパティやハンドラの一覧を表示するポップアップ・メニューが存在しており、これがソースの長さによっては膨大な量になるため、ポップアップ・メニューに「セパレータ」や「目印(数種類のアイコン指定可能)」をpragma markによって表示させます。長くなるメニューを見やすくする、という役割があります。
pragma markはプログラムの実行には何も影響しません。
Xcode上で編集中のソースコードに「pragma mark」形式のコメントがあると、Xcodeがそれを読み取って、ポップアップ・メニュー上に指定内容を表示します。



ただし、外部エディタ(ASObjC Explorer 4やScript Debugger)を併用しないと、途中からXcodeがまともにpragma markを表示しなくなることは日常茶飯事であることを明記しておきます。
–> Download pragma_mark_test Xcode Project
→ PRAGMA MARKのコメント行の先頭にTABを入れないとまともに表示される模様。
