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.
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.
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: