Help with macOS system status bar menu

Moderators: LCMark, LCfraser

Post Reply
trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 911
Joined: Sat Apr 08, 2006 3:06 pm
Location: Overland Park, Kansas
Contact:

Help with macOS system status bar menu

Post by trevordevore » Wed May 02, 2018 8:00 pm

After reviewing the resources shared in the "FFI Help wrapping ObjC" thread I decided to fiddle with creating a status bar menu on macOS. I have been pleasantly surprised a number of times in how helpful the Extension Builder has been in reporting errors.

I have it to the point where the image file I pass to `TestMyMenu` appears in the system menu bar for a brief second and then disappears. I assume that is because all of the objects I create vanish once `TestMyMenu()` finishes executing. I'm hoping that someone with more experience than I in this area can review the code I have so far and do the following:

1) Correct mistakes in my use of ObjcId vs ObjcRetainedId.
2) Suggest the correct way to manage the variables containing references to `NSStatusBar`, `NSStatusItem`, `NSStatusBarButton`, `NSMenu`, `NSMenuItem`, and `NSImage`. I'm guessing that `NSStatusItem` and `NSMenu` need to be script local variables. I don't even have an educated guess about the others.

Once I can get this basic test functioning so that it is persistent then I will go back and flesh out a proper API.

Thanks in advance.

Code: Select all

library com.livecode.extensions.devore.macstatusmenu

	use com.livecode.foreign
	use com.livecode.objc

	metadata title is "Mac Status Menu"
	metadata author is "Trevor DeVore"
	metadata version is "0.1.0"

	/*
	Pointer
	CBool
	CUInt
	CInt
	CFloat
	CDouble
	ZStringNative
	ObjcId
	ObjcRetainedId
	ObjcAutoreleasedId

	-1 = NSVariableStatusItemLength
	-2 = NSSquareStatusItemLength
	*/

	private foreign handler NSSystemStatusBar() returns ObjcId binds to "objc:NSStatusBar.+systemStatusBar"

	private foreign handler NSStatusItemCreate(in pStatusBar as ObjcId, in pLength as CFloat) returns ObjcId binds to "objc:NSStatusBar.-statusItemWithLength:"
	private foreign handler NSStatusItemButton(in pObj as ObjcId) returns ObjcId binds to "objc:NSStatusItem.button"
	private foreign handler NSStatusItemSetTitle(in pObj as ObjcId, in pTitle as ObjcId) returns nothing binds to "objc:NSStatusItem.-setTitle:"
	private foreign handler NSStatusItemSetHighlightMode(in pObj as ObjcId, in pBool as CBool) returns nothing binds to "objc:NSStatusItem.-setHighlightMode:"
	private foreign handler NSStatusItemSetMenu(in pObj as ObjcId, in pMenuObj as ObjcId) returns nothing binds to "objc:NSStatusItem.-setMenu:"

	private foreign handler NSStatusBarButtonSetTitle(in pObj as ObjcId, in pTitle as ObjcId) returns nothing binds to "objc:NSStatusBarButton.-setTitle:"
	private foreign handler NSStatusBarButtonSetImage(in pObj as ObjcId, in pImage as ObjcId) returns nothing binds to "objc:NSStatusBarButton.-setImage:"

	private foreign handler NSImageAlloc() returns ObjcRetainedId binds to "objc:NSImage.+alloc"
	private foreign handler NSImageCreateFromFile(in pObj as ObjcId, in pFilename as ObjcId) returns ObjcId binds to "objc:NSImage.-initByReferencingFile:"

	private foreign handler NSMenuAlloc() returns ObjcRetainedId binds to "objc:NSMenu.+alloc"
	private foreign handler NSMenuCreate(in pObj as ObjcId, pTitle as ObjcId) returns ObjcId binds to "objc:NSMenu.-initWithTitle:"
	private foreign handler NSMenuAddItem(in pObj as ObjcId, pItem as ObjcId) returns nothing binds to "objc:NSMenu.-addItem:"
	private foreign handler NSMenuNumberOfItems(in pObj as ObjcId) returns CInt binds to "objc:NSMenu.-numberOfItems"

	private foreign handler NSMenuItemAlloc() returns ObjcRetainedId binds to "objc:NSMenuItem.+alloc"
	private foreign handler NSMenuItemCreate(in pObj as ObjcId, pTitle as ObjcId, pAction as optional ObjcId, pKeyEquivalent as ObjcId) returns ObjcId binds to "objc:NSMenuItem.-initWithTitle:action:keyEquivalent:"

	public handler TestMyMenu(in pImagePath as String)
		unsafe
			// Menu
			variable tMenu as ObjcObject
			put NSMenuAlloc() into tMenu
			put NSMenuCreate(tMenu, StringToNSString("My Menu")) into tMenu

			// Menu Item
			variable tMenuItem as ObjcObject
			put NSMenuItemAlloc() into tMenuItem
			put NSMenuItemCreate(tMenuItem, StringToNSString("Option 1"), nothing, StringToNSString("")) into tMenuItem

			NSMenuAddItem(tMenu, tMenuItem)

			variable tCount as Integer
			put NSMenuNumberOfItems(tMenu) into tCount
			log tCount

			// Status Bar
			variable statusBar as ObjcObject
			variable tItem as ObjcObject

			put NSSystemStatusBar() into statusBar
			put NSStatusItemCreate(statusBar, -2) into tItem
			NSStatusItemSetTitle(tItem, StringToNSString("Testing"))
			NSStatusItemSetHighlightMode(tItem, true)
			NSStatusItemSetMenu(tItem, tMenu)

			variable tImage as ObjcObject
			put NSImageAlloc() into tImage
			put NSImageCreateFromFile(tImage, StringToNSString(pImagePath)) into tImage

			// Status Item
			variable tStatusItemButton as ObjcObject
			put NSStatusItemButton(tItem) into tStatusItemButton
			NSStatusBarButtonSetImage(tStatusItemButton, tImage)
			NSStatusBarButtonSetTitle(tStatusItemButton, StringToNSString("My Button!"))

		end unsafe
	end handler
