Python, Quartz, GTK, and window focus.

related: python , gtk , pygtk , quartz , pyobjc

One of my company’s products involves a small GUI application for controlling a USB peripheral that we designed.  Although the customer only requires it to target Windows at the moment, we are trying to keep it (mostly) working on Linux and OS X as well, since those may need to be supported in the future.

For easy of cross-platform-GUIness, and because I was already somewhat familiar with it, we decided to do windowing with PyGTK.  Alternatively, we could have used Tkinter, PyQT, wxpython, or pyglet… but we didn’t.  Mostly because I already knew some GTK.

So, anyway, cross-platform GTK has been a pain.  Maybe that would have been true with any of them.

For OS X, you have the option of building your GTK+ libraries with your windowing backend of choice: X11 or Quartz.  

We originally went with X11 because it’s the default of Macports, but discovered that StatusIcons (“system tray” or “notification area” icons), don’t work in X11 (because X11 isn’t running a notification area). 

So, I rebuilt everything with the Macports variant trio: “-x11 +no_x11 +quartz”, and eventually found myself with a more native-looking GTK.  And StatusIcons!  StatusIcons that don’t work worth a damn!

With the new, native, pretty Quartz windows, GTK has focus issues.  New windows pop up to the top, but do not have focus.  This is true for the main application, and dialog boxes.  And the StatusIcon menu pops up when you click on the icon, but it isn’t selectable because – get this – it doesn’t get focus when you click on it.

I spent hours trying different combinations of GTK *show*() and *activate*() and *present*() and *focus*() functions, all to no avail.  I also found this open bug report on the same issue.

Eventually I stumbled upon an unrelated Objective-C post about window focus, and solved my problem with PyObjC:

1
2
3
4
5
6
7
8
9
10
11
12
13
import platform
if platform.system() == "Darwin":
    from AppKit import NSApp

def callback(target, button, time, *args):
    if platform.system() == "Darwin":
        NSApp.activateIgnoringOtherApps_(True)
    menu.popup(None,None, None, button, time)
...
icon.connect("popup-menu", callback)
window.show_all()
if platform.system() == "Darwin":
    NSApp.activateIgnoringOtherApps_(True)

Windows actually get the system’s focus when activateIgnoringOtherApps: is called, and the StatusIcon menu works (mostly) as expected now.