Change status bar menu item dynamically?

I’m working on a script that displays a status bar icon with a menu, and I can’t figure out how to make a menu item change, depending on the state of a variable. Here’s some sample code with a Disable/Enable menu item - I’m trying to figure out how to make it say either Enable when the user has earlier chosen Disable, or say Disable when started up or when the user has earlier chosen Enable.

I’ve found a lot of information about adding items to a menu, but I haven’t found any clear answer on how to change an existing menu item. Is that possible? Here’s my sample code, which has to be run as an application, not from SD (and apologies for any incompetence in it - I still haven’t figured out how to make it display an icon instead of text in the status bar, but I suppose that will be fairly straightforward):

EDIT: Alternatively, is there a way to add/remove a checkmark next to a menu item that says “Enabled”?? I see that there is a deprecated method called NSStateOn (if I remember correctly), but I can’t find a current method for use in AppleScript.

use scripting additions
use framework "Foundation"
use framework "AppKit"

property statusItem : missing value
property interval : 1.0
property runOption : "active"

on run
	
	set my statusItem to current application's NSStatusBar's systemStatusBar's statusItemWithLength:(current application's NSVariableStatusItemLength)
	statusItem's setTitle:"Testing"
	set newMenu to current application's NSMenu's alloc()'s initWithTitle:""
	(newMenu's addItemWithTitle:"Disable/Enable" action:"action1:" keyEquivalent:"")'s setTarget:me
	newMenu's addItem:(current application's NSMenuItem's separatorItem)
	(newMenu's addItemWithTitle:"Quit" action:"terminate" keyEquivalent:"")'s setTarget:me
	statusItem's setMenu:newMenu
	
	repeat while 1 = 1
		if runOption is "active" then
			-- do something
		end if
		delay interval
	end repeat
	
end run

on action1:sender
	if runOption is "active" then
		display dialog "I won't be active." buttons ("OK") default button 1 giving up after 1
		set runOption to "inactive"
	else
		display dialog "OK, I'll be active again" buttons ("OK") default button 1 giving up after 1
		set runOption to "active"
	end if
end action1:

to terminate()
	tell me to quit
end terminate

To answer my own question: I found some code by Shane (of course - who else?) that pointed the way, though my version of it is mostly incompetent. At least it seems to work.

As you can see from the createMenu routine, the menu gets deleted and re-created every time something changes. Probably there’s a way to do this without re-creating the menu, but I can’t guess what it might be.

If anyone wants something like this, you can probably do it better. The code below seems to work when saved as an application and the two items specified in the comment at the top are added to the app’s info.plist.


-- to prevent the app icon from appearing in the dock 
-- and to prevent the Esc key from exiting, 
-- add "Application is background only" - "YES" 
-- and "Application is Agent (UIElement)" - "YES" 
-- to the  info.plist of the AppleScript app

use scripting additions
use framework "Foundation"
use framework "AppKit"

property statusItem : missing value
property interval : 1
property runOption : "active"

on run
	my createMenu()
	repeat while 1 = 1
		-- do something
		delay interval
	end repeat
end run

on createMenu()
	try
		current application's NSStatusBar's systemStatusBar()'s removeStatusItem:statusItem
	end try
	set my statusItem to current application's NSStatusBar's systemStatusBar's statusItemWithLength:(current application's NSVariableStatusItemLength)
	statusItem's setTitle:"Menu"
	set newMenu to current application's NSMenu's alloc()'s initWithTitle:""
	(newMenu's addItemWithTitle:"About Menu" action:"action1:" keyEquivalent:"")'s setTarget:me
	newMenu's addItem:(current application's NSMenuItem's separatorItem)
	set returnValue to (newMenu's addItemWithTitle:"Interval 1" action:"action2:" keyEquivalent:"")
	returnValue's setTarget:me
	if interval is 1 then
		returnValue's setState:1
	end if
	set returnValue to (newMenu's addItemWithTitle:"Interval 2" action:"action3:" keyEquivalent:"")
	returnValue's setTarget:me
	if interval is 2 then
		returnValue's setState:1
	end if
	set returnValue to (newMenu's addItemWithTitle:"Interval 3" action:"action4:" keyEquivalent:"")
	returnValue's setTarget:me
	if interval is 3 then
		returnValue's setState:1
	end if
	newMenu's addItem:(current application's NSMenuItem's separatorItem)
	if runOption is "active" then
		set returnValue to (newMenu's addItemWithTitle:"Enabled" action:"action5:" keyEquivalent:"")
		returnValue's setTarget:me
		returnValue's setState:1
	else
		set returnValue to (newMenu's addItemWithTitle:"Click to enable" action:"action5:" keyEquivalent:"")
		returnValue's setTarget:me
	end if
	newMenu's addItem:(current application's NSMenuItem's separatorItem)
	(newMenu's addItemWithTitle:"Quit" action:"terminate" keyEquivalent:"")'s setTarget:me
	statusItem's setMenu:newMenu
end createMenu

on action1:sender
	display dialog "This app doesn't do anything." buttons ("OK") default button 1
end action1:

on action2:sender
	set interval to 1
	my createMenu()
end action2:

on action3:sender
	set interval to 2
	my createMenu()
end action3:

on action4:sender
	set interval to 3
	my createMenu()
end action4:

on action5:sender
	if runOption is "disabled" then
		set runOption to "active"
		my createMenu()
	else
		set runOption to "disabled"
		my createMenu()
	end if
end action5:

to terminate() -- quit handler is not called from normal NSApplication terminate:
	current application's NSStatusBar's systemStatusBar's removeStatusItem:statusItem
	if name of current application does not start with "Script" then tell me to quit
end terminate

My solution turns out to have been incompetent, as anyone here who looked at it could see. It was having problems with Bartender 4 (the terrific utility that manages icons on the status bar), and I sent a feedback comment to the programmer, Ben Surtees, who came back with essentially a total rewrite of my code.

EDIT: The new code is below, but my main reason is for posting is to put in a recommendation for Bartender 4, plus a word of praise for the most generous and expert tech support that I’ve ever had in many years of writing about software.


-- rewritten by Ben Surtees at macbartender.com

-- to prevent the app icon from appearing in the dock and to prevent the Esc key from exiting, 
-- add "Application is background only" - "YES"  and "Application is Agent (UIElement)" - "YES" 
-- to info.plist of the AppleScript app

use scripting additions
use framework "Foundation"
use framework "AppKit"

property statusItem : missing value
property interval : 1.0
property runOption : "active"

on run
	my createStatusItem()
end run

on idle
	-- do something
	return interval
end idle

on createStatusItem()
	
	try
		current application's NSStatusBar's systemStatusBar()'s removeStatusItem:statusItem
	end try
	set my statusItem to current application's NSStatusBar's systemStatusBar's statusItemWithLength:(current application's NSVariableStatusItemLength)
	statusItem's setTitle:"Menu"
	my createMenu()
	
end createStatusItem

on createMenu()

	set newMenu to current application's NSMenu's alloc()'s initWithTitle:""
	(newMenu's addItemWithTitle:"About Menu" action:"action1:" keyEquivalent:"h")'s setTarget:me
	newMenu's addItem:(current application's NSMenuItem's separatorItem)
	set returnValue to (newMenu's addItemWithTitle:"Timer: 0.25 sec" action:"action2:" keyEquivalent:"2")
	returnValue's setTarget:me
	if interval is 0.25 then
		returnValue's setState:1
	end if
	set returnValue to (newMenu's addItemWithTitle:"Timer: 0.5 sec" action:"action3:" keyEquivalent:"5")
	returnValue's setTarget:me
	if interval is 0.5 then
		returnValue's setState:1
	end if
	set returnValue to (newMenu's addItemWithTitle:"Timer: 1.0 sec" action:"action4:" keyEquivalent:"1")
	returnValue's setTarget:me
	if interval is 1.0 then
		returnValue's setState:1
	end if
	newMenu's addItem:(current application's NSMenuItem's separatorItem)
	if runOption is "active" then
		set returnValue to (newMenu's addItemWithTitle:"Enabled" action:"action5:" keyEquivalent:"e")
		returnValue's setTarget:me
		returnValue's setState:1
	else
		set returnValue to (newMenu's addItemWithTitle:"Click to enable" action:"action5:" keyEquivalent:"e")
		returnValue's setTarget:me
	end if
	newMenu's addItem:(current application's NSMenuItem's separatorItem)
	(newMenu's addItemWithTitle:"Quit" action:"terminate" keyEquivalent:"q")'s setTarget:me
	statusItem's setMenu:newMenu
	
end createMenu

on action1:sender
	display dialog "This app doesn't do anything at all." buttons ("OK") default button 1
end action1:

on action2:sender
	set interval to 0.25
	my createMenu()
end action2:

on action3:sender
	set interval to 0.5
	my createMenu()
end action3:

on action4:sender
	set interval to 1.0
	my createMenu()
end action4:

on action5:sender
	if runOption is "disabled" then
		set runOption to "active"
		my createMenu()
	else
		set runOption to "disabled"
		my createMenu()
	end if
end action5:

to terminate() -- quit handler is not called from normal NSApplication terminate:
	if name of current application does not start with "Script" then tell me to quit
end terminate
1 Like