Check if menu item is available

I was wrong.

The code was testing the availability of a shortcut.


tell application "System Events"
	tell (first application process whose frontmost is true) -- Or a named, frontmost process.
		tell menu 1 of menu bar item 4 of menu bar 1
			set nbMenuItem to count every menu item
			set liste to {}
			repeat with i from 1 to nbMenuItem
				set mName to get name of menu item i
				try
					mName
				on error
					set mName to missing value
				end try
				set mAttr to get value of attribute "AXMenuItemCmdChar" of menu item i
				try
					mAttr
				on error
					set mAttr to missing value
				end try
				set end of liste to {mName, mAttr}
			end repeat
		end tell
	end tell
end tell
liste
--> {{"Annuler", "Z"}, {"Rétablir", "Z"}, {missing value, missing value}, {"Couper", "X"}, {"Copier", "C"}, {"Coller", "V"}, {"Coller et appliquer le style actuel", "V"}, {"Coller une référence", "V"}, {"Supprimer", missing value}, {"Tout sélectionner", "A"}, {missing value, missing value}, {"Copier le style", "C"}, {"Coller le style", "V"}, {missing value, missing value}, {"Compléter", missing value}, {missing value, missing value}, {"Rechercher", missing value}, {"Orthographe", missing value}, {"Parole", missing value}, {missing value, missing value}, {"Démarrer Dictée", missing value}, {"Caractères spéciaux.", "T"}}

Yvan KOENIG (VALLAURIS, France) mercredi 20 février 2013 17:17:11

You are too naive or too optimistic (maybe both).

They are a lot of « rules » defined for instance in « Mac OS X Human Interface Guidelines » which are violated by third party developer and alas by Apple developers too.

If you are curious like me, under 10.8.2, open the System Preference « Desktop and Energy Saver » and close it.
Open Disk Utility and select the Partition tab and quit the app.
After that, open the Console and search for the word « deprecated » and you will see how Apple apply its own rules.
So, why third party developers would apply them ?
As you are using AppleScript, you will also see deprecated calls which were already flagged in 10.7.

Yvan KOENIG (VALLAURIS, France) mercredi 20 février 2013 17:29:10

Sorry for digging this old thread out, but I’m just about to work on the release of Markdown for Keyboard Maestro 2. This used to work, but now I’m on Mavericks beta 3 and it doesn’t anymore. I tested this with a virtual Mountain Lion and it still works there. Does anyone know how to make this work again? Accessibility for AppleScript Editor and System Events need to be ticked on in the privacy settings.

Here’s the script, just in case.

tell application "System Events"
	tell (first application process whose frontmost is true) -- Or a named, frontmost process.
		set CutEnabled to (enabled of first menu item of menu 1 of menu bar item 4 of menu bar 1 whose value of attribute "AXMenuItemCmdChar" is "X" and value of attribute "AXMenuItemCmdModifiers" is 0)
	end tell
end tell

I’m puzzled by Mavericks behavior.
I tested with Safari but it behave the same in other applications.
menu item 2 of menu 4 is cmd + shift + Z
its attribute “AXMenuItemCmdModifiers” is true (10.8.4 → 1)

menu item 4 of menu 4 is cmd + X
its attribute “AXMenuItemCmdModifiers” is false (10.8.4 → 0)

menu item 5 of menu 4 is cmd + C
its attribute “AXMenuItemCmdModifiers” is false (10.8.4 → 0)

menu item 6 of menu 4 is cmd + V
its attribute “AXMenuItemCmdModifiers” is false (10.8.4 → 0)

menu item 7 of menu 4 is cmd +option + shift + V
its attribute “AXMenuItemCmdModifiers” is true (10.8.4 → 3)

I don’t know if it’s a new feature or if it’s a bug.

Alas, since 2013/06/11 I am unable to enter the SEEDs area (Apple is aware of that) so I can’t report oddities.

