Almost every productive person I know wishes that he or she had more time. Most of us wonder where our time really goes; often it’s noon by the time I finish reading my feeds and following up on interesting URLs. (Fortunately, I’m not a morning person, so I wouldn’t accomplish much before noon anyway.)

I’ve often wished for a short X.org program that would run in the background and monitor which window had active focus. If I tracked that for a few hours or days, I’d be able to perform some interesting statistical analysis to see where I actually spend my time.

Writing a prototype took about ten minutes, thanks to Dennis Paulsen’s X11::GUITest Perl module (see Test-Driving X11 GUIs by George Nistorica for more):

Perl Prototype

#!/usr/bin/perl

use strict;
use warnings;

use X11::GUITest qw( GetInputFocus GetWindowName GetParentWindow );

while (1)
{
    my $w    = GetInputFocus();
    my $name = GetWindowName( $w );

    until ($name)
    {
        $w    = GetParentWindow( $w );
        $name = GetWindowName(   $w );
    }

    warn "$name\n";
    sleep( 1 );
}

I did notice in some brief manual testing that not all windows report a name; I suspect that some of the X11 toolkits use multiple Xlib windows to make what appears to be a single window in the GUI. (Firefox was one offender.) That’s the reason behind the loop to get a real window name.

C Prototype

After browsing the XS source code of the Perl module, I decided to write my own version in straight C against Xlib. (The X Window System programming books sitting six feet behind me on a bookshelf come in useful once in a while.) Though I’ve never found a program like this before, and I’m sure there are half a dozen that you’re rushing to tell me about right now, here’s the source code to the C version.


#include <X11/Xlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <unistd.h>

static int (*old_error_handler)(Display *, XErrorEvent *) = NULL;
static Display *d;

int ignore_bad_window(Display *d, XErrorEvent *e)
{
    /* Ignore bad window errors here, handle elsewhere */
    if (e->error_code != BadWindow) {
        assert(old_error_handler);
        (*old_error_handler)(d, e);
    }

    return 0;
}

int
check_window(Display *d, Window w)
{
    XWindowAttributes wattrs = {0};
    int status;

    old_error_handler = XSetErrorHandler(ignore_bad_window);
    status            = XGetWindowAttributes(d, w, &wattrs);

    if (!status)
    {
        fprintf( stderr, "Bad window %d (%d)\n", (int)w, status );
        exit( EXIT_FAILURE );
    }

    XSetErrorHandler(old_error_handler);

    return 1;
}

char *
get_window_name(Display *d, Window w)
{
    char *name = NULL;

    if (!check_window(d, w))
        return NULL;

    XFetchName(d, w, &name);

    return name;
}

Window
get_parent_window(Display *d, Window w)
{
    Window       root   = 0;
    Window       parent = 0;
    Window      *children;
    unsigned int num_children;

    if (XQueryTree(d, w, &root, &parent, &children, &num_children))
        XFree(children);

    return parent;
}

static void
sigint_handler(int signo)
{
    if (d)
        XCloseDisplay(d);

    exit( EXIT_SUCCESS );
}

int
main (int argc, char * argv[])
{
    if (signal( SIGINT, sigint_handler ) == SIG_ERR) {
        fprintf( stderr, "Couldn't handle SIGINT!\n" );
        exit( EXIT_FAILURE );
    }

    d = XOpenDisplay(NULL);

    if (!d)
    {
        fprintf( stderr, "Cannot connect to X server\n" );
        exit( EXIT_FAILURE );
    }

    while (1)
    {
        Window focused = 0;
        int    revert  = 0;
        char  *name    = NULL;

        XGetInputFocus(d, &focused, &revert);

        do {
            name = get_window_name(d, focused);

            if (name)
                break;

            focused = get_parent_window(d, focused);
        } while (!name);

        printf( "Focus in window '%s'\n", name );
        XFree( name );

        sleep( 1 );
    }
}

Save it as watch_focus.c and compile it with:

$ gcc -Wall -lX11 -o watch_focus watch_focus.c

Run it with ./watch_focus and use Ctrl-C to finish.

Future Expansion

Right now the statistics and reporting aren’t terribly useful, and the one-second sleep might be too short. However, the internals of the program are there. Reporting the time spent in a window, as well as the window ID, seems useful. Perhaps instead of printing to standard output, the right decision is to write to a log file for the current user. It might be nice to detect when the user is idle and ignore reporting then, too.

This was a fun little diversion, and I only went down one rabbit-hole: trying to listen for all focus change events in the X server. Apparently you can only do that for one window at a time, which seems sensible.

A lot of programmers I know don’t do much GUI programming because there are so many GUI toolkits available. Though this isn’t a GUI program, it hooks into Xlib, and it wasn’t a painful experience at all. I wouldn’t dream of suggesting that more GUI programs should use Xlib directly, but there are probably plenty of great programs that could make use of X.org for various non-display reasons.