Random thoughts, tips & tricks about Slackware-Linux, Lego and Star Wars

QEMU and Brazilian keyboards

March 11th, 2011 by Niels Horn in ,

This post explains how to get QEMU working on a host with a pt_BR (ABNT2) keyboard fro Brazil. These keyboards have the "dead" accent-keys and a few modifications that are specific to this layout.

The Problem

For a long time there have been some problems with Brazilian keyboards and QEMU.
In theory, using the standard QEMU, without a special keyboard layout and the normal SDL interface, all keys should be passed directly to the guest operating system as native "scancodes".
But the Brazilian ABNT2 keyboard layout has two extra keys that were not working in the guest:
- the key with the "slash" ('/'), "question-mark" ('?') and "degrees"
- the "period" next to the numeric keypad

As I use mostly Linux as the guest operating system, I tried to find out the keycodes with then "showkey" command, but nothing happened…
On the host, the two keys show up as 89 and 121 respectively.

Use the Source, Luke!

So QEMU was not sending the keycodes to the host operating system…
I started to study how keyboards are handled in general (OK, I already knew something about this), and how QEMU handles them.
This is the big advantage of Free, Open-Source Software: If you don't like the way it works, you are free to change it :)

Traditionally, the first 88 keycodes are standard. This comes from the old PC keyboard that had only 88 keys. Then came the AT keyboard with 104 keys, and then came all the different layouts, "internet" and "multi-media" keyboards, etc…
And the two keys I needed are 89 and 121 - not in the 88-key standard!

Now QEMU is a program that runs under "X" in Linux, and here the standard keycodes are numbered 9 - 96 (console-keycode + 8).
In the QEMU source tree we can find the program that handles the SDL user-interface as ui/sdl.c
Here's an interesting part of the code:

static uint8_t sdl_keyevent_to_keycode(const SDL_KeyboardEvent *ev)
{
 int keycode;
 static int has_evdev = -1;
 if (has_evdev == -1)
 has_evdev = check_for_evdev();
 keycode = ev->keysym.scancode;
 if (keycode < 9) {
 keycode = 0;
 } else if (keycode < 97) {
 keycode -= 8; /* just an offset */
 } else if (keycode < 158) {
 /* use conversion table */
 if (has_evdev)
 keycode = translate_evdev_keycode(keycode - 97);
 else
 keycode = translate_xfree86_keycode(keycode - 97);
 } else if (keycode == 208) { /* Hiragana_Katakana */
 keycode = 0x70;
 } else if (keycode == 211) { /* backslash */
 keycode = 0x73;
 } else {
 keycode = 0;
 }
 return keycode;
}

This part shows clearly that codes 9-96 are "translated" to the console-keycodes by subtracting 8 and the rest is handled by a translate_evdev_keycode function.
Nice! So I started to look for that translate function and found it in ui/x_keymap.c: (shortened for readability)

static const uint8_t evdev_keycode_to_pc_keycode[61] = {
 0, /* 97 EVDEV - RO ("Internet" Keyboards) */
 0, /* 98 EVDEV - KATA (Katakana) */
 0, /* 99 EVDEV - HIRA (Hiragana) */
 0x79, /* 100 EVDEV - HENK (Henkan) */
...
 0, /* 127 EVDEV - PAUS */
 0, /* 128 EVDEV - ???? */
 0, /* 129 EVDEV - I129 ("Internet" Keyboards) */
 0xf1, /* 130 EVDEV - HNGL (Korean Hangul Latin toggle) */
 0xf2, /* 131 EVDEV - HJCV (Korean Hangul Hanja toggle) */
...
 0, /* 156 EVDEV - I157 */
 0, /* 157 EVDEV - I158 */
};
uint8_t translate_xfree86_keycode(const int key)
{
 return x_keycode_to_pc_keycode[key];
}
uint8_t translate_evdev_keycode(const int key)
{
 return evdev_keycode_to_pc_keycode[key];
}

Now, I was looking for 89+8=97 and 121+8=129 and guess what? They are both defined as "0", so indeed - nothing is passed to the guest operating system :)

