收到有关窗口标题更改的通知

rr-*_*rr- 10 window-manager xorg

...没有投票。

我想检测当前聚焦的窗口何时发生变化,以便我可以更新系统中的一个自定义 GUI。

兴趣点:

  • 实时通知。有 0.2 秒的延迟是可以的,有 1 秒的延迟是可以的,有 5 秒的延迟是完全不可接受的。
  • 资源友好性:出于这个原因,我想避免轮询。xdotool getactivewindow getwindowname每隔半秒运行一次,效果很好……但是每秒生成 2 个进程对我的系统来说是否友好?

在 中bspwmbspc subscribe每次窗口焦点发生变化时,都可以使用它打印一行包含一些(非常)基本统计信息的行。这种方法一开始看起来不错,但是监听它不会检测到窗口标题何时自行更改(例如,以这种方式更改 Web 浏览器中的选项卡将不会被注意到。)

那么,在 Linux 上每半秒产生一个新进程是否可以,如果不是,我该如何做得更好?

我想到的一件事是尝试模仿窗口管理器的作用。但是我可以独立于工作窗口管理器为诸如“窗口创建”、“标题更改请求”等事件编写钩子,还是我需要自己成为窗口管理器?我需要root吗?

(我想到的另一件事是查看xdotool的代码并仅模拟我感兴趣的事情,这样我就可以避免所有进程产生样板,但它仍然会进行轮询。)

rr-*_*rr- 7

好吧,感谢@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()
Run Code Online (Sandbox Code Playgroud)

它不是xdotool天真地运行,而是同步侦听 X 生成的事件,这正是我所追求的。


sso*_*low 5

我无法让您的焦点更改方法在 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())
Run Code Online (Sandbox Code Playgroud)

我为某人编写的作为示例的更完整注释的版本在这个要点中。

更新:现在,它还演示了后半部分(收听_NET_WM_NAME)完全按照要求执行。

更新 #2: ...第三部分:回退到WM_NAMEif xterm 之类的东西尚未设置_NET_WM_NAME。(后者是 UTF-8 编码的,而前者应该使用称为复合文本的传统字符编码,但是,由于似乎没有人知道如何使用它,因此程序会抛出其中的任何字节流,并且xprop 只是假设它将是 ISO-8859-1。)