Thanks to the enormously generous help of Ben Surtees at mac bartender.com, I was able to create a menu bar app in AppleScript, but I can’t figure out one detail. In the code below, the title of the app is “Menu,” but I haven’t figured out how to use an icon instead of a title. There are a few posts on StackOverflow and here, including this one:
but I haven’t been able to modify this code so that it uses an image file in the Resources folder of the applet. (Also, it’s not clear - to me at least - whether the image file needs to be a specific size like 32x32 or whether the OS will scale down a larger image.)
I’ll be very grateful for any help.
-- 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
@Mark_FX - Thank you for that pointer. I’ve been struggling to make it work. The following code runs, but nothing get displayed in the status bar. The resource png file does exist. I’m clearly missing something obvious, but I can’t imagine what it is. If you can help at all, I’ll be very grateful.
-- 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 statusBar : missing value
property statusItemImage : 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"
-- new code here
set imageFilePath to POSIX path of (path to resource "Menu.png") -- Your file name
set my statusItemImage to current application's NSImage's alloc()'s initWithContentsOfFile:imageFilePath
set my statusBar to current application's NSStatusBar's systemStatusBar()
set statusImageSize to (statusBar's thickness()) - 4
statusItemImage's setSize:{statusImageSize, statusImageSize}
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
Firstly its worth saying that AppleScriptObjC code examples should be in the AppleScriptObjC category of the forum, maybe one of the moderators will move it across there.
Secondly your ‘createStatusItem()’ method is not laid out correctly, you need to get a reference to the system’s ‘NSStatusBar’, and then add the newly created ‘NSStatusItem’.
Like this basic example below.
use scripting additions
use framework "AppKit"
use framework "Foundation"
property myApp : a reference to current application
property statusBar : missing value
property statusItem : missing value
property statusItemImage : missing value
property statusItemTitle : "Status Item Title"
on run {}
-- Remove this Thread check code, once your stand alone App bundle is built and running.
if my NSThread's isMainThread() as boolean then
my createStatusItem:statusItemTitle
else
my performSelectorOnMainThread:"createStatusItem:" withObject:statusItemTitle waitUntilDone:true
end if
end run
on createStatusItem:title
-- Get the systems Status Bar object
set my statusBar to myApp's NSStatusBar's systemStatusBar()
-- load a standard MacOS image, but you can load an image from the appBundle
set my statusItemImage to myApp's NSImage's imageNamed:(myApp's NSImageNameAdvanced)
set statusBarThickness to statusBar's thickness() -- Get thickness of the Status Bar
-- Set the Image size to be 4 pixels less that the thickness of the Status Bar, and square.
statusItemImage's setSize:(myApp's NSMakeSize((statusBarThickness - 4), (statusBarThickness - 4)))
-- Create the Status Item with a title and image, for just an image, then set empty title string.
set my statusItem to statusBar's statusItemWithLength:(myApp's NSVariableStatusItemLength)
statusItem's button's setTitle:title
statusItem's button's setImage:statusItemImage
statusItem's button's setImagePosition:(myApp's NSImageLeft)
my createMenuItems()
end createStatusItem:
on createMenuItems()
set statusItemMenu to myApp's NSMenu's alloc()'s initWithTitle:""
set doStuffMenuItem to myApp's NSMenuItem's alloc()'s initWithTitle:"DoStuff" action:"doStuff" keyEquivalent:"d"
doStuffMenuItem's setTarget:me
statusItemMenu's addItem:doStuffMenuItem
set sepMenuItem to myApp's NSMenuItem's separatorItem()
statusItemMenu's addItem:sepMenuItem
set quitMenuItem to myApp's NSMenuItem's alloc()'s initWithTitle:"Quit" action:"quitStatusItem" keyEquivalent:"q"
quitMenuItem's setIndentationLevel:2
quitMenuItem's setTarget:me
statusItemMenu's addItem:quitMenuItem
statusItem's setMenu:statusItemMenu
end createMenuItems
on doStuff()
-- Do stuff here when the doStuffMenuItem is clicked
end doStuff
on quitStatusItem()
-- Remove this Thread check code, once your stand alone App bundle is built and running.
if my NSThread's isMainThread() as boolean then
my removeStatusItem()
else
my performSelectorOnMainThread:"removeStatusItem" withObject:(missing value) waitUntilDone:true
end if
if name of myApp does not start with "Script" then
tell me to quit
end if
end quitStatusItem
on removeStatusItem()
statusBar's removeStatusItem:statusItem
end removeStatusItem
on idle {}
return 1.0
end idle
on quit {}
continue quit
end quit
Ive used a standard macOS image in my example, but you can load the image as you have been doing in your own code.
But copy and paste the above code into a new Script Editor file, and click the run button to see it in action.
Do remember that when loading an image from Resources, that it will only work properly when your app package is built, and the requested image file is in your app’s Resources folder.
Doing so when running in the Script Editor app, means that it will be looking in the Script Editor’s Resources folder instead, so won’t work as expected.
@Mark_FX - That was exactly what I needed to know, and I can’t thank you enough. I spent hours trying to get that right, and now you’ve made it entirely clear. Thank you again!