Skip to content

Commit

Permalink
[macOS] Allow setting status item interactive removal behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
clintharrison committed Oct 18, 2023
1 parent a47cb5b commit c7a4dc1
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ When running as an app bundle, you may want to add one or both of the following

Consult the [Official Apple Documentation here](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1).

On macOS, it's possible to set the underlying
[`NSStatusItemBehavior`](https://developer.apple.com/documentation/appkit/nsstatusitembehavior?language=objc)
with `systray.SetRemovalAllowed(true)`. When enabled, the user can cmd-drag the
icon off the menu bar.

## Credits

- https://github.com/xilp/systray
Expand Down
3 changes: 3 additions & 0 deletions example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func onReady() {
systray.SetTitle("Awesome App")
systray.SetTooltip("Pretty awesome棒棒嗒")
mChange := systray.AddMenuItem("Change Me", "Change Me")
mAllowRemoval := systray.AddMenuItem("Allow removal", "macOS only: allow removal of the icon when cmd is pressed")
mChecked := systray.AddMenuItemCheckbox("Unchecked", "Check Me", true)
mEnabled := systray.AddMenuItem("Enabled", "Enabled")
// Sets the icon of a menu item. Only available on Mac.
Expand Down Expand Up @@ -78,6 +79,8 @@ func onReady() {
select {
case <-mChange.ClickedCh:
mChange.SetTitle("I've Changed")
case <-mAllowRemoval.ClickedCh:
systray.SetRemovalAllowed(true)
case <-mChecked.ClickedCh:
if mChecked.Checked() {
mChecked.Uncheck()
Expand Down
1 change: 1 addition & 0 deletions systray.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ void setIcon(const char* iconBytes, int length, bool template);
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template);
void setTitle(char* title);
void setTooltip(char* tooltip);
void setRemovalAllowed(bool allowed);
void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked, short isCheckable);
void add_separator(int menuId);
void hide_menu_item(int menuId);
Expand Down
6 changes: 6 additions & 0 deletions systray_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,9 @@ func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes
cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0]))
C.setMenuItemIcon(cstr, (C.int)(len(templateIconBytes)), C.int(item.id), true)
}

// SetRemovalAllowed sets whether a user can remove the systray icon or not.
// This is only supported on macOS.
func SetRemovalAllowed(allowed bool) {
C.setRemovalAllowed((C.bool)(allowed))
}
21 changes: 21 additions & 0 deletions systray_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
self->menu = [[NSMenu alloc] init];
[self->menu setAutoenablesItems: FALSE];
[self->statusItem setMenu:self->menu];
// Once the user has removed it, the item needs to be explicitly brought back,
// even restarting the application is insufficient.
// Since the interface from Go is relatively simple, for now we ensure it's always
// visible at application startup.
self->statusItem.visible = TRUE;
systray_ready();
}

Expand All @@ -79,6 +84,16 @@ - (void)applicationWillTerminate:(NSNotification *)aNotification
systray_on_exit();
}

- (void)setRemovalAllowed:(BOOL)allowed {
NSStatusItemBehavior behavior = [self->statusItem behavior];
if (allowed) {
behavior |= NSStatusItemBehaviorRemovalAllowed;
} else {
behavior &= ~NSStatusItemBehaviorRemovalAllowed;
}
self->statusItem.behavior = behavior;
}

- (void)setIcon:(NSImage *)image {
statusItem.button.image = image;
[self updateTitleButtonStyle];
Expand Down Expand Up @@ -266,6 +281,12 @@ void setTooltip(char* ctooltip) {
runInMainThread(@selector(setTooltip:), (id)tooltip);
}

void setRemovalAllowed(bool allowed) {
// must use an object wrapper for the bool, to use with performSelectorOnMainThread:
NSNumber *allow = [NSNumber numberWithBool:(BOOL)allowed];
runInMainThread(@selector(setRemovalAllowed:), (id)allow);
}

void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked, short isCheckable) {
MenuItem* item = [[MenuItem alloc] initWithId: menuId withParentMenuId: parentMenuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
free(title);
Expand Down
5 changes: 5 additions & 0 deletions systray_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
SetIcon(regularIconBytes)
}

// SetRemovalAllowed sets whether a user can remove the systray icon or not.
// This is only supported on macOS.
func SetRemovalAllowed(allowed bool) {
}

// SetIcon sets the icon of a menu item. Only works on macOS and Windows.
// iconBytes should be the content of .ico/.jpg/.png
func (item *MenuItem) SetIcon(iconBytes []byte) {
Expand Down
5 changes: 5 additions & 0 deletions systray_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,11 @@ func SetTooltip(tooltip string) {
}
}

// SetRemovalAllowed sets whether a user can remove the systray icon or not.
// This is only supported on macOS.
func SetRemovalAllowed(allowed bool) {
}

func addOrUpdateMenuItem(item *MenuItem) {
err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
if err != nil {
Expand Down

0 comments on commit c7a4dc1

Please sign in to comment.