Extending the X keyboard map with xkb
martin f. krafft
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
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 variations 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:
-
The
-option grp:win_switch
argument has been turned into an additional includegroup(win_switch)
on thexkb_symbols
line, just like the model, layout, and variant are responsible for other aspects in the output. -
The output seems related to what
xkbcomp
dumped into thexkb.dump
file we created earlier. Upon closer inspection, the dump file is a pre-processed version of the keyboard map, withinclude
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
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:
-
Make the Win-Hyphen key combination generate an “en” dash (–), and Win-Shift-Hyphen an “em” dash (—).
-
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 <AE11> { symbols[Group2] = [ endash, emdash ] }; }; partial modifier_keys xkb_symbols "caps_mod4" { replace key <CAPS> { [ VoidSymbol, VoidSymbol ] }; modifier_map Mod4 { <CAPS> }; };
Now let me explain these in turn:
-
We used the option
grp:win_switch
earlier, which toldxkb
that we would like to use the windows keys to switch to group 2. In the custom symbols file, we now define the symbols to be generated for each key, when the second group has been selected.Key
<AE11>
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 italphanumeric_keys
to tellxkb
that I would be modifying alphanumeric keys inside it. If I also wanted to change modifier keys, I would also specifymodifier_keys
.The rest should be straight-forward. You can get the names of available symbols from
keysymdef.h
,1 stripping theXK_
prefix. -
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 theMod4
modifier event, which I can later use to bind key combinations for my window manager (awesome).A note about
VoidSymbol
(as opposed to e.g.Hyper L
as the symbol to be generated by the caps lock key: As far as I understood, Control, Shift, and Caps are necessary symbols, but all the others (including Alt and Meta) are actually just symbols and independent from any other function of the keys. In particular,modifier_map
seems completely independent of the symbols. As many applications use Meta/Alt, you won’t get around generating those, but I don’t know of applications that use Super or Hyper, so I prefer not to generate them.
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'
xev
can also tell you which modifiers are in effect. For that, you have to
hold the modifying key down, press another key, and take note of the state
field, which is the hexadecimal sum of all modifiers in effect. The ones I
found are:
0x0001 Shift 0x0002 Caps 0x0004 Control 0x0008 Alt 0x0010 NumLock 0x0030 Mod3 0x0040 Mod4 0x2000 Mode_switch
Thanks to Marius Gedminas for the hint.
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 few bugs 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, which 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.
-
/usr/include/X11/keysymdef.h
on a Debian system, package x11proto-core-dev. ↩︎