Ser notificado sobre alterações no título da janela
9
... sem sondagem.
Quero detectar quando a janela atualmente focada muda para que eu possa atualizar uma parte da GUI personalizada no meu sistema.
Pontos de interesse:
notificações em tempo real. Ter 0,2s de atraso é bom, ter 1s de atraso é meh, ter 5s de atraso é totalmente inaceitável.
facilidade de uso de recursos: por esse motivo, quero evitar pesquisas. Executar xdotool getactivewindow getwindownamecada, digamos, meio segundo, funciona muito bem ... mas a geração de 2 processos por segundo é tão amigável ao meu sistema?
Em bspwm, pode-se usar o bspc subscribeque imprime uma linha com algumas estatísticas (muito) básicas, toda vez que o foco da janela muda. Essa abordagem parece boa no começo, mas ouvir isso não será detectada quando o título da janela for alterado por si só (por exemplo, alterar guias no navegador da Web passará despercebido dessa maneira).
Então, está gerando um novo processo a cada meio segundo, certo no Linux, e se não, como posso fazer as coisas melhor?
Uma coisa que me vem à mente é tentar imitar o que os gerenciadores de janelas fazem. Mas posso escrever ganchos para eventos como "criação de janela", "solicitação de alteração de título" etc. independentemente do gerenciador de janelas em funcionamento ou preciso me tornar um gerenciador de janelas? Preciso de raiz para isso?
(Outra coisa que me veio à mente é examinar xdotoolo código e emular apenas as coisas que me interessam, para que eu possa evitar todo o processo que gera o clichê, mas ainda assim seria uma pesquisa.)
Não consegui que sua abordagem de mudança de foco funcionasse de forma confiável no Kwin 4.x, mas os gerenciadores de janelas modernos mantêm uma _NET_ACTIVE_WINDOWpropriedade na janela raiz na qual você pode ouvir alterações.
Aqui está uma implementação em Python exatamente disso:
#!/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())
A versão mais comentada que escrevi como exemplo para alguém está nessa essência .
ATUALIZAÇÃO: Agora, também demonstra a segunda metade (ouvindo _NET_WM_NAME) para fazer exatamente o que foi solicitado.
ATUALIZAÇÃO # 2: ... e a terceira parte: voltando ao WM_NAMEcaso de algo como o xterm não ter sido definido _NET_WM_NAME. (O último é codificado em UTF-8, enquanto o primeiro deve usar uma codificação de caracteres herdada chamada texto composto , mas, como ninguém parece saber como trabalhar com ele, você obtém programas que emitem o fluxo de bytes que houver nele e xpropapenas assumem será ISO-8859-1.)
Obrigado, essa é uma abordagem claramente mais limpa. Eu não estava ciente dessa propriedade.
RR
@ rr- Eu atualizei para demonstrar também a observação, _NET_WM_NAMEentão meu código agora fornece uma prova de conceito exatamente para o que você pediu.
precisa saber é o seguinte
6
Bem, graças ao comentário do @ Basile, aprendi muito e criei a seguinte amostra de trabalho:
_NET_WM_NAME
então meu código agora fornece uma prova de conceito exatamente para o que você pediu.Bem, graças ao comentário do @ Basile, aprendi muito e criei a seguinte amostra de trabalho:
Em vez de executar
xdotool
ingenuamente, ele escuta de forma síncrona os eventos gerados pelo X, que é exatamente o que eu estava procurando.fonte