Mendapatkan pemberitahuan tentang perubahan judul jendela

9

... tanpa jajak pendapat.

Saya ingin mendeteksi ketika jendela yang saat ini fokus berubah sehingga saya dapat memperbarui sepotong GUI khusus di sistem saya.

Tempat menarik:

  • pemberitahuan waktu nyata. Memiliki lag 0,2 tidak apa-apa, memiliki lag 1s adalah meh, memiliki 5s lag sama sekali tidak dapat diterima.
  • keramahan sumber daya: untuk alasan ini, saya ingin menghindari jajak pendapat. Menjalankan xdotool getactivewindow getwindownamesetiap, katakanlah, setengah detik, bekerja dengan sangat baik ... tetapi apakah memunculkan 2 proses per detik semuanya ramah terhadap sistem saya?

Dalam bspwm, seseorang dapat menggunakan bspc subscribeyang mencetak garis dengan beberapa statistik dasar (sangat), setiap kali jendela fokus berubah. Pendekatan ini tampaknya bagus pada awalnya, tetapi mendengarkan ini tidak akan mendeteksi kapan judul jendela berubah dengan sendirinya (misalnya, mengubah tab di peramban web tidak akan diperhatikan dengan cara ini.)

Jadi, apakah melahirkan proses baru setiap setengah detik tidak masalah di Linux, dan jika tidak, bagaimana saya bisa melakukan hal-hal yang lebih baik?

Satu hal yang muncul di pikiran saya adalah mencoba meniru apa yang dilakukan manajer jendela. Tetapi bisakah saya menulis kait untuk acara seperti "pembuatan jendela", "permintaan perubahan judul" dll. Secara independen dari manajer jendela yang berfungsi, atau apakah saya harus menjadi manajer jendela itu sendiri? Apakah saya perlu root untuk ini?

(Hal lain yang muncul di benak saya adalah untuk melihat xdotoolkode dan hanya meniru hal-hal yang menarik bagi saya sehingga saya dapat menghindari semua proses pemijahan boilerplate, tetapi masih akan menjadi polling.)

rr-
sumber

Jawaban:

4

Saya tidak bisa mendapatkan pendekatan perubahan fokus Anda untuk bekerja dengan andal di bawah Kwin 4.x, tetapi manajer jendela modern mempertahankan _NET_ACTIVE_WINDOWproperti di jendela root yang Anda dapat mendengarkan perubahannya.

Inilah implementasi Python untuk hal itu:

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

Versi yang lebih lengkap dengan komentar yang saya tulis sebagai contoh untuk seseorang ada di intisari ini .

UPDATE: Sekarang, itu juga menunjukkan babak kedua (mendengarkan _NET_WM_NAME) untuk melakukan apa yang diminta.

UPDATE # 2: ... dan bagian ketiga: Kembali ke WM_NAMEjika sesuatu seperti xterm belum diatur _NET_WM_NAME. (Yang terakhir adalah UTF-8 dikodekan sementara yang pertama seharusnya menggunakan pengkodean karakter warisan yang disebut teks majemuk tetapi, karena tidak ada yang tahu cara bekerja dengannya, Anda mendapatkan program membuang aliran byte apa pun yang mereka miliki di sana dan xprop hanya dengan asumsi ini akan menjadi ISO-8859-1.)

ssokolow
sumber
Terima kasih, itu jelas pendekatan yang lebih bersih. Saya tidak mengetahui properti ini.
rr-
@ rr- Saya memutakhirkannya untuk juga menunjukkan menonton _NET_WM_NAMEsehingga kode saya sekarang memberikan bukti konsep untuk apa yang Anda minta.
ssokolow
6

Terima kasih atas komentar @ Basile, saya belajar banyak dan menghasilkan sampel kerja berikut:

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

Alih-alih berjalan secara xdotoolnaif, ia mendengarkan secara serentak peristiwa yang dihasilkan oleh X yang persis seperti yang saya kejar.

rr-
sumber
jika Anda menggunakan window manager xmonad maka kebutuhan Anda untuk memasukkan XMonad.Hooks.EwmhDesktops ke dalam konfigurasi Anda
Vasiliy Kevroletin