How to access submenu item under subheading group with AppleScript - applescript

I can't access the "library-test" submenu item (see screenshot). My guess is that it has to do with the fact that the item is grouped/offset from the other items in the menu with the "Symbols" heading.
I tried treating "Symbols" as its own menu item and that didn't work.
Has anyone run into this before? Thanks!
AppleScript:
tell application "System Events"
tell process "Sketch"
set frontmost to true
click menu item "Rectangle" of menu of menu item "library-test" of menu "Insert" of menu bar 1
end tell
end tell
Error message in Script Editor:
"Can’t get menu item "library-test" of menu "Insert" of menu bar 1 of process "Sketch".
Edit: UI Browser Screen Reader tool suggests that the path is correct:

I'm afraid it's bad news: I just downloaded Sketch and had a play around with the menu hierarchy. Your reference is correct, but it appears it's only valid for a short amount of time after the menu closes. For whatever reason, Sketch seems to hot load particular menu items on the fly, and unload them again when the menu is closed.
I obviously don't have your exist menu combinations, because I don't have those particular symbols, but here's one that you're probably familiar with:
menu item "Document" of menu "Insert" of menu bar item "Insert" of menu bar 1 of process "Sketch"
I had the menu open initially, and then closed it to go into Script Editor and bash out that menu item reference. It worked fine, and I got a bunch of properties and attribute names for it:
{UIProperties:{minimum value:missing value ¬
, orientation:missing value, position:{195, 264} ¬
, class:menu item, accessibility description:missing value ¬
, role description:"menu item", focused:missing value ¬
, title:"Document", size:{173, 19}, help:missing value ¬
, entire contents:{}, enabled:true, maximum value:missing value ¬
, role:"AXMenuItem", value:missing value, subrole:missing value ¬
, selected:true, name:"Document", description:"menu item"} ¬
, UIAttributes:{"AXEnabled", "AXFrame", "AXParent", "AXChildren", ¬
"AXSize", "AXMenuItemCmdGlyph", "AXRole", "AXMenuItemPrimaryUIElement", ¬
"AXServesAsTitleForUIElements", "AXMenuItemCmdModifiers", "AXPosition", ¬
"AXTitle", "AXHelp", "AXMenuItemCmdChar", "AXRoleDescription", ¬
"AXSelected", "AXMenuItemCmdVirtualKey", "AXMenuItemMarkChar"} ¬
, UIActions:{"AXCancel", "AXPress"}}
About 20-30 seconds later, the exact same menu item reference threw an error, reporting that menu item "Document" wasn't found. So I ran this:
name of menu items of menu "Insert" of menu bar item "Insert" of menu bar 1 of process "Sketch"
You can probably infer that this will return the names of all the menu items within the "Insert" menu, and one would expect "Document" to be in there:
{"New Page", missing value, "Shape", "Vector", "Pencil", missing value, "Text", ¬
"Image…", missing value, "Artboard", "Slice", "Hotspot", missing value, missing value}
Nope. For comparison, here's what the menu looks like on screen:
The missing value items are fine—they correspond to a horizontal divider, except the last one, which corresponds to the "Symbol" sub-section header. What's curious is that the list terminates there; there's not even a placeholder for the additional menu items that literally do not exist an anymore (until I open the menu back up again).
I would guess that the only way to get access to menu items like that is to have your script click the menu to open it up, then click the menu item, and so on... Very inelegant, and I personally would rather throw my computer into the sun than do that, but I don't see another option here.
The application is apparently scriptable, but it doesn't come with a terminology dictionary, so there's not a lot we can do there, either.

A peculiarity of AppleScript GUI scripting is that elements of objects are always a to-many relationship, even when we might naturally presume a to-one relationship. You and I know that a menu item can only have one menu attached to it, but AppleScript doesn't, so we still have to specify an index for the menu. Things should work correctly if you write:
click menu item "Rectangle" of menu 1 of menu item "library-test" [...]