I hope that somebody here will be able to file a report about this new behavior to get the truth about it.

Yvan KOENIG (VALLAURIS, France) samedi 20 juillet 2013 14:27:33

I’m back under 10.8.4. and added the vlues returned in this system.
As you may see, the new behavior is annoying because it’s not so precise than the old one.

Thanks, Yvan, very helpful. I did not know about the odd behavior you mention. I’m a bit farther though with my problem.

The script errors on this line:

tell application "System Events"
	tell (first application process whose frontmost is true)
		tell menu 1 of menu bar item 4 of menu bar 1
			set cutEnabled to (first menu item whose value of attribute "AXMenuItemCmdChar" is "X")
			-- Invalid index
		end tell
	end tell
end tell

Although the following:

tell application "System Events"
	tell (first application process whose frontmost is true)
		tell menu 1 of menu bar item 4 of menu bar 1
			get properties of attribute "AXMenuItemCmdChar" of menu item 4
		end tell
	end tell
end tell

returns:

--{value:"X", class:attribute, settable:false, name:"AXMenuItemCmdChar"}

I will restart Mavericks because I just checked the values returned, not the behavior with the used syntax.

KOENIG Yvan (VALLAURIS, France) samedi 20 juillet 2013 15:18:15

Hello

Some things are really bad.
I tested with TextEdit so I was able to run the script with nothing selected : menu item inactive
or with something selected : menu item active
(1) the whose filter is no longer usable. We must use something like that :


tell application "System Events"
	set theProcess to (first application process whose frontmost is true)
	
	set theProcess to process "TextEdit"
	tell theProcess
		tell menu 1 of menu bar item 4 of menu bar 1
			#set cutEnabled to (properties of (get first menu item whose value of attribute "AXMenuItemCmdChar" is "X"))
			-- Invalid index
			set theValues to value of attribute "AXMenuItemCmdChar" of every menu item
			set {maybe, cutenabled} to {0, false}
			repeat with i from 1 to count theValues
				if item i of theValues is "X" then
					set maybe to i
					exit repeat
				end if
			end repeat
			if maybe = 0 then
				# no item whose shortcut letter is X in the menu
			else
				# test the value of attribute "AXMenuItemCmdModifiers"
				tell menu item maybe
					properties of every attribute
					set cutenabled to value of attribute "AXMenuItemCmdModifiers" is in {1, true}
				end tell
			end if
			
		end tell
	end tell	
end tell

In both cases the properties of the attributes of menu item Cut were the same :

{
{value:“AXMenuItem”, class:attribute, settable:false, name:“AXRole”},
{value:“commande de menu”, class:attribute, settable:false, name:“AXRoleDescription”},
{value:menu “Édition” of menu bar item “Édition” of menu bar 1 of application process “TextEdit” of application “System Events”, class:attribute, settable:false, name:“AXParent”},
{value:false, class:attribute, settable:false, name:“AXEnabled”},
{value:{0, 54}, class:attribute, settable:false, name:“AXPosition”},
{value:{298, 19}, class:attribute, settable:false, name:“AXSize”},
{value:“Couper”, class:attribute, settable:false, name:“AXTitle”},
{value:missing value, class:attribute, settable:false, name:“AXHelp”},
{value:false, class:attribute, settable:true, name:“AXSelected”},
{value:“X”, class:attribute, settable:false, name:“AXMenuItemCmdChar”},
{value:missing value, class:attribute, settable:false, name:“AXMenuItemCmdVirtualKey”},
{value:missing value, class:attribute, settable:false, name:“AXMenuItemCmdGlyph”},
{value:false, class:attribute, settable:false, name:“AXMenuItemCmdModifiers”},
{value:missing value, class:attribute, settable:false, name:“AXMenuItemMarkChar”},
{value:missing value, class:attribute, settable:false, name:“AXMenuItemPrimaryUIElement”}}

