10

I want to record all incoming key pressed events no matter what window is in focus or where the pointer is.

I have written a sample code which should capture the key pressed events of the current Window in focus.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <stdint.h>
#include <stdarg.h>
#include <errno.h>
#include <pthread.h>
#include <X11/Xlib.h>
#include <X11/Xos.h>
#include <X11/Xfuncs.h>
#include <X11/Xutil.h>

#include <X11/Xatom.h>
int _invalid_window_handler(Display *dsp, XErrorEvent *err) {
    return 0;
}

int main() 
{
    Display *display = XOpenDisplay(NULL); 
    int iError;
    KeySym k;
    int revert_to;
    Window window;
    XEvent event;
    Time time;
    XSetErrorHandler(_invalid_window_handler);
    XGetInputFocus(display, &window, &revert_to);
    XSelectInput(display, window, KeyPressMask | KeyReleaseMask );
    iError = XGrabKeyboard(display, window,
                          KeyPressMask | KeyReleaseMask,
                          GrabModeAsync,
                          GrabModeAsync,
                          CurrentTime); 
    if (iError != GrabSuccess && iError == AlreadyGrabbed) {
        XUngrabPointer(display, CurrentTime);
        XFlush(display);
        printf("Already Grabbed\n");    
    } else if (iError == GrabSuccess) {
        printf("Grabbed\n");
    }
    while(1) {
          XNextEvent(display,&event);
          switch (event.type) {
              case KeyPress : printf("Key Pressed\n"); break;
              case KeyRelease : printf("Key Released\n"); break;
              case EnterNotify : printf("Enter\n"); break;
          }
    }
    XCloseDisplay(display);
    return 0;
}

I am calling XGrabKeyboard to capture the keyboard as the application who created the window might have the keyboard events already grabbed. With the above mentioned code I am able to grab the keyboard but am unable to receive either of KeyPress or KeyRelease events for any keys on the keyboard inside the while loop. Is there anything that I am missing in the code due to which I am unable to receive the events ? Any help is highly appreciated.

My final aim is to capture key press events on the screen irrespective of the Window in focus. I have given the sample code for only window in focus for the code to be readable . I would do XQueryTree to get all the Windows and apply the same logic given above to get the expected result.

  • Why not use the event subsystem then? – Ignacio Vazquez-Abrams Jul 03 '12 at 09:20
  • Could you please elaborate on the event subsystem ? I have given KeyPressMask , KeyReleaseMask as the events to capture in the call to XSelectInput. Thanks –  Jul 03 '12 at 10:39
  • 2
    Ignacio Vazquez-Abrams refers to the Linux kernel input subsystem, which is accessible via the character devices in `/dev/input/`. It bypasses X completely, and gets the HID events directly from the kernel -- but also requires root privileges. – Nominal Animal Jul 04 '12 at 21:19

2 Answers2

8

You need to have a mapped window to be able to grab the keyboard. Here is a proof of concept:

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <stdio.h>

int main()
{
    Display *display;
    Window   window, rootwindow;
    XEvent   event;
    KeySym   escape;

    display = XOpenDisplay(NULL);
    rootwindow = DefaultRootWindow(display);
    window = XCreateWindow(display, rootwindow,
                           -99, -99, 1, 1, /* x, y, width, height */
                           0, 0, InputOnly, /* border, depth, class */
                           CopyFromParent, /* visual */
                           0, NULL); /* valuemask and attributes */

    XSelectInput(display, window, StructureNotifyMask | SubstructureRedirectMask | ResizeRedirectMask | KeyPressMask | KeyReleaseMask);
    XLowerWindow(display, window);
    XMapWindow(display, window);

    do {
        XNextEvent(display, &event);
    } while (event.type != MapNotify);

    XGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
    XLowerWindow(display, window);

    escape = XKeysymToKeycode(display, XK_Escape);
    printf("\nPress ESC to exit.\n\n");
    fflush(stdout);

    while (1) {

        XNextEvent(display, &event);

        if (event.type == KeyPress) {
            printf("KeyPress: keycode %u state %u\n", event.xkey.keycode, event.xkey.state);
            fflush(stdout);

        } else
        if (event.type == KeyRelease) {

            printf("KeyRelease: keycode %u state %u\n", event.xkey.keycode, event.xkey.state);
            fflush(stdout);

            if (event.xkey.keycode == escape)
                break;
        } else
        if (event.type == UnmapNotify) {

            XUngrabKeyboard(display, CurrentTime);
            XDestroyWindow(display, window);
            XCloseDisplay(display);

            display = XOpenDisplay(NULL);
            rootwindow = DefaultRootWindow(display);
            window = XCreateWindow(display, rootwindow,
                                   -99, -99, 1, 1, /* x, y, width, height */
                                   0, 0, InputOnly, /* border, depth, class */
                                   CopyFromParent, /* visual */
                                   0, NULL); /* valuemask and attributes */

            XSelectInput(display, window, StructureNotifyMask | SubstructureRedirectMask | ResizeRedirectMask | KeyPressMask | KeyReleaseMask);
            XLowerWindow(display, window);
            XMapWindow(display, window);

            do {
                XNextEvent(display, &event);
            } while (event.type != MapNotify);

            XGrabKeyboard(display, window, False, GrabModeAsync, GrabModeAsync, CurrentTime);
            XLowerWindow(display, window);

            escape = XKeysymToKeycode(display, XK_Escape);

        } else {

            printf("Event type %d\n", event.type);
            fflush(stdout);
        }
    }

    XUngrabKeyboard(display, CurrentTime);

    XDestroyWindow(display, window);
    XCloseDisplay(display);
    return 0;
}

