Obtenir des notifications sur les changements de titre de la window

… sans interrogation.

Je veux détecter quand la window actuellement ciblée change afin que je puisse mettre à jour un morceau d'interface graphique personnalisée dans mon système.

Points d'intérêts:

  • notifications en time réel. Avoir un retard de 0,2 est correct, avoir un décalage de 1 est meh, avoir un retard de 5s est totalement inacceptable.
  • convivialité des ressources: pour cette raison, je veux éviter les sondages. Exécuter xdotool getactivewindow getwindowname chaque, disons, une demi-seconde, fonctionne tout à fait bien … mais est frayant 2 process une seconde tout ce qui amical à mon système?

Dans bspwm , on peut utiliser bspc subscribe qui imprime une ligne avec quelques statistics (très) basiques, à chaque changement de focus de la window. Cette approche semble intéressante au premier abord, mais l'écoute ne détecte pas le changement de titre de la window (par exemple, la modification des tabs dans le browser Web passera inaperçue de cette façon).

Donc, est-ce que le nouveau process de génération est correct toutes les demi-secondes sur Linux, et sinon, comment puis-je faire mieux?

Une chose qui me vient à l'esprit est d'essayer d'imiter ce que font les gestionnaires de windows. Mais est-ce que je peux écrire des crochets pour des events tels que "création de window", "request de changement de titre" etc. indépendamment du gestionnaire de windows de travail, ou ai-je besoin de devenir un gestionnaire de windows lui-même? Ai-je besoin de racine pour cela?

(Une autre chose qui m'est venue à l'esprit est de regarder le code de xdotool et d'émuler seulement les choses qui m'intéressent afin que je puisse éviter tout le process de frayage de process, mais il serait toujours interroger.)

Je ne pouvais pas faire fonctionner votre approche de changement de focus de manière fiable sous Kwin 4.x, mais les gestionnaires de windows modernes conservent une propriété _NET_ACTIVE_WINDOW dans la window racine à laquelle vous pouvez écouter les modifications.

Voici une implémentation Python de cela:

 #!/usr/bin/python from contextlib import contextmanager import Xlib import Xlib.display disp = Xlib.display.Display() root = disp.screen().root NET_ACTIVE_WINDOW = disp.intern_atom('_NET_ACTIVE_WINDOW') NET_WM_NAME = disp.intern_atom('_NET_WM_NAME') # UTF-8 WM_NAME = disp.intern_atom('WM_NAME') # Legacy encoding last_seen = { 'xid': None, 'title': None } @contextmanager def window_obj(win_id): """Simplify dealing with BadWindow (make it either valid or None)""" window_obj = None if win_id: try: window_obj = disp.create_resource_object('window', win_id) except Xlib.error.XError: pass yield window_obj def get_active_window(): win_id = root.get_full_property(NET_ACTIVE_WINDOW, Xlib.X.AnyPropertyType).value[0] focus_changed = (win_id != last_seen['xid']) if focus_changed: with window_obj(last_seen['xid']) as old_win: if old_win: old_win.change_atsortingbutes(event_mask=Xlib.X.NoEventMask) last_seen['xid'] = win_id with window_obj(win_id) as new_win: if new_win: new_win.change_atsortingbutes(event_mask=Xlib.X.PropertyChangeMask) return win_id, focus_changed def _get_window_name_inner(win_obj): """Simplify dealing with _NET_WM_NAME (UTF-8) vs. WM_NAME (legacy)""" for atom in (NET_WM_NAME, WM_NAME): try: window_name = win_obj.get_full_property(atom, 0) except UnicodeDecodeError: # Apparently a Debian distro package bug title = "<could not decode characters>" else: if window_name: win_name = window_name.value if isinstance(win_name, bytes): # Apparently COMPOUND_TEXT is so arcane that this is how # tools like xprop deal with receiving it these days win_name = win_name.decode('latin1', 'replace') return win_name else: title = "<unnamed window>" return "{} (XID: {})".format(title, win_obj.id) def get_window_name(win_id): if not win_id: last_seen['title'] = "<no window id>" return last_seen['title'] title_changed = False with window_obj(win_id) as wobj: if wobj: win_title = _get_window_name_inner(wobj) title_changed = (win_title != last_seen['title']) last_seen['title'] = win_title return last_seen['title'], title_changed def handle_xevent(event): if event.type != Xlib.X.PropertyNotify: return changed = False if event.atom == NET_ACTIVE_WINDOW: if get_active_window()[1]: changed = changed or get_window_name(last_seen['xid'])[1] elif event.atom in (NET_WM_NAME, WM_NAME): changed = changed or get_window_name(last_seen['xid'])[1] if changed: handle_change(last_seen) def handle_change(new_state): """Replace this with whatever you want to actually do""" print(new_state) if __name__ == '__main__': root.change_atsortingbutes(event_mask=Xlib.X.PropertyChangeMask) get_window_name(get_active_window()[0]) handle_change(last_seen) while True: # next_event() sleeps until we get an event handle_xevent(disp.next_event()) 

La version plus entièrement commentée que j'ai écrite comme exemple pour quelqu'un est dans cet essentiel .

UPDATE: Maintenant, il montre également la seconde moitié (écoute de _NET_WM_NAME ) pour faire exactement ce qui a été demandé.

UPDATE # 2: … et la troisième partie: WM_NAME sur WM_NAME si quelque chose comme xterm n'a pas défini _NET_WM_NAME . (Ce dernier est codé en UTF-8 alors que le premier est censé utiliser un encoding de caractères hérité appelé composé , mais comme personne ne semble savoir comment travailler avec, les programmes lancent n'importe quel stream d'octets et xprop en supposant que ce sera ISO-8859-1.)

Eh bien, grâce au commentaire de @ Basile, j'ai beaucoup appris et j'ai trouvé l'échantillon de travail suivant:

 #!/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_atsortingbutes(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_atsortingbutes(event_mask=Xlib.X.PropertyChangeMask) window_name = window.get_full_property(NET_WM_NAME, 0).value except Xlib.error.XError: window_name = None print(window_name) event = disp.next_event() 

Plutôt que de xdotool naïvement xdotool , il écoute de manière synchrone les events générés par X, ce qui est exactement ce que je recherchais.