The Bazaar with Landmines (or How To Extend XKb the Right Way)

We’re talking Linux for a change. I started using Linux in high school; it was cool and edgy, and made me feel like a hero from Gibson’s books. Then, after university, I grew tired with spending more time on configuring the damn thing than getting work done, and moved to MacOs after a brief stint with Windows.

Now I’m back with Linux, as Apple is turning into shitty Apple from the 90s, only this time around imbued with the arrogance (but not the vision) of Steve Jobs. And Linux has grown so much. It’s pretty cool and inspiring that something this useful has come from the cooperation of people creating things for their own use (business and private), and sharing them.

An extensible system that cannot be extended

And the whole thing is rigged to explode on touch.

Well, I’m exaggerating. For the first n attempts at extending it, it simply refuses to budge, and graciously ignores any and all changes made to it. Until you find the cracks in its armor. Then it explodes. Well, no, still exaggerating. Mostly just fizzles and makes a mess. But still.

There are plenty of (rather good) sources out there on how XKb components (geometries, types, keycode tables, symbol tables) work, and how to build them. The problem is when you attempt to use what you built. Rules come into the question, and the whole thing just goes to the birds.

Add the new layout declaration to /usr/share/X11/xkb/rules/evdev.xml (copy & modify us layout section)

The above sentence was part of a Stackoverflow answer upvoted 19 times and marked as a right answer. What is wrong with this? On a system with package management (ie. all modern Linux systems), you do not edit anything under /usr/share. For development testing you might, maybe, to quickly check if something works, but not in production. Not as a solution. Ever.

XKb is not user extensible. It’s not that it wasn’t designed to be user extensible. In fact, it was designed to be! It’s incredibly simple to just drop in files with appropriate symbol, type or keycode data, and have them automatically read! Even the rulesets are, in their own right, extremely simple and easy to extend! However, someone, somewhere, in the process of XOrg becoming what it is today, decided that users don’t really need to extend XKb, and that third party redistributable keyboard layouts should not exist.

What are rulesets

However, that’s only for a small number of predefined behaviors that could have equally been simple macros. Mostly you just register all the types, keycodes, geometries and symbols individually as rules, under the same names, to now be accessible instead as “models”, “layouts” and “options”. As if that was somehow more user friendly, lol.

Here’s the lowdown. XOrg uses the rule file /usr/share/X11/xkb/rules/evdev, and its ancillary description files by default. One rule file. No drop-and-play here, like with all the other files. It’s this file. And you could edit it, but it even tells you in the first line that you shouldn’t edit it.

(Ideally, as a best practice, you don’t even add things to /usr/share on a package managed system, except by installing packages. You manually add things to /usr/local/share. You edit things in /etc. You store volatile data in /var. It’s not that hard.)

Back when, XOrg had the ability to have a custom rules file specified in xorg.conf. Now that we no longer have user editable xorg.conf files (not that I miss the foul beasts of course), this seems out of reach. Well, a mostly undocumented feature of /etc/default/keyboard is that you can indeed specify the XKBRULES variable, which sets just that…

…but Gnome just uses the rule file /usr/share/X11/xkb/rules/evdev. Period. It uses just that. Fuck XKBRULES. If it is a configurable setting, it’s not documented in any Google-indexed location.

In fact, Gnome uses its own XKb settings, which, in my experience, unpredictably either overrides what you set in /etc/default/keyboard, or it doesn’t. For example, I once set ctrl:swapcaps in the default file (and only there), and Gnome started applying it. Later I added more options, and it ignored those. Then to test things, I removed ctrl:swapcaps, but Gnome continued to apply it.

…and, also, at the same time, setting XKBRULES breaks the console settings. After editing /etc/default/keyboard, you can have the console use the same keyboard settings, by running the following command (on a Debian-derived system):

sudo dpkg-reconfigure -phigh console-setup

However, the console, by default, uses the ruleset /usr/share/X11/xkb/rules/base. Because evdev has stuff the console can’t handle. That default is then overridden by /etc/defaults, and running the above action gets a shitload of warnings if you copied and edited evdev to make your custom ruleset (which you should, for XOrg…)