For sure filing a bug report is needed but as I wrote I am unable to do that since 2013/06/11

Worse, as I wrote here about Mavericks, maybe Apple will ban me from SEEDs :roll eyes:

Yvan KOENIG (VALLAURIS, France) samedi 20 juillet 2013 15:56:59

You can go to the dev forums. :wink: (In case someone’s wondering the dev forums are also down. I think it’s day 3 now.)

I try your workaround, though I believe whose is still working. Try the following:

set cutEnabled to (enabled of first menu item whose value of attribute "AXMenuItemCmdModifiers" is false)

The bug could be that whose can’t use strings anymore?

Whose is down for the test upon the value of attribute “AXMenuItemCmdChar” only in Mavericks. It behaves flawlessly in Mountain Lion.

As I can’t file a report, I will not waste time switching between two systems.
I will continue to test new products only when Apple engineers will decide to wake up and restore my broken access. :stuck_out_tongue:

whose seems to be usable for value of attribute “AXMenuItemCmdModifiers” but alas, it’s the value itself which is no longer meaningful. It’s set to false when the menu item is active and when it’s inactive.

KOENIG Yvan (VALLAURIS, France) samedi 20 juillet 2013 16:28:39

Gotcha. I have been unsuccessful getting this to work. The code always returns, no matter whether the Cut item is enabled or not. I can’t wrap my head around the code any longer at the moment. Will try again later. Thanks for all the help!

What with this one :


set theApp to "TextEdit"
activate application theApp
tell application "System Events"
	#tell (first application process whose frontmost is true) -- Or a named, frontmost process.
	tell process theApp
		set CutEnabled to false
		set maybe to first menu item of menu 1 of menu bar item 4 of menu bar 1 whose value of attribute "AXMenuItemCmdChar" is "X"
		if value of attribute "AXMenuItemCmdModifiers" of maybe is in {0, false} then
			set CutEnabled to enabled of maybe
		end if
end tell
end tell

Don’t forget that if you run the script from the Script Editor, it’s the Editor which is at front.
In my late code, I failed to activate TextEdit before calling System Events.
Due to that the tested parameters were those of the Editor, not those of TextEdit.

KOENIG Yvan (VALLAURIS, France) samedi 20 juillet 2013 17:13:05

You are not the first, nor the last one, to do such a thing. :wink:

My late proposal fails under Mavericks.

Nigel Garvey sent a code which does the trick :


tell application "TextEdit" to activate

tell application "System Events"
	tell (first application process whose frontmost is true)
		set {AXMenuItemCmdCharVals, AXMenuItemCmdModifierVals, enabledVals} to {value of attribute "AXMenuItemCmdChar", value of attribute "AXMenuItemCmdModifiers", enabled} of every menu item of menu 1 of menu bar item 4 of menu bar 1
	end tell
end tell

repeat with i from 1 to (count AXMenuItemCmdCharVals)
	if ((item i of AXMenuItemCmdCharVals is "X") and (item i of AXMenuItemCmdModifierVals is in {0, false})) then
		set cutenabled to item i of enabledVals
		exit repeat
	end if
end repeat
cutenabled

I just had to replace a test upon 0 to a test upon the list {0, false}.

Just for info, under pre Mavericks systems, I saw these values for the attribute “AXMenuItemCmdModifiers” :

0, 1, 2, 3, 8, 10, 24 and I’m sure that I missed some other ones.

Under Mavericks we get only true or false.

Yvan KOENIG (VALLAURIS, France) samedi 20 juillet 2013 21:47:37

Indeed, Yvan and Nigel, this latest version does the trick. Thanks for all your help! I couldn’t figure this out on my own.

Since my last message I discovered other numerical values for the attribute “AXMenuItemCmdModifiers”

The entire list is :
0 cmd +
1 cmd + maj +
2 cmd + option +
3 cmd + option + maj +
4 cmd + ctrl +
6 cmd + option + ctrl +
8
10
12 ctrl +
13 ctrl + maj +
24 fn fn

