Understanding AppleScript UI-Scripting to Click Menus

I am by no means an expert when it comes to AS. I can look at the code and "kinda" understand what's happening, if it's super basic.
Looking at another script and after some trial and error, I was able to click a certain menu item in Logic, but then the other options inside that menu aren't clicked.
Here's what I have so far:

activate application "Logic Pro X"
tell application "System Events"
	tell process "Logic Pro X"
		
		click menu button 2 of group 1 of group 1 of group 3 of window 1
		click menu item 9 of menu 1 of menu item 23 of menu 1 of group 1 of group 1 of group 3 of window 1
		
	end tell
end tell

So if I remove the second "click" line, I am able to click the Functions button:
image

But if I include the second "click" it doesn't go to the Humanize option:
image

I know I could use the Insert by Typing / Pasting action, but can't this be done with just the AS? I tried with 2 different actions for the 2 different clicks, but no luck...

This is what UI Browser shows me when I hover over Humanize:
image

@noisneil are you there?

I just found another issue, maybe someone can help me understand this?
So when I open multiple windows, that script may no longer work, because what used to be "window 1" is now "window 2" for example. I'm experiencing this right now.

Is there a way to select the upfront window, regardless of its name?

Looking at the screenshot, I can see that this is not a native Mac app. It's UI is constructed from unconventional components, similar in appearance to Adobe apps. So the fact that thess are exposed at all to accessibility is a credit to the devs who must have been conscientious.

Were you dealing with a traditional macOS menu in the menu bar, you woukdn't need to click or open the individual menu nodes as all menu items are kept in active memory to be triggered directly without having to be visible on screen.

I suspect this is not the case with this app. But let's test:

activate application "Logic Pro X"

tell application "System Events" to tell the process ¬
		"Logic Pro X" to tell window 1's group 3's ¬
		group 1's group 1
		tell a reference to the menu button 2 to ¬
				if not (exists) then return false

		tell a reference to its first menu ¬
				to if exists then return ¬
				the entire contents

		return true
end tell

There are three possible outcomes to this test provided it executes successfully: if it returns a whole list of object references, the news is good; f it returns true, which is what I suspect it will return, then see below. If it returns false, then there's something preventing it recognising the menu button "Functions".

