Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get scancode rather than keycode on Linux using X11

I'm trying to listen to keyboard input (using an X11 event loop) and get scancodes. These scancodes should refer to the physical location of a key, rather than the character it types. The problem is, all I can get are KeySyms and KeyCodes, which are mapped differently for different languages (QWERTY vs QWERTZ for example).

My current solution is to read the "/usr/share/X11/xkb/keycodes/evdev" file. It contains the mappings of key locations to key codes. Using this I can simply translate any keycode back to a scancode. My guess is this is not a stable way of doing things though. I don't know much about Linux at all. That's why I thought asking here might be a good idea.

Is it safe to assume the these evdev mappings are being used by most user's machines? If not, where else could I find key mappings that are actually being used? Or is there a better solution to all of this?

like image 864
ComfyS Avatar asked Jul 24 '16 13:07

ComfyS


People also ask

How to find the scancode of a key in Linux?

How find the scancode of a key? 1 First, switch to Virtual Console. [see Linux: How to use Virtual Console] 2 Then, type showkey --scancodes. 3 Then, type any key, it'll print its scancode in hexadecimal. 4 To exit, just wait for 10 seconds.

How do I find the X11 key code in Linux terminal?

In terminal, type xev, then press key to find that key's X11 keycode and keysym. to quit, mouse click on the close box of the popup window.

How do I map scancodes to keycodes?

There are two ways of mapping scancodes to keycodes : The preferred method is to use udev because it uses hardware information (which is a quite reliable source) to choose the keyboard model in a database. It means that if your keyboard model has been found in the database, your keys are recognized out of the box .

How do I find the keycode of a key?

xev lets you find the keycode / keysym of a key. type xev in terminal, then press a key. It'll display output in terminal. note, mouse movement will generate lots of outputs.


1 Answers

I had the same problem and I've just found a solution. Let's start with the obvious first.

If you want to get specific keys such as "W" or "4", no matter where they're located, you can just convert the keycode you receive from the event into a KeySym. In this case "W" is XK_W and XK_w and "4" is XK_4 (and XK_dollar on most keyboards).

However, sometimes you want to get keys such as "the nth key of the mth row". You need key names to do that. In this case "W" is AD02 and "4" is AE04 on QWERTY keyboards.

Let's say you are making a game in which the player needs to use the WASD keys to move. If you look for KeySyms it's going to work fine on QWERTY keyboards, but people using other keyboard layouts such as AZERTY, QWERTZ and DVORAK will have trouble. So in this case it's better to use key names.

Using key names is actually pretty easy, but the documentation is very messy (but I still recommend you take a look at it). I had to take a look at GLFW's source code (specifically src/x11_init.c) because I was clueless. This method requires Xkb, but you were already using it so I guess that's no problem.

First you need to get the keyboard map and obtain symbolic names. We only want key names so we use XkbKeyNamesMask.

#include <X11/XKBlib.h>

XkbDescPtr KbDesc = XkbGetMap(XDisplay, 0, XkbUseCoreKbd);
XkbGetNames(XDisplay, XkbKeyNamesMask, KbDesc);

Then, at the event loop you can use the KbDesc->names->keys array to get the key name for a specific keycode:

XEvent Event;
XNextEvent(XDisplay, &Event);

switch (Event.type)
{
case KeyPress:
    /* I'm not sure this 'if' is necessary, but better safe than sorry */
    if ((Event.xkey.keycode >= KbDesc->min_key_code) && (Event.xkey.keycode <= KbDesc->max_key_code))
    {
        /* Copy key name into Name */
        char Name[XkbKeyNameLength + 1];
        memcpy(Name, KbDesc->names->keys[Event.xkey.keycode].name, XkbKeyNameLength);
        Name[XkbKeyNameLength] = '\0';   /* Null terminator */

        if (strcmp(Name, "AD02") == 0)   /* Is it W (for QWERTY and QWERTZ) / Z (for AZERTY) / comma (for DVORAK) / ц (for Russian) etc... ? */
        {
            /* Do something... */
        }
        else if (strcmp(Name, "AE04") == 0)   /* Is it 4 (for most keyboards) / whatever's in its place? */
        {
            /* Do something... */
        }
        /* ... */
    }

    /* ... */
}

And that's it. It seems to work pretty well so far. I'd like to mention that special keys have very different key names. For example, Left Shift is LFSH, Left Control is LCTL, Space is SPCE and Escape is ESC.

I hope it helps.

like image 82
LHLaurini Avatar answered Sep 18 '22 06:09

LHLaurini