diff --git a/README.md b/README.md index ad1f7e86..e44165a4 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/example/main.go b/example/main.go index b0f350d5..b88ac018 100644 --- a/example/main.go +++ b/example/main.go @@ -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. @@ -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() diff --git a/systray.h b/systray.h index 888c8290..7141ec67 100644 --- a/systray.h +++ b/systray.h @@ -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); diff --git a/systray_darwin.go b/systray_darwin.go index 740ec5b5..252ca91e 100644 --- a/systray_darwin.go +++ b/systray_darwin.go @@ -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)) +} diff --git a/systray_darwin.m b/systray_darwin.m index 884fa432..ecf21ace 100644 --- a/systray_darwin.m +++ b/systray_darwin.m @@ -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(); } @@ -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]; @@ -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); diff --git a/systray_linux.go b/systray_linux.go index ec0b751f..0b1b786f 100644 --- a/systray_linux.go +++ b/systray_linux.go @@ -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) { diff --git a/systray_windows.go b/systray_windows.go index 6bdc803d..13b4411d 100644 --- a/systray_windows.go +++ b/systray_windows.go @@ -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 {