Friday, September 12, 2008

RMLVO keyboard configuration

This article explains how different keyboard layouts are selected these days. RMLVO stands for rules, models, layouts, variants and options.

  • Rules are simply sets of combinations that result in - hopefully - useful keyboards. Rules combine MLVO to a full keyboard description.
  • Models describe the physical keyboard, both in geometry and in keycodes (see below).
  • Layouts describe the language dependent literals.
  • Variants are variations of or additions to layouts to cover larger or different key ranges.
  • Options are extras added, independent of the keyboard layout, such as special key behaviours and control keys.

RMLVO is the preferred way of selecting keyboard layouts, as it simplifies it for the user by combining meaningful defaults that are then converted into XKB's internal form of keycodes, types, compat, symbols and geometry (Ktcsg). In this article, I will ignore Ktcsg as much as possible, which will leave some questions unanswered. A follow-up article on XKB's internal form will come in the hopefully near future.
The commandline interface to RMLVO configuration is setxkbmap. Here's an example of how setxkbmap converts RMVLO into Ktcsg:

whot@possum:~$> setxkbmap -print -rules evdev -model evdev -layout us \
-variant intl -option compose:caps
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us(intl)+inet(evdev)+level3(ralt_switch_for_alts_toggle)+group(alts_toggle)+group(alt_shift_toggle)+compose(caps)" };
xkb_geometry { include "pc(pc104)" };

This basic description is then passed to xkbcomp which compiles the full keyboard description and loads it into the server. For this article, I will only explain how setxkbmap achieves the above description from the commandline options. I will also ignore geometry, which describes the keyboard but does not have any effect on its actual working. All you need to know for now is that geometry describes each physical key size and their relationship to each other, so you could paint a funky graphic of your keyboard.

Let's go through the other stuff:

First an important distinction: We have scancodes, Keycodes and Keysyms. Scancodes are what the physical keyboard provides. They don't change.
Keycodes are standardised 3-4 letter descriptions of key meanings (e.g. ESC for Escape, but also AD01 for "first key in second key row", usually the q-key). Keycodes have no language-dependency. Keysym is the symbolic representation of a letter in a particular language (e.g. 'A').

Key to understanding the inner works of keyboard support in X is accepting that those three need to be mapped against each other. For example, if you hit the Q key, the keyboard may generate a scancode of 24, mapped to AD01, mapped to "q". A keyboard event contains the keycode, but a client, you *never never* think in keycodes or scancodes. You always think in keysyms, and use the scan/keycode as a method of getting there. Why? Modifiers. If you press shift, you still get the same scan/keycode when hitting a key (just test this with xev). The keysym however is different - usually the capital form of the respective letter. A keycode/scancode itself has no meaning, it only gets it in combination with the current keyboard symbol table and the modifier state. And both modifier state and keyboard symbol table may change at any time.

Back to our RMLVO configuration: setxkbmap uses libxkbfile to parse /usr/share/X11/xkb/ (package xkeyboard-config) and combine the files there into a meaningful selection.

setxkbmap -print [args]

applies the given args to the rule set and prints the output to the screen. If you leave out -print, you'll load the new configuration in the server too.

The default ruleset used to be xfree86 or xorg (a symlink to base). For evdev, the ruleset is now "evdev".
Let's look at /usr/share/X11/xkb/rules/xorg. In the first part of the file, we see a number of model definitions, these all contribute to the geometry and the symbols generated. Ignoring most of the top of the rules/xorg file which consists of variable declarations, the interesting part starts at the following line:
! model  = keycodes
i.e. which model maps to which keycodes.
The one that is to be used most commonly these days is evdev, i.e. the line

! model = keycodes
evdev = evdev

tells us that for the model evdev, the keycodes "evdev" are to be used. OLPC also uses evdev, and the xfree86 keycode range is the fallback. Note that if you're using the *rules* evdev, you don't need to specify the *model* evdev, it's implied (and you thought XKB was confusing, eh?).

The full tables of scancode - keycode mapping are in /usr/share/X11/xkb/keycodes/, where the file name is the model specified. In the evdev model, scancode 24 maps to AD01, the key next to TAB. Evdev is a bit special here, as the kernel takes care of the physical differences of the keyboards, providing a uniform scancode range for all keyboards. In the xfree86 keycode table, the scancodes are the actual physical codes.

So now we have our device-dependent scancodes mapped to device-independent keycodes. What do we do with keycodes? They are mapped to the language-specific literals that we eventually want to see on the screen when we hit a key.

This is specified further down the rules/xorg file:
!model layout variant = symbols

This table lists which symbol tables are to be loaded if a specific model, layout and variant has been selected. Layout is basically the language, variants can be e.g. intl or nodeadkeys. There's another table like that, but just mapping model and layout to symbols, without variants.
US keyboards are covered by a fallback in the ML->S mapping table.

!model layout = symbols
* * = pc+%l%(v)

Following this fallback line, the commands

setxkbmap -print -layout us
setxkbmap -print -layout us -variant intl

result in pc+us and pc+us(intl), respectively.

The actual symbol mappings are in /usr/share/X11/xkb/symbols/. Let's have a look at the symbol table for AD01 (the key next to TAB) in the US layout (/usr/share/X11/xkb/symbols/us):

key <AD01> { [ q, Q ] }

It says that the literals q or Q are provided by this keycode. Which one, depends on the Shift level. We can have up to 8 levels per key, each level is triggered by one or more modifiers. Most commonly, Level2 is triggered by Shift and/or CapsLock but that's Ktscg territory so run away as fast as you can.
I didn't discuss options here either because they are just more of the same and only become really important when we discuss Ktcsg.

That's basically it. We know how by going through the various files we can assemble a basic description of how the keyboard should map the physical scancode into a literal keysym. If we can accept that xkbcomp parses the files appropriately to get *full* scancode-keycode mapping, and a *full* keycode-keysym mapping, we now know how a little command-line invocation smartly combines to the correct layout.

As said before, setxkbmap -print shows the current settings, alternatively setxkbmap with parameters changes them

whot@possum:~$> setxkbmap -print -layout us -option "" -variant intl -model evdev
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us(intl)+inet(evdev)" };
xkb_geometry { include "pc(pc104)" };

You don't have to specify everything all the time, setxkbmap knows your current setting through the _XKB_RULES_NAMES property (xprop -root | grep XKB). So a command of
setxkbmap -layout de
is enough to switch to german layout.

Let's sum up:

XKB keyboard configuration is provided by xkeyboard-config which provides a set of Rules. Rules state that how certain Models, Layouts and Variants are combined into XKB's internal Ktscg format. Once we have this combination of Ktcsg, we can submit it to xkbcomp, which compiles the full keyboard description and loads it into the server. This however, will be another article.

Thanks to Daniel Stone and Timo Aaltonen for their feedback.