Submit Hint Search The Forums LinksStatsPollsHeadlinesRSS
14,000 hints and counting!

A better way to activate menu items from AppleScript System
There have been several specific hints on this site which use UI scripting to perform tasks that applications' AppleScript dictionaries don't otherwise allow. Unfortunately, UI scripting is a complicated pain to get right. In particular, the code Apple uses as a demonstration on apple.com is very long, and not particularly intuitive:
tell application "Finder"
    activate
end tell

tell application "System Events"
    tell process "Finder"
        tell menu bar 1
            tell menu bar item "View"
                tell menu "View"
                    tell menu item "Arrange By"
                        tell menu "Arrange By"
                            click menu item "Size"
                        end tell
                    end tell
                end tell
            end tell
        end tell
    end tell
end tell
To that end, I created a helper function, which I call menu_click, which can be added to any script to allow the above code to instead be written as:
tell application "Finder" to activate
menu_click({"Finder", "View", "Arrange By", "Size"})
I find these two lines much clearer and easier to understand than the 19 lines of code that Apple uses for the same purpose.

To be fair, even without my function, this code could be written:
tell application "Finder" to activate
tell application "System Events"
    click menu item "Size" of ((process Finder)'s (menu bar 1)'s ¬
        (menu bar item "View")'s (menu "View")'s ¬
        (menu item "Arrange By")'s (menu "Arrange By"))
end tell
I still like my version better ;).

To be even fairer, Apple tries to help out with their own helper functions. These are useful, but they are overly complicated to use, because a separate function is needed for each level of menu hierarchy the script author wants to use. For instance, the do_menu handler can execute the File -> New command, but not the Edit -> Insert -> Line Break command, which requires the more complicated do_submenu handler. For more deeply nested menu options, we'd eventually need a do_subsubsubmenu handler, if we continued in Apple's footsteps.

Thus, the menu_click handler was born. Just add the following code to your script, and then call menu_click with a list containing the application name, followed by the menu command. Just so we're clear, I'll repeat myself. Copy and paste the "code" section that immediately follows this sentence into your script verbatim (it can be anywhere: top or bottom makes no difference). Then anywhere in the rest of your script, you can call the helper function.

Code
-- `menu_click`, by Jacob Rus, September 2006
-- 
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- Execute the specified menu item.  In this case, assuming the Finder 
-- is the active application, arranging the frontmost folder by date.

