カテゴリー: GUI Scripting

YouTube Picture In Pictureウィンドウの制御

Posted on by

AppleScriptでYouTubeのPicture In Pictureウィンドウを操作したいという話を見かけて、そんなことは一度も考えたことがなかったので調べてみました。macOS 10.13あたりで搭載されたPicture In Pictureのムービー再生機能をAppleScriptから操作します。

結論からいえば、Picture In PictureはmacOS側が用意している専用のプログラム「PIPAgent」(/System/Library/CoreServices/PIPAgent.app)がPicture In Pictureの処理を行っています。Webブラウザの内蔵機能ではなく、各ブラウザで共通してこれを呼び出すようです。

Picture In Picture機能は、たとえばSafariでYouTubeムービーを再生した状態で、マウスの右クリックをムービー上で2回行うことで、その機能が呼び出せます。


さんかくYouTubeムービーの上でマウスの右クリックを行なった状態(1回目)


さんかくYouTubeムービーの上でマウスの右クリックを行なった状態(2回目)。ここで「ピクチャインピクチャにする」を実行するとPIP表示になる


さんかくピクチャインピクチャ表示状態

プロセス一覧で「それっぽい名前」のプロセスに当たりをつけてGUI Scriptingからウィンドウを操作すれば、ウィンドウの位置と大きさをAppleScriptから変更できました。

ただし、このPIPAgentのウィンドウは画面のすみにしか置けませんし、サイズについてもいくつかのサイズを指定できても細かい大きさは指定できません。ここで紹介するAppleScriptも、自分の環境(1920×1200)の画面上で試したものであり、複数の画面をつないでいたり、2x Retinaや3x Retinaなどの解像度の画面をつないである場合には、座標値を書き換えたほうがよいでしょう(あくまで、実験レベルのコードです)。

PIPAgentのウィンドウの縦横比はオリジナルのムービーのものが維持されるため、AppleScriptでサイズを指定しても縦横比が崩れることはありません。

なお、「ピクチャインピクチャ」表示状態にするまで、PIPAgent.appは起動しません。同プロセスの起動を確認したうえでウィンドウの制御を行う必要があります。プロセス確認については、山のように方法がありますのでお好きなように。

AppleScript名:YouTube Picture In Pictureウィンドウの制御.scpt

– Created by: Takaaki Naganoya
– Created on: 2024年07月22日

– Copyright © 2024 Piyomaru Software, All Rights Reserved

tell application "Safari" to activate

tell application "System Events"
try
tell process "PIPAgent"
set wCount to unix id
end tell
on error
return –YouTubeのPicture In Picture表示プロセスが起動していない
end try

tell process "PIPAgent"
set wCount to count every window
if wCount = 0 then return
tell window 1
set size to {500, 282}
repeat 10 times
set position to {1493, 800}
delay 3
set position to {0, 800}
delay 3
set position to {0, 0}
delay 3
set position to {1493, 0}
delay 3
end repeat
end tell
end tell
end tell

GUI Scriptingに邪魔な各種パレットをクローズ

Posted on by

「操作自動化」の観点からするとメイン機能、「業務自動化」の観点からすると必要悪、操作対象のアプリのGUI部品の状況を想定どおりに設定しておけないと、Script動作の再現がなかなか大変なので、極力使わないでおきたいGUI Scripting。

PagesのAppleScript対応機能は必要な機能が呼び出せないのと、現在表示中のページ+5ページまでしか各種オブジェクトの属性値にアクセスできないので、書類の表示倍率を強制的に変更する必要があります。PagesのAppleScript用語辞書にそのような機能は実装されていないので、仕方なくGUI Scriptingで組むことに。

そんな中、各種パレットが表示されていると、パレットがwindow 1として認識されるため、作業前にパレットを消去しておく必要を感じました。

そこで、指定アプリのパレット表示状態を検知してクローズするAppleScriptを書いてみました。実行すると、

表示されている各パレットを、

順次クローズしていきます。

当初、もっと簡単にできるものとばかり思って、window 1(実際にはパレット)にcloseコマンドを実行したものの、それでは実行できず......地道にクローズボタンを探してclickするという処理内容になりました。

もっとお手軽に書いてしまってもよかったものの、日本語環境でだけ動くScriptというのも、それはそれでいまひとつなので(別に自分は日本語環境でだけ動けばよいのですが)、言語環境に依存しないように書いておきました。

よく使いそうな部品なので、ライブラリ化して呼び出すとよいでしょう。

AppleScript名:指定アプリのパレットウィンドウを閉じる.scpt

– Created by: Takaaki Naganoya
– Created on: 2024年06月26日

– Copyright © 2024 Piyomaru Software, All Rights Reserved

set aRes to closeAllPallettes("Pages") of me

on closeAllPallettes(appName)
activate application appName
delay 0.1 –this number depends of how fast the CPU (slower CPU require larger number. 0,1 for M1)

tell application "System Events"
tell process appName
if (count (every window)) = 0 then return
repeat
tell window 1
set aSubrole to subrole
if (aSubrole = "AXFloatingWindow") or (aSubrole = "AXSystemFloatingWindow") then
try
set bList to every button whose subrole = "AXCloseButton"
set aButton to first item of bList
tell aButton to click
end try
else
exit repeat
end if
end tell
end repeat
end tell
end tell

return true
end closeAllPallettes

指定のPages書類の言語設定を英語に

電子書籍「AppleScript The Latest Reference for v2.8」の翻訳作業を行うにあたって、同書を作成しているPagesを操作して、書類の言語設定を変更するAppleScriptを記述しました。なぜこれが必要になってくるかといえば、英単語のハイフネーション方式をネイティブの方式に合わせる必要があったためです。Pages自体のメニューなどのUIの表示は日本語のままで使い続けられる、というのがメリットでしょう。

本記事にScriptは掲載しておきますが、動作のためには同書に同梱しているライブラリ「dynamic Menu Clicker」が必要であり、ここではScriptの内容そのものは重要視していません。

一度作成して、テストデータで問題なく動作していたものの、数時間後に同じ書類に対して同じScriptを実行してみたら、エラー続出で動かなくなっていた、という怪奇現象が発生。この問題の解決に時間がかかりました。

このトラブルが発生した箇所はGUI Scriptingでウィンドウの上に表示されたシート、の上のポップアップボタンをクリックするという記述箇所です。

結論からいえば、Pages上で作業をしていたときにカラーピッカーのパレットを表示させたままにしたため、これがWindow 1とカウントされ、ドキュメントのウィンドウはWindow 2と認識されたため、問題が発生しました。

しかも、Pagesを後ろに持っていくとカラーピッカーのパレットは表示されなくなります。これで、何が原因なのかがわかりにくく、まさに怪奇現象のように振る舞いました。こんなのは久しぶりです。

結局、ウィンドウの名称をすべて取得して、カラーピッカーが表示されていたらドキュメント表示ウィンドウのインデックスを2にする、というやっつけで対処することになりました。

カラーピッカーはPagesが最前面のときには表示され、背面に回したときには表示されなくなります。この種類のフローティングパレットはPagesに4つ搭載されており、より広域に配布するScriptであればこれらをすべて表示させた状態を想定する必要があることでしょう。

ただし、本Scriptは自分1人が使うことを想定しているため、「パレットの非表示状態を確認したうえでScriptを実行」という申し送りをScript内に記述することで回避することにしました。ただ、なかなか原因がわからなかったので、英訳プロジェクトの第1日目にしてけっこう消耗しました。

AppleScript名:指定のPages書類の言語設定を英語に.scpt

– Created by: Takaaki Naganoya
– Created on: 2023年11月06日

– Copyright © 2023 Piyomaru Software, All Rights Reserved

use AppleScript version "2.8" — Monterey (12) or later
use framework "Foundation"
use scripting additions

use dClick : script "dynamicClicker"
use dLib : script "display drop dialog"

–注意:使用前にPagesのカラーパレットが表示されていないことを確認すること!!!!

set aMainMes to "Drop Pages Document"
set aSubMes to "Drag and Drop Pages files to Table (.pages)"
set aUTI to "com.apple.iwork.pages.sffpages"
set aRes to (display drop dialog aUTI main message aMainMes sub message aSubMes with initial folder "" OK button title "Execute")

activate application "Pages"

repeat with i in aRes
set j to i as alias

tell application "Pages"
open j
end tell

chengeLangTo() of me

tell application "Pages"
close front document with saving
end tell

end repeat

on chengeLangTo()
set appName to "Pages" –Application Name
set aList to {"ファイル", "詳細", "言語と地域..."} –Localized Menu Title Structure
set aRes to clickSpecifiedMenuElement(appName, aList) of dClick
delay 1
changeDocLanguageSetting("英語", "アメリカ合衆国") of me –"English" , "United States"
–changeDocLanguageSetting("日本語", "日本") of me
end chengeLangTo

