Replies: 5 comments 4 replies
-
// Package systray provides a macOS-specific system tray implementation.
package systray
type MenuItem struct {
id int
title string
handler func()
ref any
subMenu *Menu
parentMenu *Menu
isSubMenu bool
}
type Menu struct {
items []*MenuItem
ref any
}
var rootMenu *Menu
// Initialize initializes the systray
func Initialize() {
rootMenu = &Menu{
items: make([]*MenuItem, 0),
}
initialize()
}
func SetIcon(iconPath string) { updateIcon(iconPath) }
func SetTooltip(tooltipText string) { updateTooltip(tooltipText) }
func SetIconClickHandler(handler func()) { updateIconClickHandler(handler) }
func AddSeparator() { addSeparator() }
func AddMenuItem(title string, handler func()) *MenuItem {
if rootMenu.items == nil {
rootMenu.items = make([]*MenuItem, 0)
}
item := &MenuItem{
id: len(rootMenu.items),
title: title,
handler: handler,
parentMenu: rootMenu,
}
rootMenu.items = append(rootMenu.items, item)
addMenuItem(item)
return item
}
// AddSubMenuItem creates a submenu for the given menu item
func (item *MenuItem) AddSubMenuItem(title string, handler func()) *MenuItem {
if item.subMenu == nil {
item.subMenu = &Menu{
items: make([]*MenuItem, 0),
}
createSubMenu(item)
}
subItem := &MenuItem{
id: len(item.subMenu.items),
title: title,
handler: handler,
parentMenu: item.subMenu,
isSubMenu: true,
}
item.subMenu.items = append(item.subMenu.items, subItem)
addSubMenuItem(item, subItem)
return subItem
}
// AddSeparator adds a separator to this menu item's submenu
func (item *MenuItem) AddSeparator() {
if item.subMenu == nil {
item.subMenu = &Menu{
items: make([]*MenuItem, 0),
}
createSubMenu(item)
}
// Call the function to add a separator to this item's submenu
addSubmenuSeparator(item)
}
func ClearMenuItems() {
clearMenuItems()
rootMenu.items = nil
}//go:build darwin
// +build darwin
package systray
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Cocoa
#include <stdlib.h>
// Function declarations for systray operations
void systray_init();
void systray_set_icon(const char* path);
void systray_set_tooltip(const char* tooltip);
int systray_add_menu_item(const char* title, const char* tooltip);
void systray_add_separator();
void systray_update_menu_item(int menuId, const char* title);
void systray_remove_menu_item(int menuId);
void systray_clear_menu_items();
// Function declarations for submenu support
void systray_create_submenu(int parentId);
int systray_add_submenu_item(int parentId, const char* title, const char* tooltip);
void systray_add_submenu_separator(int parentId);
// These functions are implemented in Go
extern void systray_ready();
extern void systray_menu_item_selected(int menu_id);
extern void systray_icon_clicked();
*/
import "C"
import (
"runtime"
"sync"
"unsafe"
)
var (
menuLock sync.Mutex
initialized bool
)
var (
menuItemHandlers = make(map[int]func())
iconClickHandler func()
menuItemRefs = make(map[int]*MenuItem)
)
// initialize initializes the systray and shows the icon in the status bar
func initialize() {
menuLock.Lock()
defer menuLock.Unlock()
if !initialized {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
C.systray_init()
initialized = true
}
}
// updateIcon sets the systray icon
func updateIcon(iconPath string) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cPath := C.CString(iconPath)
C.systray_set_icon(cPath)
C.free(unsafe.Pointer(cPath))
}
// updateTooltip sets the systray tooltip (no-op now)
func updateTooltip(tooltipText string) {
// Tooltip support removed
}
// updateIconClickHandler sets the handler for icon clicks
func updateIconClickHandler(handler func()) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
iconClickHandler = handler
}
//export systray_ready
func systray_ready() {
// This is called when the systray is ready
}
//export systray_menu_item_selected
func systray_menu_item_selected(menuID C.int) {
menuItemHandlers[int(menuID)]()
}
// addMenuItem adds a menu item to the systray
func addMenuItem(item *MenuItem) {
menuLock.Lock()
defer menuLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cTitle := C.CString(item.title)
menuID := int(C.systray_add_menu_item(cTitle, nil))
C.free(unsafe.Pointer(cTitle))
item.id = menuID
menuItemHandlers[menuID] = item.handler
menuItemRefs[menuID] = item
}
func addSeparator() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
C.systray_add_separator()
}
func removeMenuItem(item *MenuItem) {
menuLock.Lock()
defer menuLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if item != nil && item.id >= 0 {
// Remove from UI first
C.systray_remove_menu_item(C.int(item.id))
// Then remove handler
delete(menuItemHandlers, item.id)
delete(menuItemRefs, item.id)
item.id = -1 // Mark as removed
}
}
// createSubMenu creates a submenu for the given menu item
func createSubMenu(item *MenuItem) {
menuLock.Lock()
defer menuLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if item != nil && item.id >= 0 {
C.systray_create_submenu(C.int(item.id))
if item.subMenu == nil {
item.subMenu = &Menu{items: make([]*MenuItem, 0)}
}
}
}
// addSubMenuItem adds an item to a submenu
func addSubMenuItem(parent *MenuItem, item *MenuItem) {
menuLock.Lock()
defer menuLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cTitle := C.CString(item.title)
// Add to the parent's submenu using the Objective-C implementation
menuID := int(C.systray_add_submenu_item(C.int(parent.id), cTitle, nil))
C.free(unsafe.Pointer(cTitle))
item.id = menuID
menuItemHandlers[menuID] = item.handler
menuItemRefs[menuID] = item
if parent.subMenu != nil {
parent.subMenu.items = append(parent.subMenu.items, item)
}
}
// addSubmenuSeparator adds a separator to a submenu
func addSubmenuSeparator(parent *MenuItem) {
menuLock.Lock()
defer menuLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
if parent != nil && parent.id >= 0 {
C.systray_add_submenu_separator(C.int(parent.id))
}
}
func clearMenuItems() {
menuLock.Lock()
defer menuLock.Unlock()
runtime.LockOSThread()
defer runtime.UnlockOSThread()
C.systray_clear_menu_items()
menuItemHandlers = make(map[int]func())
menuItemRefs = make(map[int]*MenuItem)
}
//export systray_icon_clicked
func systray_icon_clicked() {
if iconClickHandler != nil {
iconClickHandler()
}
} |
Beta Was this translation helpful? Give feedback.
-
|
@alexec the code you posted works but partially. It shows the tray icon ( |
Beta Was this translation helpful? Give feedback.
-
|
You can't call SetIconClickHandler if you add any menus. This is a Darwin limitation. |
Beta Was this translation helpful? Give feedback.
-
|
Trying to get it working: systray.Initialize()
systray.SetIcon("./build/appicon.png")
systray.SetIconClickHandler(func() {
log.Println("Tray icon clicked")
})This shows the icon, but log message is not there when I click it. @alexec does it work for you? Could you please share a working code example? 🙏 |
Beta Was this translation helpful? Give feedback.
-
|
Systray will not be supported in v2. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Is your feature request related to a problem? Please describe.
I'd like a systray in v2. v3 is alpha for 3(?) years now.
The https://github.com/getlantern/systray does not work because of objc linking conflict.
The APIs for systray are public.
Describe the solution you'd like
SysTray.
Describe alternatives you've considered
No response
Additional context
No response
Beta Was this translation helpful? Give feedback.
All reactions