Monday, February 16, 2009

Got keyboard layout?



You've probably heard me ranting about this before, especially if you know me in person, but Xlib development and XKb in particular is not the most pleasant think in the world. And this could not be more true when you're coming from a pampered and spoiled Qt environment.

Try getting the current keyboard layout from an X11 session and you'll quickly understand what I'm talking about. Why not just use QApplication::keyboardInputLocale()? Well, true, this is what you would usually do:


QString keyboardLayout = qApp->keyboardInputLocale().name();


Which should give you en_US if you're running under that locale. However QApplication::keyboardInputLocale() is broken. So now you're on your own.

The issue here is that there is no simple way of doing that in Xlib/Xkb. The X server loads the layouts on startup and compiles them from the symbol files usually defined in /usr/share/X11/xkb/symbols into key syms and key codes, which we'll call key maps. Now I might be wrong about this, but I did spend more then just a few hours trying to figure this out. Surprisingly, the X devs did not think it necessary to know the symbol layout name at runtime. You can get the compiled keymaps and group names, which for the first in the us symbols file is name[Group1]= "USA";. This is how you would go that:


#include <X11/XKBlib.h>
...

XkbDescPtr keyboard = XkbGetKeyboard(display,
XkbAllComponentsMask,
XkbUseCoreKbd);

XkbStateRec state;
XkbGetState(m_display, XkbUseCoreKbd, &state);

unsigned int group = (unsigned int) state.group;

QString symbols = XGetAtomName(display, keyboard->names->symbols);
QString name = XGetAtomName(display, keyboard->names->groups[group]);

qDebug() << "symbols" << symbols;
qDebug() << "name" << name;

...


However, I have little need for a "USA". I want a "us". So what did I discover in my more then a few o hour long research. Basically two ways of doing this. The VirtualBox guys have a list of their own keyboard symbols (key maps) and compare them to the current key maps from the X server. The one that closes resembles the one form the X server will be the best guess. Crazy...

The other approach I found was to parse the symbols list together with the current group index. While this is slightly more intuitive then the VirtualBox approach, it's still insane that one has to go out of their way like this.

This is what XGetAtomName(display, keyboard->names->symbols) call above returns


pc_us_se_2_il_3_nec_vndr/jp_4_inet(evdev)_group(alt_shift_toggle)


Which is similar to what you can get from the command line using .


xkb_symbols { include "pc+us+se:2+inet(evdev)+group(alt_shift_toggle)" };


Now all that's left is to parse that string to get and combine it with the group index to get the current one. I'll let you do the math from here.

Edit: Be careful where you call those XKb functions from as you might get synchronization issues such as Xlib: unexpected async reply

Edit: Looks like QApplication::keyboardInputLocale() works if there is only one keyboard layout.

Edit: Got a task opened by Qt.

0 comments: