...没有民意调查。
我想检测当前聚焦的窗口何时发生变化,以便我可以更新系统中的一段自定义 GUI。
兴趣点:
- 实时通知。 0.2秒的延迟还可以,1秒的延迟就没什么了,5秒的延迟就完全不可接受了。
- 资源友好性:因此,我想避免轮询。
xdotool getactivewindow getwindowname
每隔半秒运行一次,效果很好......但是每秒生成 2 个进程对我的系统是否友好?
在 中bspwm
,每次窗口焦点发生变化时,都可以使用bspc subscribe
它打印一行包含一些(非常)基本统计数据的行。这种方法一开始看起来不错,但是听这个方法不会检测到窗口标题何时自行更改(例如,通过这种方式更改网络浏览器中的选项卡将不会被注意到。)
那么,在 Linux 上每半秒生成一个新进程可以吗?如果不行,我怎样才能做得更好呢?
我想到的一件事是尝试模仿窗口管理器的做法。但是我可以独立于工作窗口管理器为“窗口创建”、“标题更改请求”等事件编写钩子吗?还是我需要成为窗口管理器本身?我需要 root 才能执行此操作吗?
(我想到的另一件事是查看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:...第三部分:回退到WM_NAME
如果像 xterm 这样的东西还没有设置_NET_WM_NAME
。 (后者是 UTF-8 编码的,而前者应该使用称为 的遗留字符编码复合文本但是,由于似乎没有人知道如何使用它,因此程序会抛出其中的任何字节流,并且xprop
只是假设它将是 ISO-8859-1。)