To identify menu items whose shortcut contains Escape, Delete, up arrow, down arrow, left arrow, right arrow it’s useful to extract also :
value of attribute “AXMenuItemCmdVirtualKey”
value of attribute “AXMenuItemCmdGlyph”

Here is a script which return the descriptions of menu items (1st level) of an application.


set theApp to "Safari"

activate application theApp

set params to {}

tell application "System Events" to tell process theApp to tell menu bar 1
	set nbM to count menu bar items
	repeat with i from 1 to nbM
		tell menu 1 of menu bar item i
			copy theApp & ", menu " & i & " ( " & name & " )" to end of params
			repeat with j from 1 to count menu items
				set maybe to {}
				tell menu item j
					copy name to end of maybe
					
					try
						copy value of attribute "AXMenuItemCmdChar" to end of maybe
					on error
						copy "" to end of maybe
					end try
					
					try
						copy value of attribute "AXMenuItemCmdVirtualKey" to end of maybe
					on error
						copy "" to end of maybe
					end try
					
					try
						copy value of attribute "AXMenuItemCmdGlyph" to end of maybe
					on error
						copy "" to end of maybe
					end try
					
					try
						copy value of attribute "AXMenuItemCmdModifiers" to end of maybe
					on error
						copy "" to end of maybe
					end try
				end tell # menu item j
				
				copy my recolle(maybe, tab) to end of params
			end repeat
			
		end tell # menu 1 of .
		copy "" to end of params
	end repeat
end tell # System Events .

set params to my recolle(params, return)
set nomDuRapport to (path to desktop as text) & theApp & (do shell script "date +_%Y%m%d_%H%M%S.txt")

my writeto(nomDuRapport, params, text, false)

#=====

on recolle(l, d)
	local oTIDs, t
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set t to "" & l
	set AppleScript's text item delimiters to oTIDs
	return t
end recolle

#=====
(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData, dataType, apendData)
	-- targetFile is the path to the file you want to write
	-- theData is the data you want in the file.
	-- dataType is the data type of theData and it can be text, list, record etc.
	-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it
	try
		set targetFile to targetFile as text
		set openFile to open for access file targetFile with write permission
		if not apendData then set eof of openFile to 0
		write theData to openFile starting at eof as dataType
		close access openFile
		return true
	on error
		try
			close access file targetFile
		end try
		return false
	end try
end writeto

#=====

As is, it apply to Safari.

KOENIG Yvan (VALLAURIS, France) mercredi 24 juillet 2013 12:39:43

Magnifiqué. :slight_smile:

( I can’t speak a word of French, but this is really brilliant Yvan! )

Thanks, Yvan.

It looks like a jerry-rigged bit-flag system:

Bit 0 set (value = 1): shift down
Bit 1 set (value = 2): option down
Bit 2 set (value = 4): control down
Bit 3 set (value = 8): command UP
Bits 3 and 4 set (value = 24): fn fn

Hello Nigel

I understood the bit scheme but I don’t understand some values.
In every applications, Force quit. has the shortcut cmd + option + escape
value of attribute “AXMenuItemCmdChar” is missing
value of attribute “AXMenuItemCmdVirtualKey” is 53
value of attribute “AXMenuItemCmdGlyph” is 27 (ASCII value for escape)
value of attribute “AXMenuItemCmdModifiers” is 2 (cmd + option)

Do you know a resource giving explanations about VirtualKey and Glyph values ?

Start Dictation has Glyph 148 and Modifiers 24

Page précédente 123 100 0 cmd + left arrow
Page suivante 124 101 0 cmd + right arrow

Vider la corbeille. 51 23 1 cmd + maj + delete
Vider la corbeille 51 23 3 cmd + option + maj + delete
Vider la corbeille en mode sécurisé. 53 27 6 cmd + option + ctrl + escape ???
Vider la corbeille en mode sécurisé 10 cmd UP + option but the item is also visible without option. Would be more consistent if the item was also defined with the modifiers 8.