on changeDocLanguageSetting(targLang, targCountry)
activate application "Pages"
tell application "System Events"
tell process "Pages"
set winNum to 1
set nList to name of every window
if "テキストのカラー" is in nList then set winNum to 2 –Pagesのカラーパレットが表示状態の場合

tell window winNum
tell sheet 1

click pop up button 1
tell pop up button 1
tell menu 1
click menu item targLang
end tell
end tell

click pop up button 2
tell pop up button 2
tell menu 1
click menu item targCountry
end tell
end tell

click button "OK"
end tell
end tell
end tell
end tell
end changeDocLanguageSetting

新発売:AppleScript実践的テクニック集(1)GUI Scripting

Piyomaru Software Booksの60冊目。GUIアプリケーションを強引に動かす「GUI Scripting」について動作原理から気をつけるべき点、実戦レベルのScriptで注意している点やサンプルなどをまとめた「AppleScript実践的テクニック集(1)GUI Scripting」を発売しました。本文283ページとなっています。

販売ページ

「AppleScript実践的テクニック集」とは、従来の「基礎テクニック集」の枠組みでは収まりきらないテーマを扱う新シリーズです。基礎テクニック集は32ページ前後のコンパクトな構成にするために、いろいろ省略した部分もありましたが、この枠組みに入り切らないテーマを別枠でご紹介することにしたものです。

ページ数の上限をとくに設けず、徹底的に情報を入れる設計です。それでいて、「基礎テクニック集」ゆずりの図や表を多用した構成はそのままです。

使い所さえ間違えなければ強力な武器になるGUI Scriptingについて、動作原理からノウハウ、注意すべきポイントや実戦レベルのScriptで気をつけていること、さまざまな「ありがちな処理」のサンプルなどを紹介する1冊です。

しかくGUI Scripting (UI Element Scripting)とは?

AS用語辞書にすべての機能は掲載されていません1
AS用語辞書にすべての機能は掲載されていません2
AppleScript非対応機能を呼ぶGUI Scripting
GUI Scriptingとは?
GUI ScriptingはSystem Eventsごしに操作
操作対象のアプリケーション1
操作対象のアプリケーション2

しかくAppleScript非対応機能を操作する

KeynoteでASから利用できない機能を呼ぶ
GUIアプリケーションの挙動確認が欠かせません
メニュー項目操作の実例
実行結果のリストアップ1
実行結果のリストアップ2

しかくGUI Scriptingの実行を許可する

デフォルト状態だとGUI Scriptingは無効
AppleScript実行アプリケーションを登録
設定するとGUI Scriptingが有効に
スクリプトメニューも登録必須
資料:macOS上のAppleScript実行環境1
資料:macOS上のAppleScript実行環境2

しかくGUI部品への参照を求める方法

メニュー項目:メニューを頂点としたツリー
ウィンドウ上のオブジェクト:ウィンドウが基準
簡単なGUI部品の求め方

しかくGUI Scriptingでやりたい特徴的な操作

GUI Scriptingの有効チェック
属性値の取得(properties)
属性値の取得(AX-attributes)
GUI部品のクリック
指定座標のクリック
参考資料:GUI Scriptingの座標系
ポップアップメニューの項目選択
キー入力操作
項目選択
コンテクストメニューの表示
スクロール操作
ファイル保存/書き出し
自分で発生させたモーダルなダイアログのクリック
ドラッグ&ドロップ操作
日本語IMの文字入力モード取得/設定

しかくマウスカーソルの強制移動&強制クリック

マウスカーソルを移動させる必要性
マウスカーソルの強制移動とクリック

しかく実戦レベルのAppleScriptにおけるGUI Scripting解説

指定フォルダ以下のPagesなどをPDF出力して連結
実際のメインScript部分
generatePDFLibの当該箇所
本プログラムが環境の影響を受けた点
参考資料:デスクトップの表示/非表示切り替え

しかくAccessibility Inspectorの使い方

Xcodeに入っているGUI部品探索ツール
Accessibility Inspectorの画面構成1
Accessibility Inspectorの画面構成2
プロセス一覧から対象を選択してInspection
指定プロセスのGUI部品の追跡中の画面表示
GUI部品の追跡ポーズ中の画面表示

しかくOSやアプリケーションのアップデートに備える

OSアップデートごとにGUI構成は変わる
GUI Scripting処理部分だけをサブルーチンに分離
GUI Scripting処理部分をライブラリに分離
OSアップデートの影響を受けにくい構造に

しかくGUI Scriptingの信頼性は?

GUI Scriptingの信頼性は?
一般的な信頼性の計測方法
経験に基づく傾向と対策
GUI Scriptingで直面した問題とその解決策
指定した処理の終了前に次の処理が行われる
同じ名前のプロセスが存在していると名称衝突1
同じ名前のプロセスが存在していると名称衝突2

しかくGUI Scriptingサンプル集

指定のアプリケーションの全メニュータイトルを取得
選択中のテキストを取得
選択中のテキストを書き換え
Safariの最前面のウィンドウへの参照を得る
GUI部品への参照から所属するアプリケーション名を取得
Dockに登録されているアイコンの情報を取得
Keynoteで選択中のテキストを縦書きに
CotEditorで最前面のウィンドウを縦書きに

しかくGUI Scripting資料集

click【クリック】コマンド
key code【キーコード】コマンド
keystroke【キーストローク】コマンド
perform【パフォーム】コマンド
select【セレクト】コマンド
application【アプリケーション】クラス
action【アクション】クラス
application process【アプリケーションプロセス】クラス
attribute【アトリビュート】クラス
browser【ブラウザ】クラス
busy indicator【ビジーインディケータ】クラス
button【ボタン】クラス
checkbox【チェックボックス】クラス
color well【カラーウェル】クラス
column【カラム】クラス
combo box【コンボボックス】クラス
desk accessory process【デスクアクセサリプロセス】クラス
drawer【ドロワー】クラス
group【グループ】クラス
grow area【グローエリア】クラス
image【イメージ】クラス
incrementor【インクリメンタ】クラス
list【リスト】クラス
menu【メニュー】クラス
menu bar【メニューバー】クラス
menu bar item【メニューバーアイテム】クラス
menu button 【メニューボタン】クラス
menu item 【メニューアイテム】クラス
outline 【アウトライン】クラス
pop over 【ポップオーバー】クラス
pop up button 【ポップアップボタン】クラス
process 【プロセス】クラス
progress indicator 【プログレスインジケータ】クラス
radio button 【ラジオボタン】クラス
radio group【ラジオグループ】クラス
relevance indicator【レレベンスインジケータ】クラス
row【ロー】クラス
scroll area【スクロールエリア】クラス
scroll bar 【スクロールバー】クラス
sheet 【シート】クラス
slider【スライダ】クラス
splitter【スプリッタ】クラス
splitter group【スプリッタグループ】クラス
static text【スタティックテキスト】クラス
tab group【タブグループ】クラス
table【テーブル】クラス
text area【テキストエリア】クラス
text field【テキストフィールド】クラス
toolbar【ツールバー】クラス
UI element【ユーアイエレメント】クラス
value indicator【バリューインディケータ】クラス
window【ウインドウ】クラス

しかくAppleScript資料集

macOSバージョンとAppleScriptの動向
macOSとAppleScriptの要素技術史
各macOSごとのAppleScript解説
macOS内AppleScript補助ツールの歴史
System EventsのAppleScript用語辞書変更点
AppleScript 各ランタイム環境情報
AppleScript予約語一覧
AppleScriptのエラーコード
あとがき
奥付

Dockに登録されている項目の情報を取得する

Dockの情報が取得できることは、はるかかなた昔から知っていましたが、実際に使える用途がなかったのでScriptを組んで放置状態になっていました(本Scriptのオリジナルは2008年に書いてありました)。存在を思い出したので、動作を再確認してみました。

–> {{minimum value:missing value, orientation:missing value, position:{1878, 40}, class:UI element, accessibility description:missing value, role description:”アプリケーションDock項目”, focused:missing value, title:”Finder”, size:{30, 22}, help:missing value, entire contents:{}, enabled:missing value, maximum value:missing value, role:”AXDockItem”, value:missing value, subrole:”AXApplicationDockItem”, selected:false, name:”Finder”, description:”アプリケーションDock項目”}, {minimum value:missing value, orientation:missing value, position:{1878, 62}, class:UI element, accessibility description:missing value, role description:”アプリケーションDock項目”, focused:missing value, title:”Safari”, size:{30, 22}, help:missing value, entire contents:{}, enabled:missing value, maximum value:missing value, role:”AXDockItem”, value:missing value, subrole:”AXApplicationDockItem”, selected:false, name:”Safari”, description:”アプリケーションDock項目”},…}

実行のためには、AppleScript実行プログラム(スクリプトエディタなど)に対してシステム環境設定>セキュリティとプライバシー>プライバシー>アクセシビリティでGUI Scriptingの実行を許可しておく必要があります。

