XKB Walkthrough

Series of documents to better understand and configure XKB.

XKB can be both straightforward and intimidating.

In this article are gathered the notes of a personal journey to get a better understanding of what's going on between the moment you press a keyboard key and the resulting action.

The following text is based on few others documentations collected on the web. Thanks to those guys!

It may contain inaccuracies, outdated info or even false statements. Don't hesitate to contribute to extend or correct anything by sending a Pull Request.

XKB?

XKB is referred to as an extension of X Window System. Therefore other strategies to deal with the keyboard inputs, like xmodmap predates it.

The core code source is available on http://cgit.freedesktop.org/xorg/xserver/tree/xkb

The configuration files are located elsewhere, in this canonical place : http://www.freedesktop.org/wiki/Software/XKeyboardConfig/ (associated bug tracker)

xkbcomp is the compiler in charge of turning human readable configuration files to xkb compatible files. The source of this tools is located on http://cgit.freedesktop.org/xorg/app/xkbcomp

On your machine, you may want to explore the content of /usr/share/X11/xkb or ~/.xkb

Pipeline overview

Let's say you have a common QWERTY keyboard in front of you. Now press the physical key labeled W. The mechanical switch will register your gesture and send a signal through the circuit board to the microcontroller hidden inside the plastic box. This piece of circuitry will process this info and pass it to the computer along the USB cable. This event is listened by the operating system's kernel, which itself pass it to X.

At this instant X receives a number, called the scancode. For example on my machine, this number received is 25. Hum… not very useful since I expected to receive a w somehow.

Here's the main purpose of XKB : to act as the translator between those values. Somewhere, the mapping between the scancode 25 and the letter w has to be described :

scancode 25 ⇒ ??? ⇒ w

Keycodes files : from scancodes to keycodes

There's in fact an intermediary representation between the scancodes and symbols called keycodes. This is handy shortcut to manipulate more meaningful names.

So the answer is :

scancode 25 ⇒ <AD02> ⇒ w

Don't see the benefits? At first sight <AD02> seems as arbitrary as 25!

Let's see another example :

scancode 9 ⇒ <ESC> ⇒ Esc

This mapping is more obvious for special keys, isn't it?

Symbols files : from keycodes to symbols

A few different levels of verbosity can be used to improve readability and editing.

Let's start with a big declarative version :

key <AE01> {
  type[group1] = "FOUR_LEVEL", symbols[group1] = [ 1, exclam, doublelowquotemark, exclamdown ],
  type[group2] = "TWO_LEVEL" , symbols[group2] = [ exclam, exclamdown ]
};
key <AE02> {
  type[group1] = "FOUR_LEVEL", symbols[group1] = [ 2, at, leftdoublequotemark, leftsinglequotemark ],
  type[group2] = "TWO_LEVEL" , symbols[group2] = [ parenleft ]
};

Unassigned arrays are considered symbols arrays :

key <AE01> {
  type[group1] = "FOUR_LEVEL", [ 1, exclam, doublelowquotemark, exclamdown ],
  type[group2] = "TWO_LEVEL" , [ exclam, exclamdown ]
};
key <AE02> {
  type[group1] = "FOUR_LEVEL", [ 2, at, leftdoublequotemark, leftsinglequotemark ],
  type[group2] = "TWO_LEVEL" , [ parenleft ]
};

There's a cascading behavior that can be tamed to stay DRY :

key.type[group1] = "FOUR_LEVEL";
key.type[group2] = "TWO_LEVEL";

key <AE01> {[ 1 , exclam , doublelowquotemark  , exclamdown          ],[ exclam , exclamdown ]};
key <AE02> {[ 2 , at     , leftdoublequotemark , leftsinglequotemark ],[ parenleft ]};

Group1, group1 and 1 are synonymous keys :

key.type[1] = "FOUR_LEVEL";
key.type[2] = "TWO_LEVEL";

key <AE01> {[ 1 , exclam , doublelowquotemark  , exclamdown          ],[ exclam , exclamdown ]};
key <AE02> {[ 2 , at     , leftdoublequotemark , leftsinglequotemark ],[ parenleft ]};

Several constants have special meaning :

Let's say you're only interested in defining the group2. You could write something like this :

key <AE01> {[ NoSymbol , NoSymbol , NoSymbol , NoSymbol ],[ exclam ,
exclamdown ]};

All those NoSymbol are a bit cumbersome. What about a more precise target :

key <AE01> { symbols[2] = [ exclam , exclamdown ]};

Even shorter :

key <AE01> { [], [ exclam , exclamdown ]};

include

include "compose(menu)"

For a programmer, this statement looks like calling a compose function with a menu argument. This is not exactly this meaning. It rather means : find the compose file (by default in /usr/share/X11/xkb/symbols/) and get me the map called menu.

Let's have a look at the aforementioned compose file :

…

partial modifier_keys
xkb_symbols "menu" {
  key <MENU> { type[Group1]="TWO_LEVEL", [ Multi_key, Multi_key ] };
};

…

The trick is revealed! To include something is just a shortcut to avoid writing a section already defined. Nothing magic.

If the map is not specified, like in this case :

include "compose"

If there's only one map defined in the compose file, there's no ambiguity. But if this file contains several maps, when xbcomp transforms our human readable configuration into a machine readable one, it issues the following warning :

Warning:          No map in include statement, but "compose" contains several
                  Using first defined map, "ralt"

It's quite self-explanatory : the first map in the file is implicitly selected. If a map is preceded by default, it will have the priority. More info in this commit.

In some case include statements can turn into behemoths, like :

include "file1(map3)+file7+file2(map1)|file1(map2)"

Two separators are involved :

You may stumble on include of evdev files. Have a look at event device to learn more about this.

self include

A block can include other blocks from the same file. Example taken from /usr/share/X11/xkb/symbols/fr dedicated to French symbols:

partial alphanumeric_keys
xkb_symbols "olpc" {

    include "fr(basic)"
…
}

This block refers to the basic block in the same file (which itself includes the latin block):

default  partial alphanumeric_keys
xkb_symbols "basic" {

    include "latin"

    name[Group1]="French";

    key <AE01>  { [ ampersand,          1,  onesuperior,   exclamdown ] };
    key <AE02>  { [    eacute,          2,   asciitilde,    oneeighth ] };
…

Modifiers

The weird keys living on the edge of your keyboards.

Historically, in the core protocol (before XKB) they were only 8 of them :

Shift Lock Ctrl Mod1 Mod2 Mod3 Mod4 Mod5

Which is not very in adequation with modern keyboards. Look under your fingers. I'm pretty sure you have at least a few other ones. Alt and the Windows key possibly on both sides on your space bar. If you're on a mac you'll find a Cmd and a Options keys. On European keyboards, there's even an AltGr key.

On old or exotic keyboards, the family is even broader : Super, Hyper, Meta, Compose… often in Left and Right versions!

To take a group picture, let's run xmodmap. Here's the output on my system:

xmodmap:  up to 4 keys per modifier, (keycodes in parentheses):

shift       Shift_L (0x32),  Shift_R (0x3e)
lock
control     Control_L (0x25),  Control_L (0x42)
mod1        Alt_L (0x40),  Meta_L (0xcd)
mod2        Num_Lock (0x4d)
mod3        Hyper_L (0xcf)
mod4        Super_L (0x64),  Super_L (0x66),  Super_R (0x86),  Super_L (0xce)
mod5        ISO_Level3_Shift (0x5c),  Mode_switch (0xcb