![창 제목 변경에 대한 알림 받기](https://linux55.com/image/68611/%EC%B0%BD%20%EC%A0%9C%EB%AA%A9%20%EB%B3%80%EA%B2%BD%EC%97%90%20%EB%8C%80%ED%95%9C%20%EC%95%8C%EB%A6%BC%20%EB%B0%9B%EA%B8%B0.png)
...설문조사가 없습니다.
내 시스템의 사용자 정의 GUI를 업데이트할 수 있도록 현재 초점이 맞춰진 창이 변경되는 시기를 감지하고 싶습니다.
가볼만한 곳:
- 실시간 알림. 0.2초의 지연은 괜찮고, 1초의 지연은 아무것도 아니며, 5초의 지연은 전혀 용납할 수 없습니다.
- 자원 친화성: 따라서 폴링을 피하고 싶습니다.
xdotool getactivewindow getwindowname
0.5초마다 실행하면 괜찮지만...내 시스템이 초당 2개의 프로세스를 생성하는 것이 좋은가요?
에서는 이를 사용하여 창 포커스가 변경될 때마다 일부 (매우) 기본 통계가 포함된 줄을 인쇄 bspwm
할 수 있습니다 . bspc subscribe
이 방법은 처음에는 괜찮아 보이지만 이 방법을 듣는 것은 창 제목이 저절로 변경되는 시기를 감지하지 못합니다(예를 들어 웹 브라우저에서 탭을 이런 식으로 변경하는 것은 알아차리지 못합니다).
그렇다면 Linux에서 0.5초마다 새로운 프로세스를 생성해도 괜찮을까요? 그렇지 않다면 어떻게 하면 더 잘할 수 있을까요?
마음에 떠오르는 한 가지는 창 관리자가 수행하는 작업을 에뮬레이트하려는 것입니다. 하지만 작업 중인 창 관리자와 별도로 "창 생성", "제목 변경 요청" 등과 같은 이벤트에 대한 후크를 작성할 수 있습니까? 아니면 내가 직접 창 관리자가 되어야 합니까? 이 작업을 수행하려면 루트가 필요합니까?
(내가 생각한 또 다른 것은 코드를 보고 xdotool
내가 관심 있는 것만 시뮬레이션하여 상용구를 생성하는 모든 프로세스를 피하지만 여전히 폴링하는 것입니다.)
답변1
@Basile의 의견 덕분에 저는 많은 것을 배웠고 다음과 같은 실제 예제를 생각해냈습니다.
#!/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()
xdotool
단순히 실행하는 대신 X에서 생성된 이벤트를 동기적으로 수신 합니다 . 이것이 바로 제가 추구하는 것입니다.
답변2
Kwin 4.x에서는 포커스 변경 방법이 안정적으로 작동하도록 할 수 없었지만 최신 창 관리자는 _NET_ACTIVE_WINDOW
변경 사항을 수신할 수 있는 루트 창 속성을 유지 관리합니다.
다음은 Python 구현입니다.
#!/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())
누군가의 예로서 내가 작성한 더 완전한 주석 버전은 다음과 같습니다.이 점.
고쳐 쓰다:이제 후반부(듣기 _NET_WM_NAME
)가 정확히 필요에 따라 작동한다는 것도 보여줍니다.
업데이트 #2:...파트 3: WM_NAME
xterm과 같은 것이 아직 설정되지 않은 경우 에 대한 대체 _NET_WM_NAME
. (후자는 UTF-8로 인코딩된 반면, 전자는 다음과 같은 레거시 문자 인코딩을 사용해야 합니다.복합 텍스트그러나 아무도 그것을 사용하는 방법을 모르는 것 같기 때문에 프로그램은 그 안에 있는 바이트 스트림을 무엇이든 던지고xprop
그냥 가정ISO-8859-1이 됩니다. )