Reconfiguring Keyboard Keys with XKB¶
Updated: May 07, 2024
Recently two keys in my laptop keyboard went off, none of my effort successfully bought back those two keys. So, I was thinking what should I do now, here are the list of things I came up
Replace the keyboard.
Add the missing keys to one already working key as
Level3
key.
The first one is what we normally do, eventually, I will also going to do if I have chance to buy replacement for my laptop keyboard. In the mean time, I need to have a temporary workaround. The second one will make my keyboard loose two other keys instead of the already lost keys. So, I opted for third one.
Scan Codes¶
When you press and release a key in your keyboard, it generates scan codes [2]. Usually these are two bytes, the first byte gets generated when you press and the second byte get generated when you release flipping the most significant bit. For example, when you press and release ESC
key, it generates 0x01 0x81
bytes, the first byte is 000000001
which got generated when you pressed the ESC
key, the second byte is 10000001
which got generated when you released ESC
key. You can get the scan codes of each key through showkey -s [3] command.
Kernel Keycodes¶
When kernel [4] receives these scan codes
from keyboard event, the input driver converts these scan codes to kernel keycodes [5]. These keycodes are defined under /usr/include/linux/input-event-codes.h
headers. Kernel maintains one scancode-to-keycode table, you can map any scan-code to any keycode using setkeycodes [6] command. To view the current scancode-to-keycode table, you can use getkeycodes [7] command.
Console Keymaps¶
The kernel’s scancode-to-keycode table is not enough for the tty
driver, so it defines another table called keymaps [8]. Apart from the normal keys, there are control keys in our keyboard. This keymaps
table defines what should happen when a control key and normal key pressed. we can use dumpkeys [9] command to see the current keymaps
table and we can use loadkeys [10] command to load a new keymaps table (these keymaps table definitions are available under /usr/share/keymaps
directory). Here is one simple example to to load custom keymap
$ # this commands are run from tty3 (you need to press ctrl+alt+f3 to switch to tty3)
$ gzip /usr/share/kbd/keymaps/i386/qwerty/us.map.gz | sed '/ 51 =/s/comma/less/g;/ 51 =/s/less/comma/g' | sudo tee /usr/share/kbd/keymaps/i386/qwerty/test.map
$ echo "compose 'h' 'a' to U+0B95" >> /usr/share/keymaps/i386/querty/test.map
$ loadkeys test
$ # if you press , key now, it will come as < key. if you press shift + , keys (< key) it will come as , key.
$ # if you press left-alt + right-alt, compose will be activated.
$ # With compose activated, if you press 'h' and 'a', then tty driver will generate க
$ # (unicode = 0B95, utf-8 = 0xe0 0xae 0x95) in the console.
$ loadkeys us
$ # switched back to default us keymap
Most of Keymaps
table contains “ <keycode> = <level0 keysym> <level1 keysym> … <level256 keysym> “ lines. Here <keycode>
represents the kernel keycodes
from scancode-to-keycodes table, <level0 keysym>
means what character to use when that particular keycode key got pressed, <level1 keysym>
means what character to use when Shift
modifier is active and keycode key got pressed. Shift modifier will get activated when you press Shift
key. There are 9 modifier keys available and each modifier carries different weight,
Shift (weight = 1)
AltGr (weight = 2)
Control (weight = 4)
Alt (weight = 8)
ShiftL (weight = 16)
ShiftR (weight = 32)
CtrlL (weight = 64)
CtrlR (weight = 128)
CapsShift (weight = 256)
With the help of these modifiers, we can choose the level
, the sum of weight of all the activated modifiers decides the level. For example, lets assume the current activated keymap have a line like this,
keycode 30 = a b c d e f g h i j k l m n o p q r s t u v w x y z
When we press Control + Shift + a
, then the level will be 5
(weight of Control
is 4
, weight of Shift
is 1
, so the sum of weight of currently activated modifiers is 5
, so the current level is 5
), thus, f
(which is in the level5
position) is the final keysym
, so f
will be shown in the console.
When we simply press a
, then the level will be 0
(there is no modifiers currently active, so the current level is 0), thus a
(which is in the level0
position) is the final keysym
, so a
will be shown in the console.
Note
the command dumpkeys -f
will not show all the levels of a particular keycode, the first line of the output may say keymaps 0-2,4-6,8-9,12
which means, the output contains only the levels level0
, level1
, level2
, level4
, level5
, level6
, level8
, level9
, level12
. So make sure you interpret the dumpkeys -f
output correctly.
Compose Key¶
Apart from keycode assignment, you can add compose key sequences in keymaps
, For example, lets assume the current activated keymap have a line like this,
compose 'h' 'a' to U+0B95
When we press leftalt + rightalt
, the compose modifier gets activated (Alt + AltGr
which is the default key combo to activate compose). After the compose modifier activated, if you press ‘h’ key and ‘a’ key, then you will get ‘க’ in the console.
XKB¶
XKB [11] is another table similar to keymaps
but for Xorg [12] and also used by Wayland [13]. XKB
does not use kernel keycodes
directly like keymaps
table, but converts the kernel keycodes
to xkeycodes
(<kernel-keycode> + 8 = <xkeycode>). Again, these xkeycodes
are mapped into xkeys
. These xkeycodes
to xkeys
mapping is defined under /usr/share/X11/xkb/keycodes
directory. These xkeys
are then used in symbol tables. These symbol tables are available under /usr/share/X11/xkb/symbols
directory. Each symbol table contains lines similar to this,
// example symbol table saved as /usr/share/X11/xkb/symbols/demo
xkb_symbols "demo" {
key <AC01> {
type[Group1] = "TWO_LEVEL",
type[Group2] = "FOUR_LEVEL",
symbols[Group1] = [ 'a', 'A' ],
symbols[Group2] = [ 'a', 'A', 'b', 'B' ]
};
include "level3(ralt_switch)"
include "group(win_space_toggle)"
include "compose(menu_altgr)"
}
XKB
contains 8 groups
and 256 levels
for each group, so you can load 2048 keysyms
on a single xkey
. The above symbol table only defines Two Groups, The first Group Group1
type is TWO_LEVEL
, which means, it has only Level1
and Level2
.
Note
XKB
levels starts from level1
instead of level0
like in keymaps table
. When there is no modifiers currently active, XKB
chooses level1 keysym
but keymaps table
chooses level0 keysym
.
The second Group Group2
have FOUR_LEVEL
, means, Level1
, Level2
, Level3
, Level4
. So, the above table totally loads 6 keysyms
to <AC01> xkey
(‘A’ key in keyboard).
By default, Group1
will be active, so, when we press ‘a’ key in keyboard, Level1
will be selected and the keysym a
will be used, because there is no modifier currently active.
When we press Shift + a
key in keyboard, Level2
will be selected and the keysym A
will be used, this is because we used type[Group1] = "TWO_LEVEL"
, this TWO_LEVEL
type defines which modifier enables which Level. These type definitions are inside /usr/share/X11/xkb/types
directory. If you look at /usr/share/X11/xkb/types/basic
file, for TWO_LEVEL
, you can see that map[Shift] = Level2
line, which means, Shift
modifier will enable Level2
.
To use the keysyms
in Group2
, we have to switch to Group2
by pressing Win + Space
keys (The above symbol table includes group(win_space_toggle)
, the definition of win_space_toggle
is in /usr/share/X11/xkb/symbols/group
file). Assume that you successfully switched to Group2
.
Now, when you press RightAlt + a
key in keyboard, Level3
will be selected and keysym b
will be used. because we used type[Group2] = "FOUR_LEVEL"
, this FOUR_LEVEL
type definition is inside /usr/share/X11/xkb/types/extra
file. If you look at this file, for FOUR_LEVEL
, you can see map[LevelThree] = Level3
, which means, LevelThree
modifier will enable Level3
. Also, you can see map[Shift+LevelThree] = level4
, which means, Pressing both Shift
modifier and LevelThree
modifier will enable Level4
. The above symbol table includes level3(ralt_switch)
, the definition of ralt_switch
is in /usr/share/X11/xkb/symbols/level3
file. If you look at that definition, it will say that <RALT> will generate ISO_Level3_Shift
keysym. This ISO_Level3_Shift
keysym interpretation is defined in /usr/share/X11/xkb/compat/iso9995
file, In this file, you can see that ISO_Level3_Shift
sets Modifier=LevelThree
.
When you press Shift + RightAlt + a
key in keyboard, Level4
will be selected and keysym B
will be used. We already know from the previous paragraph that FOUR_LEVEL
type defines Shift + RightAlt
selects Level4
level.
Multi Key¶
Just like keymaps table's Compose Key
, XKB
also have facility to Compose multiple keys to generate a single keysym
. We have to first activate Compose
modifier by pressing Menu + AltGr
keys, because in the above table, we used compose(menu_altgr)
, the definition of menu_altgr
is inside /usr/share/X11/xkb/symbols/compose
file. If you look at the definition, MENU
Key with AltGr
sets Multi_key
modifier. This modifier enables Compose
facility. The key combinations for Compose
are under /usr/share/X11/locale/<locale>/Compose
file, here <locale> is the LANG
code. If you look at Compose file for en_US.UTF-8 (/usr/share/X11/locale/en_US.UTF-8/Compose
file), you can see that pressing o
and c
will produce ©
character. So, Pressing MENU + Altgr o c
will produce ©
unicode character.
Modifications for my broken Keyboard¶
After learning all these things, I finally have this override saved in /usr/share/X11/xkb/symbols/local
file.
partial alphanumeric_keys modifier_keys
xkb_symbols "override" {
key <I151> {
type[Group1] = "TWO_LEVEL",
symbols[Group1] = [ ISO_Level3_Latch ]
};
key <AB03> {
type[Group1] = "THREE_LEVEL",
symbols[Group1] = [ c, C, Multi_key ]
};
key <AC06> {
type[Group1] = "THREE_LEVEL",
symbols[Group1] = [ h, H, Left ]
};
key <AC07> {
type[Group1] = "THREE_LEVEL",
symbols[Group1] = [ j, J, Down ]
};
key <AC08> {
type[Group1] = "THREE_LEVEL",
symbols[Group1] = [ k, K, Up ]
};
key <AC09> {
type[Group1] = "THREE_LEVEL",
symbols[Group1] = [ l, L, Right ]
};
key <AC10> {
type[Group1] = "FOUR_LEVEL",
symbols[Group1] = [ semicolon, colon, apostrophe, quotedbl ]
};
};
The meaning of these overrides are as follows
Pressing
Fn
activatesISO_Level3_Latch
,Latch
means, you don’t have to keep on pressing the key, one press is enough. So, once I press and releaseFn
key,LevelThree
modifier will be active.Pressing
Fn c
activatesMulti_key
.Fn h
will generate Left Arrow keysym.Fn j
will generate Down Arrow keysym.Fn k
will generate Up Arrow keysym.Fn l
will generate Right Arrow keysym.Fn ;
will generateapostrophe
keysym (single quote).Fn Shift + ;
will generatequotedbl
keysym (double quote).
I have to enable my override with the following steps,
add local:override to /usr/share/X11/xkb/rules/evdev under !option
enable ‘local:override’ to ‘xkb-options’ under gsettings
$ gsettings set org.gnome.desktop.input-sources xkb-options "['local:override']"
There are few tools which helps to write XKB
configuration files, Here are the few ones which I know
- setxkbmap [14]:
Used to compile xkb configuration files and generate data to load into Xorg Server process
- xkbcomp [15]:
Used to get set xkb configuration data from/to Xorg Server process
- xev [16]:
Used to show
xkey
codes andxkeysyms
when we press any in keyboard- xkbcli [17]:
Used to show details about each key press in keyboard (like
xev
)- evtest [18]:
Used to attach with input device and show events from that device
- libinput [19]:
Used to attach with input device and show events from that input device
Drawback of XKB¶
All the custom modification works as long as we are working locally, if we try to use any remote application, or a VM, our XKB
customization will not work, because those remote apps directly sends scan-codes to the remote location rather than the generated XKB keysyms.