diff --git a/example/main.go b/example/main.go index 5542b89..230987a 100644 --- a/example/main.go +++ b/example/main.go @@ -22,7 +22,7 @@ func main() { } func onReady() { - systray.SetIcon(icon.Data) + systray.SetTemplateIcon(icon.Data, icon.Data) systray.SetTitle("Awesome App") systray.SetTooltip("Lantern") mQuitOrig := systray.AddMenuItem("Quit", "Quit the whole app") @@ -35,12 +35,15 @@ func onReady() { // We can manipulate the systray in other goroutines go func() { - systray.SetIcon(icon.Data) + systray.SetTemplateIcon(icon.Data, icon.Data) systray.SetTitle("Awesome App") systray.SetTooltip("Pretty awesome棒棒嗒") mChange := systray.AddMenuItem("Change Me", "Change Me") mChecked := systray.AddMenuItem("Unchecked", "Check Me") mEnabled := systray.AddMenuItem("Enabled", "Enabled") + // Sets the icon of a menu item. Only available on Mac. + mEnabled.SetTemplateIcon(icon.Data, icon.Data) + systray.AddMenuItem("Ignored", "Ignored") mUrl := systray.AddMenuItem("Open Lantern.org", "my home") mQuit := systray.AddMenuItem("退出", "Quit the whole app") diff --git a/systray.h b/systray.h index 36bcf98..ea5196c 100644 --- a/systray.h +++ b/systray.h @@ -1,10 +1,12 @@ +#include "stdbool.h" + extern void systray_ready(); extern void systray_on_exit(); extern void systray_menu_item_selected(int menu_id); int nativeLoop(void); -void setIcon(const char* iconBytes, int length); -void setMenuItemIcon(const char* iconBytes, int length, int menuId); +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 add_or_update_menu_item(int menuId, char* title, char* tooltip, short disabled, short checked); diff --git a/systray_darwin.go b/systray_darwin.go new file mode 100644 index 0000000..568edab --- /dev/null +++ b/systray_darwin.go @@ -0,0 +1,37 @@ +package systray + +/* +#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc +#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit + +#include "systray.h" +*/ +import "C" + +import ( + "unsafe" +) + +// SetTemplateIcon sets the systray icon as a template icon (on Mac), falling back +// to a regular icon on other platforms. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0])) + C.setIcon(cstr, (C.int)(len(templateIconBytes)), true) +} + +// 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) { + SetTemplateIcon(iconBytes, iconBytes) +} + +// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it +// falls back to the regular icon bytes and on Linux it does nothing. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + cstr := (*C.char)(unsafe.Pointer(&templateIconBytes[0])) + C.setMenuItemIcon(cstr, (C.int)(len(templateIconBytes)), C.int(item.id), true) +} \ No newline at end of file diff --git a/systray_darwin.m b/systray_darwin.m index b0f2172..cd68d85 100644 --- a/systray_darwin.m +++ b/systray_darwin.m @@ -196,18 +196,19 @@ void runInMainThread(SEL method, id object) { waitUntilDone: YES]; } -void setIcon(const char* iconBytes, int length) { +void setIcon(const char* iconBytes, int length, bool template) { NSData* buffer = [NSData dataWithBytes: iconBytes length:length]; NSImage *image = [[NSImage alloc] initWithData:buffer]; [image setSize:NSMakeSize(16, 16)]; + image.template = template; runInMainThread(@selector(setIcon:), (id)image); } -void setMenuItemIcon(const char* iconBytes, int length, int menuId) { +void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) { NSData* buffer = [NSData dataWithBytes: iconBytes length:length]; NSImage *image = [[NSImage alloc] initWithData:buffer]; [image setSize:NSMakeSize(16, 16)]; - + image.template = template; NSNumber *mId = [NSNumber numberWithInt:menuId]; runInMainThread(@selector(setMenuItemIcon:), @[image, (id)mId]); } diff --git a/systray_linux.c b/systray_linux.c index 72cd614..cb26906 100644 --- a/systray_linux.c +++ b/systray_linux.c @@ -167,7 +167,7 @@ gboolean do_quit(gpointer data) { return FALSE; } -void setIcon(const char* iconBytes, int length) { +void setIcon(const char* iconBytes, int length, bool template) { GBytes* bytes = g_bytes_new_static(iconBytes, length); g_idle_add(do_set_icon, bytes); } @@ -181,7 +181,7 @@ void setTooltip(char* ctooltip) { free(ctooltip); } -void setMenuItemIcon(const char* iconBytes, int length, int menuId) { +void setMenuItemIcon(const char* iconBytes, int length, int menuId, bool template) { } void add_or_update_menu_item(int menu_id, char* title, char* tooltip, short disabled, short checked) { diff --git a/systray_linux.go b/systray_linux.go new file mode 100644 index 0000000..1f508c7 --- /dev/null +++ b/systray_linux.go @@ -0,0 +1,29 @@ +package systray + +/* +#cgo darwin CFLAGS: -DDARWIN -x objective-c -fobjc-arc +#cgo darwin LDFLAGS: -framework Cocoa -framework WebKit + +#include "systray.h" +*/ +import "C" + +// SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back +// to a regular icon on other platforms. +// templateIconBytes and iconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + SetIcon(regularIconBytes) +} + +// 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) { +} + +// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it +// falls back to the regular icon bytes and on Linux it does nothing. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { +} diff --git a/systray_nonwindows.go b/systray_nonwindows.go index 4868b55..b380ce7 100644 --- a/systray_nonwindows.go +++ b/systray_nonwindows.go @@ -28,7 +28,7 @@ func quit() { // for other platforms. func SetIcon(iconBytes []byte) { cstr := (*C.char)(unsafe.Pointer(&iconBytes[0])) - C.setIcon(cstr, (C.int)(len(iconBytes))) + C.setIcon(cstr, (C.int)(len(iconBytes)), false) } // SetTitle sets the systray title, only available on Mac. @@ -60,13 +60,6 @@ func addOrUpdateMenuItem(item *MenuItem) { ) } -// SetIcon sets the icon of a menu item. Only available on Mac. -// iconBytes should be the content of .ico/.jpg/.png -func (item *MenuItem) SetIcon(iconBytes []byte) { - cstr := (*C.char)(unsafe.Pointer(&iconBytes[0])) - C.setMenuItemIcon(cstr, (C.int)(len(iconBytes)), C.int(item.id)) -} - func addSeparator(id int32) { C.add_separator(C.int(id)) } diff --git a/systray_windows.go b/systray_windows.go index 1cb453c..2ad5ea6 100644 --- a/systray_windows.go +++ b/systray_windows.go @@ -150,7 +150,7 @@ type menuItemInfo struct { ItemData uintptr TypeData *uint16 Cch uint32 - Item windows.Handle + BMPItem windows.Handle } // The POINT structure defines the x- and y- coordinates of a point. @@ -178,34 +178,13 @@ type winTray struct { } // Loads an image from file and shows it in tray. -// LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx // Shell_NotifyIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb762159(v=vs.85).aspx func (t *winTray) setIcon(src string) error { - const IMAGE_ICON = 1 // Loads an icon - const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file - const LR_DEFAULTSIZE = 0x00000040 // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero const NIF_ICON = 0x00000002 - // Save and reuse handles of loaded images - h, ok := t.loadedImages[src] - if !ok { - srcPtr, err := windows.UTF16PtrFromString(src) - if err != nil { - return err - } - res, _, err := pLoadImage.Call( - 0, - uintptr(unsafe.Pointer(srcPtr)), - IMAGE_ICON, - 0, - 0, - LR_LOADFROMFILE|LR_DEFAULTSIZE, - ) - if res == 0 { - return err - } - h = windows.Handle(res) - t.loadedImages[src] = h + h, err := t.loadIconFrom(src) + if err != nil { + return err } t.nid.Icon = h @@ -433,13 +412,14 @@ func (t *winTray) createMenu() error { return nil } -func (t *winTray) addOrUpdateMenuItem(menuId int32, title string, disabled, checked bool) error { +func (t *winTray) addOrUpdateMenuItem(menuId int32, title string, disabled, checked bool, hIcon windows.Handle) error { // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647578(v=vs.85).aspx const ( MIIM_FTYPE = 0x00000100 MIIM_STRING = 0x00000040 MIIM_ID = 0x00000002 MIIM_STATE = 0x00000001 + MIIM_BITMAP = 0x00000080 ) const MFT_STRING = 0x00000000 const ( @@ -464,6 +444,10 @@ func (t *winTray) addOrUpdateMenuItem(menuId int32, title string, disabled, chec if checked { mi.State |= MFS_CHECKED } + if hIcon > 0 { + mi.Mask |= MIIM_BITMAP + mi.BMPItem = hIcon + } mi.Size = uint32(unsafe.Sizeof(mi)) // We set the menu item info based on the menuID @@ -594,6 +578,37 @@ func (t *winTray) getVisibleItemIndex(val int32) int { return -1 } +// Loads an image from file to be shown in tray or menu item. +// LoadImage: https://msdn.microsoft.com/en-us/library/windows/desktop/ms648045(v=vs.85).aspx +func (t *winTray) loadIconFrom(src string) (windows.Handle, error) { + const IMAGE_ICON = 1 // Loads an icon + const LR_LOADFROMFILE = 0x00000010 // Loads the stand-alone image from the file + const LR_DEFAULTSIZE = 0x00000040 // Loads default-size icon for windows(SM_CXICON x SM_CYICON) if cx, cy are set to zero + + // Save and reuse handles of loaded images + h, ok := t.loadedImages[src] + if !ok { + srcPtr, err := windows.UTF16PtrFromString(src) + if err != nil { + return 0, err + } + res, _, err := pLoadImage.Call( + 0, + uintptr(unsafe.Pointer(srcPtr)), + IMAGE_ICON, + 0, + 0, + LR_LOADFROMFILE|LR_DEFAULTSIZE, + ) + if res == 0 { + return 0, err + } + h = windows.Handle(res) + t.loadedImages[src] = h + } + return h, nil +} + func nativeLoop() { if err := wt.initInstance(); err != nil { log.Errorf("Unable to init instance: %v", err) @@ -652,35 +667,67 @@ func quit() { ) } -// SetIcon sets the systray icon. -// iconBytes should be the content of .ico for windows and .ico/.jpg/.png -// for other platforms. -func SetIcon(iconBytes []byte) { +func iconBytesToFilePath(iconBytes []byte) (string, error) { bh := md5.Sum(iconBytes) dataHash := hex.EncodeToString(bh[:]) iconFilePath := filepath.Join(os.TempDir(), "systray_temp_icon_"+dataHash) if _, err := os.Stat(iconFilePath); os.IsNotExist(err) { if err := ioutil.WriteFile(iconFilePath, iconBytes, 0644); err != nil { - log.Errorf("Unable to write icon data to temp file: %v", err) - return + return "", err } } + return iconFilePath, nil +} +// SetIcon sets the systray icon. +// iconBytes should be the content of .ico for windows and .ico/.jpg/.png +// for other platforms. +func SetIcon(iconBytes []byte) { + iconFilePath, err := iconBytesToFilePath(iconBytes) + if err != nil { + log.Errorf("Unable to write icon data to temp file: %v", err) + return + } if err := wt.setIcon(iconFilePath); err != nil { log.Errorf("Unable to set icon: %v", err) return } } +// SetTemplateIcon sets the systray icon as a template icon (on macOS), falling back +// to a regular icon on other platforms. +// templateIconBytes and iconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + SetIcon(regularIconBytes) +} + // SetTitle sets the systray title, only available on Mac. func SetTitle(title string) { // do nothing } -// SetIcon sets the icon of a menu item. Only available on Mac. +// 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) { - // do nothing + iconFilePath, err := iconBytesToFilePath(iconBytes) + if err != nil { + log.Errorf("Unable to write icon data to temp file: %v", err) + return + } + + h, err := wt.loadIconFrom(iconFilePath) + if err != nil { + log.Errorf("Unable to load icon from temp file: %v", err) + return + } + + err = wt.addOrUpdateMenuItem(item.id, item.title, item.disabled, item.checked, h) + if err != nil { + log.Errorf("Unable to addOrUpdateMenuItem: %v", err) + return + } } // SetTooltip sets the systray tooltip to display on mouse hover of the tray icon, @@ -693,13 +740,21 @@ func SetTooltip(tooltip string) { } func addOrUpdateMenuItem(item *MenuItem) { - err := wt.addOrUpdateMenuItem(item.id, item.title, item.disabled, item.checked) + err := wt.addOrUpdateMenuItem(item.id, item.title, item.disabled, item.checked, 0) if err != nil { log.Errorf("Unable to addOrUpdateMenuItem: %v", err) return } } +// SetTemplateIcon sets the icon of a menu item as a template icon (on macOS). On Windows, it +// falls back to the regular icon bytes and on Linux it does nothing. +// templateIconBytes and regularIconBytes should be the content of .ico for windows and +// .ico/.jpg/.png for other platforms. +func (item *MenuItem) SetTemplateIcon(templateIconBytes []byte, regularIconBytes []byte) { + item.SetIcon(regularIconBytes) +} + func addSeparator(id int32) { err := wt.addSeparatorMenuItem(id) if err != nil {