Close has two shortcuts cmd + W and cmd + maj + W
Save As. has two shortcuts cmd + S and cmd + maj + S
Move to Trash has two shortcuts cmd + delete and cmd + option + delete

but cmd + Z and cmd + maj + Z are two different commands.

Worse, cmd + A is Select All while cmd + maj + A is « Remplissage automatique de formulaires ». Honestly I don’t guess the relation between these two items.

Problem of consistency.

I don’t understand why Hide Safari and Hide others are always visible but
Quit Safari and Quit and keep windows behave differently (the late one appears only when option is depressed).

And worse, why all these useful informations are no longer available in “you know what” ? :wink:

KOENIG Yvan (VALLAURIS, France) mercredi 24 juillet 2013 15:34:49

Take a look at Carbon Accessibility Reference > Attributes > Menu-specific attributes

Enhanced version treating also sub-menu items.


set theApp to "TextEdit"

activate application theApp

set params to {"Name" & tab & "Char" & tab & "VirtualKey" & tab & "Glyph" & tab & "Modifiers" & tab & "Shortcut"}

tell application "System Events" to tell process theApp to tell menu bar 1
	set nbM to count menu bar items
	repeat with i from 1 to nbM
		tell menu 1 of menu bar item i
			copy theApp & ", menu " & i & " ( " & name & " )" to end of params
			repeat with j from 1 to count menu items
				set maybe to {}
				tell menu item j
					set theName to name
					try # required for the first item of Help menu
						theName
					on error
						set theName to ""
					end try
					copy theName to end of maybe
					
					try # required for the first item of Help menu
						set theChar to value of attribute "AXMenuItemCmdChar"
					end try
					try
						theChar
					on error
						set theChar to ""
					end try
					copy theChar to end of maybe
					
					try
						copy value of attribute "AXMenuItemCmdVirtualKey" to end of maybe
					on error
						copy "" to end of maybe
					end try
					
					try # required for the first item of Help menu
						set theGlyph to value of attribute "AXMenuItemCmdGlyph"
					end try
					try
						theGlyph
					on error
						set theGlyph to ""
					end try
					copy theGlyph to end of maybe
					
					try # required for the first item of Help menu
						set theModifiers to value of attribute "AXMenuItemCmdModifiers"
					end try
					try
						theModifiers
					on error
						set theModifiers to ""
					end try
					copy theModifiers to end of maybe
					
					set prefix to ""
					if theName > "" then
						set prefix to my buildPrefix(theModifiers)
						if theChar > "" then
							if prefix > "" then copy prefix & theChar to end of maybe
						else
							# here, theChar is empty. We may have to decipher a glyph
							set nameOfGlyph to my specialKey(theGlyph)
							if nameOfGlyph > "" then copy prefix & nameOfGlyph to end of maybe
						end if # theChar
					end if # theName
					#copy my recolle(maybe, tab) to end of params # Take care of possible submenus
					
					# Now treat possible submenus
					if {i, j} is not {1, 9} then # Skip Apple > Recent items
						try
							tell menu 1
								set k to count menu items
								# We are here if the menu item has submenu
								# Pass the title of the menu
								copy theName to end of params
								repeat with k from 1 to count menu items
									set maybe to {}
									tell menu item k
										set theName to name
										try
											theName
										on error
											set theName to ""
										end try
										copy ">  " & theName to end of maybe
										try
											set theChar to value of attribute "AXMenuItemCmdChar"
										end try
										try
											theChar
										on error
											set theChar to ""
										end try
										copy theChar to end of maybe
										
										try
											copy value of attribute "AXMenuItemCmdVirtualKey" to end of maybe
										on error
											copy "" to end of maybe
										end try
										
										set theGlyph to value of attribute "AXMenuItemCmdGlyph"
										try
											theGlyph
										on error
											set theGlyph to ""
										end try
										copy theGlyph to end of maybe
										
										set theModifiers to value of attribute "AXMenuItemCmdModifiers"
										try
											theModifiers
										on error
											set theModifiers to ""
										end try
										copy theModifiers to end of maybe
										
										set prefix to ""
										if theName > "" then
											set prefix to my buildPrefix(theModifiers)
											
											if theChar > "" then
												if prefix > "" then copy prefix & theChar to end of maybe
											else
												# here, theChar is empty. We may have to decipher a glyph
												set nameOfGlyph to my specialKey(theGlyph)
												if nameOfGlyph > "" then copy prefix & nameOfGlyph to end of maybe
											end if # theChar
										end if # theName
										
										copy my recolle(maybe, tab) to end of params
										
									end tell # menu item k
								end repeat
							end tell
						on error
							# We are here if the item has no submenus
							copy my recolle(maybe, tab) to end of params
						end try
					else
						# We are here if {i,j} is {1,9} Apple > Recent Items has no shortcut.
						copy theName to end of params
					end if # {i,j} is not {1,9}
				end tell # menu item j
			end repeat
			
		end tell # menu 1 of .
		copy "" to end of params
	end repeat
