Linux – How to properly change the keyboard mapping

keyboardkeyboard-layoutlinuxx11xorg

I will try to describe the problem in details. Very often I use external keyboard with my netbook. The keyboard is "TK Stealth":


Click to zoom

You can see, that the numpad, is very similar to the classic numpad, but the arrow keys are actually mapped differently – as the additional arrow keys on a wide keyboards.

I want to make them to be mapped as in a numpad, i.e. "8" == "Up", "2" == "Down", "4" == "Left", "6" == "Right" and so on.

These settings must work only if this type of keyboard is attached.

I tried to make this using xmodmap /home/johnfound/TKStelth with the following map file "TKStelth":

keycode  79 = KP_Home KP_Home KP_Home KP_Home
keycode  80 = KP_Up KP_Up KP_Up KP_Up
keycode  81 = KP_Prior KP_Prior KP_Prior KP_Prior
keycode  83 = KP_Left KP_Left KP_Left KP_Left
keycode  84 = KP_Begin KP_Begin KP_Begin KP_Begin
keycode  85 = KP_Right KP_Right KP_Right KP_Right
keycode  87 = KP_End KP_End KP_End KP_End
keycode  88 = KP_Down KP_Down KP_Down KP_Down
keycode  89 = KP_Next KP_Next KP_Next KP_Next
keycode  90 = KP_Insert KP_Insert KP_Insert KP_Insert
keycode  91 = KP_Delete KP_Delete KP_Delete KP_Delete

It works actually, but there are ugly side effects. For example, sometimes the layout is restored to the default and I must run the above script manually. Including the script to the initialization scripts caused some conflicts/locks that made the OS to hang for several minutes after resuming from suspend and changing screen resolutions. This way, I was forced to remove the scripts from the initialization scripts.

I read somewhere that xmodmap is actually the old way to handle keyboard layouts.

So, the question: How to configure Linux to handle this and only this keyboard properly?

Additional information: Manjaro Linux with XFCE. The keyboard is configured with two layouts – US and Bulgarian and they must stay after the above configuration changes.

Best Answer

As long as I found the proper solution, I will answer my own question.

There is a program named keyfuzz that can change the keyboard maps used by the kernel, based on input devices - i.e. separately for every keyboard attached to the computer.

There are two problems with this program that are not described properly in the documentation:

  1. The USB keyboards generate scan codes, other than the scan codes of keyboards attached to ps/2 port. This way, if you need to remap USB keyboard, you will need a way to know the scan codes of the keys. The tool "showkey", usually recommended for testing scancode and keycode will not do the job, because it reads form /dev/console that emits "standard" scan codes, regardless of the keyboard.

In order to test the scan codes of the keyboard based on its /dev/input/KEYBOARD address, you need to use the program named getscancodes. Notice that the downloaded file from the above link is not properly compressed. It is named getscancodes.tar.gz, but is compressed with ZIP algorithm. The package contains the source code as well as precompiled binaries.

In my case the keyfuzz configuration file looks this way:

### evdev 1.0.0., driver 'TK Stealth keyboard'
### Proper old-style numpad handling

0x70059    107
0x7005A    108
0x7005B    109
0x7005C    105
0x7005D    108
0x7005E    106
0x7005F    102
0x70060    103
0x70061    104

0x70062    110
0x70063    111
  1. The program "keyfuzz" starts as a service during the boot, in order to patch the tables as early as possible. Unfortunately, the USB keyboards are added to the devices later, so when the keyfuzz starts, there is no keyboard to be patched, even if the USB keyboard is attached during the boot.

The solution is to use udev rules files and to start keyfuzz on adding the needed keyboard.

In order to do it, you need to add a file /etc/udev/rules.d/mykeyboard.rules, containing (in my case):

ACTION=="add", ATTRS{idVendor}=="2516", RUN+="/usr/lib/systemd/scripts/keyfuzz start"

Now, after plugging the keyboard, the keyfuzz start script will start and patch the keyboard decoding tables.