Nhận thông báo về thay đổi tiêu đề cửa sổ


9

... mà không bỏ phiếu.

Tôi muốn phát hiện khi cửa sổ hiện đang tập trung thay đổi để tôi có thể cập nhật một phần GUI tùy chỉnh trong hệ thống của mình.

Điểm quan tâm:

  • thông báo thời gian thực. Có độ trễ 0,2 giây là được, có độ trễ 1 giây là meh, có độ trễ 5s là hoàn toàn không thể chấp nhận được.
  • thân thiện với tài nguyên: vì lý do này, tôi muốn tránh bỏ phiếu. Chạy xdotool getactivewindow getwindownamemỗi giây, nửa giây, hoạt động khá ổn ... nhưng liệu sinh ra 2 quy trình có thân thiện với hệ thống của tôi không?

Trong bspwm, người ta có thể sử dụng bspc subscribein một dòng với một số (rất) số liệu thống kê cơ bản, mỗi lần thay đổi tiêu điểm cửa sổ. Cách tiếp cận này ban đầu có vẻ tốt, nhưng nghe điều này sẽ không phát hiện ra khi tiêu đề cửa sổ tự thay đổi (ví dụ: thay đổi các tab trong trình duyệt web sẽ không được chú ý theo cách này.)

Vì vậy, việc sinh ra quá trình mới cứ sau nửa giây là ổn trên Linux, và nếu không, làm thế nào tôi có thể làm mọi thứ tốt hơn?

Một điều xuất hiện trong đầu tôi là cố gắng mô phỏng những gì các nhà quản lý cửa sổ làm. Nhưng tôi có thể viết các hook cho các sự kiện như "tạo cửa sổ", "yêu cầu thay đổi tiêu đề", vv độc lập với trình quản lý cửa sổ làm việc hay tôi cần phải trở thành người quản lý cửa sổ? Tôi có cần root cho cái này không?

(Một điều khác xuất hiện trong đầu tôi là xem xdotoolmã của nó và chỉ mô phỏng những thứ mà tôi quan tâm để tôi có thể tránh tất cả quá trình sinh ra nồi hơi, nhưng nó vẫn sẽ bỏ phiếu.)

Câu trả lời:


4

Tôi không thể khiến cách tiếp cận thay đổi tập trung của bạn hoạt động đáng tin cậy trong Kwin 4.x, nhưng các nhà quản lý cửa sổ hiện đại duy trì một thuộc _NET_ACTIVE_WINDOWtính trên cửa sổ gốc mà bạn có thể lắng nghe để thay đổi.

Đây là một triển khai Python chỉ:

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

Phiên bản được bình luận đầy đủ hơn mà tôi đã viết như một ví dụ cho một người nào đó nằm trong ý chính này .

CẬP NHẬT: Bây giờ, nó cũng cho thấy nửa sau (nghe _NET_WM_NAME) để làm chính xác những gì được yêu cầu.

CẬP NHẬT # 2: ... và phần thứ ba: Quay trở lại WM_NAMEnếu một cái gì đó như xterm chưa được đặt _NET_WM_NAME. (Cái sau được mã hóa UTF-8 trong khi cái trước được cho là sử dụng một ký tự kế thừa được gọi là văn bản ghép , nhưng, vì dường như không ai biết cách làm việc với nó, bạn nhận được các chương trình ném bất kỳ luồng byte nào vào đó và xprop chỉ giả sử nó sẽ là ISO-8859-1.)


Cảm ơn, đó là cách tiếp cận rõ ràng sạch hơn. Tôi đã không nhận thức được tài sản này.
rr-

@ rr- Tôi đã cập nhật nó để chứng minh việc xem _NET_WM_NAMEvì vậy mã của tôi hiện cung cấp bằng chứng về khái niệm cho chính xác những gì bạn yêu cầu.
ssokolow

6

Chà, nhờ nhận xét của @ Basile, tôi đã học được rất nhiều và đưa ra mẫu làm việc sau:

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

Thay vì chạy một xdotoolcách ngây thơ, nó lắng nghe một cách đồng bộ các sự kiện do X tạo ra, đó chính xác là những gì tôi đã theo đuổi.


nếu bạn đang sử dụng trình quản lý cửa sổ xmonad thì bạn cần phải bao gồm XMonad.Hooks.EwmhDesktops vào cấu hình của bạn
Vasiliy Kevroletin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.