Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions v3/UNRELEASED_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ After processing, the content will be moved to the main changelog and this file

## Fixed
<!-- Bug fixes -->
- Fix macOS system tray menu real-time updates using NSMenuDelegate (#4630)
- Implement macOS system tray onMenuOpen/onMenuClose callbacks for parity with Windows and Linux

## Deprecated
<!-- Soon-to-be removed features -->
Expand Down
43 changes: 43 additions & 0 deletions v3/pkg/application/systemtray_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type macosSystemTray struct {
nsStatusItem unsafe.Pointer
nsImage unsafe.Pointer
nsMenu unsafe.Pointer
nsMenuDelegate unsafe.Pointer
iconPosition IconPosition
isTemplateIcon bool
parent *SystemTray
Expand Down Expand Up @@ -93,12 +94,51 @@ func systrayClickCallback(id C.long, buttonID C.int) {
systemTray.processClick(button(buttonID))
}

//export systrayMenuOpenCallback
func systrayMenuOpenCallback(id C.long) {
systemTray := systemTrayMap[uint(id)]
if systemTray == nil {
return
}
if systemTray.parent.onMenuOpen != nil {
systemTray.parent.onMenuOpen()
}
}

//export systrayMenuCloseCallback
func systrayMenuCloseCallback(id C.long) {
systemTray := systemTrayMap[uint(id)]
if systemTray == nil {
return
}
if systemTray.parent.onMenuClose != nil {
systemTray.parent.onMenuClose()
}
}

func (s *macosSystemTray) setIconPosition(position IconPosition) {
s.iconPosition = position
}

func (s *macosSystemTray) setMenu(menu *Menu) {
s.menu = menu
if menu == nil {
s.nsMenu = nil
return
}

// Update the menu structure
globalApplication.dispatchOnMainThread(func() {
menu.Update()
// Update the nsMenu pointer to the new menu
s.nsMenu = (menu.impl).(*macosMenu).nsMenu

// Set up the delegate if not already done
if s.nsMenuDelegate == nil {
s.nsMenuDelegate = C.createMenuDelegate(s.nsMenu, C.long(s.id))
}
C.setMenuDelegate(s.nsMenu, s.nsMenuDelegate)
})
}

func (s *macosSystemTray) positionWindow(window Window, offset int) error {
Expand Down Expand Up @@ -167,6 +207,9 @@ func (s *macosSystemTray) run() {
s.menu.Update()
// Convert impl to macosMenu object
s.nsMenu = (s.menu.impl).(*macosMenu).nsMenu
// Set up the menu delegate for real-time updates
s.nsMenuDelegate = C.createMenuDelegate(s.nsMenu, C.long(s.id))
C.setMenuDelegate(s.nsMenu, s.nsMenuDelegate)
}
})
}
Expand Down
9 changes: 8 additions & 1 deletion v3/pkg/application/systemtray_darwin.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
- (void)statusItemClicked:(id)sender;
@end

@interface MenuDelegate : NSObject <NSMenuDelegate>
@property (nonatomic) void* menuPtr;
@property (nonatomic) long trayID;
@end

void* systemTrayNew(long id);
void systemTraySetLabel(void* nsStatusItem, char *label);
void systemTraySetANSILabel(void* nsStatusItem, void* attributedString);
Expand All @@ -21,4 +26,6 @@ void systemTrayGetBounds(void* nsStatusItem, NSRect *rect, void **screen);
NSRect NSScreen_frame(void* screen);
void windowSetScreen(void* window, void* screen, int yOffset);
int statusBarHeight();
void systemTrayPositionWindow(void* nsStatusItem, void* nsWindow, int offset);
void systemTrayPositionWindow(void* nsStatusItem, void* nsWindow, int offset);
void* createMenuDelegate(void* menuPtr, long trayID);
void setMenuDelegate(void* nsMenu, void* delegate);
36 changes: 36 additions & 0 deletions v3/pkg/application/systemtray_darwin.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "systemtray_darwin.h"

extern void systrayClickCallback(long, int);
extern void systrayMenuOpenCallback(long);
extern void systrayMenuCloseCallback(long);

// StatusItemController.m
@implementation StatusItemController
Expand All @@ -16,6 +18,25 @@ - (void)statusItemClicked:(id)sender {

@end

// MenuDelegate implementation
@implementation MenuDelegate

- (void)menuNeedsUpdate:(NSMenu *)menu {
// This method is called automatically by macOS when the menu is about to be displayed
// or when it needs updating while already open
// The Go side will handle the actual menu rebuilding through menu.Update()
}

- (void)menuWillOpen:(NSMenu *)menu {
systrayMenuOpenCallback(self.trayID);
}

- (void)menuDidClose:(NSMenu *)menu {
systrayMenuCloseCallback(self.trayID);
}

@end

// Create a new system tray
void* systemTrayNew(long id) {
StatusItemController *controller = [[StatusItemController alloc] init];
Expand Down Expand Up @@ -242,3 +263,18 @@ void systemTrayPositionWindow(void* nsStatusItem, void* nsWindow, int offset) {
windowFrame.origin.y = windowY;
[(NSWindow*)nsWindow setFrame:windowFrame display:YES animate:NO];
}

// Create a new menu delegate
void* createMenuDelegate(void* menuPtr, long trayID) {
MenuDelegate *delegate = [[MenuDelegate alloc] init];
delegate.menuPtr = menuPtr;
delegate.trayID = trayID;
return (void*)delegate;
}

// Set the delegate on a menu
void setMenuDelegate(void* nsMenu, void* delegate) {
NSMenu *menu = (NSMenu *)nsMenu;
MenuDelegate *menuDelegate = (MenuDelegate *)delegate;
[menu setDelegate:menuDelegate];
}
Loading