AppleScript名:Dockに登録されている項目の情報を取得する.scpt
tell application "System Events"
tell application process "Dock"
tell list 1
–アプリケーションのDock項目
set apList to every UI element whose subrole is "AXApplicationDockItem"

set nURLlist to {}
repeat with i in apList
set anURL to properties of i
set the end of nURLlist to anURL
end repeat
end tell
end tell
end tell

return nURLlist

PFiddlesoft UI Browserが製品終了に

AppleScriptの環境を構成する大きな要素部品である「PFiddlesoft UI Browser」が、開発者のBill Cheesemanのリタイアに伴い、製品終了になることが2022年4月17日に表明されました。

GUI Scriptingの登場と同時に、この技術を完全にカバーし、生産性を格段に向上させるツールとしてUI Browserが登場しました。これを持っているかどうかでGUI ScriptingによるScript記述は天と地ほども差が出ます。

AppleScriptを書くのであれば、まず必携といっても過言ではないというツールであったわけです。

Script DebuggerとUI Browserを柱として、AppleScript系の開発環境が構築されてきたわけで、たいへんに重要なパーツであったことは誰にも否定できないことでしょう。

Bill Cheesemanの動向は定期的にウォッチしていたのですが、「引退した法律家からプログラマに転身」「いいかげん、けっこう高齢」といった認識は持っていたものの、「PreFab Software」から改名した「PFiddlesoft」というユニット名を使っていたあたりで「誰かに引き継ぎを考えているんだろう」と(勝手に)思っていました。

ただ、C++でゴリゴリにmacOSの深いところを叩きまくるようなアクセシビリティ系のプログラムを組むことはストレスが大きかったことでしょう。Cocoaの上からAppleScriptでOSのサービスを叩いていても「またバグが」「勝手な仕様変更が」などと苛立たしいことこの上ないわけで、もっと深い部分で叩いていたら、よりストレスは大きなものとなっていたことでしょう。

正直、このあたりの技術について同等の理解と技術力を有している人間といわれると、世界でも6人ぐらいしかいないことでしょう。Shane、Mark、Sal、Has、あとはAppleの現役エンジニア(世間せまっ!)。

その割にユーザーサポートの負担なども重圧となっていたことでしょうし、Billが続けられないという英断をしたことについては尊重すべきだと考えます。

とはいえ、その大きすぎる「穴」をどうやって埋めていくかというテーマを抱えてしまっているわけです。LateNight Softwareが継承する、とかいった話は(技術的な)可能性としてありそうではあるものの、(マーケティング的かつ費用的な意味では)ちょっとわかりません。

UI Browserの代替技術、代替製品という意味では「Piyomaru Dynamic Menu Clicker」があります。メニューについていえば、「このアプリケーションのこのメニューのこの項目」と指定すると、それを強制的にクリックするような仕組みをすでに作れています。

ただ、Webブラウザ上のコンテンツをGUI Scriptingを用いて操作するような用途では、ちょっと代替手段を持っていません。

UI Browserの基礎的な部品を各種Frameworkとして提供しているものの、ちょっとAppleScriptから気軽に叩いて機能を呼び出すというのは難しそうだと思われました。

UI Browser自体、販売終了するということなので、必要な人はすぐに購入すべきでしょう。そして、Webサイトも閉じる予定とのことなので、必要な資料などをPFiddlesoftのWebサイトからダウンロードしてバックアップしておくべきでしょう。

macOS 11.5+Keynote 11.1でGUI Scriptingに障害?

GUI Scriptingを(メニューやキーボードショートカットについては)ラッピングして、ファイル名に「アプリケーション名@メニュー項目名>メニュー項目」などと書くだけで該当するメニュー項目の実行を行える「Piyo Menu Clicker v1.0 for Stream Deck」のリリース候補版を試していたら、macOS 11.5+Keynote 11.1でエラーが出まくりました。

# その後の調査により、よりによってStream Deckの制御ソフトウェア「Stream Deck.app」が起動しているとこの問題が発生することがわかりました。Stream Deck用の制御Scriptを使おうとしたらStream Deckのソフトウェア自体が実行を邪魔していたとは….

–> Browse Demo Movie

メニューバーのメニュー項目を指し示そうとして、

AppleScript名:Keynoteで新規書類を実行.scpt
activate application "Keynote"
tell application "System Events"
tell process "Keynote"
click menu item 1 of menu 1 of menu bar item 3 of menu bar 1
end tell
end tell

のようなお気軽な内容を試してみたところ、エラーに。メニューが存在していて、GUI側からマウスで普通に操作できるのですが......

他のPagesであるとかSafariなどで同様の記述を行なってもエラーになりません。Keynoteだけ問題が発生しているようです。

AppleScript名:Pagesで新規書類を作成.scpt
activate application "Pages"
tell application "System Events"
tell process "Pages"
click menu item 1 of menu 1 of menu bar item 3 of menu bar 1
end tell
end tell
AppleScript名:Safariで新規ウィンドウを表示.scpt
activate application "Safari"
tell application "System Events"
tell process "Safari"
click menu item 1 of menu 1 of menu bar item 3 of menu bar 1
end tell
end tell

Keynoteのmenu bar 1に対してmenu bar itemのtitle(メニュー項目のテキスト)を求めると、「Apple」しか返ってきません。

tell application "System Events"
tell process "Keynote"
tell menu bar 1
set mList to title of every menu bar item
–> {"Apple"}
end tell
end tell
end tell

★Click Here to Open This Script

iOSアプリをM1 Mac上で起動した場合にこのような挙動になるのか調べてみても、ちゃんとiOSアプリもメニュー項目を取得できます。iOSアプリについては(苦労はするものの)AppleScriptから強引に操作することは可能なので、Keynoteよりは出来がいいといえます。

追記:
手元にあるバージョンの異なるmacOS環境でこの処理を試してみました。

macOS 12.0beta+Keynote v11.1→異常
macOS 11.5+Keynote v11.1→異常
macOS 10.15.7+Keynote v11.1→正常
macOS 10.14.6+Keynote v10.1→異常
macOS 10.13.6+Keynote v9.1→正常

追記2:
異常が出ていた環境では、すべてStream Deckの制御ソフトウェアをインストールして起動していました。Stream Deck.appを終了させると問題が発生しないことを確認しました。Stream Deck Softwareのせいでした。

Stream Deckから各種ソフトウェアを操作するために作ったソフトウェアが、Stream Deckソフトウェアによって実行を阻害されているという事実の前に言葉もありません。

elgatoのWebフォームからレポートを送っていますが、elgatoにはいろいろ送っても、一度も返事をもらったことがないのでなんとも(日本代理店のソフトバンクC&Sとは確認のためにやりとりしていますが...)。

厳密に言うと、Stream Deckのオンラインストアから入手可能な「Keynote」プラグイン(Keynoteのプレゼン再生コントロールを行うelgato製のプラグイン)をインストールするとGUI ScriptingのKeynoteに対する実行が阻害されます。

–> Elgato Keynote plugin checking demo


さんかくまさか、elgato純正のプラグインがKeynoteへのGUI Scriptingの実行(メニューへのアクセス)を妨害しているとは思いませんでした。このプラグインはインストールしてはいけないものだと思います。機能もたいしたことはないし、害悪でしかありません。即刻Storeから撤去してほしいものです

Keynoteで選択中のテキストアイテムからテキスト取り出し

Keynoteで選択中のテキストアイテムからテキスト情報を抽出するAppleScriptです。

# 本内容は当時のKeynote v11.xの状況を反映したもので、その後リリースされたv12ではselectionを取得できるように変更されました

FileMaker Pro Scripting Bookの英語版を来る日も来る日も作っており、気づけばぜんぜんScriptを書いていないので「Piyomaru Software」ではなく「Piyomaru Publishing」だ、などと言っている今日このごろです。Keynoteを毎日使っていますが、微妙に痒いところに手が届かないので、使えば使うほどAppleScriptで機能を補いたくなってきます。

Keynoteに「selected objects」といった「選択中のオブジェクト」を求めるAppleScript用語が用意されていないため、本来やりたい「選択中の部品のデータを処理して元の部品に書き戻す」「選択中の部品からデータを抜き出す」といった処理ができません。selectionで取得できるのが「選択中のスライド」だというのが非常に残念です。

Keynote書類でテキストアイテムを選択し、コピーすると......テキスト情報は取り出せません。Finder上でクリップボード内容を確認してみると、あろうことか「PNGイメージ」と表示されます。

コピーされたクリップボードの内容を解析して、そのオブジェクト情報からテキストを抽出できるとよいだろうか、などとも考えたのですが、

Keynote内部オブジェクトで、ちょっと手強そうです。

というわけで、Keynote側には一切機能がないわけですが、選択中のオブジェクトをコピーし、新規ドキュメントにペーストしたうえで新規ドキュメント上のオブジェクトからテキストを取り出し、新規ドキュメントを破棄することにしました。

[フレーム]

