@@ -16,19 +16,27 @@ - (void)statusItemClicked:(id)sender;
1616- (void )statusItemRightClicked : (id )sender ;
1717@end
1818
19+ @interface TrayIconMenuDelegate : NSObject <NSMenuDelegate >
20+ @property (nonatomic , assign ) nativeapi::TrayIcon* trayIcon;
21+ @end
22+
1923namespace nativeapi {
2024
2125// Private implementation class
2226class TrayIcon ::Impl {
2327 public:
24- Impl () : ns_status_item_(nil ), delegate_(nil ), visible_(false ) {}
28+ Impl () : ns_status_item_(nil ), delegate_(nil ), menu_delegate_( nil ), visible_(false ) {}
2529
26- Impl (NSStatusItem * status_item) : ns_status_item_(status_item), delegate_(nil ), visible_(false ) {
30+ Impl (NSStatusItem * status_item) : ns_status_item_(status_item), delegate_(nil ), menu_delegate_( nil ), visible_(false ) {
2731 if (status_item) {
2832 // Create and set up delegate
2933 delegate_ = [[TrayIconDelegate alloc ] init ];
3034 delegate_.trayIcon = nullptr ; // Will be set later
3135
36+ // Create menu delegate
37+ menu_delegate_ = [[TrayIconMenuDelegate alloc ] init ];
38+ menu_delegate_.trayIcon = nullptr ; // Will be set later
39+
3240 // Set up click handlers
3341 [status_item.button setTarget: delegate_];
3442 [status_item.button setAction: @selector (statusItemClicked: )];
@@ -39,21 +47,26 @@ - (void)statusItemRightClicked:(id)sender;
3947 }
4048
4149 ~Impl () {
42- // First, clean up delegate to prevent callbacks
50+ // First, clean up delegates to prevent callbacks
4351 if (delegate_) {
4452 delegate_.trayIcon = nullptr ; // Clear the raw pointer first
4553 delegate_ = nil ;
4654 }
4755
56+ if (menu_delegate_) {
57+ menu_delegate_.trayIcon = nullptr ; // Clear the raw pointer first
58+ menu_delegate_ = nil ;
59+ }
60+
4861 // Then clean up the status item
4962 if (ns_status_item_) {
5063 // Remove target and action to prevent callbacks after destruction
5164 if (ns_status_item_.button ) {
5265 [ns_status_item_.button setTarget: nil ];
5366 [ns_status_item_.button setAction: nil ];
5467 }
55- // Clear the menu to break potential circular references
56- [ ns_status_item_ setMenu: nil ] ;
68+ // Clear menu reference
69+ ns_status_item_. menu = nil ;
5770 [[NSStatusBar systemStatusBar ] removeStatusItem: ns_status_item_];
5871 ns_status_item_ = nil ;
5972 }
@@ -66,6 +79,7 @@ - (void)statusItemRightClicked:(id)sender;
6679
6780 NSStatusItem * ns_status_item_;
6881 TrayIconDelegate* delegate_;
82+ TrayIconMenuDelegate* menu_delegate_;
6983 std::shared_ptr<Menu> context_menu_;
7084 std::string title_;
7185 std::string tooltip_;
@@ -77,13 +91,19 @@ - (void)statusItemRightClicked:(id)sender;
7791 if (pimpl_->delegate_ ) {
7892 pimpl_->delegate_ .trayIcon = this ;
7993 }
94+ if (pimpl_->menu_delegate_ ) {
95+ pimpl_->menu_delegate_ .trayIcon = this ;
96+ }
8097}
8198
8299TrayIcon::TrayIcon (void * tray) : pimpl_(std::make_unique<Impl>((__bridge NSStatusItem *)tray)) {
83100 id = -1 ; // Will be set by TrayManager when created
84101 if (pimpl_->delegate_ ) {
85102 pimpl_->delegate_ .trayIcon = this ;
86103 }
104+ if (pimpl_->menu_delegate_ ) {
105+ pimpl_->menu_delegate_ .trayIcon = this ;
106+ }
87107}
88108
89109TrayIcon::~TrayIcon () {
@@ -171,24 +191,11 @@ - (void)statusItemRightClicked:(id)sender;
171191}
172192
173193void TrayIcon::SetContextMenu (std::shared_ptr<Menu> menu) {
174- // First, clean up old menu reference
175- if (pimpl_->context_menu_ ) {
176- if (pimpl_->ns_status_item_ ) {
177- [pimpl_->ns_status_item_ setMenu: nil ];
178- }
179- pimpl_->context_menu_ .reset ();
180- }
181-
194+ // Store the menu reference
195+ // Don't set the menu directly to the status item, as this would cause
196+ // macOS to take over click handling and prevent our custom click events
197+ // Instead, we'll show the menu manually in our click handler
182198 pimpl_->context_menu_ = menu;
183-
184- // Set the menu as the status item's menu for right-click
185- if (pimpl_->ns_status_item_ && menu) {
186- // Get the NSMenu from the Menu object
187- NSMenu * nsMenu = (__bridge NSMenu *)menu->GetNativeObject ();
188- if (nsMenu) {
189- [pimpl_->ns_status_item_ setMenu: nsMenu];
190- }
191- }
192199}
193200
194201std::shared_ptr<Menu> TrayIcon::GetContextMenu () {
@@ -242,30 +249,46 @@ - (void)statusItemRightClicked:(id)sender;
242249 return pimpl_->visible_ ;
243250}
244251
245-
246- bool TrayIcon::ShowContextMenu (double x, double y) {
252+ bool TrayIcon::OpenContextMenu (double x, double y) {
247253 if (!pimpl_->context_menu_ ) {
248254 return false ;
249255 }
250256
251- // Show the context menu at the specified coordinates
257+ // Open the context menu at the specified coordinates
252258 return pimpl_->context_menu_ ->ShowAsContextMenu (x, y);
253259}
254260
255- bool TrayIcon::ShowContextMenu () {
256- if (!pimpl_->context_menu_ ) {
261+ bool TrayIcon::OpenContextMenu () {
262+ if (!pimpl_->context_menu_ || !pimpl_-> ns_status_item_ || !pimpl_-> ns_status_item_ . button ) {
257263 return false ;
258264 }
259265
260- // Get the bounds of the tray icon to show menu near it
261- Rectangle bounds = GetBounds ();
262- if (bounds.width > 0 && bounds.height > 0 ) {
263- // Show menu below the tray icon
264- return pimpl_->context_menu_ ->ShowAsContextMenu (bounds.x , bounds.y + bounds.height );
265- } else {
266- // Fall back to showing at mouse location
267- return pimpl_->context_menu_ ->ShowAsContextMenu ();
266+ // Use the Swift approach: set menu to status item and simulate click
267+ // Get the native NSMenu object from our Menu wrapper
268+ NSMenu * nativeMenu = (__bridge NSMenu *)pimpl_->context_menu_ ->GetNativeObject ();
269+ if (!nativeMenu) {
270+ return false ;
268271 }
272+
273+ // Set our menu delegate to handle menu close events
274+ [nativeMenu setDelegate: pimpl_->menu_delegate_];
275+
276+ // Set the menu to the status item (like Swift version)
277+ pimpl_->ns_status_item_ .menu = nativeMenu;
278+
279+ // Simulate a click to show the menu (like Swift version)
280+ [pimpl_->ns_status_item_.button performClick: nil ];
281+
282+ return true ;
283+ }
284+
285+ bool TrayIcon::CloseContextMenu () {
286+ if (!pimpl_->context_menu_ ) {
287+ return true ; // No menu to close, consider success
288+ }
289+
290+ // Close the context menu
291+ return pimpl_->context_menu_ ->Close ();
269292}
270293
271294// Internal method to handle click events
@@ -293,6 +316,12 @@ - (void)statusItemRightClicked:(id)sender;
293316 }
294317}
295318
319+ void TrayIcon::ClearStatusItemMenu () {
320+ if (pimpl_->ns_status_item_ ) {
321+ pimpl_->ns_status_item_ .menu = nil ;
322+ }
323+ }
324+
296325} // namespace nativeapi
297326
298327// Implementation of TrayIconDelegate
@@ -314,11 +343,6 @@ - (void)statusItemClicked:(id)sender {
314343 (event.type == NSEventTypeLeftMouseUp && (event.modifierFlags & NSEventModifierFlagControl))) {
315344 // Right click or Ctrl+Left click
316345 trayIcon->HandleRightClick ();
317-
318- // Show context menu if available (check again for safety)
319- if (_trayIcon && _trayIcon->GetContextMenu ()) {
320- _trayIcon->ShowContextMenu ();
321- }
322346 } else if (event.type == NSEventTypeLeftMouseUp) {
323347 // Check for double click
324348 if (event.clickCount == 2 ) {
@@ -337,3 +361,16 @@ - (void)statusItemRightClicked:(id)sender {
337361}
338362
339363@end
364+
365+ // Implementation of TrayIconMenuDelegate
366+ @implementation TrayIconMenuDelegate
367+
368+ - (void )menuDidClose : (NSMenu *)menu {
369+ // Check if trayIcon is still valid before proceeding
370+ if (_trayIcon) {
371+ // Call a public method to clear the menu (we'll add this method)
372+ _trayIcon->ClearStatusItemMenu ();
373+ }
374+ }
375+
376+ @end
0 commit comments