获得有关窗口标题更改的通知


9

...无需投票。

我想检测当前焦点窗口何时更改,以便可以在系统中更新一个自定义GUI。

兴趣点:

  • 实时通知。有0.2s的延迟是可以的,有1s的延迟是meh,有5s的延迟是完全不能接受的。
  • 资源友好:由于这个原因,我想避免轮询。运行xdotool getactivewindow getwindowname每一秒钟(例如半秒)的工作还算不错……但是生成2个进程每秒对我的系统是否如此友好?

在中bspwmbspc subscribe每当窗口焦点改变时,就可以使用它打印带有(非常)基本统计信息的行。这种方法乍一看似乎不错,但是当窗口标题本身发生更改时,侦听此方法将无法检测到(例如,在网络浏览器中更改选项卡将不会被注意)。

那么,在Linux上每隔半秒钟就可以生成一个新进程吗?如果没有,我该如何做得更好?

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

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

Answers:


4

我无法让您的焦点更改方法在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: ...以及第三部分:退回到WM_NAME是否未设置xterm之类的东西_NET_WM_NAME。(后者是UTF-8编码的,而前者应该使用称为复合文本的旧式字符编码,但是,由于似乎没人知道如何使用它,因此,您会得到程序将它们在那里拥有的任何字节流都扔掉了,xprop 只是假设它将是ISO-8859-1。)


谢谢,这显然是更清洁的方法。我不知道此属性。
rr-

@ rr-我对其进行了更新,以演示观看过程,_NET_WM_NAME因此我的代码现在为您的要求提供了概念证明。
ssokolow

6

好吧,多亏@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生成的事件,这正是我所追求的。


如果您使用的是xmonad窗口管理器,则需要将XMonad.Hooks.EwmhDesktops包含在您的配置中
Vasiliy Kevroletin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.