end library
Trevor DeVore
ScreenSteps - http://www.screensteps.com

Levure Application Framework: https://github.com/trevordevore/levure
LiveCode Resources for Developers: http://livecode.bluemangolearning.com

PaulDaMacMan
Posts: 190
Joined: Wed Apr 24, 2013 4:53 pm

Re: Help with macOS system status bar menu

Post by PaulDaMacMan » Wed May 02, 2018 11:10 pm

I was going to take a crack at this myself to add to the macOS tools lib, but I got too into the AVMIDPlayer thing, along with some other, paying, work right now, along with my day job and fam/kids...shwew.

I don't know if it will help you figure anything out but I put NSStatusBar Menu AppleScriptObjC source up on github here:https://github.com/PaulMcClernan/Status ... es/Scripts
It's also an example of using Inter App Communication between LiveCode & AppleScript. The nice thing with doing it this way is that it's a small AppleScript applet (AppleScriptObjC actually), low memory footprint, then it launches an embedded LiveCode Standalone in the AppleScript applet's bundle, the standalone has a key set in it's plist so it's icon doesn't show in the dock and two apps communicate with each other via AppleScript/AppleEvents. The AppleScriptObjC Applet builds it's menu items read from a line delimited menu.txt file stored in it's bundle. It's kind of a work in progress, an experiment.
Last edited by PaulDaMacMan on Wed May 02, 2018 11:11 pm, edited 1 time in total.

trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 911
Joined: Sat Apr 08, 2006 3:06 pm
Location: Overland Park, Kansas
Contact:

Re: Help with macOS system status bar menu

Post by trevordevore » Wed May 02, 2018 11:18 pm

Thanks for the link Paul. I actually have an external that creates status bar menus that I use in my apps which has served as a guide as I’ve worked on the LCB code.
Trevor DeVore
ScreenSteps - http://www.screensteps.com

Levure Application Framework: https://github.com/trevordevore/levure
LiveCode Resources for Developers: http://livecode.bluemangolearning.com

PaulDaMacMan
Posts: 190
Joined: Wed Apr 24, 2013 4:53 pm

Re: Help with macOS system status bar menu

Post by PaulDaMacMan » Thu May 03, 2018 2:57 am

Ah, is that part of Levure Application Framework? I keep meaning to check that out but never get around to it.

trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 911
Joined: Sat Apr 08, 2006 3:06 pm
Location: Overland Park, Kansas
Contact:

Re: Help with macOS system status bar menu

Post by trevordevore » Thu May 03, 2018 3:06 am

No, the external isn't part of the framework. It is just an external that I use. The LCB version is something that could be made available, however.
Trevor DeVore
ScreenSteps - http://www.screensteps.com

Levure Application Framework: https://github.com/trevordevore/levure
LiveCode Resources for Developers: http://livecode.bluemangolearning.com

PaulDaMacMan
Posts: 190
Joined: Wed Apr 24, 2013 4:53 pm

Re: Help with macOS system status bar menu

Post by PaulDaMacMan » Fri May 04, 2018 2:57 pm

have it to the point where the image file I pass to `TestMyMenu` appears in the system menu bar for a brief second and then disappears
I just tried it, I don't even get the image showing even for a brief second.

I moved the object variables to module scoped

Code: Select all

private variable myVariable as ObjcRetainedId
changing a them to

Code: Select all

