Thread: Detect keyboard layout (linux)

  1. #1
    Registered User
    Join Date
    Jan 2016
    Posts
    8

    Detect keyboard layout (linux)

    Hello everyone, I am working on a code which use osg and keyboard input.
    I have implemented an eventHandler in osg which do action on keyboard input, but I want it to be independant of the keyboard layout :
    I use "wasd" key to move with a qwerty keyboard (like in any fps games), but if the user have an azerty keyboard I want to use "zqsd" key.

    My first solution was to use the keyCode instead of keySymbol. It worked well in my computer : the keyCodes are the same if I use a azerty or qwerty layout. But I have read that it depend on the hardware and driver ... So I revert my change and use KeySymbol but I first detect the keyboard layout and change the control key.
    In order to detect the keyboard layout I call the command "setxkbmap -print" and parse the output (the second line contain either "aliases(qwerty)" or "aliases(azerty)".

    I'm not really satisified with this answer because in my (short) experience, system call and parsing output can be source of portability issues and are hard to maintain. Is there any better options ?

    note : the users will most likely have ubuntu 10.04 to 14.04 distribution (64 and 32 bits if that change anything)

    Thanks !
    Last edited by merwyn; 01-12-2016 at 05:03 AM.

  2. #2
    Programming Wraith GReaper's Avatar
    Join Date
    Apr 2009
    Location
    Greece
    Posts
    2,739
    Just an idea: How about sticking with defaults, using the keysyms, and provide a way of remapping them to the user, whenever they deem appropriate. Granted, that may seem a little amateurish, but dealing with every single keyboard layout would be a nightmare.
    Devoted my life to programming...

  3. #3
    Registered User
    Join Date
    Jan 2016
    Posts
    8
    Quote Originally Posted by GReaper View Post
    Just an idea: How about sticking with defaults, using the keysyms, and provide a way of remapping them to the user, whenever they deem appropriate. Granted, that may seem a little amateurish, but dealing with every single keyboard layout would be a nightmare.
    This part of the program is supposed to be a simple viewer, it doesn’t have any menu nor configuration files and it usually displayed for less than 5 minutes. I think it will take too much time to modify it to add a menu for remapping key and saving it, and it have to be really simple for the user. Moreover half of the "target" user have azerty keyboard, the other half qwerty ...

    I only have to care about this 2 layout, and only use 8 keys.

  4. #4
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Do the keys actually conflict? It does not look like they do, to me.

    So, you could simply accept both W and Z for "up", and both A and Q for left (and S for down and D for right).

  5. #5
    Registered User
    Join Date
    Jan 2016
    Posts
    8
    Unfortunaly they conflict, I also use q/e (a/e in azerty). The other key are the space bar and C (the same in both layout).


    EDIT : If this can lead to a better solution, i'll add that I don't need to detect the layout dynamically. I can do that at the beginning of the program or even at compilation time (we use cmake).
    Last edited by merwyn; 01-12-2016 at 08:04 AM.

  6. #6
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    I'd just pick a nonconflicting set of control keys, myself.
    I've always preferred the cursor keys over WASD, anyway.

    If you do have to use those conflicting keys, then the setxkbmap -print option is definitely acceptable. (You can do the equivalent via X11 XKB extensions, but it requires a lot more code than using setxkbmap.) Here's how I'd do it (note, untested!):
    Code:
    typedef enum {
        LAYOUT_UNKNOWN = 0,
        LAYOUT_QWERTY = 1,
        LAYOUT_AZERTY = 2,
    } keyboard_layout;
    
    static keyboard_layout detect_keyboard_layout(void)
    {
        char   *line = NULL;
        size_t  size = 0;
        ssize_t len;
        keyboard_layout result = KEYBOARD_UNKNOWN;
        FILE   *in;
    
        in = popen("LANG=C LC_ALL=C setxkbmap -print", "rb");
        if (!in)
            return LAYOUT_UNKNOWN;
    
        while (1) {
            len = getline(&line, &size, in);
            if (strstr(line, "aliases(qwerty)"))
                result = LAYOUT_QWERTY;
            if (strstr(line, "aliases(azerty)"))
                result = LAYOUT_AZERTY;
        }
    
        free(line);
        pclose(in);
    
        return result;
    }
    When you package your program for distribution, include setxkbmap in the required packages. For Ubuntu, that is the x11-xkb-utils package. Normally it is absolutely required by x.org (the X server), so it should be installed anyway, but there's no harm in specifically listing it.




    Making the program configurable is not that hard, really. For example, if you save the following as example.c,
    Code:
    #define  _POSIX_C_SOURCE 200809L
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #include <X11/Xlib.h>
    #include <X11/keysym.h>
    #include <wordexp.h>
    
    /* Paths where the keyboard configuration is located,
     * later ones overriding previous ones.
    */
    static const char *const config_paths[] = {
        "/etc/yourapp/config",
        "~/.yourapp/config",
        NULL
    };
    
    /* Keyboard controls are defined using
     *  key <config> <string>
    */
    static struct {
        const char *const  config;
        KeySym             keysym;
        KeyCode            keycode;
    } control[] = {
        /* control[0] */ { "up",     XK_W, 0 },
        /* control[1] */ { "down",   XK_S, 0 },
        /* control[2] */ { "left",   XK_A, 0 },
        /* control[3] */ { "right",  XK_S, 0 },
        /* control[4] */ { "foo",    XK_Q, 0 },
        /* control[5] */ { "bar",    XK_E, 0 },
        /* control[6] */ { "space",  XK_space, 0 },
    };
    
    #define CONTROL_COUNT (sizeof control / sizeof control[0])
    
    static char *rtrim(char *const s)
    {
        if (s) {
            char *end = NULL;
            char *cur = s;
            while (*cur)
                if (*cur == '\t' || *cur == '\n' || *cur == '\v' ||
                    *cur == '\f' || *cur == '\r' || *cur == ' ')
                    ++cur;
                else
                    end = ++cur;
            if (end)
                *end = '\0';
        }
        return s;
    }
    
    static char *spaces(char *const s, char *const none)
    {
        if (s) {
            char *c = s;
            while (*c == '\t' || *c == '\n' || *c == '\v' ||
                   *c == '\f' || *c == '\r' || *c == ' ')
                c++;
            return (c > s) ? c : none;
        } else
            return none;
    }
    
    void load_controls(Display *const display)
    {
        char   *line = NULL;
        size_t  size = 0;
        size_t  p, i, k;
        ssize_t n;
    
        for (p = 0; config_paths[p] != NULL; p++) {
            wordexp_t alternatives;
    
            if (wordexp(config_paths[p], &alternatives, WRDE_NOCMD))
                continue;
    
            for (i = 0; i < alternatives.we_wordc; i++) {
                FILE *in;
                char *name, *value, *ends;
    
                in = fopen(alternatives.we_wordv[i], "rb");
                if (!in)
                    continue;
    
                while (1) {
                    n = getline(&line, &size, in);
                    if (n <= 0)
                        break;
    
                    /* Skip leading whitespace. */
                    name = spaces(line, line);
    
                    /* Require "key". */
                    if (!(name[0] == 'K' || name[0] == 'k')) continue;
                    if (!(name[1] == 'E' || name[1] == 'e')) continue;
                    if (!(name[2] == 'Y' || name[2] == 'y')) continue;
    
                    /* Require whitespace. */
                    name = spaces(name + 3, NULL);
                    if (!name) continue;
    
                    /* Find end of name. */
                    ends = name + strcspn(name, "\t\n\v\f\r ");
                    if (ends == name) continue;
    
                    /* Find start of value. */
                    value = spaces(ends, NULL);
                    if (!value) continue;
    
                    /* Terminate name. */
                    *ends = '\0';
    
                    /* Value continues till the end of the line. */
                    value = rtrim(value);
    
                    /* Find if this is a control we know of. */
                    for (k = 0; k < CONTROL_COUNT; k++)
                        if (!strcmp(name, control[k].config)) {
                            KeySym  ks;
    
                            ks = XStringToKeysym(value);
                            if (ks == NoSymbol)
                                continue;
    
                            control[k].keysym = ks;
    
                            if (display)
                                control[k].keycode = XKeysymToKeycode(display, ks);
                        }
                }
                if (ferror(in)) {
                    fprintf(stderr, "Warning: Error reading file '%s'.\n", alternatives.we_wordv[i]);
                    fflush(stderr);
                }
                if (fclose(in)) {
                    fprintf(stderr, "Warning: Error closing file '%s'.\n", alternatives.we_wordv[i]);
                    fflush(stderr);
                }
            }
    
            wordfree(&alternatives);
        }
    
        if (display)
            for (k = 0; k < CONTROL_COUNT; k++)
                if (!control[k].keycode)
                    control[k].keycode = XKeysymToKeycode(display, control[k].keysym);
    
        free(line);
    }
    
    
    
    int main(int argc, char *argv[])
    {
        Display *display;
        size_t   k;
    
        display = XOpenDisplay(NULL);
        if (!display) {
            fprintf(stderr, "No X support.\n");
            return EXIT_FAILURE;
        }
        load_controls(display);
        XCloseDisplay(display);
    
        for (k = 0; k < CONTROL_COUNT; k++)
            printf("'%s' is symbol 0x%lx, keycode 0x%lx\n", control[k].config, (unsigned long)control[k].keysym, (unsigned long)control[k].keycode);
    
        return EXIT_SUCCESS;
    }
    and compile it using e.g.
    Code:
    gcc -Wall -Wextra -O2 $(pkg-config --cflags x11) example.c $(pkg-config --libs x11) -o example
    you'll see how to read the keyboard configuration easily from a configuration file (.yourapp/config in the user's home directory). For example, to change the 'foo' key to the left shift, create the directory .yourapp in your home directory, and create there file config with contents
    key foo Shift_L
    The configuration files are read whenever the program starts, you don't need to recompile it.

  7. #7
    Unregistered User Yarin's Avatar
    Join Date
    Jul 2007
    Posts
    2,158
    Quote Originally Posted by merwyn View Post
    I use "wasd" key to move with a qwerty keyboard (like in any fps games), but if the user have an azerty keyboard I want to use "zqsd" key.
    Require the user to navigate up when the interface is first started. This way if W or Z is pressed first you know which layout they're using.

    Nominal's solution may work, but only in X, and it's complicated.

  8. #8
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    The configuration example should be trivial to modify to use whatever interface you want; it's just an example. I was too lazy to install osg and find out its keyboard support capabilities. There does not seem to be any relevant documentation on the web as to its keycode mapping functions -- at least I didn't find one. That's why (and because OP used 'setxkbmap') I used X11 instead.

    Anyway, Yarin has a very good point. However, combining the two approaches should be even better -- and is what many games do:

    If the program is run with, say, --scan-keys parameter, or there is no per-user configuration file, the program starts with displaying a dialog prompting the user to press each key in turn. The keys are then saved to the per-user configuration file, and used on subsequent runs. (Games usually have a menu with this option, but a command-line parameter is good enough, too.)
    (In this case, you can just save the keycodes or symbols or whatever osg best supports, in binary.)

    I like the dialog to show all the commands (keys it will ask), so that I can decide keys I'd like to use for which purpose before pressing the first one.

  9. #9
    Registered User
    Join Date
    Jan 2016
    Posts
    8
    Thanks for all your answers

    Quote Originally Posted by Nominal Animal View Post
    If you do have to use those conflicting keys, then the setxkbmap -print option is definitely acceptable. (You can do the equivalent via X11 XKB extensions, but it requires a lot more code than using setxkbmap.) Here's how I'd do it (note, untested!):
    That's nearly what I have done, and it work well (at least in my computer, need to test on other). Can you explain why you add "LANG=C LC_ALL=C" in the popen command ? Also what's the difference between mode 'r' and 'rb' ?
    From the documentation :
    The behaviour of popen() is specified for values of mode of r and w. Other modes such as rb and wb might be supported by specific implementations, but these would not be portable features.

    Quote Originally Posted by Nominal Animal
    I was too lazy to install osg and find out its keyboard support capabilities. There does not seem to be any relevant documentation on the web as to its keycode mapping functions -- at least I didn't find one. That's why (and because OP used 'setxkbmap') I used X11 instead.
    OSG scan for keyCode and then call XkbKeycodeToKeysym() to get the keySym, and send this keySym to an eventHandler. I work in the eventHandler so I don't have access to the original keyCode exept if I call XkbKeySymToKeyCode(). As I said, this keycode is independant of my keyboard layout (only depend of the physical position of the key) But it seems that it's depend on the keyboard model and driver.

    This program will only be used in linux, and for ~ 95% in ubuntu, so I guess I'll stick with this method.

  10. #10
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by merwyn View Post
    Can you explain why you add "LANG=C LC_ALL=C" in the popen command?
    Many system commands are localized. For example:
    LANG=fi_FI LC_ALL=fi_FI date
    outputs
    ke 13.1.2016 12.48.08 +0200
    whereas
    LANG=C LC_ALL=C date
    outputs
    Wed Jan 13 12:48:08 EET 2016
    assuming you have the Finnish locale (fi_FI.utf8) installed like I do.

    The C (or POSIX) locale is the untranslated one, so putting LANG=C LC_ALL=C in front of the command (in sh and compatible shells; popen() always uses sh in POSIX systems) makes sure the command provides untranslated output.

    Although setxkbmap -print is currently not translated, it does not mean it won't be in the future. I prefer to make sure I'm not bitten by such.

    Quote Originally Posted by merwyn View Post
    Also what's the difference between mode 'r' and 'rb'?
    Nothing in sane OSes. Feel free to fix that to "r" instead. I just did it from old habit, because Windows popen() uses "r" to mean "do wonky stuff to the input" whereas "rb" means "give me the input as-is".

    Still, I like the idea of using a configuration file to store the KeySyms, and scanning them at the first invocation or whenever a specific command-line flag is used. I really dislike WASD controls, you see, and always remap them to cursor keys (for my right hand) if I can. Not all people like FPS games!

  11. #11
    Unregistered User Yarin's Avatar
    Join Date
    Jul 2007
    Posts
    2,158
    Quote Originally Posted by Nominal Animal View Post
    Nothing in sane OSes.
    Okay, but what about Windows, Linux, and OS X?

  12. #12
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Yarin View Post
    Okay, but what about ..
    Ha ha. But no, I deem OSes that provide C libraries that by default insist on mangling the contents of the files non-sane.

    Old Mac OS (prior to X) used to have really funky files, with resources attached that you couldn't reach just by reading the data. Some of those resources were used by the UI for icons, some where internally used by the application. It works fine if you never transfer files to or from a different OS. If you did.. well, the results were much funkier than you'd imagine.

    Newline mangling is peanuts compared to those issues, although all the various little problems associated with the insane "binary"/"text" file handling differences are like a million papercuts. I'd understand them in a high-level language, but in the base C libraries, it's sheer idiocy.

    (Just before the end of the millenium, I debugged an issue in a mixed PC-Mac lab with printing. Certain jobs printed from Macs via a Linux AppleTalk fileserver would get corrupted, sometimes. Typically if they contained bitmap graphics; text and vector stuff tended to be okay. It turned out to be newline convention related issue -- there was an extra 10 <-> 13 conversion for binary data on the Mac end. Fortunately, there was a workaround.)

    If I need to read text input that is possibly originated in non-POSIXy systems, I always resort to universal newline support (that is, \r\n \n\r \r \n are all considered newlines, and are checked in that order). Anything else is just stabbing yourself in the brain with deformed cacti.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 14
    Last Post: 10-12-2012, 02:39 AM
  2. C keyboard hooks for linux
    By codecaine_21 in forum C Programming
    Replies: 6
    Last Post: 11-10-2010, 09:25 PM
  3. Detecting keyboard and mouse in linux
    By sameer.nalwade in forum C Programming
    Replies: 3
    Last Post: 11-22-2008, 04:24 AM
  4. Get windows Keyboard layout
    By PVoronin in forum Windows Programming
    Replies: 2
    Last Post: 09-19-2007, 04:27 AM
  5. Linux: Send keyboard input to background process
    By xErath in forum Tech Board
    Replies: 2
    Last Post: 12-09-2004, 07:02 PM