Extending the X keyboard map with xkb

4721
Article Source madduck
April 19, 2009, 8:41 am

xmodmap has long been the only way to modify the keyboard map of the X server, short of the complex configuration daemon approaches used by the large desktop managers, like KDE and GNOME. But it has always been a hack: it modifies the X keyboard map and thus requires a baseline to work from, kind of like a patch needs the correct context to be applicable.

Worse yet, xmodmap weirdness required me to invoke it twice to get the effect I wanted.

When the recent upgrade to X.org 7.4 broke larger parts of my elaborate xmodmap configuration, I took the time to finally ditch xmodmap and implement my modifications as proper xkb configuration.

Background information

I had tried before to use per-user xkb configuration, but could not find the answers I want. It was somewhat by chance that I found Doug Palmer’s “Unreliable Guide to XKB configuration” at the same time that Julien Cristau and Matthew W. S. Bell provided me the necessary hints on the #xorg/irc.freenode.org IRC channel to get me started.

The other resource worth mentioning is Ivan Pascal’s collection of XKB documents, which were instrumental in my gaining an understanding of xkb.

And just as I am writing this document, Debian’s X Strike Force have published their Input Hotplug Guide, which is a nice complement to this very document you are reading right now, since it focuses on auto-configuration of xkb with HAL. The default xkb configuration comes with a lot of flexibility, and often you don’t need anything else.

But when you do, then this is how to do it:

Installing a new keyboard map

The most basic way to install a new keyboard map is using xkbcomp, which can also be used to dump the currently installed map into a file. So, to get a bit of an idea of what we’ll be dealing with, please run the following commands:

xkbcomp $DISPLAY xkb.dump
editor xkb.dump
xkbcomp xkb.dump $DISPLAY

The file is complex and large, and it completely went against my aesthetics to simply edit it to have xkb work according to my needs. I sought a way in which I could use as much as possible of the default configuration, and only place self-contained additional snippets in place to do the things I wanted done differently.

setxkbmap and rule files

Thus began my voyage into the domain of rule files. But before we dive into those, let’s take a look at setxkbmap. Despite the trivial invocation of e.g. setxkbmap us to install a standard US-American keyboard map, the command also takes arguments. More specifically, it allows you to specify the following high-level parameters, which determine the sequence of events between key press and an application receiving a KeyPress event:

  • Model: the keyboard model, which defines which keys are where
  • Layout: the keyboard layout, which defines what the keys actually are
  • Variant: slight variantions in the layout
  • Options: configurable aspects of keyboard features and possibilities

Thus, with the following command line, I would select a US layout with international (dead) keys for my Thinkpad keyboard, and switch to an alternate symbol group with the windows keys (more on that later):

setxkbmap -model thinkpad -layout us -variant intl -option grp:win_switch

In many cases, between all combinations of the aforementioned parameters, this is all you ever need.

But I wanted more.

If you append -print to the above command, it will print the keymap it would install, rather than installing it:

% setxkbmap -model thinkpad -layout us -variant intl -option grp:win_switch -print
xkb_keymap {
xkb_keycodes { include "evdev+aliases(qwerty)" };
xkb_types { include "complete" };
xkb_compat { include "complete" };
xkb_symbols { include "pc+us(intl)+inet(evdev)+group(win_switch)" };
xkb_geometry { include "thinkpad(us)" };
};

There are two things to note:

  1. The -option grp:win_switch argument has been turned into an additional include group(win_switch) on the xkb_symbols line, just like the model, layout, and variant are responsible for other aspects in the output.

  2. The output seems related to what xkbcomp dumped into the xkb.dump file we created earlier. Upon closer inspection, it turns out that the dump file is simply a pre-processed version of the keyboard map, with include instructions exploded.

At this point, it became clear to me that this was the correct way forward, and I started to investigate those points in order.

The translation from parameters to an xkb_keymap stanza by setxkbmap is actually governed by a rule file. A rule is nothing more than a set of criteria, and what setxkbmap should do in case they all match. On a Debian system, you can find this file in /usr/share/X11/xkb/rules/evdev, and /usr/share/X11/xkb/rules/evdev.lst is a listing of all available parameter values.

The xkb_symbols include line in the above xkb_keymap output is the result of the following rules in the first file, which setxkbmap had matched (from top to bottom) and processed:

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

! model = symbols
* = +inet(evdev)

! option = symbols
[...]
grp:win_switch = +group(win_switch)

It should now not be hard to deduce the xkb_symbols include line quoted above, starting from the setxkbmap command line. I’ll reproduce both for you for convenience:

setxkbmap -model thinkpad -layout us -variant intl -option grp:win_switch
xkb_symbols { include "pc+us(intl)+inet(evdev)+group(win_switch)" };

