Search code examples
pythonfocusdetectxlibapplication-name

How to “correctly” detect application name when changing focus event occurs with python xlib


I want to detect applications window name when changing focus event occurs with python xlib, so in the first step I use this code:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import Xlib.display
import time


display = Xlib.display.Display()
while True:
    window = display.get_input_focus().focus
    wmname = window.get_wm_name()
    wmclass = window.get_wm_class()
    if wmclass is None and wmname is None:
        window = window.query_tree().parent
        wmname = window.get_wm_name()
    print "WM Name: %s" % ( wmname, )
    time.sleep(3)

But I want a correct way, then I research about xlib events and find Input Focus Events and write this code:

#!/usr/bin/env python
#-*- coding:utf-8 -*-
import Xlib.display
from Xlib import X

def main():
    display = Xlib.display.Display(':0')
    root = display.screen().root
    root.change_attributes(event_mask=Xlib.X.FocusChangeMask)

    while True:
        event = root.display.next_event()
        #if event.type == X.FocusIn or event.type == X.FocusOut:
        if event.type == X.FocusOut :
            window = display.get_input_focus().focus
            wmname = window.get_wm_name()
            wmclass = window.get_wm_class()
            if wmclass is None and wmname is None:
                window = window.query_tree().parent
                wmname = window.get_wm_name()
            print "WM Name: %s" % ( wmname, )

if __name__ == "__main__":
    main()

Sadly it's not work correctly especially in tabbed browsing on google chrome and firefox, so Is there a correct way for this situation?


Solution

  • Your code is almost right, but it misses two things:

    • rather than listening only to focus changes, it should also listen to window property events which include changes of WM_NAME property, that also happen when you cycle tabs in your browser.
    • rather than listening only in root window, it should listen to every window (that gets focused). You can attach the event handler the same way as you do with the root window.

    That being said, here is a working sample:

    #!/usr/bin/python3
    import Xlib
    import Xlib.display
    
    disp = Xlib.display.Display()
    root = disp.screen().root
    
    NET_WM_NAME = disp.intern_atom('_NET_WM_NAME')
    NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')
    
    root.change_attributes(event_mask=Xlib.X.FocusChangeMask)
    while True:
        try:
            window_id = root.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0]
            window = disp.create_resource_object('window', window_id)
            window.change_attributes(event_mask=Xlib.X.PropertyChangeMask)
            window_name = window.get_full_property(NET_WM_NAME, 0).value
        except Xlib.error.XError: #simplify dealing with BadWindow
            window_name = None
        print(window_name)
        event = disp.next_event()