on menu_click(mList)
    local appName, topMenu, r

    -- Validate our input
    if mList's length < 3 then error "Menu list is not long enough"

    -- Set these variables for clarity and brevity later on
    set {appName, topMenu} to (items 1 through 2 of mList)
    set r to (items 3 through (mList's length) of mList)

    -- This overly-long line calls the menu_recurse function with
    -- two arguments: r, and a reference to the top-level menu
    tell app "System Events" to my menu_click_recurse(r, ((process appName)'s ¬
        (menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click

on menu_click_recurse(mList, parentObject)
    local f, r

    -- `f` = first item, `r` = rest of items
    set f to item 1 of mList
    if mList's length > 1 then set r to (items 2 through (mList's length) of mList)

    -- either actually click the menu item, or recurse again
    tell app "System Events"
        if mList's length is 1 then
            click parentObject's menu item f
        else
            my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
        end if
    end tell
end menu_click_recurse
Example:

Here is a (very simple) example demonstrating the use of these functions. To get the example to work, make sure you copy both the helper functions above and the example code following into the same script.
-- This example script turns on the "iTunes Visualizer" visualizer, full screen
tell app "iTunes" to activate
menu_click({"iTunes", "View", "Visualizer", "iTunes Visualizer"})
menu_click({"iTunes", "View", "Turn On Visualizer"})
menu_click({"iTunes", "View", "Full Screen"})
Happy menu-item selecting. Remember to turn on UI scripting before you run these scripts, or they won't work.

[robg adds: I haven't tested this one.]
    •    
  • Currently 3.67 / 5
  You rated: 4 / 5 (15 votes cast)
 
[79,530 views]  

A better way to activate menu items from AppleScript | 25 comments | Create New Account
Click here to return to the 'A better way to activate menu items from AppleScript' hint
The following comments are owned by whoever posted them. This site is not responsible for what they say.
Excellent!
Authored by: jecwobble on Sep 28, '06 10:37:13AM

Thanks for the great hint. I'm sure there is probably a good reason why, but why not include the "tell application X to activate" line at the beginning of your helper function?



[ Reply to This | # ]
Excellent!
Authored by: jakecigar on Sep 28, '06 07:38:31PM

if he moved the activate into the helper, it wouldn't be 100% generic. it would just be a itunes script.



[ Reply to This | # ]
Not quite what I asked
Authored by: jecwobble on Sep 29, '06 08:24:03AM
He passes the app name ("iTunes" in his example) to the helper function. What I was asking is why not use that function argument to tell the appropriate app to activate. Like the "WHY NOT..." part below:

on menu_click(mList)
    local appName, topMenu, r

    -- Validate our input
    if mList's length < 3 then error "Menu list is not long enough"

    -- Set these variables for clarity and brevity later on
    set {appName, topMenu} to (items 1 through 2 of mList)
    set r to (items 3 through (mList's length) of mList)

    -- WHY NOT DO SOMETHING LIKE THIS FIRST?
    tell app appName to activate

    -- This overly-long line calls the menu_recurse function with
    -- two arguments: r, and a reference to the top-level menu
    tell app "System Events" to my menu_click_recurse(r, ((process appName)'s ¬
        (menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click



[ Reply to This | # ]
Not quite what I asked
Authored by: jacobolus on Sep 29, '06 06:47:25PM

Yeah, that makes quite a bit of sense. I guess I never thought about it. One thing worth pondering is that I think some applications may do something if you activate them when they're already frontmost (applications can handle the activate message however they want AFAIK). So if we grab multiple menu items in a row, the app will end up being activated several times. Still, this probably a non-issue, so go ahead and add that line.



[ Reply to This | # ]
Excellent!
Authored by: Cezary Okupski on Apr 19, '09 07:37:54AM

BTW you can go shorter and put it "active application X" instead of "tell application X to activate"



[ Reply to This | # ]
Non english Menus
Authored by: juanfal on Sep 28, '06 12:05:58PM

Its a pity that this really generic and clever function fails with non english menus.



[ Reply to This | # ]
Non english Menus
Authored by: osxpounder on Sep 28, '06 01:03:22PM

I haven't seen a non-English OSX menu, but I'm thinking that, if it's like menus I saw in German PCs years ago, the position of the menu item doesn't change ... only its name changes.

Which might not be any help to know, but, *if* you can write a script that finds menu items to click by some index of their position on the menu, you might be able to craft a script that doesn't care what language the menu itself uses.

What do you think? Is it possible? Crazy?



[ Reply to This | # ]
Non english Menus
Authored by: FG on Sep 28, '06 01:08:32PM

Are you sure ? I just used it with Pages and french menus, and it worked perfectly.
I pasted the code in Script Editor then added these two lines :

tell application "Pages" to activate
menu_click({"Pages", "Insertion", "Note de bas de page"})

And it worked (it inserts a footnote).

FG



[ Reply to This | # ]
A fourth way
Authored by: boredzo on Sep 28, '06 07:29:22PM

Here's a fourth syntax you can use:

tell application "System Events"
	click menu item "Size" &not;
		in menu "Arrange By" of menu item "Arrange By" &not;
		in menu "View" of menu bar item "View" &not;
		in menu bar 1 of process "Finder"
end tell

(“of” and “in” are synonyms, in case you're wondering.)



[ Reply to This | # ]
A fourth way
Authored by: jacobolus on Sep 29, '06 06:50:15PM
Yeah, I know you can get child items in a bottom-up way rather than a top-down one, but it's less intuitive to me. I guess you do avoid all the () and 's in there. I still like my way better ;)

[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: jakecigar on Sep 28, '06 07:35:43PM

that looks great, how about a way to test if a menu item is available, so I can toggle on and off the visualizer!

What a great general pupose script! THANKS!



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: DeltaTee on Sep 29, '06 08:09:10AM

Use a try/on error block to do what you want. If the error block is entered, the menu didn't exist.



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: joeholmes on Sep 29, '06 11:04:49AM

Can any of this be used to select "Login Window..." from the user menu? So far I haven't found a way to set a command key to switch immediately to the login window... (And I haven't found anything via a search of Mac OS X Hints...)

Thanks!



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: coolsoldier on Sep 29, '06 03:12:55PM

It doesn't use this function, but I script the "Login Window" item this way:


tell application "System Events"
	tell process "SystemUIServer"
		tell menu bar 1
			set menuExtras to (value of attribute "AXChildren")
			tell menuExtras
				set userExtra to (first item whose (value of attribute "AXDescription") contains "user")
			end tell
			tell (userExtra)
				perform action "AXPress"
			end tell
			tell (userExtra)
				tell menu 1
					click menu item "Login Window…"
				end tell
			end tell
		end tell
	end tell
end tell

This should work if you are using 10.4.x, use English as your system language, and have the FUS menu displayed in the menu bar. It works for me, at any rate.



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: jacobolus on Sep 29, '06 08:14:59PM

I made a few changes to the script, and pasted it on the pastie pastebin. There are no functional changes, but the code is a bit nicer. One change in this version is the use of "car" and "cdr" instead of "f" and "r", mostly as a joke for the sake of hmelman, the author of the recent Quicksilver manual. Also, apparently it's possible to do items k through end of aList instead of items k through (length of aList) of aList. Nice!

Let me finally put an obligatory plug for using TextMate to write AppleScripts. We've added some very handy snippets that will fill in quite a bit of your code for you. I'm going to try to get a screencast done sometime in the next few weeks showing how to use the bundle to best effect.



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: dwormuth on Dec 10, '06 12:39:12PM

Any particular reason these scripts wouldn't work with Thunderbird?

I'm trying to make a shortcut that will activate the Thunderbird -> Tools -> Delete Mail Marked as Junk in Folder menu item.



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: dwormuth on Dec 10, '06 01:12:50PM

Sorry, I subsequently used the cleaned up script from "pastie" site and it now works fine.



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: kdatt on Jul 30, '08 07:50:06AM

Hi, is there any way to save your menu_click as a script,
then recall it when you make a new script
ie
%%%%%%%%%%%%%%%%%%%%%%
run script file "...:menu click1.scptd"

tell application "Finder" to activate
menu_click({"Finder", "View", "Arrange By", "Size"})
%%%%%%%%%%%%%%%%%%%%%%%%%


that would make it alot easier when creating new scripts that will use you menu click.

cheers, kdatt



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: leenoble_uk on Apr 19, '09 10:01:14AM

You've probably figured this out already as it's been some time since your post. But anyway...

I keep all my common code in a file called Common code.scpt and keep it in my Library/Scripts folder.

(*
PUT THE FOLLOWING LINE AT THE TOP OF ANY SCRIPT YOU LIKE
set commonScript to load script alias ((path to library folder from user domain as string) & "Scripts:Common code.scpt")

AND THEN USE THIS LINE TO CLICK A MENU ITEM
menu_click("Finder", "View", "...", "...") of commonScript
*)


---
Brought to you by S C Johnson, a family multinational conglomerate.



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: jonswar on Oct 09, '09 09:34:03AM

I love this function, but the following

menu_click({"Mail", "Message", "Move To", "Keep"})

Takes 4-10 seconds to execute on my mac. Other applescripts generally execute quickly. Any idea how to profile this to determine what's taking the time? I would love to use this and not struggle through menu expressions as before!



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: dcottle on Nov 30, '10 12:05:38PM

Wonderful hint.

How would you indicate the Finder's Apple menu? I've been looking for a way to engage the recent items for quite some time.

Also, when running

tell application "Finder" to activate
tell application "System Events"
click menu item "Size" of ((process Finder)'s (menu bar 1)'s ¬
(menu bar item "View")'s (menu "View")'s ¬
(menu item "Arrange By")'s (menu "Arrange By"))
end tell

I get this error: The Variable "Finder" is not defined. Should it be in Quotes?

Also, this code:

tell application "System Events"
click menu item "Size" &not;
in menu "Arrange By" of menu item "Arrange By" &not;
in menu "View" of menu bar item "View" &not;
in menu bar 1 of process "Finder"
end tell

gives an error for ";". Is this a typo?



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: dcottle on Nov 30, '10 12:08:21PM

Answer my own question:

&not; should be the end of line character (option-L; I don't know how to type it correctly in this forum).



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: dcottle on Nov 30, '10 12:17:20PM
Answer my other question--how to get the "Apple" menu--(and figured out how to display code):

tell application "Finder" to activate
tell application "System Events"
	click menu item ¬
		"Sleep" in menu "Apple" of menu bar item ¬
		"Apple" in menu bar 1 of process "Finder"
end tell


[ Reply to This | # ]
Half a decade later, this script still ROCKS!
Authored by: mistersquid on May 23, '12 08:00:06PM

Thanks so much for this handy pair of functions. Works great even in Mac OS X 10.7.4.



[ Reply to This | # ]
A better way to activate menu items from AppleScript
Authored by: PDXIII on Sep 26, '13 09:26:28AM

This is still a brilliant helper function, but It does not work when I try to put it in a loop. What’s the problem with that. Sorry, I am not good in applescript.

I would like to make the same action for a couple of selected files in Adobe Acrobat. It works well for a single file but not with a selection.

Greetz,
PDXIII

[code]
-- `menu_click`, by Jacob Rus, September 2006
--
-- Accepts a list of form: `{"Finder", "View", "Arrange By", "Date"}`
-- Execute the specified menu item. In this case, assuming the Finder
-- is the active application, arranging the frontmost folder by date.

on menu_click(mList)
local appName, topMenu, r

-- Validate our input
if mList's length < 3 then error "Menu list is not long enough"

-- Set these variables for clarity and brevity later on
set {appName, topMenu} to (items 1 through 2 of mList)
set r to (items 3 through (mList's length) of mList)

-- This overly-long line calls the menu_recurse function with
-- two arguments: r, and a reference to the top-level menu
tell application "System Events" to my menu_click_recurse(r, ((process appName)'s ¬
(menu bar 1)'s (menu bar item topMenu)'s (menu topMenu)))
end menu_click

on menu_click_recurse(mList, parentObject)
local f, r

-- `f` = first item, `r` = rest of items
set f to item 1 of mList
if mList's length > 1 then set r to (items 2 through (mList's length) of mList)

-- either actually click the menu item, or recurse again
tell application "System Events"
if mList's length is 1 then
click parentObject's menu item f
else
my menu_click_recurse(r, (parentObject's (menu item f)'s (menu f)))
end if
end tell
end menu_click_recurse

-- main function below

tell application "Finder"
try
repeat with currentFile in items of (get selection)
set filepath to currentFile as text
tell application "Adobe Acrobat Pro"
open filepath

menu_click({"Acrobat", "Datei", "Speichern als...", "PDF mit erweiterten Reader-Funktionen", "Kommentieren und messen aktivieren..."})


end tell
end repeat
on error e
return e
end try
end tell

[/code]



[ Reply to This | # ]