Linux get notification on focused gui window change
Asked Answered
M

3

9

In linux, is it possible to get notifications when the currently focused GUI app changes? I'm writing an app that tracks how long a user stays on each GUI app(per process, not within one process), and need some way to access this information. I'm doing this in c++.


Here is what I have found so far:
xprop -id $(xprop -root | awk '/_NET_ACTIVE_WINDOW\(WINDOW\)/{print $NF}') | awk '/_NET_WM_PID\(CARDINAL\)/{print $NF}'

This prints out the pid of the currently focused app, but would require me to pull every so often. I would rather not pull, but I will if I have to. It also assumes that all GUI's are going through x11, which may not be an unreasonable assumption, but is not entirely portable.

Another approach would be to write a shared object that hooks into various gui functions, and then modify the host system's ld.so.preload file to load this shared object with every process. This assumes that all gui apps are using dynamically linked graphics libraries. I would also have to write hooks for every graphics library to ensure total coverage. And in researching GTK (I'm doing my testing on a system running Gnome), I haven't found any functions that are called on window switches. I haven't looked very hard though.


Is there a way to get notifications through x11 for this sort of thing? Or other graphics libraries for that matter?

Edit:

Okay, this is what I have so far, based off @Andrey 's code:

#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];
}

But get_window_pid always returns -1, even though using xprop -id $(xprop -root | awk '/_NET_ACTIVE_WINDOW\(WINDOW\)/{print $NF}') | awk '/_NET_WM_PID\(CARDINAL\)/{print $NF}' correctly returns the pid of the active window. What am I doing wrong?

Murrah answered 7/11, 2013 at 15:53 Comment(1)
get_window_pid uses the root window instead of the window specified in the event. get_window_pid must be declared like this: get_window_pid(e.xproperty.display, e.xproperty.window, e.xproperty.atom); and called like this: pid_t get_window_pid( Display * d, Window w, Atom atom)Assumptive
W
4

Example in JavaScript using node-x11:

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));
          });
        }
      });
    }
  });
});
Winglet answered 7/11, 2013 at 22:27 Comment(3)
Does this intercept other window's messages? Or is this how one particular program handles its own active window changes? If the latter, it gives me an idea on how to use this in an injected shared object (I'd have to figure out the corresponding c++ code.)Murrah
It intercepts root window property changes. Initiator of the property change is your window manager (and if it does now comply to standards.freedesktop.org/wm-spec/wm-spec-latest.html this approach won't work)Winglet
If anyone needs a Python implementation of this, I wrote one in this answer.Caseation
B
2

Finally I got it.
compile: 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;
}

Blob answered 8/4, 2020 at 10:37 Comment(0)
B
0

Just want to add another possible answer

There is a tool devilspie (https://www.nongnu.org/devilspie2/) that is made specifically for this. It runs a series of lua scripts triggered with an application starting, getting focus or blur, then running a process.

It's all lua based but you can easily run a 3rd party tool, for instance I used os.execute("my bash script")

Barong answered 28/11, 2022 at 19:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.