XKB Walkthrough
Series of documents to better understand and configure XKB.
- https://www.x.org/wiki/XKB/
- https://en.wikipedia.org/wiki/X_keyboard_extension
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 :
NoSymbol
→ acts as a placeholder if you want to define a later symbolVoidSymbol
→ like a /dev/null symbol : it produces no actions
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 :
+
will override an existing definition|
will augment an existing definition
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