Keltek
Keltek

Reputation: 660

Detect window focus changes with XCB

I'm writing a program with XCB that needs to detect whenever a window gains or loses focus. So far I have this but it just hangs on the xcb_wait_for_event call, never entering the loop. What am I missing here to grab root events? Or am I just going about this totally wrong and there's a better way than listening to the root?

#include <stdio.h>
#include <stdlib.h>
#include <xcb/xcb.h>

int main (int argc, char **argv)
{
    xcb_connection_t* conn = xcb_connect(NULL, NULL);
    if (xcb_connection_has_error(conn)) {
        printf("Cannot open daemon connection.");
        return 0;
    }

    xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;

    uint32_t values[] = { XCB_EVENT_MASK_FOCUS_CHANGE };
    xcb_change_window_attributes(
        conn,
        screen->root,
        XCB_CW_EVENT_MASK,
        values);

    xcb_generic_event_t *ev;
    while ((ev = xcb_wait_for_event(conn))) {
        printf("IN LOOP\n");
        switch (ev->response_type & 0x7F) {
        case XCB_FOCUS_IN:
        case XCB_FOCUS_OUT:
            printf("IN CASE\n");
            break;
        default:
            printf("IN DEFAULT\n");
            break;
        }
        free(ev);
    }

    return 0;
}

Upvotes: 3

Views: 1595

Answers (1)

Uli Schlachter
Uli Schlachter

Reputation: 9867

The focus events are only sent when the window that you selected these events on receives or loses the focus, see https://www.x.org/releases/X11R7.5/doc/x11proto/proto.html:

FocusIn FocusOut

[...]

These events are generated when the input focus changes and are reported to clients selecting FocusChange on the window.

To use this, you would have to select this event mask on all windows and also watch for the creation of new windows.


I would suggest a different approach: Watch for PropertyChangeNotify events on the root window to see when the _NET_ACTIVE_WINDOW property changes. This property should be kept up to date by the WM, according to EWMH.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <xcb/xcb.h>

static xcb_atom_t intern_atom(xcb_connection_t *conn, const char *atom)
{
    xcb_atom_t result = XCB_NONE;
    xcb_intern_atom_reply_t *r = xcb_intern_atom_reply(conn,
            xcb_intern_atom(conn, 0, strlen(atom), atom), NULL);
    if (r)
        result = r->atom;
    free(r);
    return result;
}

int main (int argc, char **argv)
{
    xcb_connection_t* conn = xcb_connect(NULL, NULL);
    if (xcb_connection_has_error(conn)) {
        printf("Cannot open daemon connection.");
        return 0;
    }

    xcb_atom_t active_window = intern_atom(conn, "_NET_ACTIVE_WINDOW");
    xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data;

    uint32_t values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE };
    xcb_change_window_attributes(
        conn,
        screen->root,
        XCB_CW_EVENT_MASK,
        values);

    xcb_flush(conn);

    xcb_generic_event_t *ev;
    while ((ev = xcb_wait_for_event(conn))) {
        printf("IN LOOP\n");
        switch (ev->response_type & 0x7F) {
        case XCB_PROPERTY_NOTIFY: {
            xcb_property_notify_event_t *e = (void *) ev;
            if (e->atom == active_window)
                puts("active window changed");
            break;
        }
        default:
            printf("IN DEFAULT\n");
            break;
        }
        free(ev);
    }

    return 0;
}

Upvotes: 4

Related Questions