我正在尝试找到一种方法来检测在 Ubuntu 16.04 中打开窗口(任何窗口)的事件
我希望能够检测“窗口打开”事件并检查打开的窗口是否是我想要的窗口,然后运行 bash 脚本或 C/C++ 函数。
到目前为止,我发现我可以使用wmctrl -l
来查找哪些窗口已打开。我可以使用此命令,也许grep
可以查找我所寻找的窗口是否已打开,然后根据此信息采取行动。
我宁愿不进行轮询,因为我不想让应用程序在窗口打开时处于空闲状态。操作应尽可能“即时”。
是否有我可以监听的事件或信号来实现此目的?从内核、窗口管理器 (Compiz) 或可能是某些发生更改的日志文件?
编辑: 需要澄清的是,我有一个应用程序(不受我控制),它可能随时显示一个窗口,这个窗口没有标题,但它确实设置了 WM_CLASS(WM_CLASS 对于应用程序的所有窗口都是相同的)。我想根据显示(或创建,以最佳/更简单为准)此窗口的事件采取行动。
窗口似乎不是在主应用程序窗口“内”打开的。使用xwininfo -children -id <window-id>
显示主应用程序和所寻找的窗口位于不同的分支上,连接到“根窗口”。
分支如下所示,其中 R 是“根节点”;A 是主应用程序分支的根节点,Y 是具有寻求窗口 W 的分支的根:
R
/ \
A Y
/\ \
B C X
\ \
Q W
所以我希望能够找到YXW的独特结构
我不确定我是否必须监听所有窗口,但我假设我必须检查“根窗口”内发生的情况并尝试找到所寻找的窗口。
答案1
我找到了两种解决这个问题的方法。
- 在 bash 脚本中
xprop -spy -root _NET_ACTIVE_WINDOW
结合使用该命令。grep
- 创建一个 C++ 应用程序(也可以是 C 或 Python,我的项目一开始就是用 C++ 编写的),使用 Xlib 库来监听来自X-服务器。
我最终使用了替代方案 1,但我将在下面提供一些有关这两种方案的信息。
使用 xprop:
创建所需窗口的应用程序始终将新窗口置于顶部并处于焦点位置。该xprop -spy <window-id>
命令允许监听属性的变化,<window-id>
并且-root
是“根窗口”(上文问题树中的 R)的 ID。要监听特定属性的变化,我们可以提供属性的名称(在本例中,该名称_NET_ACTIVE_WINDOW
包含当前处于焦点位置的窗口的 ID),查看规格.然后我们得到如下输出流:
_NET_ACTIVE_WINDOW(WINDOW): window id # 0x3c00010
并可用于grep
提取 ID。要检查活动窗口是否是所寻找的窗口,我们需要知道是什么让它独一无二,这可能因人而异,但最有可能的第一个过滤器是属性WM_CLASS
,查看描述。这里有一个小例子:
#!/bin/bash
class_name=$1
# regex for extracting hex id's
grep_id='0[xX][a-zA-Z0-9]\{7\}'
xprop -spy -root _NET_ACTIVE_WINDOW | grep --line-buffered -o $grep_id |
while read -r id; do
class="`xprop -id $id WM_CLASS | grep $class_name`"
if [ -n "$class" ]; then
# Found a window with the correct WM_CLASS now what makes your
# window unique?
fi
done
Bash要点: 这里是问题中案例的要点,其中树是识别因素。
限制xprop -spy
:它并不专门监听窗口打开,而是监听窗口聚焦的情况。这意味着如果窗口保持打开状态,失去焦点然后再次聚焦,此脚本仍将报告此事件。
Xlib 编程: 这更复杂,但也更强大。一些很好的入门资源如下:
为此,必须打开一个连接并注册为 X 服务器的监听器:
// Open connection to X server
Display *dsp = XOpenDisplay(NIL);
assert(dsp);
// Start listening to root window
XSelectInput(dsp, DefaultRootWindow(dsp), SubstructureNotifyMask);
取决于人们正在寻找哪些事件事件掩码应该被选择。
对于连续应用程序(想要处理后续事件),可能需要设置一个错误处理函数,该函数应返回 0(可能带有警告),以便执行能够顺利继续,如下所示:
XSetErrorHandler(bad_window_handler);
在循环中,可以处理来自 X 服务器的事件
XEvent e;
XNextEvent(dsp, &e); // blocks until next event from X-server
为了处理事件,e
我们可以检查e
e.type == CreateNotify // A window was created
e.type == ReparentNotify // A window got a new parent
e.type == MapNotify // A window was drawn
e.type == DestroyNotify // A window was destroyed
这XEvent
结构根据事件类型包含不同类型的结构体信息。
需要以下库:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
并且应用程序应该使用标志进行编译-lX11
以包含 Xlib 库。
Xlib 要点 + 陷阱: 我创建了两个用于监听 X-server 事件的 gist。请注意,我对用于识别的窗口树结构感兴趣。其他窗口可能具有其他属性来唯一地标识窗口。
首先监听CreateNotify
事件以确定是否创建了具有正确 WM_CLASS 的窗口,将其称为 W。此时窗口可能不在正确的树结构中。例如,它可能被创建为根的子窗口,而不是由应用程序控制的窗口。因此,我们监听事件ReparentNotify
以查看 W 是否有新的父窗口。
不幸的是,我们也无法保证这里的树结构是正确的,因为其他窗口稍后可能会添加到 W 的树中。但如果 W 的树结构是唯一的,并且它的形状不是具有相同类的另一个窗口的子树,我们至少可以通过检查找到这些窗口ReparentNotify
(我们甚至可能不需要检查,CreateNotify
因为我们可以在任何时候检查 WM_CLASS,我们有窗口 ID)。
第二监听MapNotify
事件并检查窗口树的结构。然后在树中查找正确的 WM_CLASS。之后可以继续确定结构是否正确。
再次强调,MapNotify
我们无法保证树结构“完成”。但是,结构似乎通常会在此事件发生后不久“稳定下来”。为了合理地确保结构不会改变,我在收集树之前添加了一个短暂的暂停。我不知道这个暂停应该持续多长时间,以免对树造成其他更改的风险,但 500 毫秒对我来说似乎效果很好。
杂项: X11 窗口可以在许多不同的语言,所以我想用多种不同的语言也可以听到它们。
一些用于调查窗口的工具:
xprop
xwininfo
特别是选择-tree
或-children
答案2
为了在窗口打开时执行命令而不使用 while 循环进行轮询,可以使用 xdotool:-
<code_that_triggers_window_opening> & xdotool search --sync --onlyvisible --name 'window_name' && <command_to_excute_on_window_open_event>
我不知道 xdotool 是否通过轮询在内部实现此功能。
这个想法的所有功劳都归功于这个答案:https://askubuntu.com/a/1187586