Search code examples
c++linuxx11xlibkeyboard-hook

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?


Solution

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