mirror of
https://github.com/netbirdio/systray.git
synced 2026-05-22 17:08:41 -07:00
Merge support for nested menus on macOS, from https://github.com/Arshiamidos/systray
This commit is contained in:
@@ -41,6 +41,12 @@ func onReady() {
|
||||
mChecked := systray.AddMenuItem("Unchecked", "Check Me")
|
||||
mEnabled := systray.AddMenuItem("Enabled", "Enabled")
|
||||
systray.AddMenuItem("Ignored", "Ignored")
|
||||
|
||||
subMenuTop := systray.AddMenuItem("SubMenu", "SubMenu Test (top)")
|
||||
subMenuMiddle := subMenuTop.AddSubMenuItem("SubMenu - Level 2", "SubMenu Test (middle")
|
||||
subMenuMiddle.AddSubMenuItem("SubMenu - Level 3", "SubMenu Test (bottom)")
|
||||
subMenuBottom2 := subMenuMiddle.AddSubMenuItem("Panic!", "SubMenu Test (bottom)")
|
||||
|
||||
mUrl := systray.AddMenuItem("Open UI", "my home")
|
||||
mQuit := systray.AddMenuItem("退出", "Quit the whole app")
|
||||
|
||||
@@ -67,6 +73,8 @@ func onReady() {
|
||||
mEnabled.Disable()
|
||||
case <-mUrl.ClickedCh:
|
||||
systray.ShowAppWindow("https://www.getlantern.org")
|
||||
case <-subMenuBottom2.ClickedCh:
|
||||
panic("panic button pressed")
|
||||
case <-mToggle.ClickedCh:
|
||||
if shown {
|
||||
mQuitOrig.Hide()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
@@ -21,10 +22,13 @@ github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 h1:RVMGIuuNgrpGB7I79f6xfhG
|
||||
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e h1:VAzdS5Nw68fbf5RZ8RDVlUvPXNU6Z3jtPCK/qvm4FoQ=
|
||||
github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190919044723-0c1ff786ef13 h1:/zi0zzlPHWXYXrO1LjNRByFu8sdGgCkj2JLDdBIB84k=
|
||||
|
||||
+38
-8
@@ -1,13 +1,13 @@
|
||||
/*
|
||||
Package systray is a cross platfrom Go library to place an icon and menu in the
|
||||
notification area.
|
||||
Supports Windows, Mac OSX and Linux currently.
|
||||
Package systray is a cross-platform Go library to place an icon and menu in the notification area.
|
||||
|
||||
Methods can be called from any goroutine except Run(), which should be called
|
||||
at the very beginning of main() to lock at main thread.
|
||||
*/
|
||||
package systray
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -36,6 +36,28 @@ type MenuItem struct {
|
||||
disabled bool
|
||||
// checked menu item has a tick before the title
|
||||
checked bool
|
||||
// parent item, for sub menus
|
||||
parent *MenuItem
|
||||
}
|
||||
|
||||
func (item *MenuItem) String() string {
|
||||
if item.parent == nil {
|
||||
return fmt.Sprintf("MenuItem[%d, %q]", item.id, item.title)
|
||||
}
|
||||
return fmt.Sprintf("MenuItem[%d, parent %d, %q]", item.id, item.parent.id, item.title)
|
||||
}
|
||||
|
||||
// newMenuItem returns a populated MenuItem object
|
||||
func newMenuItem(title string, tooltip string, parent *MenuItem) *MenuItem {
|
||||
return &MenuItem{
|
||||
ClickedCh: make(chan struct{}),
|
||||
id: atomic.AddInt32(¤tID, 1),
|
||||
title: title,
|
||||
tooltip: tooltip,
|
||||
disabled: false,
|
||||
checked: false,
|
||||
parent: parent,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -97,9 +119,7 @@ func Quit() {
|
||||
//
|
||||
// It can be safely invoked from different goroutines.
|
||||
func AddMenuItem(title string, tooltip string) *MenuItem {
|
||||
id := atomic.AddInt32(¤tID, 1)
|
||||
item := &MenuItem{nil, id, title, tooltip, false, false}
|
||||
item.ClickedCh = make(chan struct{})
|
||||
item := newMenuItem(title, tooltip, nil)
|
||||
item.update()
|
||||
return item
|
||||
}
|
||||
@@ -109,6 +129,16 @@ func AddSeparator() {
|
||||
addSeparator(atomic.AddInt32(¤tID, 1))
|
||||
}
|
||||
|
||||
// AddSubMenuItem adds nested sub-menu item with designated title and tooltip, returning a channel
|
||||
// that notifies whenever that menu item is clicked.
|
||||
//
|
||||
// It can be safely invoked from different goroutines.
|
||||
func (item *MenuItem) AddSubMenuItem(title string, tooltip string) *MenuItem {
|
||||
child := newMenuItem(title, tooltip, item)
|
||||
child.update()
|
||||
return child
|
||||
}
|
||||
|
||||
// SetTitle set the text to display on a menu item
|
||||
func (item *MenuItem) SetTitle(title string) {
|
||||
item.title = title
|
||||
@@ -121,7 +151,7 @@ func (item *MenuItem) SetTooltip(tooltip string) {
|
||||
item.update()
|
||||
}
|
||||
|
||||
// Disabled checkes if the menu item is disabled
|
||||
// Disabled checks if the menu item is disabled
|
||||
func (item *MenuItem) Disabled() bool {
|
||||
return item.disabled
|
||||
}
|
||||
@@ -165,7 +195,7 @@ func (item *MenuItem) Uncheck() {
|
||||
item.update()
|
||||
}
|
||||
|
||||
// update propogates changes on a menu item to systray
|
||||
// update propagates changes on a menu item to systray
|
||||
func (item *MenuItem) update() {
|
||||
menuItemsLock.Lock()
|
||||
defer menuItemsLock.Unlock()
|
||||
|
||||
@@ -10,7 +10,8 @@ void setTooltip(char* tooltip);
|
||||
void configureAppWindow(char* title, int width, int height);
|
||||
void showAppWindow(char* url);
|
||||
void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked);
|
||||
void add_or_update_submenu_item(int parent,int menuId, char* title, char* tooltip, short disabled, short checked);
|
||||
void add_separator(int menuId);
|
||||
void hide_menu_item(int menuId);
|
||||
void show_menu_item(int menuId);
|
||||
void quit();
|
||||
void quit();
|
||||
+116
-14
@@ -48,6 +48,7 @@
|
||||
|
||||
@interface AppDelegate: NSObject <NSApplicationDelegate>
|
||||
- (void) add_or_update_menu_item:(MenuItem*) item;
|
||||
- (void) add_or_update_submenu_item:(NSArray*) imageAndMenuId;
|
||||
- (IBAction)menuHandler:(id)sender;
|
||||
@property (assign) IBOutlet NSWindow *window;
|
||||
@end
|
||||
@@ -112,20 +113,21 @@
|
||||
NSNumber* menuId = [sender representedObject];
|
||||
systray_menu_item_selected(menuId.intValue);
|
||||
}
|
||||
|
||||
- (void) add_or_update_menu_item:(MenuItem*) item
|
||||
{
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: item->menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
menuItem = [menu addItemWithTitle:item->title action:@selector(menuHandler:) keyEquivalent:@""];
|
||||
- (void)add_or_update_menu_item:(MenuItem *)item {
|
||||
NSMenuItem *menuItem;
|
||||
menuItem = find_menu_with_parent(menu, item->menuId);
|
||||
if (menuItem == NULL) {
|
||||
menuItem = [menu addItemWithTitle:item->title
|
||||
action:@selector(menuHandler:)
|
||||
keyEquivalent:@""];
|
||||
[menuItem setTarget:self];
|
||||
[menuItem setRepresentedObject: item->menuId];
|
||||
[menuItem setRepresentedObject:item->menuId];
|
||||
[menuItem setTag:[item->menuId integerValue]];
|
||||
|
||||
}
|
||||
else {
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
} else {
|
||||
[menuItem setTitle:item->title];
|
||||
[menuItem setTag:[item->menuId integerValue]];
|
||||
[menuItem setTarget:self];
|
||||
}
|
||||
[menuItem setToolTip:item->tooltip];
|
||||
if (item->disabled == 1) {
|
||||
@@ -140,6 +142,94 @@
|
||||
}
|
||||
}
|
||||
|
||||
NSMenuItem *find_menu_with_parent(NSMenu *ourMenu, NSNumber *parent) {
|
||||
NSMenuItem *foundItem = [ourMenu itemWithTag:[parent integerValue]];
|
||||
if (foundItem == NULL) {
|
||||
NSArray *menu_items = ourMenu.itemArray;
|
||||
int i;
|
||||
for (i = 0; i < [menu_items count]; i++) {
|
||||
NSMenuItem *i_item = [menu_items objectAtIndex:i];
|
||||
if (i_item.hasSubmenu) {
|
||||
NSMenuItem *foundItem2 = find_menu_with_parent(i_item.submenu, parent);
|
||||
if (foundItem2 == NULL) {
|
||||
|
||||
} else {
|
||||
foundItem = foundItem2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundItem;
|
||||
} else {
|
||||
return foundItem;
|
||||
}
|
||||
};
|
||||
|
||||
- (void)add_or_update_submenu_item:(NSArray *)imageAndMenuId {
|
||||
|
||||
NSNumber *parent = [imageAndMenuId objectAtIndex:0];
|
||||
MenuItem *newItem = [imageAndMenuId objectAtIndex:1];
|
||||
|
||||
NSMenuItem *foundItem = find_menu_with_parent(menu, parent);
|
||||
|
||||
if (foundItem == NULL) {
|
||||
NSLog(@"%s", ">>> foundItem == NULL - this should not occur!");
|
||||
}
|
||||
|
||||
if (foundItem.hasSubmenu) {
|
||||
NSMenu *oldMenu = foundItem.submenu;
|
||||
|
||||
NSMenuItem *tempItem = [oldMenu addItemWithTitle:newItem->title
|
||||
action:@selector(menuHandler:)
|
||||
keyEquivalent:@""];
|
||||
tempItem.tag = [newItem->menuId integerValue];
|
||||
tempItem.title = newItem->title;
|
||||
tempItem.action = @selector(menuHandler:);
|
||||
tempItem.target = self;
|
||||
tempItem.representedObject = newItem->menuId;
|
||||
|
||||
//[oldMenu addItem:tempItem];
|
||||
if (newItem->disabled == 1) {
|
||||
tempItem.enabled = FALSE;
|
||||
} else {
|
||||
tempItem.enabled = TRUE;
|
||||
}
|
||||
if (newItem->checked == 1) {
|
||||
tempItem.state = NSControlStateValueOn;
|
||||
} else {
|
||||
tempItem.state = NSControlStateValueOff;
|
||||
}
|
||||
[foundItem setSubmenu:oldMenu];
|
||||
} else {
|
||||
|
||||
NSMenu *newMenu = [[NSMenu alloc] init];
|
||||
NSMenuItem *tempItem = [newMenu addItemWithTitle:newItem->title
|
||||
action:@selector(menuHandler:)
|
||||
keyEquivalent:@""];
|
||||
|
||||
tempItem.tag = [newItem->menuId integerValue];
|
||||
tempItem.title = newItem->title;
|
||||
tempItem.toolTip = newItem->tooltip;
|
||||
tempItem.representedObject = newItem->menuId;
|
||||
|
||||
[tempItem setTarget:self];
|
||||
|
||||
if (newItem->disabled == 1) {
|
||||
tempItem.enabled = FALSE;
|
||||
} else {
|
||||
tempItem.enabled = TRUE;
|
||||
}
|
||||
if (newItem->checked == 1) {
|
||||
tempItem.state = NSControlStateValueOn;
|
||||
} else {
|
||||
tempItem.state = NSControlStateValueOff;
|
||||
}
|
||||
[foundItem setSubmenu:newMenu];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (void) add_separator:(NSNumber*) menuId
|
||||
{
|
||||
[menu addItem: [NSMenuItem separatorItem]];
|
||||
@@ -161,11 +251,10 @@
|
||||
NSNumber* menuId = [imageAndMenuId objectAtIndex:1];
|
||||
|
||||
NSMenuItem* menuItem;
|
||||
int existedMenuIndex = [menu indexOfItemWithRepresentedObject: menuId];
|
||||
if (existedMenuIndex == -1) {
|
||||
menuItem = find_menu_with_parent(menu, menuId);
|
||||
if (menuItem == NULL) {
|
||||
return;
|
||||
}
|
||||
menuItem = [menu itemAtIndex: existedMenuIndex];
|
||||
menuItem.image = image;
|
||||
}
|
||||
|
||||
@@ -243,6 +332,19 @@ void add_or_update_menu_item(int menuId, char* title, char* tooltip, short disab
|
||||
runInMainThread(@selector(add_or_update_menu_item:), (id)item);
|
||||
}
|
||||
|
||||
void add_or_update_submenu_item(int parent, int menuId, char *title,
|
||||
char *tooltip, short disabled, short checked) {
|
||||
MenuItem *item = [[MenuItem alloc] initWithId:menuId
|
||||
withTitle:title
|
||||
withTooltip:tooltip
|
||||
withDisabled:disabled
|
||||
withChecked:checked];
|
||||
free(title);
|
||||
free(tooltip);
|
||||
NSNumber *parent2 = [NSNumber numberWithInt:parent];
|
||||
runInMainThread(@selector(add_or_update_submenu_item:),
|
||||
@[ parent2, (id)item ]);
|
||||
}
|
||||
void add_separator(int menuId) {
|
||||
NSNumber *mId = [NSNumber numberWithInt:menuId];
|
||||
runInMainThread(@selector(add_separator:), (id)mId);
|
||||
|
||||
@@ -199,6 +199,11 @@ void add_or_update_menu_item(int menu_id, char* title, char* tooltip, short disa
|
||||
g_idle_add(do_add_or_update_menu_item, mii);
|
||||
}
|
||||
|
||||
void add_or_update_submenu_item(int parent_id, int menu_id, char* title, char* tooltip, short disabled, short checked) {
|
||||
// TODO: add support for sub-menus
|
||||
add_or_update_menu_item(int menu_id, char* title, char* tooltip, short disabled, short checked) ;
|
||||
}
|
||||
|
||||
void add_separator(int menu_id) {
|
||||
MenuItemInfo *mii = malloc(sizeof(MenuItemInfo));
|
||||
mii->menu_id = menu_id;
|
||||
|
||||
+18
-7
@@ -58,13 +58,24 @@ func addOrUpdateMenuItem(item *MenuItem) {
|
||||
if item.checked {
|
||||
checked = 1
|
||||
}
|
||||
C.add_or_update_menu_item(
|
||||
C.int(item.id),
|
||||
C.CString(item.title),
|
||||
C.CString(item.tooltip),
|
||||
disabled,
|
||||
checked,
|
||||
)
|
||||
if item.parent == nil {
|
||||
C.add_or_update_menu_item(
|
||||
C.int(item.id),
|
||||
C.CString(item.title),
|
||||
C.CString(item.tooltip),
|
||||
disabled,
|
||||
checked,
|
||||
)
|
||||
} else {
|
||||
C.add_or_update_submenu_item(
|
||||
C.int(item.parent.id),
|
||||
C.int(item.id),
|
||||
C.CString(item.title),
|
||||
C.CString(item.tooltip),
|
||||
disabled,
|
||||
checked,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// SetIcon sets the icon of a menu item. Only available on Mac.
|
||||
|
||||
@@ -144,6 +144,7 @@ func addOrUpdateMenuItem(item *MenuItem) {
|
||||
if err != nil {
|
||||
fail("Unable to set menu item enabled", err)
|
||||
}
|
||||
// TODO: add support for sub-menus
|
||||
}
|
||||
|
||||
func (item *MenuItem) SetIcon(iconBytes []byte) {
|
||||
|
||||
Reference in New Issue
Block a user