Benachrichtigung über Änderungen des Fenstertitels
9
... ohne abzufragen.
Ich möchte erkennen, wann sich das aktuell fokussierte Fenster ändert, damit ich eine benutzerdefinierte Benutzeroberfläche in meinem System aktualisieren kann.
Punkte von Interessen:
Echtzeitbenachrichtigungen. Eine Verzögerung von 0,2 s ist in Ordnung, eine Verzögerung von 1 s ist meh, eine Verzögerung von 5 s ist völlig inakzeptabel.
Ressourcenfreundlichkeit: Aus diesem Grund möchte ich Polling vermeiden. xdotool getactivewindow getwindownameJede halbe Sekunde laufen zu lassen , funktioniert ganz gut ... aber ist das Laichen von 2 Prozessen pro Sekunde für mein System so freundlich?
In bspwmkann verwendet werden, bspc subscribewelche eine Linie mit einigen (sehr) grundlegenden Statistiken druckt, jedes Mal, wenn sich der Fensterfokus ändert. Dieser Ansatz scheint zunächst gut zu sein, aber wenn Sie ihn anhören, wird nicht erkannt, wann sich der Fenstertitel von selbst ändert (z. B. bleibt das Ändern der Registerkarten im Webbrowser auf diese Weise unbemerkt.)
Ist es unter Linux in Ordnung, jede halbe Sekunde einen neuen Prozess zu erzeugen, und wenn nicht, wie kann ich die Dinge besser machen?
Eine Sache, die mir in den Sinn kommt, ist zu versuchen, zu emulieren, was Fenstermanager tun. Aber kann ich unabhängig vom Arbeitsfenstermanager Hooks für Ereignisse wie "Fenstererstellung", "Titeländerungsanforderung" usw. schreiben oder muss ich selbst Fenstermanager werden? Benötige ich dafür root?
(Eine andere Sache, die mir in den Sinn kam, war, mir den xdotoolCode anzusehen und nur die Dinge zu emulieren, die mich interessieren, damit ich den gesamten Prozess vermeiden kann, der Boilerplate erzeugt, aber es würde immer noch eine Abfrage geben.)
Ich konnte Ihren Fokusänderungsansatz unter Kwin 4.x nicht zuverlässig zum Laufen bringen, aber moderne Fenstermanager behalten eine _NET_ACTIVE_WINDOWEigenschaft im Stammfenster bei, auf die Sie nach Änderungen warten können.
Hier ist eine Python-Implementierung von genau dem:
#!/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_attributes(event_mask=Xlib.X.NoEventMask)
last_seen['xid'] = win_id
with window_obj(win_id) as new_win:
if new_win:
new_win.change_attributes(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_attributes(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())
Die ausführlichere Version, die ich als Beispiel für jemanden geschrieben habe, ist in diesem Kern enthalten .
UPDATE: Jetzt wird auch die zweite Hälfte (Anhören _NET_WM_NAME) demonstriert , um genau das zu tun, was angefordert wurde.
UPDATE # 2: ... und der dritte Teil: Zurückgreifen auf, WM_NAMEwenn etwas wie xterm nicht eingestellt ist _NET_WM_NAME. (Letzteres ist UTF-8-codiert, während Ersteres eine Legacy-Zeichencodierung verwenden soll, die als zusammengesetzter Text bezeichnet wird. Da jedoch niemand zu wissen scheint, wie man damit arbeitet, erhalten Sie Programme, die den darin enthaltenen Bytestrom werfen und xpropnur annehmen es wird ISO-8859-1 sein.)
Danke, das ist ein klar saubererer Ansatz. Diese Eigenschaft war mir nicht bekannt.
rr-
@ rr- Ich habe es aktualisiert, um auch das Anschauen zu demonstrieren, _NET_WM_NAMEsodass mein Code jetzt einen Proof of Concept für genau das bietet, was Sie gefragt haben.
ssokolow
6
Dank des Kommentars von @ Basile habe ich viel gelernt und mir das folgende Arbeitsprobe ausgedacht:
_NET_WM_NAME
sodass mein Code jetzt einen Proof of Concept für genau das bietet, was Sie gefragt haben.Dank des Kommentars von @ Basile habe ich viel gelernt und mir das folgende Arbeitsprobe ausgedacht:
Anstatt
xdotool
naiv zu laufen , hört es synchron auf Ereignisse, die von X generiert wurden, genau das, wonach ich gesucht habe.quelle