Listening to keyboard events without consuming them in X11 - Keyboard hooking

旧巷老猫 提交于 2019-11-29 04:26:05

Here's my quick and dirty example

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <ctype.h>


int main ()
{
    Display* d = XOpenDisplay(NULL);
    Window root = DefaultRootWindow(d);
    Window curFocus;
    char buf[17];
    KeySym ks;
    XComposeStatus comp;
    int len;
    int revert;

    XGetInputFocus (d, &curFocus, &revert);
    XSelectInput(d, curFocus, KeyPressMask|KeyReleaseMask|FocusChangeMask);

    while (1)
    {
        XEvent ev;
        XNextEvent(d, &ev);
        switch (ev.type)
        {
            case FocusOut:
                printf ("Focus changed!\n");
                printf ("Old focus is %d\n", (int)curFocus);
                if (curFocus != root)
                    XSelectInput(d, curFocus, 0);
                XGetInputFocus (d, &curFocus, &revert);
                printf ("New focus is %d\n", (int)curFocus);
                if (curFocus == PointerRoot)
                    curFocus = root;
                XSelectInput(d, curFocus, KeyPressMask|KeyReleaseMask|FocusChangeMask);
                break;

            case KeyPress:
                printf ("Got key!\n");
                len = XLookupString(&ev.xkey, buf, 16, &ks, &comp);
                if (len > 0 && isprint(buf[0]))
                {
                    buf[len]=0;
                    printf("String is: %s\n", buf);
                }
                else
                {
                    printf ("Key is: %d\n", (int)ks);
                }
        }

    }
}

It's not reliable but most of the time it works. (It is showing keys I'm typing into this box right now). You may investigate why it does fail sometimes ;) Also it cannot show hotkeys in principle. Hotkeys are grabbed keys, and only one client can get a grabbed key. Absolutely nothing can be done here, short of loading a special X11 extension designed for this purpose (e.g. XEvIE).

user2029077

Thanks to n.m.'s answer and parsa's comment, this is my final code:

#include <X11/Xlib.h>
#include <stdlib.h>
#include <iostream>

using namespace std;


void* TaskCode(void* parg)
{
    int keycode = *((int*)parg);
    cout<< "\n\n" << keycode << "\n\n";
    if(keycode == XKeysymToKeycode(XOpenDisplay(0),'a'))
        system("espeak -v en " "\"a\"");
    delete (int*)parg;
    return 0;
}

void Action(int keycode)
{
    pthread_t thread;
    pthread_attr_t  attrs;
    pthread_attr_init(&attrs);
    pthread_attr_setdetachstate(&attrs,PTHREAD_CREATE_DETACHED);
    pthread_attr_setstacksize(&attrs, 1000);
    int* pthread_arg = new int;
    *pthread_arg = keycode;
    pthread_create(&thread,&attrs, TaskCode, (void*) pthread_arg);
}

int MyX11ErrorHandler(Display *, XErrorEvent *error_event)
{
   cout << "\n\n" "An X11-Functions error occured. Probably the focused window was closed.""\n"
           "This error will be ignored." "\n";
   cout<< "error_code: " << (unsigned)error_event -> error_code << "\n";
   cout<< "minor_code: " << (unsigned)error_event -> minor_code << "\n";
   cout<< "request_code: " << (unsigned)error_event -> request_code << "\n";
   cout<< "resourceid: " << error_event -> resourceid << "\n";
   cout<< "serial; " << error_event -> serial << "\n";
   cout<< "type: " << error_event -> type << "\n\n";
   return 0;
}

int main()
{
    Display* display = XOpenDisplay(0);
    Window root = DefaultRootWindow(display);
    Window current_focus_window;
    int revert;

    XSetErrorHandler(MyX11ErrorHandler);

    XGetInputFocus(display, &current_focus_window, &revert);
    XSelectInput(display,current_focus_window,KeyPressMask | KeyReleaseMask | FocusChangeMask);

    while(true)
    {
        XEvent event;
        XNextEvent(display, &event);
        switch (event.type)
        {
            case FocusOut:
                if(current_focus_window != root)
                    XSelectInput(display, current_focus_window, 0);
                XGetInputFocus(display, &current_focus_window, &revert);
                if(current_focus_window == PointerRoot)
                    current_focus_window = root;
                XSelectInput(display, current_focus_window, KeyPressMask|KeyReleaseMask|FocusChangeMask);
                break;

            case KeyPress:
                Action(event.xkey.keycode);
                break;
        }
    }
}

Add these to a Qt Creator's project .pro file:

LIBS += -lX11
LIBS += -lpthread
LIBS += -lXtst

Any improvement suggestions is appreciated.

To archive I also add my final code with grabbing:

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <iostream>

using namespace std;

void* TaskCode(void* parg)
{
    int keycode = *((int*)parg);
    cout<< "\n\n" << keycode << "\n\n";
    system("espeak -v en " "\"a\"");
    delete (int*)parg;
    return 0;
}


void SendKeyEvent(Display *display, XEvent event)
{
    Window current_focus_window;
    XKeyEvent& xkey = event.xkey;

    int current_focus_revert;
    XGetInputFocus(display, &current_focus_window, &current_focus_revert);       
    xkey.state = Mod2Mask;

    XSendEvent(display, InputFocus,  True, xkey.type, (XEvent *)(&event));
}

int GrabKey(Display* display, Window grab_window, int keycode)
{
    unsigned int    modifiers       = Mod2Mask; // numlock on
    //Window          grab_window     = DefaultRootWindow(display);
    Bool            owner_events    = True;
    int             pointer_mode    = GrabModeAsync;
    int             keyboard_mode   = GrabModeAsync;

    XGrabKey(display, keycode, modifiers, grab_window, owner_events, pointer_mode, keyboard_mode);
    return keycode;
}

void UngrabKey(Display* display, Window grab_window, int keycode)
{
    unsigned int    modifiers       = Mod2Mask; // numlock on

   // Window grab_window = DefaultRootWindow(display);
    XUngrabKey(display,keycode,modifiers,grab_window);
}


void Action(int keycode)
{
    pthread_t thread;
    int* pthread_arg = new int;

    *pthread_arg = keycode;
    pthread_create(&thread,0, TaskCode, (void*) pthread_arg);
}

int main()
{
    Display*    display = XOpenDisplay(0);
    Window      root    = DefaultRootWindow(display);
    XEvent      event;

    int keycode = XKeysymToKeycode(display,'a');
    GrabKey(display,root,keycode);

    XSelectInput(display, root, KeyPressMask | KeyReleaseMask);
    while(true)
    {
        XNextEvent(display, &event);
        switch(event.type)
        {
            case KeyPress:
                Action(event.xkey.keycode);
            case KeyRelease:
                SendKeyEvent(display,event);
            default:
                break;
        }
    }

    XCloseDisplay(display);
}

Everything is good except that, unlike the code in question, it ignores language layout. Pressing a types a whatever regradless of language layout!

Vladimir Panteleev

As an alternative to listening to X events, it's also possible to listen to Linux input events directly: https://stackoverflow.com/a/27693340/21501

This has the benefit that it's possible to modify the event stream in-flight, and block, edit or generate input events.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!