cx11xlib

How do I convert an X11 keycode to a Unicode character in C?


I need to convert keycodes in X11 to symbols in another system. There's an easy function to convert Unicode characters to symbols for that system, so all I need to do for most keycodes is convert into a character, then convert to a symbol. The conversion to a character is a bit of a hiccup, though.

I have an XKeyEvent, which has a keycode. It seems X11 uses "keysyms" to represent characters on keys, so the most obvious way is to convert to a keysym, then a character. Both of those seem difficult, though.

There's an XKeycodeToKeysym function, but that takes a byte as the keycode instead of an unsigned int and wants an index into a something which seems never to be explained in the documentation. Not to mention that it's deprecated.

There's a (not deprecated!) XLookupKeysym function, which takes the event directly and seems to be the one I want, but also wants an unexplained index.

Then, I need to convert the KeySym to a character. The XKeysymToString function exists, but that returns a string in the "Host Portable Character Encoding", and I want it in Unicode.

Okay, maybe it'd be easier to do the conversion directly? There's XwcLookupString, which seems perfect. However, it wants an XIC, which wants an XIM, which wants a struct _XrmHashBucketRec *, which wants… uhh… Not to mention it only works on KeyPressEvents, and I need to be parsing KeyReleaseEvents as well.

I'm lost. How do I convert an X11 keycode to a character?


Solution

  • The current answer doesn't actually meet my specifications, so I'm posting this adaptation that does:

    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    #include <X11/keysymdef.h>
    
    Display *display = NULL;
    XIM xim = NULL;
    XIC xic = NULL;
    
    typedef struct KCSPair {
        int32_t key_char;
        KeySym key_sym;
    } KCSPair;
    static KCSPair getKCSPair(XKeyEvent *);
    
    int main(const int argc, const char *argv[]) {
        display = XOpenDisplay(NULL);
        if (!display) {
            return 1;
        }
        const Window window = XDefaultRootWindow(display);
        if (!window) {
            return 2;
        }
    
        setlocale(LC_ALL, "");
        XSetLocaleModifiers("");
        xim = XOpenIM(display, NULL, NULL, NULL);
        if (!xim) {
            return 3;
        }
        xic = XCreateIC(
            xim,
            XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
            XNClientWindow, window,
            XNFocusWindow, window,
            NULL
        );
        if (!xic) {
            return 4;
        }
    
        /* ... */
    
        return 0;
    }
    
    KCSPair kcs_cache[256] = {0};
    static KCSPair getKCSPair(XKeyEvent *event) {
        if (event->type != KeyPress) {
            return kcs_cache[event->keycode & 255];
        }
        KCSPair pair = {0, NoSymbol};
        wchar_t buf[3];
        buf[XwcLookupString(xic, event, buf, (sizeof(buf) / sizeof(wchar_t)) - 1, &pair.key_sym, NULL)] = 0;
        // Are we looking at a lead surrogate?
        if ((buf[0] & 0xFC00) == 0xD800) {
            // We can reasonably assume the next byte is a trail surrogate.
            pair.key_char = (buf[0] << 10) + buf[1] - 56613888;
        } else {
            pair.key_char = buf[0];
        }
        kcs_cache[event->keycode & 255] = pair;
        return pair;
    }
    

    If we can't get an XIC, we should just throw, because then we can't get valid characters. We also unfortunately need to cache KeyPress events because XwcLookupString won't return anything useful for KeyReleases.