private foreign handler NSStatusItemCreate(in pStatusBar as ObjcRetainedId, ... 
I think that's the trick to getting Object things to stick around between calls, at least that's what got my AVMIDIPlayer wrapper to start working. Though when I tried that across the board here I get a crash with:

Code: Select all

Application Specific Information:
objc_msgSend() selector name: retain
Probably need to get the right combo of retained and not, and maybe some dynamic things in autorelease pool? I would think at least the +systemStatusBar would need to be retained because it's a reference to the system wide status bar.
Hopefully Ali or someone with real knowledge can chime in. I'm just barely on the verge of understanding this stuff.

LCMark
Livecode Staff Member
Livecode Staff Member
Posts: 996
Joined: Thu Apr 11, 2013 11:27 am

Re: Help with macOS system status bar menu

Post by LCMark » Fri May 11, 2018 8:31 am

@trevordevore: Your code looks good. The general rules with regard ObjcIds etc. are:

Only use ObjcId and ObjcRetainedId in the foreign handler definitions (parameter types and return types) - they describe the ownernship semantics of values flowing in and out of objc methods.

Always use ObjcObject as the type for variables that you want to hold objc objects. The engine maps Id/RetainedId to ObjcObjects appropriately, and automatically manages the retain count of the underling objc object put into an ObjcObject type variable.

As a parameter type, ObjcId means that the passed object is being 'lent' to the method - so the method will need to retain (internally) to keep hold of it.

As a return type, ObjcId means that the method is 'lending' the caller the returned object. As long as you put such a thing straight into a variable of type ObjcObject, you don't have to worry about reference counts.

As a parameter type, ObjcRetainedId means that the object is being 'given' to the method - i.e. the callee will need to release it internally. When you pass an ObjcObject type as a ObjcRetainedId argument, the engine automatically bumps the reference count, so you don't lose your hold on the ObjcObject.

As a return type, ObjcRetainedId means the method is 'giving' and object to the caller. Again, the engine knows it doesn't need to retain the object when a ObjcRetainedId is put into an ObjcObject.

Typically, you will only need ObjcRetainedId in two situations. The first is the return type of 'alloc' methods; the second is the parameter type of an 'out' parameter of a method which gives an object. The latter case is most usually seen with NSError - where a method may produce an error which you have to handle:

- myObjMethod obj: NSFoo* error: NSError**

This would have sig something like

myObjMethod(in obj as ObjcId, out rError as ObjcRetainedId)

(In the case of errors, in particular, they need to go into an optional ObjcObject as a nil value means no error).

Note: Methods which returned 'autoreleased' objects should just use ObjcId - e.g. stringByCombiningStrings - typically such methods start with the noun related to the object rather than a verb.

In terms of the statusmenu vanishing - then from memory, NSStatusItems only stay around for as long as the app which creates them holds a reference to the NSStatusItem object. In your case all your vars are local to the handler, so the reference 'goes away' when the handler finishes. If you store the NSStatusItem in a module-level var, then it should hopefully stay around a bit longer.

trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 911
Joined: Sat Apr 08, 2006 3:06 pm
Location: Overland Park, Kansas
Contact:

Re: Help with macOS system status bar menu

Post by trevordevore » Fri May 11, 2018 1:05 pm

Thanks for the description @LCMark. I made the change to ObjcRetainedId for an NSError that is used in some other code.

I've tried moving my variables into module level vars but LiveCode starts crashing as soon as I do. I'm not sure how to troubleshoot that. Any ideas? Latest source code with non-crashing code that defines variables at handler level is here:

https://github.com/trevordevore/lc-maco ... us-bar.lcb
Trevor DeVore
ScreenSteps - http://www.screensteps.com

Levure Application Framework: https://github.com/trevordevore/levure
LiveCode Resources for Developers: http://livecode.bluemangolearning.com

PaulDaMacMan
Posts: 190
Joined: Wed Apr 24, 2013 4:53 pm

Re: Help with macOS system status bar menu

Post by PaulDaMacMan » Fri May 11, 2018 7:29 pm

Good to see some confirmation that I was on the right track (with retained and storing in module level variable).
I was just trying to wrap my head around this some more this morning, reading this stuff:
https://developer.apple.com/library/con ... 02439-SW1
https://developer.apple.com/library/con ... 8-CJBEJBHH

trevordevore
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 911
Joined: Sat Apr 08, 2006 3:06 pm
Location: Overland Park, Kansas
Contact:

Re: Help with macOS system status bar menu

Post by trevordevore » Fri Jun 15, 2018 4:20 pm

I now have the code to the point where no crashing occurs. I'm not exactly sure how I got there. It seems I had to store some ObjcObjects in a handler local variable and then assign that variable to a script local. That seems odd but I'll figure that out later.

I still can't get an icon to appear in the status bar, however. I've been converting code from an external and looking at examples such as this:

https://mackuba.eu/2015/03/04/how-to-ad ... -yosemite/

Here is a link to the current code if anyone cares to take a stab at it:

https://github.com/trevordevore/lc-maco ... us-bar.lcb
Trevor DeVore
ScreenSteps - http://www.screensteps.com

Levure Application Framework: https://github.com/trevordevore/levure
LiveCode Resources for Developers: http://livecode.bluemangolearning.com

Post Reply

Return to “LiveCode Builder”