我可以在没有轮询的情况下对窗口打开的事件采取行动吗?

Chr*_*son 13 bash events window c c++

我试图找到一种方法来检测在 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
Run Code Online (Sandbox Code Playgroud)

所以我希望我能找到 YXW 的独特结构

我不确定我是否必须听所有窗口,但我假设我必须检查“根窗口”中发生的情况并尝试找到寻找的窗口。

Chr*_*son 20

我找到了两种解决这个问题的方法。

  1. 将该xprop -spy -root _NET_ACTIVE_WINDOW命令与grepbash 脚本结合使用。
  2. 使用 Xlib 库创建一个 C++(也可能是 C 或 python,我的项目开始时使用 C++)应用程序来侦听来自X-server 的事件。

我最终使用了替代方案 1,但我将在下面提供一些信息。

使用 xprop: 创建搜索窗口的应用程序始终将新窗口放在顶部并聚焦。该xprop -spy <window-id>命令允许监听“根窗口”(上述问题中树中的 R)的 id<window-id>-rootid属性的变化。要侦听特定属性的更改,我们可以在这种情况下提供属性的名称,该名称_NET_ACTIVE_WINDOW包含当前处于焦点的窗口的 id,请参阅规范。然后我们得到这样的输出流:

_NET_ACTIVE_WINDOW(WINDOW): window id # 0x3c00010
Run Code Online (Sandbox Code Playgroud)

并且可以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
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

应根据要查找的事件选择EventMask

对于连续的应用程序(想要处理后续事件),可能想要设置一个错误处理函数,该函数应该返回 0(可能带有警告),以便执行顺利进行,如下所示:

XSetErrorHandler(bad_window_handler);
Run Code Online (Sandbox Code Playgroud)

在循环中,您可以处理来自 X 服务器的事件

XEvent e;
XNextEvent(dsp, &e); // blocks until next event from X-server
Run Code Online (Sandbox Code Playgroud)

为了处理事件,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
Run Code Online (Sandbox Code Playgroud)

XEvent 结构包含根据事件类型不同类型结构的信息。

需要以下库:

#include <X11/Xlib.h>
#include <X11/Xutil.h>
Run Code Online (Sandbox Code Playgroud)

应用程序应使用-lX11标志编译以包含 Xlib 库。

Xlib 要点 + 陷阱: 我创建了两个要点来监听来自 X 服务器的事件。请注意,我对用于识别的窗口树的结构感兴趣。其他人可能有其他属性来唯一标识窗口。

第一个侦听CreateNotify事件以确定是否创建了具有正确 WM_CLASS 的窗口,将其称为 W。此时窗口可能不在正确的树结构中。例如,它可能被创建为根的子项,而不是由应用程序控制的窗口。因此,我们监听ReparentNotify事件以查看 W 是否有新的父节点。

不幸的是,我们也不能保证这里的树结构正确,因为其他窗口可能稍后会添加到 W 的树中。但是如果 W 的树结构是唯一的,并且它的形状不是具有相同类的另一个窗口的子树,我们至少可以通过检查找到这些窗口ReparentNotify(我们甚至可能不需要检查,CreateNotify因为我们可以在任何时候检查 WM_CLASS有一个窗口ID)。

第二个监听MapNotify事件并检查窗口树的结构。然后在树中寻找正确的 WM_CLASS。在此之后,可以继续确定结构是否正确。

同样,MapNotify我们不能保证“完成”的树结构。然而,该结构似乎在此事件后的短时间内“稳定”。为了合理地确保结构不会改变,我在收集树之前添加了一个短暂的停顿。这个暂停应该多长时间,以免我不知道对树进行其他更改的风险,500ms 似乎对我来说效果很好。

杂项: X11 窗口可以用许多不同的语言创建,所以我想用许多不同的语言也可以收听它们。

一些用于调查窗口的工具:

  • xprop
  • xwininfo特别是选项-tree-children

还可以查看上述命令的源代码xpropxwininfo,以获得一些指导和启发。