GUI Scriptingを用いているので、システム環境設定の「セキュリティとプライバシー」でGUI Scriptingを許可してから実行してください。

AppleScript名:選択中のテキストアイテムからテキスト取り出し.scptd

– Created by: Takaaki Naganoya
– Created on: 2021年01月27日

– Copyright © 2021 Piyomaru Software, All Rights Reserved

use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set kList to getEveryTextFromCurrentKeynoteSlide() of me
set tRes to retDelimedText(kList, return) of me

on getEveryTextFromCurrentKeynoteSlide()
tell application "Keynote"
activate

set dCount to count every document
if dCount = 0 then
display notification "There is no Keynote document"
return {}
end if

tell front document
set sCount to count every slide
end tell
if sCount = 0 then
display notification "There is no Slide in Keynote document"
return {}
end if

end tell

–Copy
tell application "System Events"
keystroke "c" using {command down}
end tell


tell application "Keynote"
activate
set nDoc to make new document
tell nDoc
set aMaster to master slide "空白"
–set aMaster to master slide "Blank"

tell current slide
set base slide to aMaster
end tell
end tell

end tell

–Paste
tell application "System Events"
keystroke "v" using {command down}
end tell

delay 0.1 –Important!!

set tOut to {}
tell application "Keynote"
tell front document
tell current slide
set tList to every iWork item

repeat with i in tList
set aTmpStr to object text of i
set the end of tOut to aTmpStr
end repeat
end tell
end tell

–Dispose document
tell front document
close without saving
end tell
end tell

return tOut
end getEveryTextFromCurrentKeynoteSlide

on retDelimedText(aList, aDelim)
set aText to ""
set curDelim to AppleScript’s text item delimiters
set AppleScript’s text item delimiters to aDelim
set aText to aList as text
set AppleScript’s text item delimiters to curDelim
return aText
end retDelimedText

Switch Controlを起動

Switch Controlを起動するAppleScriptです。macOS標準搭載のScript Menuに入れて呼び出して使っています。Switch Controlは、障害者向けの支援機能を提供するmacOSの標準機能で、標準のマウス/トラックパッド、キーボードなどの利用が困難なユーザーに向けて少ないボタンや音声で操作する機能を提供するものです。

一般のユーザーにとってもSwitch Controlは有効活用できる機能であるため、個人的にいろいろ試しています。

もともと、Switch Controlを起動するためのコマンドは用意されていません。AppleScriptのコマンドで起動できるとか、コマンドラインから起動できるとかいった手軽な起動手段は存在していません。

......というわけで、仕方なくGUI Scriptingで画面上のチェックボックスをクリックするという不毛な処理を書いたわけですが、ただダラダラとGUI部品の階層をなぞるだけの知性のカケラもないコードを書くだけでは意味がありません。

この、クリックする対象のチェックボックスを実行言語環境が変わっても自動で検出できるようにチャレンジしてみました。

結果:失敗 追いかける対象が大きすぎたようです。システム環境設定の画面上のチェックボックスについているタイトル文字を特定するだけの話なのですが、システム環境設定(System Preferences.app)の各機能はプラグインで提供されており、システム環境設定のバンドル内のstringsファイルを追いかけても希望の文字列は得られません。

# このため、チェックボックスのタイトルを言語環境ごとに書き換える必要があります

では、実際に各プラグインのバンドル構造内でstringsファイルを取得することを試みたのですが、これにも失敗。それらしい文字列は得られるものの、文章すべてが1エントリに登録されているわけではないようで、stringsファイルでキーを指定すれば各ロケールごとの対象文字列が得られる......という理想的な処理はできませんでした。

今回のアプローチは技術的には失敗してしまいましたが、他の誰かが突破する日も来るかもしれません。自分のマシンのSSD内には、割とそうした「失敗作」のScriptも存在しており、そうした失敗作が別の機会の土台になることも多々あります。

仕事で作り込む必要のあるScriptであれば、スクリプトバンドル内に各言語ごとの文字列テーブルを自分で作って、localized stringでその値を引けるようにする感じでしょうか。OS側で対象箇所の文言を変更した場合には自分のテーブル側もアップデートする必要が出てきてしまいます。

本Scriptの冒頭でSwitch Controlが起動しているかどうかのチェックを行い、起動中であれば起動処理を行わないようにしています。この判定処理自体は、単体ではほぼ意味がありませんが、こうして組み合わせることで「不要な処理を行わない」ための部品として有効に活用できているといえます。

AppleScript名:Switch Controlを起動

– Created by: Takaaki Naganoya
– Created on: 2020年05月13日

– Copyright © 2020 Piyomaru Software, All Rights Reserved

use AppleScript version "2.4"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set aRes to launchSwitchControl() of me

on launchSwitchControl()
if current application’s NSWorkspace’s sharedWorkspace()’s isSwitchControlEnabled() = true then return true
set aLoc to (current application’s NSLocale’s currentLocale()’s languageCode()) as string

–Current Language detection
if aLoc = "en" then
set aStr to "Enable Switch Control" –English
else if aLoc = "ja" then
set aStr to "スイッチコントロールを有効にする" –Japanese
else
error "Make current language entry"
end if

tell application "System Preferences"
activate
tell pane id "com.apple.preference.universalaccess"
reveal anchor "Switch"
end tell
end tell

set hitF to false

tell application "System Events"
tell process "System Preferences"
repeat 200 times
delay 0.1
if (exists checkbox aStr of tab group 1 of group 1 of window 1) then
click checkbox aStr of tab group 1 of group 1 of window 1
set hitF to true
exit repeat
end if
end repeat
end tell
end tell

tell application "System Preferences" to quit
return hitF
end launchSwitchControl

面積で評価して、Keynoteのメインウィンドウのうち最大のもののItem Numberを返す

ウィンドウ上に複数存在するscroll areaのうち処理対象となるべきものを面積を計算することで特定するGUI Scripting系のAppleScriptです。

どーしてもGUI Scriptingでしか操作できない機能があって、それを自動化する価値があって、大幅に発生する可能性の高い労力を削減できる見込みが立ったので、一気に自動化Scriptを作成。本Scriptはその中で作成した1つの部品です。

自分が書いた処理内容は、Keynoteで作った書類の目次ページに用意した、各スライドのタイトルに実際のスライドへのリンクを付加するもの。

本来、Keynote自体のAppleScript用語辞書に標準装備されていてほしい機能です。残念ながら標準装備されていないために、自分で組む必要があったわけです。

それを作っている途中で、このメインの(Keynoteオブジェクトを配置する中央のエリア)scroll areaのIDが起動するたびに変わるという現象に直面。初期状態(インスペクタの表示状態)をそろえてもIDが変わる。たいていこうしたGUI Scriptingがらみの「怪奇現象」に直面した場合には、スクルプトエディタやアプリケーションの再起動を行えば回避できることが多いのですが、何回かためしても回避できなかったので、scroll areaの特定をID以外で行うことに。

IDが毎回(起動ごとに)変更になるので、「最大の面積を持つもの」を計算して求めるようにしてみました。

[フレーム]
AppleScript名:面積で評価して、Keynoteのメインウィンドウのうち最大のもののItem Numberを返す

– Created by: Takaaki Naganoya
– Created on: 2020年05月09日

– Copyright © 2020 Piyomaru Software, All Rights Reserved

use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set areaNum to getKenoteScrollAreaMax() of me

–面積で評価して、Keynoteのメインウィンドウのうち最大のもののItem Numberを返す
on getKenoteScrollAreaMax()
tell application "System Events"
tell process "Keynote"
tell window 1
set sCount to count every scroll area
set tmpMax to 0
set tmpMaxItem to 0
repeat with i from 1 to sCount
tell scroll area i
set tmpA to size
copy tmpA to {tmpW, tmpH}
set tmpArea to tmpW * tmpH
if tmpArea > tmpMax then
set tmpMax to tmpArea
set tmpMaxItem to i
end if
end tell
end repeat
end tell
end tell
end tell

return tmpMaxItem
end getKenoteScrollAreaMax

Previewで現在表示中のPDFのページ番号を抽出する

macOS標準搭載の画像/PDFビューワーの「Preview.app」で表示中のPDFの、現在表示中のページの番号を取得するAppleScriptです。

本来、Preview.appのような超低機能アプリケーションから強引に情報を(GUI Scriptingまで使って)取得する意味はありません。PDFビューワーとしてまっとうな機能を持っているSkimを使って表示中のページ番号を取得するのが筋です。

ただ、どうしてもPreviewでないといけないケースで、仕方なく作ったものですが、英語環境でも日本語環境でも同様に動くために作ってみたらこんな感じに。指定アプリケーション単体で言語環境を指定して起動できると、各言語環境における動作確認が手軽に行えてよいと思うものの、手段がありそうで見つかりません(Xcode上でそういう起動ができるので、不可能ではないと思うのですが)。


さんかく英語環境で実行したところ(macOS 10.14.6)


さんかく日本語環境で実行したところ(macOS 10.15.4)