There may be some strange character in the menu name, such as a soft hyphen, non-breaking hyphen, non-breaking space, etc. One solution would be to try to get the particular menu item by its index, whatever the name is. A handler to do that would be something like:
on run -- example
clickMenu at {"Sketch", "Insert", 18, "Rectangle"}
end run
to clickMenu at menuList -- {application, menu, menu/item/index, menu/item/index, ...}
try
set itemCount to (count menuList)
if itemCount < 3 then error "clickMenu handler: menu item list contains too few items"
set {appName, menuName} to items 1 thru 2 of menuList
set {menuItem, found} to {last item of menuList, false}
tell application appName to activate
tell application "System Events" -- set up the menu path
set appName to (name of application processes whose frontmost is true) as text -- process name might be different
set menuPath to menu menuName of menu bar item menuName of menu bar 1 of application process appName
if itemCount > 3 then repeat with i from 3 to (itemCount - 1) -- add sub menu items
set menuName to item i of menuList
if class of menuName is integer then set menuName to item menuName of (get name of menu items of menuPath)
set menuPath to menu menuName of menu item menuName of menuPath
end repeat
if class of menuItem is not integer then -- go for a a partial name match of the last item
repeat with anItem in (get name of menu items of menuPath)
if anItem begins with menuItem then -- can also use 'contains', etc
set {menuItem, found} to {anItem, true}
exit repeat
end if
end repeat
if not found then error "clickMenu handler: menu item " & quoted form of menuItem & " not found"
end if
if (enabled of menu item menuItem of menuPath) then -- or filter for other desired property
click menu item menuItem of menuPath
delay 0.5
return true -- success
end if
return false -- menu item not clicked
end tell
on error errorMessage number errorNumber
-- error errorMessage number errorNumber -- uncomment to pass error up the chain
log errorMessage
return false
end try
end clickMenu

Related

How to use AppleScript check if a menu item checked

see my image
I want to get the value of the menu item "Set as system proxy" (whether if it has been checked).
The problem is if I want to get that value I have to click that menu first and find the menu item. But I want it to be done in the background since I want it to be executed every 5 seconds.
My code goes like this
tell application "System Events"
tell process "ClashX"
click (menu bar itm 1 of menu bar 2)
get value of attribute "AXMenuItemMarkChar" of menu item "Set as system proxy" of menu 1 of menu bar item 1 of menu bar 2
end tell
end tell
(^^^^ This opens that menu over and over again)
and when I delete the line "click (menu bar itm 1 of menu bar 2)", which is
tell application "System Events"
tell process "ClashX"
get value of attribute "AXMenuItemMarkChar" of menu item "Set as system proxy" of menu 1 of menu bar item 1 of menu bar 2
end tell
end tell
the script cannot be done, error "System Events got an error: Can’t get menu 1 of menu bar item 1 of menu bar 2 of process "ClashX". Invalid index." number -1719 from menu 1 of menu bar item 1 of menu bar 2 of process "ClashX"
If you want to know whether or not the Set as system proxy menu item is checked for ClashX, you can check the value of the proxyPortAutoSet key in its preferences plist file, e.g.:
In Terminal:
defaults read com.west2online.ClashX 'proxyPortAutoSet'
Returns 1 when checked, and 0 when not checked.
If you want to use it in AppleScript, use a do shell script command, e.g.:
set menuItemIsChecked to ¬
(do shell script ¬
"defaults read com.west2online.CrashX 'proxyPortAutoSet'") ¬
as boolean
if menuItemIsChecked then
# Your code goes here.
end if

Automator script to automatic toggle menu setting in Simulator.app