Filling in the blanks - a patch

That looked simple to solve - a small patch to ui/x_keymap.c, recompile QEMU and we're done!
This is the patch I created:

--- qemu-kvm-0.13.0/ui/x_keymap.c 2010-10-14 12:06:47.000000000 -0300
+++ qemu-kvm-0.13.0_patched/ui/x_keymap.c 2011-03-10 22:28:21.000000000 -0300
@@ -94,7 +94,7 @@
 */
 static const uint8_t evdev_keycode_to_pc_keycode[61] = {
- 0, /* 97 EVDEV - RO ("Internet" Keyboards) */
+ 0x59, /* 97 abnt2 slash-question */
 0, /* 98 EVDEV - KATA (Katakana) */
 0, /* 99 EVDEV - HIRA (Hiragana) */
 0x79, /* 100 EVDEV - HENK (Henkan) */
@@ -126,7 +126,7 @@
 0, /* 126 EVDEV - I126 ("Internet" Keyboards) */
 0, /* 127 EVDEV - PAUS */
 0, /* 128 EVDEV - ???? */
- 0, /* 129 EVDEV - I129 ("Internet" Keyboards) */
+ 0x79, /* 129 abnt2 KP-period */
 0xf1, /* 130 EVDEV - HNGL (Korean Hangul Latin toggle) */
 0xf2, /* 131 EVDEV - HJCV (Korean Hangul Hanja toggle) */
 0x7d, /* 132 AE13 (Yen)*/

I used 0x59 for the slash/question key (that's 89 in hex) and 0x79 for the numeric-period (that's 121 in hex).
Applied the patch, recompiled and tried it. Did it work?
Well, not completely - yet...
At least "showkey" was giving a keycode, but not the one I defined in the table!
For the slash/question key I found 117 and for the numeric-period 92.
I tried all kinds of combinations, but apparently QEMU does some other kind of translation I could not find in the source (anyone have an idea?)
In the end I gave up and decided to solve this in the guest operating system, as I at least had keycodes now that I could handle.

Handling the keys in the console

I work most of the time in the console on my virtual machines, so this was my number one priority and actually simple to solve.
The keymap in the console sits in /usr/share/kbd/keymaps/i386/qwerty and for the Brazilian ABNT2 keyboard is called br-abtn2.map.gz
We can edit this file directly with vim (which will handle the gzip extraction and compression for us).
I changed the following lines at the end:

...
keycode 117 = slash question degree
 control keycode 117 = Delete
 alt keycode 117 = Meta_slash
keycode 92 = period
 shift keycode 92 = period
...

Note: these lines are already there, just changed the "89" to "117" and "121" to "92".

After this, I just had to do loadkeys br-abnt2.map and the missing keys were working in the console!

Handling the keys in X

OK, most of the time I use the console, but I do need to test programs in the graphical environment of X once in a while...
But this is quite simple as well.

X provides the possibility to map keys by creating a ".Xmodmap" file. You can create this file in your home directory (will work only for you) or system-wide for all users.
In Slackware this will be in /etc/X11/xinit/
The file is quite simple, just remember that in X the keycodes are the ones from the console +8:

keycode 125 = slash question
keycode 100 = period period

Conclusion

So, it was not the most elegant solution, but it works perfectly for me.
I have all the keys on my keyboard working in my guest operating systems now, even if they have the wrong keycodes. But as a user this doesn't bother me at all - I just want to be able to use the slash-key to change directories :)

Excerpt in Portuguese
Since this post is about a specific keyboard used here in Brazil, I finish with a small excerpt in Portuguese so that local users can find this article:

Esse artigo explica como alterar o QEMU para passar os "keycodes" para o sistema operacional que roda dentro dele, usando o teclado ABNT2 utilizado no Brasil. Depois é explicado como configurar o Linux para reconhecer de forma correta as teclas de "barra"-"ponto de interrogação" e o "ponto-decimal" ao lado to teclado numérico. Qualquer dúvida, podem deixar um comentário aqui!