[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Multiple keyboard layouts on the Linux console



Hi.

Some time ago, I explained here how to use different keyboard layouts
with X11.

https://lists.debian.org/debian-user/2020/02/msg00755.html

Now I will explain how to do it with the Linux console. Unfortunately,
it also relies on a non-standard tool.

The use-case I will take as an example is this: imagine there is on your
desktop a keyboard with fancy extra keys, including a "power" key in the
corner; and that you have a cat.

(We could set "HandlePowerKey=ignore" in /etc/systemd/logind.conf, but
that would also prevent from using the button on the box itself. The cat
does not walk on this one.) (This is an example, what I explain can work
for other kinds of keyboards.)

First, a disappointment: the standard way of setting the keyboard layout
for the Linux console, loadkeys, is global. It cannot handle several
keyboards with different layouts.

Fortunately, there is another layer of conversion: before the console
converts key codes to characters or functions, the keyboard driver
converts device-dependant scan codes into device-independent key codes.
We can tweak that.

Unfortunately, it seems no tool exists to call the kernel interfaces
that allow to change the scan code to key code conversion. I have
written my own, see the bottom of this mail.

First, we need to find out which device corresponds to the keyboard;
worse: to the key, because some keyboards create several devices, with
some keys going to one and some other keys going to another.

So, try, with root privileges:

xxd /dev/input/eventX

for various values of X (you can use other dump tools than xxd of
course) and press the key. The correct device is the one that causes
data to appear each time the key is pressed. Let us say it is
/dev/input/event6.

Next, let us find the default layout:

sudo /usr/local/sbin/evdev_map -d /dev/input/event6 -p
...
  569 000c023b       0xf0 (UNKNOWN)
  570 000c023c       0xf0 (UNKNOWN)
  571 00010081       0x74 (POWER)
  572 00010082       0x8e (SLEEP)
  573 00010083       0x8f (WAKEUP)
...

The offending key seems to have scan code 00010081. Let us try to remap
it:

sudo /usr/local/sbin/evdev_map -d /dev/input/event6 -s 00010081=A

if pressing the key now produces a 'a', we have won. Otherwise, maybe
there is another scan code mapped to the same key code, keyboards often
declare way more scan codes than they have.

No, we only need to automate it, using for example 0x0 to disable the
key ("0" would mean the char '0'). It is a job for udev:

UBSYSTEM=="input", ACTION=="add|change", \
  ATTRS{name}=="USB-compliant keyboard System Control", \
  RUN+="/usr/local/sbin/evdev_map -d $devnode -s 00010081=0x0"

(I will not develop here how to use udevadm info -a -p
/sys/class/input/event6 to get the conditions that allow to identify
this keyboard over others.)

If the purpose is to change the layout in a significant way, it may be
more complex. For example, azerty's key [1] yields '&' unshifted and '1'
shifted. To handle that, you would have to find an unused key code, and
then use loadkeys table to map it.

Hope this helps somebody.

Regards,

-- 
  Nicolas George


----8<----8<----8<----8<---- evdev_map.c ---->8---->8---->8---->8----



/*
 * evdev_map -- manipulate evdev keycode tables
 *
 * Nicolas George, 2020-08-03
 * Public domain
 */

/*
   Building:

   First, generate the table of key names:

   gcc -E -dM -x c - <<<'#include <linux/input-event-codes.h>' |
   perl -ne \
    'if (/^\#define (KEY_(\w+))\s+\S+/) { print "  { $1, \"$2\" },\n" }' \
    > /tmp/key_names.h

   Then:

   c99 -Wall -Wextra -D_XOPEN_SOURCE=600 -g -O2 -o evdev_map evdev_map.c

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h>

#if __BYTE_ORDER == __LITTLE_ENDIAN
# define REVERSE_SCANCODE 1
#endif

typedef struct Key_name {
    unsigned code;
    const char *name;
} Key_name;

static const Key_name key_names[] = {
#include "/tmp/key_names.h"
};

static void
scancode_to_string(char *out, __u8 *code, int len)
{
#ifdef REVERSE_SCANCODE
    int ic = len - 1, id = -1;
#else
    int ic = 0, id = +1;
#endif
    while (len-- > 0) {
        snprintf(out, 3, "%02x", code[ic]);
        ic += id;
        out += 2;
    }
}

static void
check_device(int dev)
{
    if (dev < 0) {
        fprintf(stderr, "No device opened\n");
        exit(1);
    }
}

static const char *
get_key_by_code(unsigned code)
{
    size_t i;

    for (i = 0; i < sizeof(key_names) / sizeof(*key_names); i++)
        if (key_names[i].code == code)
            return key_names[i].name;
    return NULL;
}

static unsigned
get_key_by_name(const char *name)
{
    size_t i;
    unsigned code, off = 0;

    for (i = 0; i < sizeof(key_names) / sizeof(*key_names); i++)
        if (strcmp(key_names[i].name, name) == 0)
            return key_names[i].code;
    sscanf(name, "%i%n", &code, &off);
    if (off == 0 || name[off] != 0) {
        fprintf(stderr, "Unknown key: %s\n", name);
        exit(1);
    }
    return code;
}

static void
print_keymap(int dev)
{
    struct input_keymap_entry ke;
    char scancode[sizeof(ke.scancode) * 2 + 1];
    const char *name;
    unsigned i;
    int ret;

    check_device(dev);
    for (i = 0; i < 0x10000; i++) {
        ke.index = i;
        ke.flags = INPUT_KEYMAP_BY_INDEX;
        ret = ioctl(dev, EVIOCGKEYCODE_V2, &ke);
        if (ret < 0) {
            if (errno == EINVAL)
                break;
            perror("ioctl(EVIOCGKEYCODE_V2)");
            exit(1);
        }
        if (ke.index != i) {
            fprintf(stderr, "Inconsistency detected: index: %d != %d\n",
                ke.index, i);
            exit(1);
        }
        if (ke.len > sizeof(ke.scancode)) {
            fprintf(stderr, "Inconsistency detected: len: %d > %zd\n",
                ke.len, sizeof(ke.scancode));
            exit(1);
        }
        scancode_to_string(scancode, ke.scancode, ke.len);
        name = get_key_by_code(ke.keycode);
        printf("%5d %s %#10x %s\n", ke.index, scancode, ke.keycode,
            name == NULL ? "?" : name);
    }
    fflush(stdout);
}

static void
set_keycode(int dev, const char *def)
{
    struct input_keymap_entry ke;
    const char *sep;
    int ic, id, off, ret;
    unsigned c, i;

    check_device(dev);
    sep = strchr(def, '=');
    if (sep == NULL ||
        (size_t)(sep - def) > 2 * sizeof(ke.scancode) ||
        (sep - def) % 2 != 0) {
        fprintf(stderr, "Invalid definition: %s\n", def);
        exit(1);
    }
    ke.len = (sep - def) / 2;
#ifdef REVERSE_SCANCODE
    ic = ke.len - 1;
    id = -1;
#else
    ic = 0;
    id = +1;
#endif
    for (i = 0; i < ke.len; i++) {
        off = 0;
        sscanf(def + i * 2, "%02x%n", &c, &off);
        if (off != 2) {
            fprintf(stderr, "Invalid scancode: %s\n", def);
            exit(1);
        }
        ke.scancode[ic] = c;
        ic += id;
    }
    ke.keycode = get_key_by_name(sep + 1);
    ret = ioctl(dev, EVIOCSKEYCODE_V2, &ke);
    if (ret < 0) {
        perror("ioctl(EVIOCSKEYCODE_V2)");
        exit(1);
    }
}

static void
usage(int ret)
{
    FILE *out = ret ? stderr : stdout;

    fprintf(out,
        "evdev_map -- manipulate evdev keycode tables\n"
        "Usage: evdev_map -d device [-p] [-s scancode=keycode]\n"
        "\n"
        "    -d device            select the input device\n"
        "    -p                   print the current map\n"
        "                         columns: index scancode keycode key_name\n"
        "    -s scancode=keycode  change the mapping for a scancode\n"
        "                         (key names work too; use 0x0 for RESERVED)\n"
        "    -h                   print this message\n"
        "Options are processed in order and can be repeated.\n"
        );
    fflush(out);
    if (ret)
        exit(ret);
}

int
main(int argc, char **argv)
{
    int dev = -1, opt;

    while ((opt = getopt(argc, argv, "d:ps:h")) >= 0) {
        switch (opt) {

            case 'd':
                if (dev >= 0)
                    close(dev);
                dev = open(optarg, O_RDONLY);
                if (dev < 0) {
                    perror(optarg);
                    exit(1);
                }
                break;

            case 'p':
                print_keymap(dev);
                break;

            case 's':
                set_keycode(dev, optarg);
                break;

            case 'h':
                usage(0);
                break;

            default:
                usage(1);
                break;
                
        }
    }
    if (optind < argc)
        usage(1);
    return 0;
}

Attachment: signature.asc
Description: PGP signature


Reply to: