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.)
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 xprophanya dengan asumsi ini akan menjadi ISO-8859-1.)
_NET_WM_NAME
sehingga kode saya sekarang memberikan bukti konsep untuk apa yang Anda minta.Terima kasih atas komentar @ Basile, saya belajar banyak dan menghasilkan sampel kerja berikut:
Alih-alih berjalan secara
xdotool
naif, ia mendengarkan secara serentak peristiwa yang dihasilkan oleh X yang persis seperti yang saya kejar.sumber