창 제목 변경에 대한 알림 받기

창 제목 변경에 대한 알림 받기

...설문조사가 없습니다.

내 시스템의 사용자 정의 GUI를 업데이트할 수 있도록 현재 초점이 맞춰진 창이 변경되는 시기를 감지하고 싶습니다.

가볼만한 곳:

  • 실시간 알림. 0.2초의 지연은 괜찮고, 1초의 지연은 아무것도 아니며, 5초의 지연은 전혀 용납할 수 없습니다.
  • 자원 친화성: 따라서 폴링을 피하고 싶습니다. xdotool getactivewindow getwindowname0.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_NAMExterm과 같은 것이 아직 설정되지 않은 경우 에 대한 대체 _NET_WM_NAME. (후자는 UTF-8로 인코딩된 반면, 전자는 다음과 같은 레거시 문자 인코딩을 사용해야 합니다.복합 텍스트그러나 아무도 그것을 사용하는 방법을 모르는 것 같기 때문에 프로그램은 그 안에 있는 바이트 스트림을 무엇이든 던지고xprop 그냥 가정ISO-8859-1이 됩니다. )

관련 정보