Scripting arbitrary menubar itemns

applescriptui

So I really want to fix this applescript I'm working on. I would like to be able to click an arbitrary item in an arbitrary submenu of an arbitrary menu in any application. Here's where I've gotten:

tell application "System Events"
    set activeApp to name of first application process whose frontmost is true
end tell

tell application "System Events" to tell process activeApp
    tell menu bar item 1 of menu bar item 1 of menu bar 1
        click
    end tell
end tell

The problem I'm getting is when I add that second "of menu bar item 1". It worked just fine up until I got to that point and now I don't know what to do? I'm pretty new to UI scripting and applescript.

Best Answer

The hierarchy of the menu bar object goes like this:

menu bar > menu bar item > menu > menu item [ > menu > menu item [...] ]

where the square brackets indicate possible sub-menus, should they exist.

Therefore, if you change this line:

    tell menu bar item 1 of menu bar item 1 of menu bar 1

to this:

    tell menu 1 of menu bar item 1 of menu bar 1

it will work.

This, of course, will always click the Apple () menu, which is menu bar item 1 of menu bar 1 for all applications in which the menu bar exists and is visible (although it belongs to whichever application is frontmost, so attempting to ask another application process to click this item will throw an error).

If you wish to click an arbitrary menu item in the menu bar to reveal its menu, you need to replace the index number 1 of the menu bar item with a random value, but one that does not exceed the number of available menu bar items.

To get the number of menu bar items, use the count command:

    set N to count menu bar items of menu bar 1 of activeApp

(You can omit of activeApp if the command is placed inside a tell process activeApp block.)

To get a random number between 1 and N, use the random number command:

    set i to random number from 1 to N

Putting that all together, your new command will look something like this:

    tell menu bar item i of menu bar 1 to click

Now each time you run the script, a random menu bar item will be clicked causing its menu to appear.

To go even further, and get your script to click a random menu item, you can apply exactly the same principles as above, bearing in mind the hierarchy I laid out at the top.

So, after retrieving a random menu bar item, next determine the maximum number of menu items and generate a random number less than or equal to this maximum:

    set M to count menu items of menu 1 of menu bar item i of menu bar 1
    set j to random number from 1 to M

Then you can click it:

    click menu item j of menu 1 of menu bar item i of menu bar 1

Applying the same principles to any sub-menus that exist for that particular menu item, you'll end up with this:

    set L to count menu items of menu 1 of menu item j of menu 1 of menu bar item i of menu bar 1
    set k to random number from 1 to L
    click menu item k of menu 1 of menu item j of menu 1 of menu bar item i of menu bar 1

However, you'll find that this—more often than not—will throw an error when trying to set L. This is because not all menu items will have sub-menus, and in the instances where no sub-menu exists, asking AppleScript to count the number of menu items [in a non-existent sub-menu] is not a sensible command.

There are two ways to handle this. Either wrap those lines in a try...end try error-catching block, like this:

    try
        set L to count menu items of menu 1 of menu item j of menu 1 of menu bar item i of menu bar 1
        set k to random number from 1 to L
        click menu item k of menu 1 of menu item j of menu 1 of menu bar item i of menu bar 1
    end try

or count the number of (sub-)menus of menu item j, and only proceed if this number is greater than 0:

    count menus of menu item j of menu 1 of menu bar item i of menu bar 1

    if the result > 0 then
        set L to count menu items of menu 1 of menu item j of menu 1 of menu bar item i of menu bar 1
        set k to random number from 1 to L
        click menu item k of menu 1 of menu item j of menu 1 of menu bar item i of menu bar 1
    end if

And so on, and so on...

Finally...

It's unlikely you will know precisely how many levels deep a particular menu tree goes: how many sub-menus within sub-menus exist for a particular menu item ? The answer is at least 0, but with no given maximum unless you check in advance yourself.

Luckily, we can AppleScript to do the checking for us. After picking a random menu bar item, every child that spawns from this (looking at the hierarchy I showed you at the top) is a menu followed by a menu item. The menu object, as you'll have noticed, only comes in singular count, so will always be referenced as menu 1. The menu items come in plural, and these are the items that need counting.

So, quite simply, having chosen a random menu bar item, we get AppleScript to do the following:

  1. count the number of menu items it contains;
  2. If this value is 0, then there are no sub-menus, so we can just click the item we have;
  3. If the value is greater than 0, then choose a random one;
  4. count the number of menu items this one contains;
  5. If this value is 0, just click it;
  6. If the value is greater than 0, then choose a random one;
  7. etc..

This process is a loop that can keep going for as many sub-menus that it finds. And that's what this code does:

    tell application "System Events" to ¬
        tell (the first application process ¬
            whose frontmost is true)
            
            set m to it
            set _m to the menu bar items of menu bar 1
            
            repeat
                if not (exists _m) then exit repeat
                
                set i to random number from 1 to (count _m)
                
                set m to item i of _m
                set _m to a reference to menu items of menu 1 of m
            end repeat
            
            click m
            
        end tell

Caveat: The horizontal lines that separate sections of a menu are also classed as menu items, as are any menu items that are disabled. These, when clicked, don't do anything.


BE CAREFUL: Clicking a menu item randomly before you know what it does can result in some undesirable outcomes. For instance, you may accidentally quit the front application; you may delete a file or eject a disk if the script runs whilst Finder is at the front; or you may log out or shut down your computer.