On the assumption that you get the value true returned back from the above script, then it infers that the issue is that whatever is not displayed on screen physically does not exist at that point in time (at least, not to System Events's knowledge).

What the above script would confirm is that you need to click the "Functions" button to bring up the menu in order to have access to its menu items. Note that the same would then be true for its submenus, in which the "Humanize" menu item resides. You therefore skipped a step, namely clicking on the menu item to which that particular submenu is attached and would be made visible. I can see in the reference you constructed to menu item 9 ("Humanize") that there's a reference to a menu item 23, which would be the "MIDI Transform" menu item that is never clicked by your script.

So, this is the likely bare minimum you'd need to stand a chance of getting access to that submenu;

tell application "System Events" to tell the process ¬
		"Logic Pro X" to tell window 1's group 3's ¬
		group 1's group 1
		tell a reference to the menu button 2
				if not (exists) then error it
				click it
		end tell
		tell a reference to menu item 23 ¬
				of the first menu of it
				my (wait for it)
				click it
				tell a reference to menu 1's ¬
						menu item 9 of it
						my (wait for it)
						click it
				end tell
		end tell
end tell


to wait for object
		tell application "System Events" to repeat 10 times
				if the object exists then return
				delay 0.5
		end repeat
		error "Timeout" from object
end wait

Yea, the way you did in your first script, referencing it by index number. It was index 1, but then you clicked another window and that one got given the index 1, while your former index 1 got relegated to index 2.

This tells you that the window with index 1 will always point to whichever window is currently active and frontmost. Therefore, just refer to it as window 1 of...

You may have tried to use the screen reader to grab the UI element path in UI Browser and noticed that it doesn't collect the path when you click Find in Browser. When this happens, it's an indicator that you need to do some manual bits and pieces, at least in my experience.

Manually typing in the element path seen when hovering over it doesn't work, so it comes down to typing, as you already know. You can do this within AppleScript if you like, but it's long-winded compared to using KM actions. You'll notice that I'm referencing the first window whose title contains "- Tracks", which answers your second question. You'll also notice that the typing action uses capital letters so as not to interfere with Logic key commands (e.g. mute region is m on my system).

Select Humanize.kmmacros (21 KB)

Macro screenshot

P.S. check your messages. :wink:

What do you mean by "native" in this case? Apps that come originally with the macOS? Such as Reminders, Notes, etc? If so, then yes, this is NOT native.
This is Logic Pro X, which is made by Apple, but doesn't come with macOS.
Hope this is what you mean...

I tried it, but nothing happens. I see the KM icon rotating, but then it stops.

It doesn't run either. I get an error:
image

I actually typed this manually. I screenshot it, because I wasn't able to get the full script for that sub-sub menu.

That's a good one. Will try it. I was checking another macro you shared with me, but didn't think about that TITLE thing. Cool!

Yes, I was having the same issue with the letter R, which I use to Rename regions. That's a good tip :slight_smile:

So I guess since UI Browser is always upfront to make it possible to find the UI element, UI Browser is always window 1, right? But if I close UI Browser, then Logic will become window 1 (if that's the app I'm using, of course...)

I was looking at your macro. So basically you can't access Humanize all via AS, right?
I used the same workflow as you, with typing, right arrow, typing, Enter.
I mean, it doesn't bother me at all, as long as it works, but was curious if that was possible all within AS? Apparently it's not, or not as easy as it sounds. No worries :slight_smile:
Thanks for sharing more tips!

For some reason, I had to change "group 4" to "group 3"... I guess Apple has probably made changes when it comes to the new version. I'm still on 10.6.3 so maybe that's why. But it's working now that I made that change!

And I didn't know you could actually type the name of the button as you did for "Functions". Awesome!

UI Browser spits this out for you. Try it for yourself.

  • Click Switch to Screen Reader in UI Browser
  • Hover over "Functions" in Logic
  • Hold ⌘ and click Find in Browser in UI Browser
  • Click the AppleScript dropdown
  • Select Click Selected Element

Yeah I saw that before, but I thought I was only able to use what's inside the parenthesis. Good to know that there's this other option.
Always learning a little bit more...
Thanks

It's far quicker to let UI Browser generate the code for you than to manually type it. :+1:t3:

Yes, but as I mentioned, I wasn't able to get the code for the Humanize sub menu, that's why I typed that section. The one for the Functions, I just copied it, but the thing is that UI Browser gives you the code "backwards", compared to the AS version which is "
button 2 > button 1 > window.
UI Browser is window > button 1 > button 2

Or am I missing something?

Yeah, it's a bit flummoxing when UI Browser doesn't return anything in its Browser. So in this case, what I would do is find a the point in the path that it will return, which is clicking the "Function" menu and then go from there. It does generate a correct code for that menu.

1 Like

I gotta explore it a bit more. I don't use it that often so I still don't know all the tricks. But I'll get better with time. Thanks for sharing these tips!

No, because windows are "contained by" applications so you have to refer to the application as well. You can do that explicitly, as @CJK did, or implicitly by putting it inside a tell application... block.

Using TextEdit as an example:

name of window 1 of application "TextEdit"

or

tell application "TextEdit"
	name of window 1
end tell
1 Like

Unless of course you do something like...

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

Incidentally, I've never been sure why tell application "System Events" needs to be stated twice.

You're still putting the window into context -- in this case, window 1 of application process frontApp of application "System Events".

Again, if you look at the "System Events" AS Dictionary you'll see that windows are "contained by" processes and processes are "contained by" the application "System Events" -- you can't refer to window 1 in the raw, you have to refer to it from where you currently are.

It's like paths in the Finder -- say you've got a subfolder called Important Docs in your document folder. If you are in your home folder you'd "go to Important Docs inside Documents", but if you're already in Documents you'd just "go to Important Docs".

Depends on how you write things. A one-line block has an implicit end at the end -- you can do the same with if:

if theResult is 1 then return "True"

...is the same as:

if theResult is 1 then
    return "True"
end if

So what you're doing above is

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

...which you could rewrite as

tell application "System Events"
	set frontApp to name of first process whose frontmost is true
	tell application process frontApp
		name of window 1
	end tell
end tell
1 Like

Great explanation. Thanks!