Get notifications when active X window changes using Python xlib
Asked Answered
G

1

6

I'd like to monitor which window is active on a Linux system running X, and when that active window gets resized or moved. I can monitor the active window (it's held in the _NET_ACTIVE_WINDOW property on the root window, and I can register for PropertyNotify events on the root window to discover when that property changes). However, I don't know how to monitor the active window to know whether it gets resized or moved.

import Xlib
import Xlib.display

disp = Xlib.display.Display()
Xroot = disp.screen().root
NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')
Xroot.change_attributes(event_mask=Xlib.X.PropertyChangeMask)

while True:
    # loop until an event happens that we care about
    # we care about a change to which window is active
    # (NET_ACTIVE_WINDOW property changes on the root)
    # or about the currently active window changing
    # in size or position (don't know how to do this)
    event = disp.next_event()
    if (event.type == Xlib.X.PropertyNotify and
            event.atom == NET_ACTIVE_WINDOW):
        active = disp.get_input_focus().focus
        try:
            name = active.get_wm_class()[1]
        except TypeError:
            name = "unknown"
        print("The active window has changed! It is now", name)

Is there a way to do this? It might involve listening to ConfigureNotify events on the currently active window (and calling change_attributes on that window when it becomes active to set an appropriate mask), but I can't get this to work.

(Note: I'm not using Gtk, so no Gtk solutions, please.)

Update: there is a rather suspect approach to detecting window resizes, by watching the active window for the value of the _NET_WM_OPAQUE_REGION property changing (since I correctly receive PropertyChange events, although I don't receive ConfigureNotify events). However, it's not clear that all window managers set this property, and this only changes on a window resize; it does not change on a window move (and nor does any other property).

Graven answered 9/2, 2020 at 20:33 Comment(0)
G
8

The way to do this is to select for SubstructureNotifyMask on the root window, and then read all ConfigureNotify events and ignore those that are not for the window we care about, thus:

import Xlib
import Xlib.display

disp = Xlib.display.Display()
Xroot = disp.screen().root
NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW')
Xroot.change_attributes(event_mask=Xlib.X.PropertyChangeMask |
                        Xlib.X.SubstructureNotifyMask)

windows = []

while True:
    # loop until an event happens that we care about
    # we care about a change to which window is active
    # (NET_ACTIVE_WINDOW property changes on the root)
    # or about the currently active window changing
    # in size or position (ConfigureNotify event for
    # our window or one of its ancestors)
    event = disp.next_event()
    if (event.type == Xlib.X.PropertyNotify and
            event.atom == NET_ACTIVE_WINDOW):
        active = disp.get_input_focus().focus
        try:
            name = active.get_wm_class()[1]
        except TypeError:
            name = "unknown"
        print("The active window has changed! It is now", name)

        # Because an X window is not necessarily just what one thinks of
        # as a window (the window manager may add an invisible frame, and
        # so on), we record not just the active window but its ancestors
        # up to the root, and treat a ConfigureNotify on any of those
        # ancestors as meaning that the active window has been moved or resized
        pointer = active
        windows = []
        while pointer.id != Xroot.id:
            windows.append(pointer)
            pointer = pointer.query_tree().parent
    elif event.type == Xlib.X.ConfigureNotify and event.window in windows:
        print("Active window size/position is now", event.x, event.y,
              event.width, event.height)
Graven answered 10/2, 2020 at 12:55 Comment(1)
Is there a specific reason why you chose to determine the active window using get_input_focus instead of querying for _NET_ACTIVE_WINDOW property as shown here, for example? (Both seem to sometimes point to different windows). This would make more sense to me as the property is whose changes you are listening here in the first place. That said, your approach works great, thank you for taking the time to write it down!Takishatakken

© 2022 - 2024 — McMap. All rights reserved.