You need to have a window displayed in order to be able to capture the keyboard. Here is the 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 drops to the bottom of the window stack, so it goes for any existing windows. You can contact the window manager (WM) to make the window flawless and transparent or marked so that there is no visible window on the screen; the code above does not bother.
The trick I used is that whenever a window is deleted by the user, say, moving to another workspace, the code destroys the old window, creates a new one and grabs the keyboard again. It should be fast enough so as not to lose any keystrokes. There may be other ways to do this, but I suspect that they require closer interaction with the window manager.
Please note that I never needed to grab the keyboard for so long, so the above approach is most likely not the easiest. It was just an approach that I think works; most likely better.