MacBookのメニューをショートカットで操作する試行錯誤
OSXでは、システム環境設定 >> キーボード >> キーボードショートカットで、よく使うメニューにショートカットを設定できる。一見、とても便利なんだけど、でも完璧じゃない...。
- 重複するショートカットも設定できるが、いざ操作してみると機能しない。
- 操作時の状況によって、その都度変化するメニューもあり、そのようなメニューに対してはショートカットを設定できない。設定できても、初回操作の時、反応しない。
- メニュー操作を完結することが目的ではなく、メニューを展開した状態を表示しておきたい場合もある。
-
- 例2:ファイル >> 最近使った項目を開く
- 常に動的に生成される。項目も一定せず、特定のメニューテキストを指定する意味がない。
- この操作で目指すのは、特定のファイルを選択して開くことではなく、最近使った項目を開くメニューが展開された状態にしておきたいのだ。
- そうすれば、最近使ったファイルを↑↓キーで簡単に選択できる。
- 例2:ファイル >> 最近使った項目を開く
GUIスクリプティングによるメニュー操作の基本
- 上記のような問題は、AppleScriptのGUIスクリプティングを利用すれば、何とか解決できる。
- しかし、メニュー操作のGUIスクリプティングは、メニューが3階層くらいになると目的のメニューまでの指定コードが冗長で、最初混乱する。
例1:表示 >> テキストエンコーディング >> Unicode (UTF-8)
tell application "System Events"
tell process "Safari"
set frontmost to true --必ず、アクティブにしておく
tell menu bar 1
UI elements
end tell
end tell
end tell
- 上記コードを実行すると、以下の結果が取得できるので、目指すメニュー操作のヒントが理解できた。
- ちなみに、ここでは関係ないが、アップルメニューなら、menu bar item "Apple"と指定すれば良いことが分かる。
{menu bar item "Apple" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "Safari" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "ファイル" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "編集" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "表示" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "履歴" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "ブックマーク" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "開発" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "GreaseKit" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "Stand" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "ウインドウ" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "ヘルプ" of menu bar 1 of application process "Safari" of application "System Events", ¬
menu bar item "Debug" of menu bar 1 of application process "Safari" of application "System Events"}
- 目的のメニューは"表示"なので、今度は tell menu bar item "表示" を追加して実行してみる。
tell application "System Events"
tell process "Safari"
set frontmost to true --必ず、アクティブにしておく
tell menu bar 1
tell menu bar item "表示"
UI elements
end tell
end tell
end tell
end tell
- 実行結果は以下。意外にも1項目しかない。しかも、"表示"が重複している感じ。
{menu "表示" of menu bar item "表示" of menu bar 1 of application process "Safari" of application "System Events"}
- 腑に落ちない疑問はあるけど、今は機械的に作業を続ける。
tell application "System Events"
tell process "Safari"
set frontmost to true --必ず、アクティブにしておく
tell menu bar 1
tell menu bar item "表示"
tell menu "表示"
UI elements
end tell
end tell
end tell
end tell
end tell
- 次の実行結果は以下。やっと、表示メニューのリストが出て来た!(... = application process "Safari" of application "System Events")
{menu item "ブックマークバーを隠す" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "ステータスバーを隠す" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "タブバーを隠す" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "ツールバーを隠す" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "ツールバーをカスタマイズ..." of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item 6 of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "中止" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "ページを再読み込み" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item 9 of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "実際のサイズ" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "拡大" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "縮小" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "テキストのみ拡大/縮小" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item 14 of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "ソースを表示" of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item 16 of menu "表示" of menu bar item "表示" of menu bar 1 of ..., ¬
menu item "テキストエンコーディング" of menu "表示" of menu bar item "表示" of menu bar 1 of ...}
- このように作業を続けると、最終的には以下のコードが導き出される。
tell application "System Events"
tell process "Safari"
set frontmost to true --必ず、アクティブにしておく
tell menu bar 1
tell menu bar item "表示"
tell menu "表示"
tell menu item "テキストエンコーディング"
tell menu "テキストエンコーディング"
tell menu item "Unicode(UTF-8)"
click
UI elements
end tell
end tell
end tell
end tell
end tell
end tell
end tell
end tell
- つまり、Safariで、表示 >> テキストエンコーディング >> Unicode (UTF-8)を操作するスクリプトである。
- clickを追加すれば、テキストエンコーディングがUTF-8に設定される。
- 例1の目的は達成されたが、それにしても分かり難いコードだ。
- こんなコードを毎回書くことを思うと、やる気が失せる。
- そもそも、なぜ"表示"、"テキストエンコーディング"が重複しているのか?
- この疑問は、上記コードを以下のように変形すると、少し納得できた。
tell application "System Events"
tell process "Safari"
set frontmost to true --必ず、アクティブにしておく
tell menu bar 1's menu bar item "表示"
tell menu "表示"'s menu item "テキストエンコーディング"
tell menu "テキストエンコーディング"'s menu item "Unicode(UTF-8)"
click
UI elements
end tell
end tell
end tell
end tell
end tell
- GUI(オブジェクト)と対応させて、やっと納得できた。なるほど!
- メニューオブジェクトは、グループ名と内包するリストアイテムで構成されている。
- 選択したリストアイテム名と、その結果展開されるメニューオブジェクトのグループ名が同じなのだ。
-
- tell menu bar 1's menu bar item "表示"
-
- tell menu "表示"'s menu item "テキストエンコーディング"
click_menuの定義
- 上記のように書いたとしても、相変わらず分かり難いコードであることに変わりない。やる気は失せる...。
- 理想としては、シンプルに以下のように書いて済ませたいのだ。
click_menu("Safari", "表示/テキストエンコーディング/Unicode(UTF-8)")
- それを実現したくて、以前の日記:GUIスクリプティングなAppleScript環境を快適にするで、click_menu()を定義した。
- 関係するところだけ抜粋すると、以下のようなコード。(今思い返すと、この頃の自分はまだ完全に理解できていなかった。無駄が多い。)
on click_menu(app_name, menu_path)
if menu_path is "" then
error "menu_path が入力されていません。"
end if
if app_name is "" then
set app_name to frontmost_app()
end if
set mp to split(menu_path, "/")
tell application "System Events"
tell process app_name
set frontmost to true --必ず、アクティブにしておく
if mp's length = 1 then
menu bar 1's (menu bar item (my number_from(mp's item 1)))
else
menu bar 1's (menu bar item (my number_from(mp's item 1)))'s menu 1
repeat with i from 2 to mp's length
if i < mp's length then
result's (menu item (my number_from(mp's item i)))'s menu 1
else
result's (menu item (my number_from(mp's item i)))
end if
end repeat
end if
click result --click:クリックする/pick:選択する--ほぼ同等だが、アイコンメニューにはclickが必須
delay 0.1 --連続してメニューを操作する時、ひと呼吸必要
end tell
end tell
end click_menu
on frontmost_app()
tell application "Finder"
set app_name to name of (path to frontmost application)
end tell
split(app_name, ".")'s item 1 as text
end frontmost_app
on split(sourceText, separator)
if sourceText = "" then return {}
set oldDelimiters to AppleScript's text item delimiters
set AppleScript's text item delimiters to {separator}
set theList to text items of sourceText
set AppleScript's text item delimiters to oldDelimiters
return theList
end split
on is_number(num)
num's class is integer or num's class is real
end is_number
on number_from(str)
try
str as number
on error
str
end try
end number_from
- 上記click_menuは、以下のような仕様となった。
- click_menu(app_name, menu_path)
- メニュー操作をシンプルに実行する
- app_nameは、操作対象のアプリケーション名。""にすると、実行時にアクティブなアプリケーションに対する操作となる。
- menu_pathは、テキストまたはリスト
click_menu("", "編集/検索/検索...") --OK
click_menu("Script Editor", {"編集", "検索", "検索..."}) --OK
click_menu("Script Editor", "編集", "検索", "検索...") --NG(リストでないので、編集をクリックする操作になってしまう)
-
- …(全角1文字)と...(半角ピリオド3文字)の種類に注意
click_menu("Script Editor", "Apple/システム環境設定…") --全角1文字
click_menu("Script Editor", "スクリプトエディタ/環境設定...") --半角ピリオド3文字
-
- 小さいカナ文字に注意(×ウィンドウ ○ウインドウ)
- アップルメニューの場合はpath_menuに"Apple"を指定する
click_menu("", "Apple/この Mac について")
-
- アイテム番号による指定も可能
- アイテム番号では区切り線も1と数える
- サービスメニューのグループ名も1と数える
- アイテム番号とアイテム名称の混在も可能
click_menu("AppleScript Editor", "AppleScript エディタ/サービス/選択部分を含む新しいテキストエディットウインドウを開く")
click_menu("AppleScript Editor", "2/5/20") --アイテム番号で指定
click_menu("AppleScript Editor", "2/サービス/選択部分を含む新しいテキストエディットウインドウを開く") --混在も可能
-
- アイコンメニュー(ステータスメニュー)の操作は、アイテム番号の選択とキー操作で行う
- アイコンメニュー(ステータスメニュー)のアイテム番号は、control-F8でステータスメニューを選択して、左端から矢印キーで移動しながら数えると確認し易い
- アイコンメニュー(ステータスメニュー)のapp_nameは、"SystemUIServer"
- "SystemUIServer"以外のアイコンメニューを操作する方法は、現状分からない...
- 例:ログインウィンドウを表示する(ゲストなし、ログインユーザーが1人だけの場合)
click_menu("SystemUIServer", "13")
shortcut("↓")
shortcut("↓")
shortcut("space")
メニュー操作の途中経過も再現する
- メニューの操作がかなり楽になったが、まだ問題がある。
- 以下のように完結する操作なら、検索ウィンドウが表示されるが...
click_menu("", "編集/検索/検索...")
- メニューのディレクトリに該当するアイテムを指定しても、何も起こらない。
click_menu("", "編集/検索")
- 期待する動作としては、マウスを編集 >> 検索とホバーした状態を表示して欲しいのだ。
- それを実現するためには、現状では以下のように書かなくてはならない。
click_menu("", "編集") click_menu("", "編集/検索")
- 途中の操作も含めて、すべての段階でクリックするように指定すれば良いのだ。
- しかし、現状のclick_menuでは2行になってしまう。ここはやはり1行で書けるようにしたい。
- コードを見直しながら書き直してみると、何と!if文が消えて、こんなにスッキリ書き直せるのであった。
on click_menu(app_name, menu_path)
if menu_path is "" then
error "menu_path が入力されていません。"
end if
if app_name is "" then
set app_name to frontmost_app()
end if
set mp to split(menu_path, "/")
tell application "System Events"
tell process app_name
set frontmost to true --必ず、アクティブにしておく
menu bar 1's menu bar item (my number_from(mp's item 1))
click result
repeat with i from 2 to mp's length
result's menu 1's menu item (my number_from(mp's item i))
click result
end repeat
delay 0.1 --連続してメニューを操作する時、ひと呼吸必要
end tell
end tell
end click_menu...(以下変更なし)...
利用例
- 上記のclick_menuは、~/Library/Scripts/_GUI.scpt として、GUIスクリプティングのライブラリのつもりで保存してある。
- 利用する時は、プロパティーあるいは変数に取り込んで、以下のようにしている。
property GUI : load script file ((path to scripts folder as text) & "_gui.scpt")
GUI's click_menu("Safari", "表示/テキストエンコーディング/Unicode(UTF-8)")
- 例2:ファイル >> 最近使った項目を開く
set GUI to load script file ((path to scripts folder as text) & "_gui.scpt")
GUI's click_menu("", "ファイル/最近使った項目を開く")
- ゲストアカウントでファストユーザースイッチ(ステータスメニューの操作)
property GUI : load script file ((path to scripts folder as text) & "_gui.scpt")
GUI's click_menu("SystemUIServer", "13/1")
- アップルメニューの最近使った項目
property parent : load script file ((path to scripts folder as text) & "_gui.scpt")
click_menu("", "Apple/最近使った項目")
メニュー操作が再現できたら、そのスクリプトにQuicksilver(Bulter、Spark等でもOKかも)でショートカットを設定して、あとは便利に使うだけ。