仕方なくGUI Scripting経由でウィンドウのタイトルを取得して、ファイル名とページ情報を文字列処理で分離しています。このあたり、英語環境と日本語環境でセパレータ(括弧)が異なるので、分離したページ情報から数字判定を行なって取得しています。Preview.appのアプリケーションバンドル内にこうしたフォーマットのテキストが存在していれば、そちらを使うべきです(見つかっていないので現状こんな感じで)。

GUI Scripting内でプロセス名を指定する箇所で、ローカライズされたプロセス名をCocoaの機能を用いて取得しています。これで、英語環境と日本語環境については問題なく共通Scriptでカバーできています。

AppleScript名:Previewで現在表示中のPDFのページ番号を抽出する.scptd

– Created by: Takaaki Naganoya
– Created on: 2020年04月29日

– Copyright © 2020 Piyomaru Software, All Rights Reserved

use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use scripting additions

set pNum to retPreviewDispPageNumber() of me

on retPreviewDispPageNumber()
set aLocName to getLocalizedName("com.apple.Preview") of me

tell application "Preview"
if (count every document) = 0 then return false
end tell

tell application "System Events"
tell process aLocName
tell window 1
set aTitle to title
end tell
end tell
end tell

set aPageInfo to pickUpFromToStr(aTitle, "(", ")") of me –English env
if aPageInfo = false then
set aPageInfo to pickUpFromToStr(aTitle, "(", ")") of me –double witdh parenthesis (Japanese env)
end if
set pList to words of aPageInfo
set hitF to false
repeat with i in pList
set nRes to chkNumeric(i) of me
if nRes = true then
set hitF to true
exit repeat
end if
end repeat

if hitF = false then return
return i as integer
end retPreviewDispPageNumber

on getLocalizedName(aBundleID as string)
set pRes to getProcessByBUndleID(aBundleID) of me
if pRes = false then return ""
set pName to pRes’s localizedName()
return pName as string
end getLocalizedName

–指定プロセスを取得する
on getProcessByBUndleID(aBundleID)
set appArray to current application’s NSRunningApplication’s runningApplicationsWithBundleIdentifier:aBundleID
if appArray’s |count|() > 0 then
set appItem to appArray’s objectAtIndex:0
return appItem
else
return false
end if
end getProcessByBUndleID

on pickUpFromToStr(aStr as string, s1Str as string, s2Str as string)
set a1Offset to offset of s1Str in aStr
if a1Offset = 0 then return false
set bStr to text (a1Offset + (length of s1Str)) thru -1 of aStr

set a2Offset to offset of s2Str in bStr
if a2Offset = 0 then return false

set cStr to text 1 thru (a2Offset – (length of s2Str)) of bStr

return cStr as string
end pickUpFromToStr

–数字のみかチェック
on chkNumeric(checkString)
set digitCharSet to current application’s NSCharacterSet’s characterSetWithCharactersInString:"0123456789"
set ret to my chkCompareString:checkString baseString:digitCharSet
return ret as boolean
end chkNumeric

on chkCompareString:checkString baseString:baseString
set aScanner to current application’s NSScanner’s localizedScannerWithString:checkString
aScanner’s setCharactersToBeSkipped:(missing value)
aScanner’s scanCharactersFromSet:baseString intoString:(missing value)
return (aScanner’s isAtEnd()) as boolean
end chkCompareString:baseString:

GUI Scriptingでコンテクストメニューのキャプチャを取得

GUI Scriptingを用いてスクリプトエディタ上でコンテクストメニューを表示させて、コンテクストメニューのキャプチャを行うAppleScriptです。macOS 10.14.6上で試しただけなので、10.15上では試していません(たぶん動くと思うのですが)。

–> Watch Demo Movie

画面キャプチャをNSImageに取得する機能については、DSCapture.frameworkをビルドして、AppleScript(実行環境はScript Debugger)から呼び出しています。

–> Download DSCapture.framework (To ~/Library/Frameworks/)

AppleScriptの鬼っ子機能である「GUI Scripting」。ある顧客が「キーボードショートカットとキーボード操作をそのままScript化することでこれを作れ」という仕様を出してきて、とても実現できない内容がまだらに含まれていたので「ナイスジョーク!」と却下した記憶があります。

「できること」(キーボードショートカット操作)に「できないこと」(目視で当該データの位置確認)を混ぜて仕様を出されると悪夢でしかありません。

あと、GUI Scriptingで操作を行うと信頼性がないうえに、信頼性を確保するためには「待つ」という処理が必要だったり、本来のAppleScriptの処理速度よりも100倍以上遅くなるので、本当に必要な箇所にだけ使う&可能なかぎり使用しないというのが自分の基本的なスタンスです。

コンテクストメニューの画面キャプチャを自動で

そんな中、新型Piyomaru Script Assistant(macOS 10.14以降対応。10.13は無視)の資料を作るために、コンテクストメニューの内容を画面キャプチャする必要に迫られました。

AppleScriptが500個ぐらい、階層化されたメニューに入っているので、これの画面キャプチャを撮るだけで一仕事です。テストで1階層分の資料を作ってみたのですが、けっこうかかります。

そこで、スクリプトエディタ上で表示させたコンテクストメニューをプログラムで撮れないかを検討することになります。

真っ先に調べたのはCocoaにそういう機能がないかどうか。自前のプログラムのビューの内容をキャプチャすることはできますが、他のアプリケーションのビューをキャプチャする機能は見つかりませんでした。

仕方がないので、画面のキャプチャをまるごと取得して、撮影対象のビューの座標から画像の切り抜きを行うようにしてみました。

GUI Scriptingでコンテクストメニューを表示させるのは無理かと思っていたのですが、できるんですね。これも冗談半分で探してみたらあっけなく方法が見つかって、

tell theTarget to perform action "AXShowMenu" --コンテクストメニューの表示

これで実際にコンテクストメニューの表示を行えました。

実現はいろいろ大変

スクリプトエディタ上の画面撮影を行うので、Scriptの実行そのものはScript Debuggerで実行。

問題になったのは2点。

1つは、画面キャプチャの撮影を行うフレームワークでメインディスプレイから画面イメージをNSImageで取得し、その結果をメイン側(暗黙のrunハンドラといったほうがいいのか)のプロパティに返しているのですが、すぐに反映されないらしくて、クラッシュを頻発。

結局、プロパティに内容が反映されたことをループで時間待ちして受信。

もう1つ困った点は、コンテクストメニューの表示解除(キャンセル)。1つのコンテクストメニュー表示&キャプチャ撮影だけでは意味がありません。選択メニュー項目を変更して、複数のコンテクストメニューのキャプチャを順次行えなくてはなりません。

いろいろな方法を試してみたものの、最前面のアプリケーションを「スクリプトエディタ」でない状態を作ればコンテクストメニュー表示状態は解除されます(このあたり、AppleScriptが純粋なプログラマーに苦手とされる理由。実際に使っているユーザーでないとこういう発想自体が出てこない)。Script Debugger側で実行しているので、Script Debuggerをactivateすることでその状態(コンテクストメニュー表示解除)を作ってみました。

そんなわけでコンテクストメニューの(選択項目を変更して全項目を選択状態にした)画面キャプチャができました。

現在は1階層分のコンテクストメニューの撮影を行なっていますが、メニュー階層をすべてAppleScript側からスキャンして、それぞれのメニューに対して、各メニュー項目を表示した状態で巡回キャプチャできるとよさそうです。

cropNSImageTo:{x1, y1, x2, y2} fromImage:theImage ハンドラは、Y座標の変換をこの用途(全画面キャプチャしたNSImageの切り抜き)の帳尻合わせのために書き換えています。この用途以外では使えないはずです。

AppleScript名:GUI Scriptingで指定したGUI部品のキャプチャを取得.scptd

– Created by: Takaaki Naganoya
– Created on: 2020年01月24日

– Copyright © 2020 Piyomaru Software, All Rights Reserved

use AppleScript version "2.4" — Yosemite (10.10) or later
use framework "Foundation"
use framework "DSCapture" –https://github.com/kiding/DSCapture.framework
use framework "AppKit"
use scripting additions

property |NSURL| : a reference to current application’s |NSURL|
property NSUUID : a reference to current application’s NSUUID
property NSString : a reference to current application’s NSString
property NSImage : a reference to current application’s NSImage
property NSPNGFileType : a reference to current application’s NSPNGFileType
property NSBitmapImageRep : a reference to current application’s NSBitmapImageRep

property targPos : {0, 0}
property targSize : {0, 0}
property screenSize : {}
property outPath : ""
property captImg : missing value

set (my captImg) to missing value

tell application "System Events"
tell process "スクリプトエディタ" –"Script Editor" (Japanese localized name)
–Editor Area Reference
set theTarg to text area 1 of scroll area 1 of splitter group 1 of splitter group 1 of window 1
end tell
end tell

repeat with i from 1 to 19

