Skip to content

Commit 22c167e

Browse files
authored
Merge pull request #262 from clintharrison/clint/macos-behavior-removal-allowed
[macOS] Allow setting status item interactive removal behavior
2 parents a47cb5b + c7a4dc1 commit 22c167e

7 files changed

Lines changed: 46 additions & 0 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ When running as an app bundle, you may want to add one or both of the following
116116

117117
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).
118118

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

121126
- https://github.com/xilp/systray

example/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func onReady() {
3737
systray.SetTitle("Awesome App")
3838
systray.SetTooltip("Pretty awesome棒棒嗒")
3939
mChange := systray.AddMenuItem("Change Me", "Change Me")
40+
mAllowRemoval := systray.AddMenuItem("Allow removal", "macOS only: allow removal of the icon when cmd is pressed")
4041
mChecked := systray.AddMenuItemCheckbox("Unchecked", "Check Me", true)
4142
mEnabled := systray.AddMenuItem("Enabled", "Enabled")
4243
// Sets the icon of a menu item. Only available on Mac.
@@ -78,6 +79,8 @@ func onReady() {
7879
select {
7980
case <-mChange.ClickedCh:
8081
mChange.SetTitle("I've Changed")
82+
case <-mAllowRemoval.ClickedCh:
83+
systray.SetRemovalAllowed(true)
8184
case <-mChecked.ClickedCh:
8285
if mChecked.Checked() {
8386
mChecked.Uncheck()

systray.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ void setIcon(const char* iconBytes, int length, bool template);
1010
void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template);
1111
void setTitle(char* title);
1212
void setTooltip(char* tooltip);
13+
void setRemovalAllowed(bool allowed);
1314
void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked, short isCheckable);
1415
void add_separator(int menuId);
1516
void hide_menu_item(int menuId);

systray_darwin.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,9 @@ func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes
3636
cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0]))
3737
C.setMenuItemIcon(cstr, (C.int)(len(templateIconBytes)), C.int(item.id), true)
3838
}
39+
40+
// SetRemovalAllowed sets whether a user can remove the systray icon or not.
41+
// This is only supported on macOS.
42+
func SetRemovalAllowed(allowed bool) {
43+
C.setRemovalAllowed((C.bool)(allowed))
44+
}

systray_darwin.m

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
7171
self->menu = [[NSMenu alloc] init];
7272
[self->menu setAutoenablesItems: FALSE];
7373
[self->statusItem setMenu:self->menu];
74+
// Once the user has removed it, the item needs to be explicitly brought back,
75+
// even restarting the application is insufficient.
76+
// Since the interface from Go is relatively simple, for now we ensure it's always
77+
// visible at application startup.
78+
self->statusItem.visible = TRUE;
7479
systray_ready();
7580
}
7681

@@ -79,6 +84,16 @@ - (void)applicationWillTerminate:(NSNotification *)aNotification
7984
systray_on_exit();
8085
}
8186

87+
- (void)setRemovalAllowed:(BOOL)allowed {
88+
NSStatusItemBehavior behavior = [self->statusItem behavior];
89+
if (allowed) {
90+
behavior |= NSStatusItemBehaviorRemovalAllowed;
91+
} else {
92+
behavior &= ~NSStatusItemBehaviorRemovalAllowed;
93+
}
94+
self->statusItem.behavior = behavior;
95+
}
96+
8297
- (void)setIcon:(NSImage *)image {
8398
statusItem.button.image = image;
8499
[self updateTitleButtonStyle];
@@ -266,6 +281,12 @@ void setTooltip(char* ctooltip) {
266281
runInMainThread(@selector(setTooltip:), (id)tooltip);
267282
}
268283

284+
void setRemovalAllowed(bool allowed) {
285+
// must use an object wrapper for the bool, to use with performSelectorOnMainThread:
286+
NSNumber *allow = [NSNumber numberWithBool:(BOOL)allowed];
287+
runInMainThread(@selector(setRemovalAllowed:), (id)allow);
288+
}
289+
269290
void add_or_update_menu_item(int menuId, int parentMenuId, char* title, char* tooltip, short disabled, short checked, short isCheckable) {
270291
MenuItem* item = [[MenuItem alloc] initWithId: menuId withParentMenuId: parentMenuId withTitle: title withTooltip: tooltip withDisabled: disabled withChecked: checked];
271292
free(title);

systray_linux.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) {
88
SetIcon(regularIconBytes)
99
}
1010

11+
// SetRemovalAllowed sets whether a user can remove the systray icon or not.
12+
// This is only supported on macOS.
13+
func SetRemovalAllowed(allowed bool) {
14+
}
15+
1116
// SetIcon sets the icon of a menu item. Only works on macOS and Windows.
1217
// iconBytes should be the content of .ico/.jpg/.png
1318
func (item *MenuItem) SetIcon(iconBytes []byte) {

systray_windows.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,11 @@ func SetTooltip(tooltip string) {
907907
}
908908
}
909909

910+
// SetRemovalAllowed sets whether a user can remove the systray icon or not.
911+
// This is only supported on macOS.
912+
func SetRemovalAllowed(allowed bool) {
913+
}
914+
910915
func addOrUpdateMenuItem(item *MenuItem) {
911916
err := wt.addOrUpdateMenuItem(uint32(item.id), item.parentId(), item.title, item.disabled, item.checked)
912917
if err != nil {

0 commit comments

Comments
 (0)