Reading the Keyboard in X11

I actually began this post well over a year ago, as I remember struggling to work out how to read keyboard input in X. Anyway, I thought I’d finally finish it off for posterity’s sake ๐Ÿ™‚

I recently had to implement some API calls in Linux to be able to return what keys were currently being pressed. As I was using Qt for the UI layer (which, by the way, is now my favourite UI framework!) I was hoping there would be some platform-agnostic call I could make. However (and kinda rightfully so) Qt doesn’t expose a way to do this, as it’s assumed you should just do things based on events.

After a few hours googling and scratching my head, I realised this wasn’t going to be as simple as I imagined, as the documentation for anything X based seems incessantly complex. Anyway, here’s how I did it:

Getting the Keys being Pressed

First I get from the QApplication the currently actibe window. Assuming we have a valid window, I then get the X11 display pointer and use this to call XQueryKeymap. This function takes an array of 32 chars, which is uses to mark what physical keys are being pressed, one key per bit.

static const uint32_t K_KEYMAP_SIZE = 32;

QWidget* window = qApp->activeWindow();
if( window )
{
    // We have a valid window, so grab the display
    const QX11Info& info = window->x11Info();

    char keyMap[K_KEYMAP_SIZE];

    // First get the bit array of all keys currently down
    XQueryKeymap(info.display(), keyMap);

    // next go through each member of keymap array
    for( uint32_t i = 0; i < K_KEYMAP_SIZE; i++ )
    {
        const char& currentChar = keyMap[i];

        // iterate over each bit and check if the bit is set
        for( uint32_t j = 0; j < 8; j++ )
        {
            // AND current char with current bit we're interested in
            bool isKeyPressed = ((1 << j) & currentChar) != 0;

            if( isKeyPressed )
            {
                // work out the keycode (is just the number of the bit that's set)
                KeyCode keyCode = (i * sizeof(char) * 8) + j;

                // convert to qt key, and append if we get a valid key
                int32_t qtkey = ConvertKeyToQtKey(keyCode, info.display());

                if( qtkey )
                {
                    downkeys.push_back(qtkey);
                }
            }
        }
    }
}

Converting the X11 key to Qt key

In the above code, once I have the bit array of buttons being pressed, I then iterate through all the keys being pressed and convert the pysical key into a Qt key. This basically involves using the XKeycodeToKeysym function, which will convert a key position into a key symbol. However, as each physical key can have multiple meanings (based on modifiers for instance) I iterate over all the symbols (using an iterator) and return the first key that I care about.

typedef QMap <keysym, int32_t="">KeySymToQtKeyMap;</keysym,>

int32_t
ConvertKeyToQtKey(
    uint32_t keyCode,
    Display* display )
{
    // This is a static map of key mappings for all the keys I need to know about, which I initialize once
    static KeySymToQtKeyMap keyMapping = InitializeKeyMapping();

    // Iterate through all the keysym's until we find one that matches.
    uint32_t i = 0;
    KeySym key = NoSymbol;
    do
    {
        key = XKeycodeToKeysym((Display*)display, keyCode, i++);

        if( (key >= XK_0 && key <= XK_9) || (key >= XK_a && key <= XK_z) || (key == XK_space) )
        {
            // ASCII Key, make sure it's changed to upper varient if a char
            return isprint(key) ? toupper(key) : key;
        }
        else if( keyMapping.contains(key) )
        {
            return keyMapping[key];
        }
    } while( key != NoSymbol);

    return 0;
}

This isn’t the perfect solution, but for my case it worked perfectly. The moral of the story is that looking at the keyboard directly is a Bad Idea and that you should always always listen to button events instead.

Leave a Comment

Your e-mail address will not be published. Required fields are marked *