Linux获取GUI窗口焦点变化通知

9
在Linux中,是否可能在当前关注的GUI应用程序更改时收到通知?我正在编写一个跟踪用户停留在每个GUI应用程序上的时间的应用程序(每个进程而不是在一个进程内),并需要一种访问此信息的方法。我正在使用C ++进行此操作。
到目前为止,我找到了以下内容:
xprop -id $(xprop -root | awk '/_NET_ACTIVE_WINDOW\(WINDOW\)/{print $NF}') | awk '/_NET_WM_PID\(CARDINAL\)/{print $NF}'

这段代码会打印出当前焦点应用程序的pid,但需要定时拉取。如果可以的话,我宁愿不拉取。此外,它假设所有GUI都通过x11进行,这可能不是一个不合理的假设,但并不完全可移植。
另一种方法是编写一个共享对象,钩入各种GUI函数,然后修改主机系统的ld.so.preload文件,以便在每个进程中加载此共享对象。这假设所有GUI应用程序都使用动态链接的图形库。我还必须为每个图形库编写钩子,以确保完全覆盖。在研究GTK(我正在运行Gnome的系统上进行测试)时,我没有找到任何在窗口切换时调用的函数。虽然我没有仔细查找。
有没有办法通过x11或其他图形库获得此类通知?
编辑:
好的,根据@Andrey的代码,这是我目前的进展:
#include <X11/Xlib.h>
#include <cstring>
#include <iostream>
using namespace std;

pid_t get_window_pid( Display * d, Window& w );

int main()
{
    Display * d;
    Window w;
    XEvent e;

    d = XOpenDisplay( 0 );
    if ( !d ) {
        cerr << "Could not open display" << endl;
        return 1;
    }

    w = DefaultRootWindow( d );
    XSelectInput( d, w, PropertyChangeMask );

    pid_t window_pid;

    for ( ;; ) {
        XNextEvent( d, &e );
        if ( e.type == PropertyNotify ) {
            if ( !strcmp( XGetAtomName( d, e.xproperty.atom ), "_NET_ACTIVE_WINDOW" ) ) {
                window_pid = get_window_pid( d, w );
                cout << window_pid << endl;
            }
        }
    }

    return 0;
}

pid_t get_window_pid( Display * d, Window& w )
{
    Atom atom = XInternAtom( d, "_NET_WM_PID", true );

    Atom actual_type;
    int actual_format;
    unsigned long nitems;
    unsigned long bytes_after;
    unsigned char *prop;

    int status;
    status = XGetWindowProperty(
        d, w, atom, 0, 1024,
        false, AnyPropertyType,
        &actual_type,
        &actual_format, &nitems,
        &bytes_after,
        &prop
    );

    if ( status || !prop )
        return -1;

    return prop[1] * 256 + prop[0];
}

但是get_window_pid总是返回-1,即使使用xprop -id $(xprop -root | awk '/_NET_ACTIVE_WINDOW\(WINDOW\)/{print $NF}') | awk '/_NET_WM_PID\(CARDINAL\)/{print $NF}'正确返回活动窗口的pid。我做错了什么?


1
get_window_pid使用根窗口而不是事件中指定的窗口。必须像这样声明get_window_pid:get_window_pid(e.xproperty.display, e.xproperty.window, e.xproperty.atom);并像这样调用:pid_t get_window_pid(Display *d, Window w, Atom atom) - tburette
3个回答

4

使用node-x11在JavaScript中的示例:

var x11 = require('x11');
x11.createClient(function(err, display) {
  var X = display.client;
  X.ChangeWindowAttributes(display.screen[0].root, { eventMask: x11.eventMask.PropertyChange });
  X.on('event', function(ev) {
    if(ev.name == 'PropertyNotify') {
      X.GetAtomName(ev.atom, function(err, name) {
        if (name == '_NET_ACTIVE_WINDOW') {
          X.GetProperty(0, ev.window, ev.atom, X.atoms.WINDOW, 0, 4, function(err, prop) {
            console.log('New active window:' + prop.data.readUInt32LE(0));
          });
        }
      });
    }
  });
});

这是否拦截其他窗口的消息?还是这是一个特定程序处理其自己活动窗口更改的方式?如果是后者,那么这给了我一个在注入的共享对象中使用它的想法(我必须找出相应的C++代码)。 - Brian Schlenker
它拦截根窗口属性更改。属性更改的发起者是您的窗口管理器(如果不符合http://standards.freedesktop.org/wm-spec/wm-spec-latest.html,此方法将无法工作)。 - Andrey Sidorov
1
如果有人需要Python实现,我在这个答案中写了一个。 - ssokolow

1
最终我搞定了。 编译:g++ ./a.cpp -lX11
#include <X11/Xlib.h>
#include <cstring>
#include <iostream>
#define MAXSTR 1000
using namespace std;

Display* display;
unsigned char *prop;

void check_status(int status, Window window)
{
    if (status == BadWindow)
    {
        printf("window id # 0x%lx does not exists!", window);
    }

    if (status != Success)
    {
        printf("XGetWindowProperty failed!");
    }
}

unsigned char *get_string_property(const char *property_name, Window window)
{
    Atom actual_type, filter_atom;
    int actual_format, status;
    unsigned long nitems, bytes_after;

    filter_atom = XInternAtom(display, property_name, True);
    status = XGetWindowProperty(display, window, filter_atom, 0, MAXSTR, False, AnyPropertyType,
                                &actual_type, &actual_format, &nitems, &bytes_after, &prop);
    check_status(status, window);
    return prop;
}

unsigned long get_long_property(const char *property_name, Window window)
{
    if (window == 0)
        return 0;
    get_string_property(property_name, window);
    unsigned long long_property = static_cast<unsigned long>(prop[0] + (prop[1] << 8) + (prop[2] << 16) + (prop[3] << 24));
    return long_property;
}

unsigned long getActiveWindowPID(Window root_window)
{
    unsigned long window;
    window = get_long_property("_NET_ACTIVE_WINDOW", root_window);
    return get_long_property(("_NET_WM_PID"), window);
}

int main()
{
    Display * d;
    Window w;
    XEvent e;

    d = XOpenDisplay( 0 );
    if ( !d ) {
        cerr << "Could not open display" << endl;
        return 1;
    }
    display = d;

    w = DefaultRootWindow( d );
    XSelectInput( d, w, PropertyChangeMask );

    pid_t window_pid;

    for ( ;; ) {
        XNextEvent( d, &e );
        if ( e.type == PropertyNotify ) {
            if ( !strcmp( XGetAtomName( d, e.xproperty.atom ), "_NET_ACTIVE_WINDOW" ) ) {
                window_pid = getActiveWindowPID(w );
                cout << window_pid << endl;
            }
        }
    }

    return 0;
}


0

只是想添加另一个可能的答案

有一个名为devilspiehttps://www.nongnu.org/devilspie2/)的工具专门用于此。它运行一系列lua脚本,当应用程序启动、获取焦点或模糊时触发,然后运行一个进程。

它完全基于lua,但您可以轻松运行第三方工具,例如我使用了os.execute("my bash script")


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接