It uses a small window (I didn't even bother to set a title for it) it lowers to bottom of the window stack, so it goes behind any existing windows. You can communicate with the window manager (WM) to make the window decorationless and transparent, or iconified, so that there is no visible window on-screen; the above code does not bother.

The trick I used is that whenever the user manages to unmap the window -- say, by moving to another workspace --, the code destroys the old window, creates a new one, and re-grabs the keyboard. It should be fast enough to not lose any keypresses. There might be other ways to do it, but I suspect they require closer interaction with the window manager.

Note that I have never needed to really grab the keyboard so persistently, so the above approach is likely not the simplest. It was just an approach I think works; there are likely better ones.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • 1
    Is there a way to capture the keyboard events while also letting them to be received by the rest of the X applications? – Cristian Ciupitu May 22 '13 at 18:23
  • 4
    You could use `XSendEvent(display, InputFocus, True, KeyPressMask|KeyReleaseMask, event)` to send grabbed keyboard events to the window that has the current input focus, but that's usually the wrong approach. If you want to monitor all keypresses, *don't*: I for one don't like keyboard sniffers. To see how hotkeys/autokeys are usually implemented, see e.g. `GtkHotKey` or `libtomboy/tomboykeybinder.c`; they grab specific keys at the root window (i.e. when not handled by other windows), and do it right. – Nominal Animal May 22 '13 at 19:23
  • 1
    Thanks! Just for the record, I want monitor all key presses to make sure that my old keyboard isn't playing any tricks on me. – Cristian Ciupitu May 23 '13 at 20:48
  • 3
    You could use the Linux input subsystem, reading keyboard events from `/dev/input/eventN` (say via the symlink in `/dev/input/by-id`) matching your keyboard, and feed them back via uinput, `/dev/uintput`. Just google for "Linux input subsystem" and for "linux" "uinput" for examples and howtos. This approach requires the filter process to have `root` privileges, but it is a much more robust approach. – Nominal Animal May 24 '13 at 13:14
7

The following command will print a list of all events of the whole X session to the console:

$ xinput test-xi2 --root

Example output:

⎡ Virtual core pointer                      id=2    [master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
⎜   ↳ USB Mouse                                 id=10   [slave  pointer  (2)]
⎜   ↳ MCE IR Keyboard/Mouse (ite-cir)           id=11   [slave  pointer  (2)]
⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
    ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
    ↳ Power Button                              id=6    [slave  keyboard (3)]
    ↳ Video Bus                                 id=7    [slave  keyboard (3)]
    ↳ Power Button                              id=8    [slave  keyboard (3)]
    ↳ Oracle USB Keyboard                       id=9    [slave  keyboard (3)]
    ↳ ITE8713 CIR transceiver                   id=12   [slave  keyboard (3)]
EVENT type 14 (RawKeyRelease)
    device: 3 (9)
    detail: 36
    valuators:

EVENT type 3 (KeyRelease)
    device: 9 (9)
    detail: 36
    flags: 
    root: 1324.55/821.81
    event: 1324.55/821.81
    buttons:
    modifiers: locked 0x10 latched 0 base 0 effective: 0x10
    group: locked 0 latched 0 base 0 effective: 0
    valuators:
    windows: root 0x9c event 0x9c child 0x7291d5
EVENT type 15 (RawButtonPress)
    device: 2 (10)
    detail: 1
    valuators:
    flags: 

EVENT type 4 (ButtonPress)
    device: 10 (10)
    detail: 1
    flags: 
    root: 1324.55/821.81
    event: 1324.55/821.81
    buttons:
    modifiers: locked 0x10 latched 0 base 0 effective: 0x10
    group: locked 0 latched 0 base 0 effective: 0
    valuators:
    windows: root 0x9c event 0x9c child 0x7291d5
EVENT type 16 (RawButtonRelease)
    device: 2 (10)
    detail: 1
    valuators:
    flags: 
Fritz
  • 1,293
  • 15
  • 27
Joe M
  • 71
  • 1
  • 3