A short note about the syntax here: group(win_switch) in the symbols column simply references the xkb_symbols stanza named win_switch in the symbols file group (/usr/share/X11/xkb/symbols/group).

Thus, the rules file maps parameters to sets of snippets to include, and the output of setxkbmap applies those rules to create the xkb_keymap output, to be processed by xkbcomp (which setxkbmap invokes implicitly, unless the -print argument was given on invocation).

It seems that for a criteria (option, model, layout, …) to be honoured, it has to appear in the corresponding listing file, evdev.lst in this case. There is also evdev.xml, but I couldn’t figure out its role.

Attaching symbols to keys

I ended up creating a symbols file of reasonable size, which I won’t discuss here. Instead, let’s solve the following two tasks for the purpose of this document:

  1. Make the Win-Hyphen key combination generate an “en” dash (–), and Win-Shift-Hyphen an “em” dash (—).

  2. Let the Caps Lock key generate Mod4, which can be used e.g. to control the window manager.

To approach these two tasks, let’s create a symbols file in ~/.xkb/symbols/xkbtest and add two stanzas to it:

partial alphanumeric_keys
xkb_symbols "dashes" {
key {
symbols[Group2] = [ endash, emdash ]
};
};

partial modifier_keys
xkb_symbols "caps_mod4" {
replace key {
[ VoidSymbol, VoidSymbol ]
};
modifier_map Mod4 { };
};

Now let me explain these in turn:

  1. We used the option grp:win_switch earlier, which told xkb that we would like to use the windows keys to switch to group 2. In the custom symbols file, we now simply define the symbols to be generated for each key, when the second group has been selected.

    Key is the hyphen key. To find out the names of all the other keys on your keyboard, you can use the following command:

    xkbprint -label name $DISPLAY - | gv -orientation=seascape -

    I had to declare the stanza partial because it is not a complete keyboard map, but can only be used to augment/modify other maps. I also declared it alphanumeric_keys to tell xkb that I would be modifying alphanumeric keys inside it. If I also wanted to change modifier keys, I would also specify modifier_keys.

    The rest should be straight-forward. You can get the names of available symbols from keysymdef.h (/usr/include/X11/keysymdef.h on a Debian system, package x11proto-core-dev), stripping the XK_ prefix.

  2. The second stanza replaces the Caps Lock key definition and prevents it from generating symbols (VoidSymbol).

    The important aspect of the second stanza is the modifier_map instruction, which causes the key to generate the Mod4 modifier event, which I can later use to bind key combinations for my window manager (awesome).

The easiest way to verify those changes is to put the setxkbmap -print output of the keyboard map you would like to use as a baseline into ~/.xkb/keymap/xkbtest, and append snippets to be included to the xkb_symbols line, e.g.:

"pc+us(intl)+inet(evdev)+group(win_switch)+xkbtest(dashes)+xkbtest(caps_mod4)"

When you try to load this keyboard map with xkbcomp, it will fail because it cannot find the xkbtest symbol definition file. You have to let the tool know where to look, by appending a path to its search list (note the use of $HOME instead of ~, which the shell would not expand):

xkbcomp -I$HOME/.xkb ~/.xkb/keymap/xkbtest $DISPLAY

You can use xev to verify the results, or just type Win-Hyphen into a terminal; does it produce –?

By the way, I found xev much more useful for such purposes when invoked as follows (thanks to Penny for the idea):

xev | sed -ne '/^KeyPress/,/^$/p'

Unfortunately, xev does not give any indication of which modifier symbols are generated. I have found no other way to verify the outcome, other than to tell my window manager to do something in response to e.g. Mod4-Enter, reloaded it, and then tried it out.

Rules again, and why I did not use them in the end

Once I got this far, I proceeded to add option-to-symbol-snippet mappings to the rules file, and added each option to the listing file too. A fewbugs [[!debbugs 524512 desc=later[[, I finally had setxkbmap spit out the right xkb_keymap and could install the new keyboard map with xkbcomp, like so:

setxkbmap -I$HOME/.xkb [...] -print | xkbcomp -I$HOME/xkb - :0

I wrote a small script to automatically do that at the start of the X session and could have gone to play outside, if it hadn’t been for the itch I felt due to the entire rule file stored in my configuration. I certainly did not like that, but I could also not find a way to extend a rule file with additional rules.

When I looked at the aforementioned script again, it suddenly became obvious that I was going a far longer path than I had to. Even though the rule system is powerful and allows me to e.g. automatically include symbol maps to remap keys on my Thinkpad, based on the keyboard model I configured, the benefit (if any) did not justify the additional complexity.

In the end, I simplified the script that loads the keyboard map, and defined a default xkb_keymap, as well as one for the Thinkpad, wich I identify by its fully-qualified hostname. If a specific file is available for a given host, it is used. Otherwise, the script uses the default.