mirror of
https://github.com/netbirdio/systray.git
synced 2026-05-22 17:08:41 -07:00
Added support for template icons on macOS
Cherry-picked https://github.com/getlantern/systray/pull/119 with significant changes to systray_windows.go to support setting icon on menu items.
This commit is contained in:
+5
-2
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+4
-3
@@ -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]);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
+90
-35
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user