Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux get notification on focused gui window change

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?

like image 926
Brian Schlenker Avatar asked Nov 07 '13 15:11

Brian Schlenker


2 Answers

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));
          });
        }
      });
    }
  });
});
like image 106
Andrey Sidorov Avatar answered Sep 23 '22 16:09

Andrey Sidorov


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

like image 38
user13495 Avatar answered Sep 19 '22 16:09

user13495