Ricevi notifiche sulle modifiche al titolo della finestra


9

... senza polling.

Voglio rilevare quando la finestra attualmente focalizzata cambia in modo da poter aggiornare una parte della GUI personalizzata nel mio sistema.

Punti di interesse:

  • notifiche in tempo reale. Avere un ritardo di 0,2 secondi va bene, avere un ritardo di 1 secondo è meh, avere un ritardo di 5 secondi è totalmente inaccettabile.
  • cordialità delle risorse: per questo motivo, voglio evitare il polling. Eseguire xdotool getactivewindow getwindownameogni, diciamo, mezzo secondo, funziona abbastanza bene ... ma generare 2 processi al secondo è tutto così amichevole per il mio sistema?

In bspwm, si può usare bspc subscribequale stampa una linea con alcune (molto) statistiche di base, ogni volta che cambia lo stato attivo della finestra. All'inizio questo approccio sembra carino, ma l'ascolto non rileverà quando il titolo della finestra cambia da solo (ad esempio, la modifica delle schede nel browser web passerà inosservata in questo modo).

Quindi, generare un nuovo processo ogni mezzo secondo va bene su Linux e, in caso contrario, come posso fare le cose meglio?

Una cosa che mi viene in mente è cercare di emulare ciò che fanno i gestori delle finestre. Ma posso scrivere hook per eventi come "creazione della finestra", "richiesta di modifica del titolo" ecc. Indipendentemente dal gestore della finestra di lavoro, o devo diventare un gestore della finestra stesso? Ho bisogno di root per questo?

(Un'altra cosa che mi è venuta in mente è guardare xdotoolil codice ed emulare solo le cose che mi interessano in modo da poter evitare tutto il processo che genera la piastra della caldaia, ma sarebbe comunque polling.)

Risposte:


4

Non riuscivo a far funzionare in modo affidabile il tuo approccio di cambiamento di focus in Kwin 4.x, ma i moderni gestori di finestre mantengono una _NET_ACTIVE_WINDOWproprietà nella finestra di root che puoi ascoltare per le modifiche.

Ecco un'implementazione Python di questo:

#!/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())

La versione più commentata che ho scritto come esempio per qualcuno è in questo senso .

AGGIORNAMENTO: Ora, mostra anche la seconda metà (ascolto _NET_WM_NAME) per fare esattamente ciò che è stato richiesto.

AGGIORNAMENTO # 2: ... e la terza parte: Tornando a WM_NAMEse qualcosa come xterm non è impostato _NET_WM_NAME. (Il secondo è codificato UTF-8 mentre il primo dovrebbe usare una codifica di caratteri legacy chiamata testo composto ma, dal momento che nessuno sembra sapere come lavorarci, ottieni programmi che lanciano qualsiasi flusso di byte che hanno lì e xprop solo assumendo sarà ISO-8859-1.)


Grazie, questo è chiaramente un approccio più pulito. Non ero a conoscenza di questa proprietà.
r-

@ rr- L'ho aggiornato per dimostrare anche la visione, _NET_WM_NAMEquindi il mio codice ora fornisce una prova del concetto esattamente per quello che mi hai chiesto.
ssokolow,

6

Bene, grazie al commento di @ Basile, ho imparato molto e ho avuto il seguente esempio di lavoro:

#!/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:
        window_name = None
    print(window_name)
    event = disp.next_event()

Invece di correre in modo xdotoolingenuo, ascolta in modo sincrono gli eventi generati da X, che è esattamente quello che stavo cercando.


se si utilizza xmonad window manager, è necessario includere XMonad.Hooks.EwmhDesktops nella propria configurazione
Vasiliy Kevroletin
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.