All this because someone thought it would be nice to have the same settings for the console and X. And failed to think of the consequences. Gnome devs not thinking of consequences is pretty much standard fare, so let’s not even talk about that.

The bazaar with barbed wire and landmines

If I wanted to create a new XKb map for, say, a custom keyboard I designed and sold on Massdrop, and distribute it as a .deb or .tar.gz file, I simply couldn’t. The only way to register new XKb settings is via editing that one fucking rules file… And you cannot have one .deb package overwrite a file belonging to another one. (Well you can, it’s just that people will rightfully think that your package is shit.)

Linux devs often have this weird obsession with having everything in one place, one repository, under one ruler. All drivers in the Kernel tree. All keyboard layouts in XKb upstream. Exceptions are discouraged and hindered with seemigly (and sometimes explicitly stated) intentional and conscious malice. What kind of bazaar is this?!

As for end user extensibility, anyone editing anything under /usr/share can expect it to be overwritten within a week. Every week. Maybe not in an LTS distro — there you might even have a month.

I really think XKb needs to be improved on this front, and the weird ways one minor change breaks a chain of seemingly unrelated things (console), and the inconsistent, fuck-it-all hardcoding of values that should be read from a system-wide setting (Gnome) really reflect on what Linux is so often missing: common sense design.

So how do I extend XKb?

Nobody tells you this. None of the documentations. Apple is definitely no friend of user customization, but at least they will tell you that “this is not user servicable”. Not XOrg, no. They tell you to go edit fucking /usr/share.

Even the doc folder in the bloody source tree is withholding crucial data from your eyes that you’d need in order to be able to successfully pull this off. See, the landmines don’t stop here. They only get better.

Adding geometries, types and symbols to the source is as straightforward as it is adding them to a running installation. Rules however… In the source tree, rules are included as a bunch of .part files, that are merged by a script. Sounds quite prudent. No longer when you see how they organized it, and how it works.

#!/bin/shINDIR=`dirname $0`
DEST=$1
shift
if [ -z "$HDR" ]; then
HDR="HDR"
fi
basename=`basename $0`
echo "// DO NOT EDIT THIS FILE - IT WAS AUTOGENERATED BY $basename FROM rules/*.part" >$DEST
for i in $*; do
if [ "$i" = "$HDR" ] || [ "$i" = "HDR" ]; then
echo >> $DEST;
read hdr
echo "$hdr" >> $DEST
elif test -f $i; then
cat $i >> $DEST || exit 1
else
cat $INDIR/$i >> $DEST || exit 1
fi
done < $HDR

This is the script that merges the .part files. If you are confused, I can relate. I don’t expect you to make even remote sense of what’s going on. This script gets as its command line parameters a string from Make, which looks like this:

evdev_parts = base.hdr.part base.lists.part \
evdev.lists.part \
HDR evdev.m_k.part \
HDR base.l1_k.part \
HDR base.l_k.part \
HDR \
HDR base.ml_g.part \
HDR base.m_g.part \
HDR base.mlv_s.part \
HDR base.ml_s.part \
HDR base.ml1_s.part \
HDR \
HDR base.ml2_s.part \
HDR base.ml3_s.part \
HDR base.ml4_s.part \
HDR \
HDR \
... [redacted for brevity]

See, those on the right hand column are the .part files in the order they are meant to be included, and the HDR’s on the left are, well, keywords. They tell the script above to read the next line from its input stream (the text file HDR that is also in the rules folder), and echo that line into the merged rules file. This is HDR:

! model  = keycodes
! layout[1] = keycodes
! layout = keycodes
! option = keycodes
! model layout = geometry
! model = geometry
! model layout variant = symbols
! model layout = symbols
! model layout[1] = symbols
! model layout[1] variant[1] = symbols
! model layout[2] = symbols
! model layout[3] = symbols
! model layout[4] = symbols
! model layout[2] variant[2] = symbols
! model layout[3] variant[3] = symbols
! model layout[4] variant[4] = symbols
! model = symbols
! model layout = symbols
... [redacted for brevity]

The structure and syntax of all XKb files is relatively well documented (unlike their actual usage), so I don’t want to bore you with ruleset syntax, but these header lines here tell the engine what the lines following them mean. Like…

