GNOME Bugzilla – Bug 549153
StatusIcon on OS X does not pop down when expected
Last modified: 2014-08-30 05:20:28 UTC
1. Set up a StatusIcon that responds to the PopupMenu signal by showing a menu (I like to use this example http://www.mono-project.com/GtkSharpNotificationIcon ). 2. Run the program, and click the StatusIcon. Notice that the menu appears. 3. Click the StatusIcon a second time. Expect: On the second click, the existing menu should go away. A third click would bring it back. Actual: On the second click, a second menu identical to the first appears. The only way to make the menu go away is to choose one of the options from it. It is possible to have several instances of the popup menu appear by continuing to click on it until it is completely covered in menus.
I can reproduce this. I'll try to look into it but I don't know when I will have time. One temporary option could be to disable the icon or just the icon's menu on Mac for now in your application.
Thanks for looking into it. Maybe I'll find some other work-around or implement a dock icon if that's not too hard, as I'm investigating this so I can get Tomboy running on OS X (and the StatusIcon is an important UI component). Overall, it's really great to see gtk+ available on OS X!
If you want to see some examples of how to do dock integration, you can checkout Gossip in GNOME SVN (e.g. the file http://svn.gnome.org/viewvc/gossip/trunk/src/gossip-mac-dock.c?view=markup)
Btw, for technical reasons it's not possible to use the dock icon's menu currently (it's because we initialize carbon before cocoa, which we have to do for other things to work). It might/should be possible to fix at some point though.
Can reproduce as well. What I notice when comparing to X11 is this: When clicking anywhere not on the window of the menu the event is swallowed and used to popdown. However, on quartz the event is still processed. Try clicking on the global menu bar or dock while the popup is active. You will notice on OS X that the click is still processed unlike X11.
(In reply to comment #5) > Can reproduce as well. What I notice when comparing to X11 is this: > > When clicking anywhere not on the window of the menu the event is swallowed and > used to popdown. However, on quartz the event is still processed. > > Try clicking on the global menu bar or dock while the popup is active. You > will notice on OS X that the click is still processed unlike X11. The behavior you describe also occurs in Windows, fwiw.
I changed the title to reflect what is really going on: menus popped up by status icons do not pop down when expected. This is a hard problem to solve. Typically, menus are popped down when you click somewhere outside the area of the menu window. For this, you need to catch an event that is outside the area. On OS X, there is no notion of desktop-wide grabs that you could use for this, also you do not receive events from other applications (so from outside your window) by default. In menus belonging to application windows the pop down works fine because we use the application deactivated event. If a menu of some window is active, and you click somewhere else on the screen, your application gets deactivated and we use this event to generate a GDK grab broken event that will pop down the menu. For menus that are popped up after a click on a status icon we have to deal with a different scenario. In this case, the application does not (always) really become active, so we cannot rely on application deactivation to pop down the menu again. So a different means has to be found to detect an event outside the application. There is a way to get events outside the application, through Carbon API in 10.5 and earlier and Cocoa API in 10.6. This cannot be applied in the status icon implementation (where it is really needed), because the status icon does not have a reference to the menu. Another gross hack would be needed to get the currently focused window (key window), which should be the menu, and send some destroy event to that. Perhaps we can enable event delivery from outside applications once a popup window (GDK_WINDOW_TEMP) is active in GDK and have some special handling. I think this remains hackish however. Best of course is to pop up a "native" menu when the status icon is clicked ... But the GtkStatusIcon API does not really seem suited for that, mainly because menu creation is fully outside its control. For now it is perhaps not a bad idea to emit "activate" instead of "popup-menu" when the status icon is clicked to avoid popping up a menu that is hard to get rid of.
I believe this is the same bug that we have just been discussing on gtk-osx: http://sourceforge.net/mailarchive/message.php?msg_id=27439497 This is a major show-stopper for me: the pop-down menu does not get focus if the application was not active when the StatusIcon is clicked. Any workarounds would be much appreciated. I am attaching a simple test case (referred to in the thread above).
Created attachment 187689 [details] ultra simple pygtk test case showing the menu problem Launch it, click on the desktop then click on the StatusIcon, the pop-down menu will not react to mouse clicks as it should (it takes a long click for it to register anything). (OSX only)
I am not certain but I may already have a fix for this that has nothing to do with the StatusIcon at all. We were having problems in Ardour with menus requiring multiple clicks, and I determined that what was missing was an enter notify event. I have a hack in my codebase that delivers this appropriately - it fixed our problem. It will take me a while to dig it out. Please bug me again if I haven't attached it within a day or two.
Bump! Do you remember if it was as simple as firing a signal on the menu by hand? Or was the workaround more complicated?
ok, this is the patch i had to use for menus: --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -1213,6 +1213,15 @@ gdk_window_quartz_show (GdkWindow *window, gboolean already_mapped) if (impl->transient_for && !GDK_WINDOW_DESTROYED (impl->transient_for)) _gdk_quartz_window_attach_to_parent (window); + if (NSPointInRect ([[impl->view window] convertScreenToBase:[NSEvent mouseLocation]], [impl->view bounds])) + { + /* When a new window (and thus view) has been created, and the mouse + * is in the window area, we will not receive an NSMouseEntered + * event. Therefore, we synthesize an enter notify event manually. + */ + _gdk_quartz_events_send_enter_notify_event (window); + } + GDK_QUARTZ_RELEASE_POOL; } I'm not sure that this will solve the bug here, but it may be relevant.
The patch sounds like it should solve the problem... but it doesn't seem to be making any difference for me. Can anyone else try that test case I attached? Is there any way we can force the application to become active when the status icon is clicked? Whatever code currently makes NSApplicationDidBecomeActive fire? Since the code works when the application is already active, my thinking is that making the application active when the StatusIcon is clicked (but before the menu is popped down) ought to solve the problem? (I just have no idea where to look to do just that - pointers welcome so I can have a go)
(In reply to comment #12) > ok, this is the patch i had to use for menus: Since which version did you need this patch? The added code looks exactly like what we have in GdkQuartzView -updateTrackingRect. Is the purpose of this patch to send a enter notify event when a hidden (but already created) window becomes visible? I think that in that case you currently do not come past updateTrackingRect (which is called from setFrame and viewDidMoveToWindow).
(In reply to comment #13) > Is there any way we can force the application to become active when the status > icon is clicked? Whatever code currently makes NSApplicationDidBecomeActive > fire? In that case it will still break in the following way: 1) Make sure the application is active. 2) Click on the status icon so a menu pops up. 3) Click on the apple menu in the left hand corner. The status icon menu will not pop down.
Thinking some more, step 3) actually needs to pop down the gtk+ menu anyway and we do receive an event when the user clicks anywhere on the menubar if the application is active. So this fix combined with forcing the application to become active when the status icon is clicked would then work. I guess that could work for now. A better fix would involve using a part of Carbon *or* using NSTrackingArea. The problem with NSTrackingArea is that it is Mac OS X 10.5+ only and we try to do an effort to keep things running on 10.4 ...
[NSView addTrackingRect:owner:userData:assumeInside:] to preserve 10.4 compatibility -- assuming we still want to do that. However, it seems to me that a better approach would be to convert the GtkPopupMenu to an NSMenu and let OSX handle displaying it. It will look more native that way and should behave correctly without a lot of hacking on our part. Since OSX will be handling the menu events directly, it might even solve the problem of activating the application's run loop. BTW, Richard's comment (#4) about Dock Menus not working is no longer correct. They work fine when using GtkOSXApplication's dock integration (in ige-mac-integration). Example of use in src/test-integration.c
(In reply to comment #17) > [NSView addTrackingRect:owner:userData:assumeInside:] to preserve 10.4 > compatibility -- assuming we still want to do that. The problem with the 10.4 API is that it doesn't allow you to get events when your application is not active. The 10.5+ API does have support for that. That's why we are still using addTrackingRect: in GdkQuartzView instead of the newer API. > > However, it seems to me that a better approach would be to convert the > GtkPopupMenu to an NSMenu and let OSX handle displaying it. It will look more > native that way and should behave correctly without a lot of hacking on our > part. Since OSX will be handling the menu events directly, it might even solve > the problem of activating the application's run loop. It is a trade-off. Either we use the GTK+ menu items and everything displays correctly, but we need to get the event handling right. The other option is to convert all GTK+ menu items to NSMenu items and in this case the non-standard cases will not display correctly. Also, what will happen if an application in question uses it's own menu items (people are free to derive from GtkMenuItem). How do we convert such items? Currently I have the impression it is easier to get the event handling right than having to deal with converting all kinds, and worst case unknown, menu items ...
(In reply to comment #18) > (In reply to comment #17) > > [NSView addTrackingRect:owner:userData:assumeInside:] to preserve 10.4 > > compatibility -- assuming we still want to do that. > > The problem with the 10.4 API is that it doesn't allow you to get events when > your application is not active. The 10.5+ API does have support for that. > That's why we are still using addTrackingRect: in GdkQuartzView instead of the > newer API. > It's a policy question. One 10.5-only function call and 10.4 support is over. With 10.7's release imminent it's probably time, anyway. > > > > However, it seems to me that a better approach would be to convert the > > GtkPopupMenu to an NSMenu and let OSX handle displaying it. It will look more > > native that way and should behave correctly without a lot of hacking on our > > part. Since OSX will be handling the menu events directly, it might even solve > > the problem of activating the application's run loop. > > It is a trade-off. Either we use the GTK+ menu items and everything displays > correctly, but we need to get the event handling right. The other option is to > convert all GTK+ menu items to NSMenu items and in this case the non-standard > cases will not display correctly. Also, what will happen if an application in > question uses it's own menu items (people are free to derive from GtkMenuItem). > How do we convert such items? > > Currently I have the impression it is easier to get the event handling right > than having to deal with converting all kinds, and worst case unknown, menu > items ... I see the trade off a bit differently: Either we use OSX's menus and OSX does the heavy lifting or we don't and beat ourselves bloody trying to handle low level events ourselves -- an OS inside an OS. What non-standard cases are you thinking of? I haven't had any reports of problems with GtkOSXApplication menus not working, in spite of the byzantine menu-synchronization and event handling that goes on to make it all work. It would be nice to get rid of all that and have the menus work in a native way. If in the quartz backend GtkMenuShell is derived from NSMenu and GtkMenuItem from NSMenuItem, then classes derived from those will work too.
(In reply to comment #19) > It's a policy question. One 10.5-only function call and 10.4 support is over. > With 10.7's release imminent it's probably time, anyway. I think that as long as we can reasonably support 10.4, we should go for it. > I see the trade off a bit differently: Either we use OSX's menus and OSX does > the heavy lifting or we don't and beat ourselves bloody trying to handle low > level events ourselves -- an OS inside an OS. OS inside an OS: I think that's a bit strongly said. The backend is more or less a mapping between Quartz and GDK, which still bears too much similarities to X, but I am confident that will continue to change in the near future. In the backend there is already quite some low level event handling going on. Currently I am looking at the Carbon way to get external events and it doesn't look all that bad. > What non-standard cases are you thinking of? I haven't had any reports of > problems with GtkOSXApplication menus not working, in spite of the byzantine > menu-synchronization and event handling that goes on to make it all work. It > would be nice to get rid of all that and have the menus work in a native way. If you happen to have a GtkMenuItem which does not have a label set, but instead uses some other random widget, then it will not show correctly because the menu synching code seems to depend on being able to find a GtkLabel. Cases like this are very likely limited, but theoretically they can exist and might turn up in the future. The point is whether one wants to deal with cases like this or clearly state the limitations of the cross-platform support. I would like to see proper support for native menus in GTK+ itself as well, but I think that the way GTK+ deals with menus has to be changed first to be less flexible so it becomes easier to map things to other platforms. > If in the quartz backend GtkMenuShell is derived from NSMenu and GtkMenuItem > from NSMenuItem, then classes derived from those will work too. GtkMenuShell derives from GtkContainer, I don't see how this can be made to derive from NSMenu instead. I will be looking at the Carbon way some more -- I have an idea of how to integrate this in the code handling popup windows and need to see if this will work.
There's another couple of reasons not to want native menus: 1) they do not support some shortcuts/bindings/accelerators that GTK does 2) its not possible to use them for dynamic rebinding as GTK allows Kristian - is the Carbon code you're looking at clearly labelled as "not deprecated"?
Regardless of whether it's "not deprecated", it's "not 64-bit". BTW, the only way now to tell what's deprecated is to look in the header files in the various subframeworks of Carbon.framework in MacOSX.10.7.sdk; the documentation has been withdrawn to "Legacy" and is no longer maintained. Let's not introduce any Carbon dependencies. That would be a big step backwards. Paul's second objection to native menus is at least not a strawman. (The label synch Kristian was complaining about isn't in ige-mac-menu any more, and was never in GtkOSXApplication; icon-only menus can be supported, even if they are a dumb idea that breaks a11y; DND, Clipboard, and probably other quartz-bits of Gtk are all tightly integrated with Cocoa classes; what works as an accelerator on quartz is determined by key-event handling, not anything in the menu code.) However, it's not behavior that Mac users expect. Paul, do you really think that your Mac users are looking for an application which duplicates the Linux UI on their Macs? Don't you think that if that was true, they'd rather run it under X11, so instant selection works? Besides, don't you use your C++ version of GtkOSXApplication which I adapted for ige-mac-integration?
(In reply to comment #22) > Regardless of whether it's "not deprecated", it's "not 64-bit". BTW, the only It is marked as "not deprecated" and "not 32-bit only", so the API has been ported to 64-bit. There are still things that are unfortunately only possible with Carbon I've been told. (Though as we've seen for this particular problem there is other API in 10.5+). > Paul's second objection to native menus is at least not a strawman. (The label > synch Kristian was complaining about isn't in ige-mac-menu any more, and was > never in GtkOSXApplication; icon-only menus can be supported, even if they are I meant this: https://github.com/jralls/ige-mac-integration/blob/master/src/getlabel.c
(In reply to comment #21) > There's another couple of reasons not to want native menus: > > 1) they do not support some shortcuts/bindings/accelerators that GTK does > 2) its not possible to use them for dynamic rebinding as GTK allows > > Kristian - is the Carbon code you're looking at clearly labelled as "not > deprecated"? Yes. And my other question to you: Are you still dealing with Tiger?
Some more thoughts: One could say that status icon menus on Mac typically do not have accelerators. So when dynamically translating GtkMenus to NSMenus, you could argue that you can "forget" about these. The other thought is that the patch I am trying to write will fix more than just the status icon menu. Currently, when you open any menu in a GTK+ application (main menu or popup menu) and you click on the Mac menu bar afterwards, the GTK+ menu will not pop down. This is because we currently do not capture the event generated by the click on the Mac menu bar, or anywhere outside of the application. So the idea is to when a grab is active, install a global event handler to capture a single mouse down event. Once we receive such an event, we break all grabs (which would popdown menus, etc) and remove the event handler again. Installing this event handler can be done either with InstallEventHandler() or with addGlobalMonitorForEventsMatchingMask:handler which is only available in 10.6+. In essence, the patch would improve the simulation of grabs we are currently doing in the GDK backend. Apart from this patch, we can still decide to turn status icons into native menus using a translation pass. As I have said before, I think it would be great to have proper support for the main menu bar, but my impression is that GTK+ core needs some changes for this to work flawlessly and to also have this work correctly and in the same way on other platforms (for example there has been much talk of GNOME going to use a global menu bar, but there should be a fallback for when GTK+ applications run under a different window manager).
> > Paul's second objection to native menus is at least not a strawman. (The label > > synch Kristian was complaining about isn't in ige-mac-menu any more, and was > > never in GtkOSXApplication; icon-only menus can be supported, even if they are > > I meant this: > > https://github.com/jralls/ige-mac-integration/blob/master/src/getlabel.c Have to set the NSMenuItem title from something. That's the something. If there's no label, then the NSMenuItem won't have a title, just like the GtkMenuItem doesn't have a label. That's out on its own because it's also used in several places to get the label for error messages. For image-only menus, the error messages would have to report something like "The 4th item on the 3rd menu in the menubar" instead.
(In reply to comment #23) > (In reply to comment #22) > > Regardless of whether it's "not deprecated", it's "not 64-bit". BTW, the only > > It is marked as "not deprecated" and "not 32-bit only", so the API has been > ported to 64-bit. There are still things that are unfortunately only possible > with Carbon I've been told. (Though as we've seen for this particular problem > there is other API in 10.5+). OK, what functions are you planning to use? As for only possible with Carbon, true. Cocoa has no way I've found to get the button metrics for the quartz theme engine, which is why I haven't rewritten it.
(In reply to comment #25) > Some more thoughts: > > One could say that status icon menus on Mac typically do not have accelerators. > So when dynamically translating GtkMenus to NSMenus, you could argue that you > can "forget" about these. > > > The other thought is that the patch I am trying to write will fix more than > just the status icon menu. > Currently, when you open any menu in a GTK+ application (main menu or popup > menu) and you click on the Mac menu bar afterwards, the GTK+ menu will not pop > down. This is because we currently do not capture the event generated by the > click on the Mac menu bar, or anywhere outside of the application. > > So the idea is to when a grab is active, install a global event handler to > capture a single mouse down event. Once we receive such an event, we break all > grabs (which would popdown menus, etc) and remove the event handler again. > Installing this event handler can be done either with InstallEventHandler() or > with addGlobalMonitorForEventsMatchingMask:handler which is only available in > 10.6+. > > In essence, the patch would improve the simulation of grabs we are currently > doing in the GDK backend. > > > Apart from this patch, we can still decide to turn status icons into native > menus using a translation pass. As I have said before, I think it would be > great to have proper support for the main menu bar, but my impression is that > GTK+ core needs some changes for this to work flawlessly and to also have this > work correctly and in the same way on other platforms (for example there has > been much talk of GNOME going to use a global menu bar, but there should be a > fallback for when GTK+ applications run under a different window manager). OK, I see where you're going with InstallEventHandler(GetEventDispatcherTarget(), ...). The only concern is that those functions aren't thread safe, but you can code around that. That also has the advantage that it goes to gdk, where the multi-backends architecture wants it. What about the other piece of the puzzle, which you discussed way back in #7, that the status icon has it's own run loop and doesn't activate the application?
Created attachment 192131 [details] [review] First attempt on a patch improving grab behavior Good news, I figured that there is a way to do this with Quartz in Mac OS X 10.4, so we can handle this without having to resort to Carbon. The API we need for this is the CGEventTap API. I was a bit unsure whether it would work due to the documentation not being fully clear, but it does seem to work just fine for me. Attached is a first patch, a work in progress, which will use such an event tap to catch mouse down events that happen outside of the GTK+ application's window when a grab is active. When such an event is found and indeed is outside any of the application's windows, it will break all grabs (which will make menus pop down). Included in the patch is a fix in GdkQuartzDeviceCore to make the status icon menu pop up correctly again. This fix will be committed separately after I have verified with the GdkDevice maintainer that it is correct. Other to do items for this patch are: - Need to check if CFRunLoopGetCurrent() will always suffice. - We should actually maintain a count of how many *different* mouse devices have an active grab. The event tap can only be removed after all these different devices have been ungrabbed. - I am still not fully happy with the behavior of the status icon. When the menu is hidden, the menus of the native status icons will still pop up. This was already a problem before patching. Also, when the status icon menu pops up when the application is inactive, there is an event missing that refrains the menu from highlighting menu items. I think regular menus also behave erratically sometimes because of this same missing event.
(In reply to comment #28) > What about the other piece of the puzzle, which you discussed way back in #7, > that the status icon has it's own run loop and doesn't activate the > application? Should not be a problem. In #7 it was a problem because we could in that case not rely on application deactivation to break the grabs / pop down the menu, because the application is not made active when you click on a status icon.
It might not be a problem for dismissing the popup menu, but I think it's probably still a problem for getting the menu to do anything if the associated application isn't active. See Antoine's #8 & 9. Antoine, could you change your example to use pygi instead of pygtk so that Kris can test against his patch?
> - I am still not fully happy with the behavior of the status icon. When the > menu is hidden, the menus of the native status icons will still pop up. This > was already a problem before patching. Also, when the status icon menu pops up > when the application is inactive, there is an event missing that refrains the > menu from highlighting menu items. I think regular menus also behave > erratically sometimes because of this same missing event. Might this be related to the problem where controls in new windows, especially dialog boxes, are sometimes unresponsive until you move the pointer out of the window and back in?
Created attachment 192297 [details] new StatusIcon + menu test code using pygi Just drop in an icon named tray.png on the path and run it
Kris, I think getting the currently active run loop is perfectly fine for *installing* the trap, but I think we should keep a reference to that specific run loop around for *removing* the trap again.
*** Bug 678368 has been marked as a duplicate of this bug. ***
GtkStatusIcon has been deprecated