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