I need to automate the process of enrolment of Face ID and Touch ID for my UITests. For this purpose, I'm working on an automator script.
My current automator script, which at the moment can automatic click "Enrolled" in the menu:
on run {input, parameters}
if application "Simulator" is running then
tell application "System Events"
set theName to name of the first process whose frontmost is true
end tell
tell application "Simulator" to activate
tell application "System Events"
tell process "Simulator"
tell menu bar 1
tell menu bar item "Hardware"
tell menu "Hardware"
tell menu item "Face ID"
tell menu "Face ID"
click (menu item "Face ID" where its name starts with "Matching")
end tell
end tell
end tell
end tell
end tell
end tell
end tell
tell application theName to activate
end if
return input
end run
The problem is as following. I need to determine, if the device already is enrolled. There is a checkmark, which shows the current state. I have tried to check if the checkmarks is there or not. But I have not been able to make it work yet.
So my question. How can I do, so the script only will press the 'Enrolled' menu item, if the checkmark isn't there?
There's an attribute of menu items called AXMenuItemMarkChar, which is either set to the character representing a check mark next to the menu item (if checked), or missing value.
use application "System Events"
tell application "Simulator" to activate
tell process "Simulator" to tell menu bar 1 to ¬
tell menu bar item "Hardware" to tell menu "Hardware" to ¬
tell menu item "Face ID" to tell menu "Face ID" to ¬
tell menu item "Enrolled"
if the value of the attribute "AXMenuItemMarkChar" is not "✓" then ¬
click it
delay 1 -- !important
return the value of the attribute "AXMenuItemMarkChar"
end tell
In my testing, the attribute returns the correct value if the Simulator app is in focus at the time. The return value of this script should always be "✓", because if the menu item isn't checked, then the script proceeds to click it and put a check mark next to it; and if the menu item is checked, then there's nothing to do but confirm that it is by getting the attribute's value.
One of the issues with your script is that you had the wrong reference to a non-existent menu item (menu item "Face ID" of menu "Face ID" of menu item "Face ID"), and then proceeded to filter it with a where clause against a name that had a different value; hence it would return no value, and you'd have no menu item to click.
Hopefully my script will work for you. Let me know if you have any problems.
The AppleScript contained in this answer was tested on MacOS 10.13. It is an example script to illustrate how to meet your objective. The delay command may need to be adjusted according to your system, and the script may benefit from some error-handling to make it robust.

How to set iTunes 11 in shuffle or repeat mode via applescript