end tell # System Events .

set params to my recolle(params, return)
set nomDuRapport to (path to desktop as text) & theApp & (do shell script "date +_%Y%m%d_%H%M%S.txt")

my writeto(nomDuRapport, params, text, false)

#tell application "Numbers" to open file nomDuRapport

#=====

on buildPrefix(the_Modifiers)
	
	if the_Modifiers is 0 then
		return "cmd + "
	else if the_Modifiers is 1 then
		return "cmd + maj + "
	else if the_Modifiers is 2 then
		return "cmd + option + "
	else if the_Modifiers is 3 then
		return "cmd + option + maj + "
	else if the_Modifiers is 4 then
		return "cmd + ctrl + "
	else if the_Modifiers is 6 then
		return "cmd + option + ctrl + "
	else if the_Modifiers is 10 then
		return "option + "
	else
		return ""
	end if
end buildPrefix

#=====

on specialKey(the_Glyph)
	if the_Glyph = 2 then
		return "tab"
	else if the_Glyph = 23 then
		return "delete"
	else if the_Glyph = 27 then
		return "escape"
	else if the_Glyph = 100 then
		return "left arrow"
	else if the_Glyph = 101 then
		return "right arrow"
	else if the_Glyph = 104 then
		return "up arrow"
	else if the_Glyph = 106 then
		return "down arrow"
	else if the_Glyph = 148 then
		return "fn fn"
	else
		return ""
	end if # the_Glyph
end specialKey

#=====

on recolle(l, d)
	local oTIDs, t
	set {oTIDs, AppleScript's text item delimiters} to {AppleScript's text item delimiters, d}
	set t to "" & l
	set AppleScript's text item delimiters to oTIDs
	return t
end recolle

#=====
(*
Handler borrowed to Regulus6633 - http://macscripter.net/viewtopic.php?id=36861
*)
on writeto(targetFile, theData, dataType, apendData)
	-- targetFile is the path to the file you want to write
	-- theData is the data you want in the file.
	-- dataType is the data type of theData and it can be text, list, record etc.
	-- apendData is true to append theData to the end of the current contents of the file or false to overwrite it
	try
		set targetFile to targetFile as text
		set openFile to open for access file targetFile with write permission
		if not apendData then set eof of openFile to 0
		write theData to openFile starting at eof as dataType
		close access openFile
		return true
	on error
		try
			close access file targetFile
		end try
		return false
	end try
end writeto

#=====

I hope that I forgot nothing.

KOENIG Yvan (VALLAURIS, France) jeudi 25 juillet 2013 17:33:28