set captImg to missing value
set outPath to (POSIX path of (path to desktop)) & ((NSUUID’s UUID()’s UUIDString()) as string) & ".png"

set {mPos, mSize} to dispMenu(i, theTarg) of me
copy mPos to {cx1, cy1}
copy mSize to {cWidth, cHeight}

screenCapture() of me

repeat 100 times
if (my captImg) is not equal to missing value then exit repeat
delay 0.1
end repeat


set bImg to (my cropNSImageTo:{cx1, cy1, cWidth, cHeight} fromImage:(my captImg))
saveNSImageAtPathAsPNG(bImg, my outPath) of me

tell current application to activate –Cancel Context Menu
delay 0.1
end repeat

on screenCapture()
current application’s DSCapture’s sharedCapture()’s |full|()’s captureWithTarget:me selector:"displayCaptureData:" useCG:false
end screenCapture

–Delegate Handler
on displayCaptureData:aSender
set aCount to aSender’s |count|()
set anImage to (aSender’s imageAtIndex:0)
set my captImg to anImage
–saveNSImageAtPathAsPNG(anImage, my outPath) of me
end displayCaptureData:

on cropNSImageTo:{x1, y1, x2, y2} fromImage:theImage
set newWidth to x2
set newHeight to y2
set theSize to (theImage’s |size|())
set oldHeight to height of theSize

— transpose y value for Cocoa coordintates
set y1 to oldHeight – newHeight – y1
set newRect to {{x:x1, y:y1}, {width:x2, height:y2}}

theImage’s lockFocus()
set theRep to NSBitmapImageRep’s alloc()’s initWithFocusedViewRect:newRect
theImage’s unlockFocus()

set outImage to NSImage’s alloc()’s initWithSize:(theRep’s |size|())
outImage’s addRepresentation:theRep

return outImage
end cropNSImageTo:fromImage:

–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 dispMenu(selInd, theTarget)
activate application "Script Editor"
tell application "System Events"
tell process "スクリプトエディタ" –"Script Editor" (Japanese localized name)

ignoring application responses
tell theTarget to perform action "AXShowMenu" –コンテクストメニューの表示
end ignoring

tell text area 1 of scroll area 1 of splitter group 1 of splitter group 1 of window 1
tell menu 1
set mPos to position
set mSize to size

set mList to every menu item
tell menu item (13 + selInd)
set aRes to properties
set selected to true
end tell
end tell
end tell

end tell
end tell

return {mPos, mSize}
end dispMenu

Keynoteで選択中の画像を特定する


Keynoteの書類上で選択中のオブジェクト(image)を特定するAppleScriptです。

Keynoteには、選択中のオブジェクトを求めるという重要な機能「selection」が実装されていません。一応、最新版のKeynoteには「selection」の予約語が存在しているものの、slide中の選択オブジェクトではなく「選択中のスライド」が返ってきます。current slideとほぼ同じ動作です。これでは実用性がいまひとつです。

ないと困るselection(get selected object)ですが、実装されていないのは仕方ありません。

そのものズバリの機能が存在していないものの、他のやりかたで選択中の画像を特定してみました。数が少なかったり重複するものが存在していない場合には有効のようです。

本Scriptでは、Keynote書類上で選択中の画像があるという前提の上で、GUI Scripting経由でコピーを行い、選択対象をクリップボードに入れます。このクリップボード内の画像のサイズを取得。次に、現在のKeynote書類の現在のスライド(ページ)上の画像(imageオブジェクト)のサイズを取得し、順次照合。同じサイズのものがあれば、それが選択中のオブジェクトであると類推します。

かなり穴の多いロジックですが、最初の一歩としては悪くないでしょう。とりあえずは、サイズで比較を行い、同一のものがあれば画像同士の類似性を計算するといった方法も検討できそうです。

お手上げになってしまうのは、画像サイズや内容ともに同一のものが複数あった場合です。その場合を除けば割と識別できそうに思えます。

また、ながらく調査を行なってきた「ローカライズ言語に依存しないGUI Scripting」を用いて選択中のオブジェクトのコピーができるとよさそうです。

AppleScript名:Keynoteで選択中の画像を特定する.scptd

– Created by: Takaaki Naganoya
– Created on: 2020年01月22日

– Copyright © 2020 Piyomaru Software, All Rights Reserved

use AppleScript version "2.7"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set kRes to getSelectedImage() of me
–> image 3 of slide 24 of document id "534F4E65-4459-4B00-95D4-34C3E020467E"

on getSelectedImage()
my executeKeynoteItemCopy() –Execute Copy From Menu

–クリップボードの内容をNSImageに
set aNSIMage to my getClipboardASImage()
if aNSIMage = false then
return false
end if

–クリップボード中のNSImageのサイズを取得
set aSize to aNSIMage’s |size|()
set selWidth to (aSize’s width) as real
set selHeight to (aSize’s height) as real

–Keynoteの最前面のドキュメントの現在のスライド上の画像からサイズを取得してクリップボード内の画像サイズと照合する
tell application "Keynote"
tell front document
tell current slide
set iList to every image

repeat with i in iList
set myHeight to (height of i) as real
set myWidth to (width of i) as real
if {myWidth, myHeight} = {selWidth, selHeight} then
return contents of i
end if
end repeat
end tell
end tell
end tell
return false
end getSelectedImage

— クリップボードの内容をNSImageとして取り出して返す
on getClipboardASImage()
set theNSPasteboard to current application’s NSPasteboard’s generalPasteboard()
set theAttributedStringNSArray to theNSPasteboard’s readObjectsForClasses:({current application’s NSImage}) options:(missing value)
if theAttributedStringNSArray = {} then return false
set theNSAttributedString to theAttributedStringNSArray’s objectAtIndex:0
return theNSAttributedString
end getClipboardASImage

on executeKeynoteItemCopy()
activate application "Keynote"
tell application "System Events"
tell process "Keynote"
–click menu item "Copy" of menu 1 of menu bar item "Edit" of menu bar 1
click menu item "コピー" of menu 1 of menu bar item "編集" of menu bar 1
end tell
end tell
end executeKeynoteItemCopy

GUI ScriptingでGUI要素のIDもOSアップデート後には見直す必要あり

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書き出し命令ぐらいは自前で実装したい気がします。

Tanzakuの実証実験用バージョン「Tanshio」の配布を開始

Tanzakuの実証試験用バージョン「Tanshio」の配布を開始しました。Tanzakuは、Piyomaru Softwareが開発中の自然言語インタフェース系自動処理プログラムの最新シリーズです。

# Tanzakuの派生シリーズや実験プログラムは「Tan-XXX」と命名します。

[フレーム]

TanshioはTanzakuで予定している各種機能の縮小版をひととおり実装して、使い勝手を調べたりユーザー環境でうまく動作するかといったことを検証するためのものです。本バージョンには2020年1月31日までの動作期限を設けています。

–> Download Tanshio(70KB)

Tanzakuの動作原理は、「ファイル名にユーザーが行いたい内容を記述することで、Tanzakuプログラムがそれを解釈して実行する」というものです(Talking Droplet)。

[フレーム]

Tanshioでは、指定アプリケーションのメニュー項目をファイル名に記述することで、その項目をクリックします。

アプリケーション名>メニュー名>メニュー項目名1>メニュー項目名2

のように、階層メニュー名称をファイル名に記述することで、一切のプログラミングなしにメニュー項目の自動クリックを実現するものです。プログラミングを一切行わず、ファイル名を付け替えるだけでメニューのクリックを行い、ファイル名の指定が正しくない場合にはアプリケーション名、メニュー名などをユーザーに問い合わせることで、正しいファイル名を自動フィードバックします。

コマンドをファイル名から取得し、ファイル名のつけかえにより動作を変更し(可塑性のあるプログラム)、いったんコマンドを受理したあとはツールのように振る舞うといったTanzakuの主な特徴を備えています。さらに、入力コマンド(ファイル名)を誤った場合にはユーザーに対して選択肢を提示して正しいコマンドへと誘導する仕様もこのTanshioに実装。実際に使ってみてウザくないかといった確認を行うためのテストベッドです。

動作OSバージョンはmacOS 10.14.6および10.15.xですが、10.14を推奨します。また、システム環境設定の「セキュリティとプライバシー」で、「アクセシビリティ」「オートメーション」などの項目にTanshioを登録する必要があるため、実行・運用にあたってはシステム管理者権限が必要になります。


さんかく初期状態のTanshio


さんかく初期状態だとファイル名にアプリケーション名やメニュー項目名が書かれていないため、エラーになる


さんかく操作アプリケーション選択


さんかく操作メニュー選択


さんかく操作メニュー項目選択(コマンドにたどり着くまで繰り返し)


さんかくTanshioが自分自身のファイル名を書き換え、正しく、アプリケーション名やメニュー名、メニュー項目名などが記入される。

この状態でTanshioを実行すると、目的のアプリケーションのメニューを操作します。