According to http://dougscripts.com/ setting shuffle and repeat modes via applescript is broken in iTunes 11.
According to this stackoverflow answer shuffle is now a playlist independent setting.
Thus, I tried to set the shuffle value via the UI, either by the LCD-ish display of iTunes or via the menu bar. All I could get was "unknown UI index" errors when trying to click the shuffle button/menu item, either in the LCD area or the menu bar. (I'm new to applescript).
If some of you could come up with a way to toggle shuffle mode on iTunes 11, that would be great. Also I'd prefer a solution based on the menu bar rather than the LCD display since the shuffle button is not always visible in the latter.
Ideally, I'd prefer a semantic-based solution over a UI-based solution but I'm not sure if it's possible (iTunes 11 applescript library seems to be outdate since it mention a "shuffle" property for "playlists" items).
For the new Music app, this works. If you're using iTunes still, change "Music" to "iTunes".
Repeat can be set to one or off or all.
tell application "Music"
set song repeat to off
end
Shuffle can be set to true or false.
tell application "Music"
set shuffle enabled to true
end
You can find more details at Dougscripts.
I liked John Sauer's approach so much I wrote myself some getters/setters for these properties using his approach. It's works well because you do not have to activate iTunes before using them. Anyway, I thought I'd post them in case they're of help to anyone. You will get or set their values using the "types" (modeled after the menu item names) as follows:
Repeat types are "Off", "All", or "One".
Shuffle types are "Off", "By Songs", "By Albums", or "By Groupings"
on getRepeatType() -- the return value is a string: Off/All/One
tell application "System Events"
tell process "iTunes"
set menuItems to menu items of menu bar 1's menu bar item "Controls"'s menu 1's menu item "Repeat"'s menu 1
set currentChoice to "unknown"
repeat with anItem in menuItems
try
set theResult to value of attribute "AXMenuItemMarkChar" of anItem
if theResult is not "" then
set currentChoice to name of anItem
exit repeat
end if
end try
end repeat
end tell
end tell
return currentChoice
end getRepeatType
on setRepeatType(repeatType) -- repeatType is a string: Off/All/One
set currentValue to my getRepeatType()
ignoring case
if currentValue is not repeatType then
tell application "System Events" to tell process "iTunes"'s menu bar 1's menu bar item "Controls"'s menu 1's menu item "Repeat"'s menu 1
if repeatType is "all" then
perform action "AXPress" of menu item "All"
else if repeatType is "one" then
perform action "AXPress" of menu item "One"
else
perform action "AXPress" of menu item "Off"
end if
end tell
end if
end ignoring
end setRepeatType
on getShuffleType() -- the return value is a string: Off/By Songs/By Albums/By Groupings
tell application "System Events"
tell process "iTunes"
set menuItems to menu items of menu bar 1's menu bar item "Controls"'s menu 1's menu item "Shuffle"'s menu 1
set onOffItemName to name of item 1 of menuItems
end tell
end tell
-- is shuffle off
ignoring case
if onOffItemName contains " on " then return "Off"
end ignoring
-- shuffle is on so find how we are shuffling
set currentChoice to "Unknown"
tell application "System Events"
tell process "iTunes"
repeat with i from 2 to count of menuItems
set anItem to item i of menuItems
try
set theResult to value of attribute "AXMenuItemMarkChar" of anItem
if theResult is not "" then
set currentChoice to name of anItem
exit repeat
end if
end try
end repeat
end tell
end tell
return currentChoice
end getShuffleType
on setShuffleType(shuffleType) -- shuffleType is a string: Off/By Songs/By Albums/By Groupings
set currentValue to my getShuffleType()
script subs
on toggleShuffleOnOff()
tell application "System Events" to perform action "AXPress" of (first menu item of process "iTunes"'s menu bar 1's menu bar item "Controls"'s menu 1's menu item "Shuffle"'s menu 1 whose name ends with "Shuffle")
end toggleShuffleOnOff
on pressBySongs()
tell application "System Events" to perform action "AXPress" of (first menu item of process "iTunes"'s menu bar 1's menu bar item "Controls"'s menu 1's menu item "Shuffle"'s menu 1 whose name ends with "Songs")
end pressBySongs
on pressByAlbums()
tell application "System Events" to perform action "AXPress" of (first menu item of process "iTunes"'s menu bar 1's menu bar item "Controls"'s menu 1's menu item "Shuffle"'s menu 1 whose name ends with "Albums")
end pressByAlbums
on pressByGroupings()
tell application "System Events" to perform action "AXPress" of (first menu item of process "iTunes"'s menu bar 1's menu bar item "Controls"'s menu 1's menu item "Shuffle"'s menu 1 whose name ends with "Groupings")
end pressByGroupings
end script
ignoring case
if shuffleType contains "off" then -- we have to make sure it's off
if currentValue does not contain "off" then subs's toggleShuffleOnOff()
else
-- make sure it's on
if currentValue contains "off" then subs's toggleShuffleOnOff()
-- select the shuffle menu item for the type
if shuffleType contains "song" and currentValue does not contain "song" then
subs's pressBySongs()
else if shuffleType contains "album" and currentValue does not contain "album" then
subs's pressByAlbums()
else if shuffleType contains "group" and currentValue does not contain "group" then
subs's pressByGroupings()
end if
end if
end ignoring
end setShuffleType
I was optimistic when I saw the AppleScript property current playlist of the iTunes application, but it doesn't work well. It's able to get and set the current playlist's name, but it can do neither for the properties shuffle or song repeat. It errors when trying to set either property, and it always returns 'false' for shuffle and 'off' for song repeat.
I think your only option is UI Scripting. Here's how to toggle shuffle through the menu bar:
tell application "System Events" to perform action "AXPress" of (first menu item of process "iTunes"'s menu bar 1's menu bar item "Controls"'s menu 1's menu item "Shuffle"'s menu 1 whose name ends with "Shuffle")
And here's how to set repeat:
tell application "System Events" to tell process "iTunes"'s menu bar 1's menu bar item "Controls"'s menu 1's menu item "Repeat"'s menu 1
perform action "AXPress" of menu item "Off"
perform action "AXPress" of menu item "All"
perform action "AXPress" of menu item "One"
end tell
For iTunes 12, this works
tell application "System Events"
tell process "iTunes" to if exists then
click menu item "Albums" of menu "Shuffle" of menu item "Shuffle" of menu "Controls" of menu bar 1
end if
end tell
Change Albums to Songs, Groupings, On and Off accordingly.
Here is another approach:
activate application "iTunes"
tell application "System Events"
tell process "iTunes"
click menu item 1 of menu 1 of menu item "Shuffle" of menu 1 of menu bar item "Controls" of menu bar 1
end tell
end tell
It looks like many of these scripts are broken in the newest iTunes. Here are two the work:
tell application "System Events" to tell UI element "iTunes" of list 1 of process "Dock"
if not (exists) then return
perform action "AXShowMenu"
click menu item "Shuffle" of menu 1
end tell
That one toggles shuffle via the Dock. You can watch the Dock menu animate when you use it.
This one toggles shuffle via the menu, invisibly:
tell application "System Events"
tell application process "iTunes"
tell menu 1 of menu item "Shuffle" of menu "Controls" of menu bar 1
if (value of attribute "AXMenuItemMarkChar" of item 1 of menu items as string) = "✓" then
click menu item 2
else
click menu item 1
end if
end tell
end tell
end tell
Both of these will work even with iTunes is in the background.
Spent some time deconstructing all the obfuscated solutions in this post (which appear to not work anymore), here's a more readable and customizable approach that works with iTunes 12.1:
tell application "System Events"
set itunesMenuBar to process "iTunes"'s first menu bar
set controlsMenu to itunesMenuBar's menu bar item "Controls"'s first menu
set shuffleMenu to controlsMenu's menu item "Shuffle"'s first menu
set shuffleOnMenuItem to shuffleMenu's menu item "On"
set shuffleSongsMenuItem to shuffleMenu's menu item "Songs"
tell process "iTunes"
click shuffleOnMenuItem
click shuffleSongsMenuItem
end tell
end tell
This will turn shuffle on and set it to shuffle songs instead of albums, and it should be pretty obvious how to change it to do other things.

Accesing third party menu extras (menulets) via applescript?

Is it possible to access third party menulets via applescript? (Those icons displayed in the top right corner of the global menu bar).
I basically want to know if a certain menu item (that is shown when you click the icon) is enabled or disabled (greyed out).
Any resources on this?
Thanks
Yes, menu items have an "enabled" property. That property is false for "greyed out" menu items. So for example, I show the clock menulet in my main menu bar. If I wanted to know the enabled property of each of its menu items I could do this...
tell application "SystemUIServer" to activate
set theProps to {}
tell application "System Events"
tell process "SystemUIServer"
set menulets to menu bar items of menu bar 1
repeat with aMenu in menulets
if (description of aMenu) is "clock" then
click aMenu -- we have to open it to access the menu items inside it
delay 0.2
set clockMenuItems to menu items of menu 1 of aMenu
repeat with aMenuItem in clockMenuItems
set end of theProps to {title of aMenuItem, enabled of aMenuItem}
end repeat
end if
end repeat
end tell
end tell
return theProps
Note that some of those menulets are not regular menulets. Those you have to treat differently but the concept is the same. You click the menulet then access its menu items and check their enable property.

In Applescript, how can I find out if a menu item is selected/focused?

I have a script for OS X 10.5 that focuses the Search box in the Help menu of any application. I have it on a key combination and, much like Spotlight, I want it to toggle when I run the script. So, I want to detect if the search box is already focused for typing, and if so, type Esc instead of clicking the Help menu.
Here is the script as it stands now:
tell application "System Events"
tell (first process whose frontmost is true)
set helpMenuItem to menu bar item "Help" of menu bar 1
click helpMenuItem
end tell
end tell
And I'm thinking of something like this:
tell application "System Events"
tell (first process whose frontmost is true)
set helpMenuItem to menu bar item "Help" of menu bar 1
set searchBox to menu item 1 of menu of helpMenuItem
if (searchBox's focused) = true then
key code 53 -- type esc
else
click helpMenuItem
end if
end tell
end tell
... but I get this error:
Can’t get focused of {menu item 1 of menu "Help" of menu bar item "Help" of menu bar 1 of application process "Script Editor" of application "System Events"}.
So is there a way I can get my script to detect whether the search box is already focused?
I solved my problem by working around it. I still don't know how to check if a menu item is selected though, so I will leave this topic open.
You need to use attribute AXMenuItemMarkChar.
Example:
tell application "System Events"
tell process "Cisco Jabber"
set X to (value of attribute "AXMenuItemMarkChar" of menu item "Available" of menu "Status" of menu item "Status" of menu "File" of menu bar item "File" of menu bar 1) is "✓" -- check if Status is "Availible"
end tell
end tell
If the menu item is checked, the return value is ✓, otherwise it is missing value.
Note: This test only works if the application whose menus are being inspected is currently frontmost.
The built in key shortcut Cmd-? (Cmd-Shift-/) already behaves like this. It moves key focus to the help menu's search field if it is not already focused, and otherwise dismisses the menu.
Using /Developer/Applications/Utilities/Accessibility Tools/Accessibility Inspector.app you can use the built-in accessibility system to look at properties of the UI element under the mouse. Take special note of the cmd-F7 action to lock focus on an element and the Refresh button. Sadly the element and property names don't directly match those in the script suite, but you can look at the dictionary for System Events or usually guess the right terminology.
Using this you can determine two things. First, the focused property isn't on the menu item, but rather there is a text field within the menu item that is focused. Second, the menu item has a selected property.
With this, I came up with:
tell application "System Events"
tell (first process whose frontmost is true)
set helpMenuItem to menu bar item "Help" of menu bar 1
-- Use reference form to avoid building intermediate object specifiers, which Accessibility apparently isn't good at resolving after the fact.
set searchBox to a reference to menu item 1 of menu of helpMenuItem
set searchField to a reference to text field 1 of searchBox
if searchField's focused is true then
key code 53 -- type esc
else
click helpMenuItem
end if
end tell
end tell
Though this still doesn't work. The key event isn't firing as far as I can tell, so something may still be hinky with the focused property on the text field.
Anyway, your click again solution seems much easier.
I just came across the need to do this myself for some file processing in Illustrator.
Here is what I came up with:
tell application "Adobe Illustrator"
activate
tell application "System Events"
tell process "Illustrator"
set frontmost to true
set activeMenuItem to enabled of menu item "Unlock All" of menu "Object" of menu bar item "Object" of menu bar 1
if activeMenuItem is true then
tell me to beep 3
else
tell me to beep 2
end if
end tell
end tell
end tell
Done.
This worked with no problem and could be used to iterate a file. I'll probably have to do this many more times in my future automation.
Good luck!
This worked for me to toggle between two menu items, based on which one is selected, using the "selected" property:
tell application "System Preferences"
reveal anchor "keyboardTab" of pane "com.apple.preference.keyboard"
end tell
tell application "System Events" to tell process "System Preferences"
tell pop up button 2 of tab group 1 of window 1
click
delay 0.2
set appControl to menu item "App Controls" of menu 1
set fKeys to menu item "F1, F2, etc. Keys" of menu 1
if selected of appControl is true then
click fKeys
else
click appControl
end if
end tell
end tell

Resources