Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I tried to write a program which hooks keyboard messages to pronounce the name of each key whenever it is pressed in Ubuntu (KDE); without interfering with normal action of keyboard in programs (just announcing the key name).

This is my program:

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

using namespace std;

void SendPressKeyEvent(Display *display, XKeyEvent xkey)
{
    Window current_focus_window;
    int current_focus_revert;
    XGetInputFocus(display, &current_focus_window, &current_focus_revert);
    xkey.type =  KeyPress;
    xkey.display = display;
    xkey.window = current_focus_window;
    xkey.root = DefaultRootWindow(display);
    xkey.subwindow = None;
    xkey.time = 1000 * time(0);
    xkey.x = 0;
    xkey.y = 0;
    xkey.x_root = 0;
    xkey.y_root = 0;
    xkey.same_screen = True;
    XSendEvent(display, InputFocus,  True, KeyPressMask, (XEvent *)(&xkey));
}

void SendReleaseKeyEvent(Display *display, XKeyEvent xkey)
{
    Window current_focus_window;
    int current_focus_revert;
    XGetInputFocus(display, &current_focus_window, &current_focus_revert);
    xkey.type =  KeyRelease;
    xkey.display = display;
    xkey.window = current_focus_window;
    xkey.root = DefaultRootWindow(display);
    xkey.subwindow = None;
    xkey.time = 1000 * time(0);
    xkey.x = 0;
    xkey.y = 0;
    xkey.x_root = 0;
    xkey.y_root = 0;
    xkey.same_screen = True;
    XSendEvent(display, InputFocus, True, KeyReleaseMask, (XEvent *)(&xkey));
}

void *TaskCode(void* arg)
{
    switch(*(int*)arg)
    {
    case 38:
        system("espeak -v en "  "\"a\"");
    }
    return 0;
}

int main()
{
    Display *display = XOpenDisplay(0);
    if(display == 0)
        exit(1);
    XGrabKeyboard(display, DefaultRootWindow(display), True, GrabModeAsync, GrabModeAsync, CurrentTime);
    XEvent event;
    while(true)
    {
        XNextEvent(display, &event);
        if(event.type == Expose)
        {

        }
        if(event.type == KeyPress)
        {
            SendPressKeyEvent(display,event.xkey);
            if(event.xkey.keycode == 38)
            {
                pthread_t thread;
                int thread_arg = event.xkey.keycode;
                pthread_create(&thread,0, TaskCode, (void*) &thread_arg);
            }
        }
        if(event.type == KeyRelease)
            SendReleaseKeyEvent(display,event.xkey);
    }
    XCloseDisplay(display);
}

This program is just for the key a which can be extended to other keys.

But when this program is running, some programs (e.g. Chromium) do not show the blinker (cursor) in their edit boxes. Also all KDE hotkeys become disabled.

How can this be fixed?

like image 977
Minimus Heximus Avatar asked Mar 30 '14 20:03

Minimus Heximus


2 Answers

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

like image 89
n. 1.8e9-where's-my-share m. Avatar answered Oct 10 '22 06:10

n. 1.8e9-where's-my-share m.


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!

like image 45
Minimus Heximus Avatar answered Oct 10 '22 06:10

Minimus Heximus