X KeyPress/Release events capturing irrespective o

2020-06-04 03:27发布

问题:

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.

回答1:

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.



回答2:

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: 


标签: linux c