さんかく別のアプリケーションや別のメニュー項目をクリックするように変更したい場合にはファイル名を「Tanshio」のような短い名前に付け替えると起動時にアプリケーション選択/メニュー選択を行う

実際に使ってみて

同じメニュー項目をトグルで差し替えるような処理を行っているアプリケーションだと(例:CotEditorの「表示」>「行番号を表示」/「行番号を非表示」)、メニュー項目名をそのまま指定しても、実行2回目になるとメニュー項目が存在していないので、エラーになってアプリケーション選択とメニュー選択のやり直しになるようです。

なーるーほーどー(汗)

画面上の指定座標にマウスカーソルを強制移動させてクリック

マウスカーソルを指定座標に強制的に移動させて、マウスクリック(プレス)を行う補助アプリケーション+呼び出しAppleScriptです。

# ご注意:マウスカーソルの移動やクリックを行うのは、本来のAppleScriptの処理ではありません
# ご注意:他のマシン上で同じ動作を再現することが(初心者には)難しいため、おすすめしません

# 本ツールはCodeSignしてMac App Storeで近日リリースする予定です

必要悪! 画面上の部品や座標をクリックする機能

マウスカーソルの強制移動とクリックは、AppleScriptではなるべく避けるべき操作ですが、ごく一部の操作を実行するため、ごくごくまれに必要になることがあります。

macOS標準装備のAppleScript専用のツール「System Events」に「click」コマンドがあり、指定のGUI部品か、あるいは画面上の座標をクリックするようになっています。

ただし、これはあくまで「指定のGUI部品や指定座標をクリックしたというメッセージ」を対象(アプリケーション)に送るというものであり、実際にマウスカーソルを移動させてクリックを実行するものではありません。

強制マウス操作ツールの歴史

それでも、アプリケーションがAppleScriptに対して機能を解放していない機能を呼び出す必要があって、かつ、メニューやボタンのクリックなどで実行できないような場合には、止むを得ず指定座標のクリックをごくまれに行うことがあります(1年に1度ぐらいの頻度)。


さんかく強制的にマウスカーソルを移動させてクリックする支援ツールの変遷

これまで、Framework呼び出しでこれらの動作を行ってきましたが、macOS 10.14で(SIPを解除しないかぎり)スクリプトエディタ上ではサードパーティのFrameworkを呼べなくなりました(AppleScriptドロップレット上ではバンドル内のFrameworkを呼べます)。

呼び出し側から一番簡単に利用できる方法は、指定座標のクリック機能を持つアプリケーションをXcode上でAppleScriptで作成しておき、sdefを定義して、AppleScript対応アプリケーションをAppleScriptで作るものです(豆腐をすりつぶして「ひろうす」を作るようなこの迂遠さ。Google翻訳で絶対に伝わらないニュアンス。パンをすりつぶしてパンを作るような、、、)。

そのため、いままでFrameworkで運用してきたプログラムを、アプリケーション化してsdefを付加し、スクリプタブルなバックグラウンドアプリケーション(Dockに表示されない、メニューやウィンドウが表示されない)にする必要が出てきます(まんまとAppleにタダ働きさせられているような気がするので気分はよくありませんが)。

自分ではほとんど使わないマウス強制移動&クリックツールを作ってみた

そこで、sdef(AppleScript用語辞書)をつけたライブラリやアプリケーションを作る方向で調査を行っていました。実際にパラメータの受け渡しをどのように行えるのか、どのあたりでハマるのか、どのぐらいの作業量になるのか。

その1つの到達点として、この補助アプリケーション「mouseClick」を作ってみました。バックグラウンド実行専用のため、起動してもDockにアイコンは表示されません(これを知らないユーザーがバックグラウンド起動専用のアプリケーションに、Mac App Sroreでいちゃもんをつけているのを見かけて、遠い目になりました)。

–> Download mouseClick.app (To /Applications)

(注記) このツールは、AppleScriptからコマンドで操作する専用のものであり、画面上には何も表示されません。

簡単なAppleScriptでマウスカーソルの移動とクリックを実行できます。初回実行時はシステム環境設定の「セキュリティとプライバシー」>「プライバシー」>「アクセシビリティ」でmouseClickに「コンピュータの制御を許可」しておく必要があります(管理者権限が必要、2回目以降は操作不要)。

forceClickでは、画面の左上を原点とした座標系を使用しています。

呼び出し側と実行側をすべてAppleScriptで組めるようになったわけで、Classic MacOS時代からこの手の「指定座標の強制クリック系ソリューション」を(止むを得ず)使ってきた身からするとなかなか感慨深いものがあります。

参考文献:
objective c 入門 CocoaアプリケーションにAppleScriptサポートを追加するにはどうすればよいですか?

AppleScript名:force click sample
tell application "mouseClick"
force click at {4, 4} –click apple menu
end tell

TeamViewerの「リモートコントロール」画面からIDとパスワードを取得(v14対応)

リモート操作ソフト「TeamViewer」の画面から、ユーザーIDとパスワードを取得するAppleScriptです。


さんかくTeamViewer v14をmacOS 10.12(左)、10.14(右)で起動したところ

GUI Scriptingの機能を使って、画面上から情報を取得し、正規表現で抽出してみました。

TeamViewerで実家のMacなど、離れた場所にあるマシンをメンテナンスする必要がある場合に、実家から電話がかかってきて、TeamViewerのパスワードとIDを口頭で教えてもらって接続する必要があるわけですが、親が高齢のためそれが心もとなくなりつつある昨今、確実に確認するために作成したものです。

その場で、(自分のために)「TeamViewerを起動してIDとパスを確認して自分あてにメールで送ってくるAppleScript」を作りかけたのですが、途中からビデオ編集の仕方を教えろなどと言われて説明する羽目に。

AppleScript名:TeamViewerの「リモートコントロール」画面からIDとパスワードを取得(v14対応)
【コメント】 ?
— Created 2019年07月15日 by Takaaki Naganoya
— 2019 Piyomaru Software
use AppleScript version "2.4"
use scripting additions
use framework "Foundation"

property NSArray : a reference to current application’s NSArray
property NSPredicate : a reference to current application’s NSPredicate
property NSMutableArray : a reference to current application’s NSMutableArray

set aRes to getIDAndPassFromTeamViewer() of me
–> {myID:"XXX XXX XXX", myPass:"xxXxXX"}

–TeamViewerの「リモートコントロール」画面からIDとパスワードを取得(v14対応)
on getIDAndPassFromTeamViewer()
–画面の表示状態を変更
selectRemoteControlRowOnTV("リモートコントロール") of me

–画面(Window)上のテキストをとりあえず全部取得
set sList to retEveryStaticTextInCurrentView() of me

set anArray to NSArray’s arrayWithArray:sList

–「使用中のID」を取得
set aPred to NSPredicate’s predicateWithFormat:"SELF MATCHES ’[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}’"
set bRes to (anArray’s filteredArrayUsingPredicate:aPred) as list
set idRes to contents of first item of bRes
–> "XXX XXX XXX"

set bList to removeItemFromList(sList, idRes) of me
set bArray to NSArray’s arrayWithArray:bList
set bPred to NSPredicate’s predicateWithFormat:"SELF MATCHES ’^[0-9a-zA-Z]+$’"
set cRes to (bArray’s filteredArrayUsingPredicate:bPred) as list
set psRes to contents of first item of cRes
–> "xxXxXX"

return {myID:idRes, myPass:psRes}
end getIDAndPassFromTeamViewer

on retEveryStaticTextInCurrentView()
activate application "TeamViewer"
tell application "System Events"
tell process "TeamViewer"
tell window 1
tell group 2
set sList to value of every static text
–> {"接続準備完了(安全な接続)", "パートナーID", "リモートコンピュータの操作", "遠隔操作を受ける許可", "使用中のID", "999 999 999", "パスワード", "xxx999", "無人アクセス"}
return sList
end tell
end tell
end tell
end tell
end retEveryStaticTextInCurrentView

–TeamViewerで「リモートコントロール」行を選択
on selectRemoteControlRowOnTV(aTargStr)
activate application "TeamViewer"
tell application "System Events"
tell process "TeamViewer"
tell table 1 of scroll area 1 of window 1
set rCount to count every row

repeat with i from 1 to rCount
tell row i
tell UI element 1
set aText to value of static text 1
end tell

if aText = aTargStr then
set selected to true
end if
end tell
end repeat

end tell
end tell
end tell
end selectRemoteControlRowOnTV

–1次元配列から指定の内容の要素をすべて削除して返す
on removeItemFromList(aTargList, aTargValue)
set anArray to NSMutableArray’s arrayWithArray:aTargList
repeat
set aInd to anArray’s indexOfObject:aTargValue
if aInd = current application’s NSNotFound or (aInd as real > 9.99999999E+8) then exit repeat
anArray’s removeObjectAtIndex:aInd
end repeat
return anArray as list
end removeItemFromList

CotEditorで編集中のMarkdown書類をPDFプレビュー

CotEditorで編集中のMarkdown書類を、MacDownでPDF書き出しして、Skimでオープンして表示するAppleScriptです。

CotEditorにMarkdownのプレビュー機能がついたらいいと思っている人は多いようですが、MarkdownはMarkdownで、方言は多いし標準がないし、1枚もののMarkdown書類だけ編集できればいいのか、本などのプロジェクト単位で編集とか、目次が作成できないとダメとか、リンクした画像の扱いをどうするのかとか、対応しようとすると「ほぼ別のソフトを作るのと同じ」ぐらい手間がかかりそうです(メンテナー様ご本人談)。

そこで、AppleScript経由で他のソフトを連携させてPDFプレビューさせてみました。これなら、誰にも迷惑をかけずに、今日この時点からすぐにMarkdownのプレビューが行えます(当然、HTML書き出ししてSafariでプレビューするバージョンははるかかなた昔に作ってあります)。

[フレーム]

ただし、OS側の機能制限の問題で、CotEditor上のスクリプトメニューから実行はできません(GUI Scriptingの実行が許可されない)。OS側のスクリプトメニューに登録して実行する必要があります。

GUI Scriptingを利用してメニュー操作を行なっているため、システム環境設定で許可しておく必要があります。

本来であれば、PDFの書き出し先フォルダ(この場合は書き出しダイアログで、GUI Scirptingを用いてCommand-Dで指定して一律に場所指定が行えるデスクトップフォルダ)に同名のPDFが存在しないかどうかチェックし、存在すれば削除するといった処理が必要ですが、面倒だったのであらかじめMarkdown書類をUUIDにリネームしておくことで、書き出されたPDFも同じくUUIDのファイル名になるため、論理上はファイル名の衝突を回避できるため、削除処理を省略しています。

AppleScript名:🌏レンダリングしてPDFプレビュー
— Created 2019年06月15日 by Takaaki Naganoya
— 2019 Piyomaru Software
use AppleScript version "2.5"
use scripting additions
use framework "Foundation"
use framework "AppKit"

property NSUUID : a reference to current application’s NSUUID
property NSWorkspace : a reference to current application’s NSWorkspace

–オープン中のMarkdown書類を取得する
tell application "CotEditor"
tell front document
set cStyle to coloring style
if cStyle is not equal to "Markdown" then
display dialog "編集中のファイルはMarkdown書類ではないようです。" buttons {"OK"} default button 1
return
end if

set aPath to path
end tell
end tell

–一時フォルダにMarkdown書類をコピー
set sPath to (path to temporary items)
tell application "Finder"
set sRes to (duplicate ((POSIX file aPath) as alias) to folder sPath with replacing)
end tell

–コピーしたMarkdown書類をリネーム
set s1Res to sRes as alias
set aUUID to NSUUID’s UUID()’s UUIDString() as text –UUIDを作成する
tell application "Finder"
set name of s1Res to (aUUID & ".md")
end tell

–Markdown書類をデスクトップにPDF書き出し
set pdfRes to exportFromMacDown(POSIX path of s1Res) of me

–PDF Viewerでオープン
tell application "Skim" –Preview.appでもOK
activate
open pdfRes
end tell

–一時フォルダに書き出したMarkdown書類を削除
tell application "Finder"
delete s1Res
end tell

–指定のMacDownファイル(alias)をデスクトップ上にPDFで書き出し
on exportFromMacDown(anAlias)
set s1Text to paragraphs of (do shell script "ls ~/Desktop/*.pdf") –pdf書き出し前のファイル一覧

tell application "MacDown"
open {anAlias}
end tell

macDownForceSave() of me

tell application "MacDown"
close every document without saving
end tell

do shell script "sync" –ねんのため

set s2Text to paragraphs of (do shell script "ls ~/Desktop/*.pdf") –pdf書き出し後のファイル一覧

set dRes to getDiffBetweenLists(s1Text, s2Text) of me –デスクトップ上のPDFファイル名一覧の差分を取得
set d2Res to (addItems of dRes)

if length of d2Res ≥ 1 then
return contents of first item of d2Res
else
error "Error in exporting PDF to desktop folder…."
end if
end exportFromMacDown

on getDiffBetweenLists(aArray as list, bArray as list)
set allSet to current application’s NSMutableSet’s setWithArray:aArray
allSet’s addObjectsFromArray:bArray

–重複する要素のみ抜き出す
set duplicateSet to current application’s NSMutableSet’s setWithArray:aArray
duplicateSet’s intersectSet:(current application’s NSSet’s setWithArray:bArray)

–重複部分を削除する
allSet’s minusSet:duplicateSet
set resArray to (allSet’s allObjects()) as list

set aSet to current application’s NSMutableSet’s setWithArray:aArray
set bSet to current application’s NSMutableSet’s setWithArray:resArray
aSet’s intersectSet:bSet –積集合
set addRes to aSet’s allObjects() as list

set cSet to current application’s NSMutableSet’s setWithArray:bArray
cSet’s intersectSet:bSet –積集合
set minusRes to cSet’s allObjects() as list

return {addItems:minusRes, minusItems:addRes}
end getDiffBetweenLists

–注意!! ここでGUI Scriptingを使用。バージョンが変わったときにメニュー階層などの変更があったら書き換え
on macDownForceSave()
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

–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 macDownForceSave

–Bundle IDからアプリケーションのPathを返す
on retAppAbusolutePathFromBundleID(aBundleID)
set appPath to NSWorkspace’s sharedWorkspace()’s absolutePathForAppBundleWithIdentifier:aBundleID
if appPath = missing value then return false
return appPath as string
end retAppAbusolutePathFromBundleID

Keynoteで指定IDのテキストフレームを縦書き化

macOS 10.13/10.14+Keynote 9.0.1で指定のIDのテキストフレームを縦書き化するAppleScriptです。

Keynote 9でサポートされた文字の縦書き表示。しかし、肝心のAppleScriptでこれを操作する機能が備わっていなかったので、強引にGUI側から操作して縦書き化させてみました。

実行前に「システム環境設定」の「セキュリティとプライバシー」>「アクセシビリティ」にて、スクリプトエディタあるいはScript Debuggerに対してアクセシビリティ機能(「下のアプリケーションにコンピュータの制御を許可」)を許可しておく必要があります。

また、macOS 10.14上にてはじめてScript DebuggerからKeynoteを動かそうとした場合には、「オートメーション」項目でScript Debuggerの操作を許可しておく必要があります。


さんかくBefore


さんかくAfter


さんかく2D Bin PackingのAppleScriptに、本ルーチンを組み込んで想定矩形座標内に文字を詰め込んでみた。日本語はフレームの回転ではなく縦書き表示できたほうが可読性が上がるかも


さんかくKeynote上でオブジェクトを選択状態にしてテキストフレームの「枠」を表示させてみるとけっこう詰まっていることがわかる

AppleScript名:Keynoteで指定IDのテキストフレームを縦書き化
set tmpID to 1

selectTextItemID(tmpID) of me
set aRes to makeTextFrameVertival(tmpID, true) of me

on makeTextFrameVertival(anID, aFlag)
tell application "System Events"
activate
set aGSflag to UI elements enabled
if aGSflag = false then return false
end tell

activate application "Keynote"
tell application "System Events"
tell process "Keynote"
tell radio button "フォーマット" of radio group 1 of toolbar 1 of window 1 –*Localized*
set aVal to value
if aVal = 0 then
click
end if
end tell

tell radio button "テキスト" of radio group 1 of window 1 –*Localized*
click
end tell

tell checkbox "縦書きテキスト" of scroll area 1 of window 1 –*Localized*
set aVal to value
if (aVal = 0) and (aFlag = true) then
click
else if (aVal = 1) and (aFlag = false) then
click
end if
end tell

end tell
end tell
return true
end makeTextFrameVertival

on selectTextItemID(anID)
tell application "Keynote"
tell front document
tell current slide
set anObj to a reference to text item anID
properties of anObj
end tell
end tell
end tell
end selectTextItemID

macOS 10.14のバグ? アクセシビリティ認証

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だと判断しました。

[追記]2020/3時点での状況

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つ。

(1)Info.plistに「Privacy – AppleEvents Sending Usage Description」のエントリを作成する(このエントリの内容の文字列が、アプリケーション初回起動時のセキュリティダイアログで表示されます)

(2)Xcode Projectの「TARGETS」でビルドターゲットを選択し、「Signning & Capabilities」>「All」で「Runtime Exceptions」の「Apple Events」にチェックを入れる

これらの設定を行って、Code Signしてあればビルドして実行すると、初回時にOSのセキュリティダイアログが表示され、そののちにGUI Scriptingの認証ダイアログが表示され、認証後、ビルドしたアプリケーションをいったん終了。

再度ビルド&実行するとOS側の「セキュリティ」認証とGUI Scriptingの(アクセシビリティの)認証が通ってアプリケーションの実行ができました。