! option = compat
grp_led:num = +lednum(group_lock)
grp_led:caps = +ledcaps(group_lock)
grp_led:scroll = +ledscroll(group_lock)
japan:kana_lock = +japan(kana_lock)
caps:shiftlock = +ledcaps(shift_lock)
grab:break_actions = +xfree86(grab_break)
! option = types
caps:internal = +caps(internal)
caps:internal_nocancel = +caps(internal_nocancel)
caps:shift = +caps(shift)
caps:shift_nocancel = +caps(shift_nocancel)
numpad:pc = +numpad(pc)
numpad:mac = +numpad(mac)
numpad:microsoft = +numpad(microsoft)
numpad:shift3 = +numpad(shift3)

So… I hope you’ve caught on by now. The .part files do not contain this header. The file naming convention hints at what that header should be, but ultimately, the right header is assigned to the parts by careful ordering in the Makefile and the HDR file.

…when Liberian rice farmers were asked to sort objects, they tended to put a knife in the same group as vegetables. This was the clever way to do it, they said, because the knife is used to cut vegetables. When asked to sort the objects as a “stupid” person would, the farmers grouped the cutting tools together, the vegetables together, and so on, as most North Americans would (Segall et al., 1990)

Unlike every other thing in XKb, rules are not grouped by functional groups such as “all the rules for the standard US layout” or “all the rules for the caps lock options”. They are grouped in the way a stupid person would group things. There’s a part file for mapping options to symbols. Another for mapping options to types. Another for mapping model and layout combination to geometries.

How did this complexity happen? How did this help anyone? I mean, even having a single big rules file would arguably be superior in terms of developer experience… And the xml that describes the UI ordering and user descriptions of each setting is a single monolithic file anyway! Except now there is a weird build structure that took me an hour to actually understand, that is not documented either in the source tree or in any accessible location online, without any apparent added value.

So, I’d like to get to the extending part please

Get the source tree. Write the layouts. Put in everything except the rules as needed (it’s easy, but don’t forget adding your files to that folder’s Makefile.am — it will be obvious where it needs to go). Then, go to the rules folder. Look at Makefile.am and HDR to determine which configuration each file pertains to (evdev, base or both), and what kind of rules should go there, and place your rules accordingly.

I suggest you then create a diff of the folder and save it somewhere safe (like github).

Change the package version. Build the package. Install the package. Configure your package manager to lock this package (not to upgrade without your specific instruction).

Now you have user keyboard layouts, properly added. You can try to contribute them to the One True Repository at freedesktop.org.

What XKb should be like

Years back, all this configuration used to reside in /etc/X11, and was meant to be somewhat user-editable. Except it was clearly system data and not configuration, most people never edited it, and those who did usually wished they hadn’t. So it was moved to /usr/share, extensibility be damned.

There should clearly be a better way.

First off, the build should be fixed. I’m tempted to make a contribution doing just this, but it might take a while.

We can get rid of HDR, have headers in the .part files, and use shell scripting and awk to group similar rules together under the same header under the merged file. I’m not sure yet if the same header actually cannot appear twice, or someone just thought it would be “prettier” if it didn’t. (In the second case, let’s just concatenate all the .part files.)

Merging xml files with identical structures is a no-brainer task for a python script, there are a number of existing snippets you can just google… So the xml description file can also be cut into parts… And the .part files rearranged into functional groups, as a clever Liberian farmer sorts things. All the rules for setting the us keyboard variants. All the rules for setting Caps Lock behavior options. All the rules for Japanese input. And so on.

Once the build system is fixed, we can just take the same tools, and put them into the dist package. Move the merging to configuration time. Installing the package installs the parts, running debconf merges them.

I don’t expect people to write new layouts in /etc, so removing the tree from there was prudent. But third party layouts should be possible and encouraged. XKb should have some kind of search path set in /etc, where it should look for component files (under /usr/local/share, under /opt/{something}, etc.) Third party layouts could just trigger a debconf run on the xkb-data package, and have themselves added.

Of course that would mean that Freedesktop.org wouldn’t reign supreme over all X keyboard layouts everywhere. But why should it? Wasn’t this supposed to be a bazaar?

There’s two kinds of programming: functional and dysfunctional.