Changing Keyboard Layouts with XKB

Introduction

For many years I've used my own adaption of the Swedish keyboard layout on many physical keyboards. I've used xmodmap to do this. Some of my xmodmap files are available here:

http://hack.org/mc/files/

Note that they are X server dependent.

The keyboard mapping system in the X.org X server has been replaced with the XKB extension. As far as I understand, the XKB implementation is also responsible for simulating the core protocol in servers such as the ones from X.org.

XKB is an advanced protocol that can do much more than the core protocol and the xmodmap tool. There's not much documentation available, particularly if you want to change the keyboard layout to something that is not provided by the system layouts and especially if you would like to do it without touching the system files and without having root access, which I think must be a pretty common scenario.

The simple way of changing keyboard layouts with XKB is to use setxkbmap(1), for instance like this:

% setxkbmap -layout se -option ctrl:nocaps

which gives me a standard Swedish keyboard with the Caps Lock key replaced by an extra Control key.

However, setxkbmap has the obvious disadvantage that it only works with the already installed keyboard layouts in your system. If you want to add your own layout you need root priveleges to edit the files. They might also be overwritten on your next update.

It's far more useful to keep your keyboard layouts in your home directory. This text describes how to do it without root priveleges and how to define your own keyboard layout from scratch.

My setup

My keyboard

My main keyboard is a PFU Happy Hacking Keyboard Professional 2, pictured here with a CST L-Trac-X trackball:

Thankfully, there's already a keyboard geometry defined for HHKB known as “hhk” in XKB, so I use that. The “hhk” geometry, however, has several variants. I use the “hhk(win2)” variant.

The variants are there because the HHKB has hardware switches on the keyboard to change its behaviour. I keep my HHKB switches set to 011010. This means “Lite ext”, which gives me Windows keys, “Backspace”, which means the rubout key gives the Backspace keycode instead of the Delete keycode, and the final switch in the on position means I want the diamond keys closest to the space bar to generate the Alt keycodes and the keys next to them to generate the Windows key keycodes.

This is what the HHKB keys generate with the switches set to 011010:

HHKB key names.

The key marked FN with a keycode of zero in the lower right is the magic “Fn” key. It doesn't generate any keycodes and is used internally by the HHKB to generate missing keys. I seldom use it myself but if you need function keys, arrow keys, PgUp, PgDn, et cetera, you press Fn and another key to generate them.

XKB Configuration Files

I store my XKB configuration files in ~/conf/xkb/ in my home directory. It looks like this with ls -1F:

brain@
hhkb.xkb
pc.xkb
symbols/
thinkpad.xkb

brain is the name of one of my computers. It's a symbolic link to hhkb.xkb.

hhkb.xkb and the other *.xkb files are complete XKB keyboard descriptions. The hhkb.xkb file contains:

xkb_keymap {
        xkb_keycodes  { include "xfree86+aliases(qwerty)" };
        xkb_types     { include "complete" };
        xkb_compat    { include "complete" };
        xkb_symbols   { include "pc+hhkb(se-complete)" };
        xkb_geometry  { include "hhk(win2)" };
};

Note the “hhkb(se-complete)” part of “xkb_symbols”. This points to my own keyboard layout. The layout is stored in the file symbols/hhkb.

NOTE! If you're using a more modern X.org X server you need to use a different keycode file instead of the xfree86 keycodes. Typically you would use:

    xkb_keycodes  {include "evdev+aliases(qwerty)" };

Given all this, you load the keyboard layout with:

% xkbcomp -I$HOME/conf/xkb $HOME/conf/xkb/hhkb.xkb $DISPLAY

NOTE! There must not be any space character after “-I”.

If you do your own Swedish layout you might want to use “pc+se(se)+your-own-thing” instead, but since “se” does things I don't want, I don't include it.

You can put this command in your .xinitrc, .xsession or what have you.

It's quite likely that if you're using Gnome, KDE, XFCE or similar desktop environments that their graphical tools for keyboard configurations will know nothing about your own layout files. I don't know how to fix that and I don't use any desktop environments myself. If you know how to fix that, I welcome contributions. Contact me!

Since I move around a bit and have my configuration files in several locations, this is what my own .xinitrc actually looks like for loading keyboard layouts (this is where the symbolic link comes in):

hname=`hostname -s`

...

if [ $DISPLAY = ":0.0" ] || [ $DISPLAY = ":0" ]
then
    if [ -f $HOME/conf/xkb/$hname ]
     then
        echo Loading XKB keymap for $hname
        xkbcomp -I$HOME/conf/xkb $HOME/conf/xkb/$hname $DISPLAY
    fi
else
    echo Loading standard XKB keymap.
    xkbcomp -I$HOME/conf/xkb $HOME/conf/xkb/pc.xkb $DISPLAY
fi

This is almost exactly what I did before with xmodmap. I haven't used XKB long enough to have a lot of symbolic links and different layouts but I guess that will build up over the years to come.

My layout

My own layouts for the HHKB are in ~/conf/xkb/symbols/hhkb in my home directory. The layouts are defined like this:

// A complete Swedish map for use with HHKB Pro 2 with SW 01 1010,
// that is Lite ext, Backspace on rubout and diamond gives Alt.
default partial alphanumeric_keys modifier_keys
xkb_symbols "se-complete" {
    key <AE01> { [ 1, exclam, leftdoublequotemark, leftsinglequotemark ] };
    key <AE02> { [ 2, quotedbl, rightdoublequotemark, rightsinglequotemark ] };
    key <AE03> { [ 3, numbersign, sterling, NoSymbol ] };
    key <AE04> { [ 4, dollar, dollar ] };
    key <AE05> { [ 5, percent, EuroSign, cent, NoSymbol ] };
    key <AE06> { [ 6, ampersand, yen, NoSymbol ] };
    key <AE07> { [ 7, slash ] };
    key <AE08> { [ 8, parenleft ] };
    key <AE09> { [ 9, parenright ] };
    key <AE10> { [ 0, equal ] };
    key <AE11> { [ plus, question ] };
    key <AE12> { [ grave, at, dead_acute, dead_grave ] };
    key <BKSL> { [ apostrophe, asterisk ] };
    key <TLDE> { [ less, greater ] };

    key <AD01> { [ q, Q ] };
    key <AD02> { [ w, W ] };
    key <AD03> { [ e, E, eacute, Eacute ] };
    key <AD04> { [ r, R ] };
    key <AD05> { [ t, T, thorn, THORN ] };
    key <AD06> { [ y, Y ] };
    key <AD07> { [ u, U, udiaeresis, Udiaeresis ] };
    key <AD08> { [ i, I ] };
    key <AD09> { [ o, O, oslash, Ooblique] };
    key <AD10> { [ p, P ] };
    key <AD11> { [ aring, Aring, braceright, bracketright ] };
    key <AD12> { [ asciitilde, asciicircum, dead_diaeresis, dead_circumflex] };

    key <AC01> { [ a, A, ae, AE ] };
    key <AC02> { [ s, S, ssharp, NoSymbol ] };
    key <AC03> { [ d, D, eth, ETH ] };
    key <AC04> { [ f, F ] };
    key <AC05> { [ g, G ] };
    key <AC06> { [ h, H ] };
    key <AC07> { [ j, J ] };
    key <AC08> { [ k, K ] };
    key <AC09> { [ l, L ] };
    key <AC10> { [ odiaeresis, Odiaeresis, bar, backslash ] };
    key <AC11> { [ adiaeresis, Adiaeresis, braceleft, bracketleft ] };

    key <AB01> { [ z, Z, guillemotleft ] };
    key <AB02> { [ x, X, guillemotright ] };
    key <AB03> { [ c, C, copyright ] };
    key <AB04> { [ v, V ] };
    key <AB05> { [ b, B ] };
    key <AB06> { [ n, N, ntilde, Ntilde ] };
    key <AB07> { [ m, M, mu, NoSymbol ] };
    key <AB08> { [ comma, semicolon ] };
    key <AB09> { [ period, colon ] };
    key <AB10> { [ minus, underscore ] };

    key <LWIN> { [ Super_L ] };
    key <LALT> { [ Meta_L ] };
    key <RALT> { [ Meta_R ] };
    key <RWIN> { [ ISO_Level3_Shift ] };

    modifier_map Mod1 { Meta_L, Meta_R };
    modifier_map Mod4 { Super_L };
    modifier_map Mod5 { ISO_Level3_Shift };
};

// Swedish map for use with HHKB Pro 2 with SW 01 1010, that is Lite ext,
// Backspace on rubout and diamond gives Alt.
partial alphanumeric_keys modifier_keys
xkb_symbols "se" {
    include "latin(type2)"

    key <AE04> { [ 4, dollar ] };
    key <AE05> { [ 5, percent, EuroSign, cent ] };        
    key <AE11> { [ plus, question, backslash ] };
    key <AE12> { [ grave, at, dead_acute, dead_grave ] };
    key <BKSL> { [ apostrophe, asterisk ] };
    key <TLDE> { [ less, greater ] };

    key <AD03> { [ e, E, eacute, Eacute ] };
    key <AD07> { [ u, U, udiaeresis, Udiaeresis ] };
    key <AD11> { [ aring, Aring, braceright, bracketright ] };
    key <AD12> { [ asciitilde, asciicircum, dead_diaeresis, dead_circumflex] };

    key <AC10> { [ odiaeresis, Odiaeresis, bar, backslash ] };
    key <AC11> { [ adiaeresis, Adiaeresis, braceleft, bracketleft ] };

    key <AB06> { [ n, N, ntilde, Ntilde ] };

    key <LWIN> { [ Super_L ] };
    key <LALT> { [ Meta_L ] };
    key <RALT> { [ Meta_R ] };
    key <RWIN> { [ ISO_Level3_Shift ] };

    modifier_map Mod1 { Meta_L, Meta_R };
    modifier_map Mod4 { Super_L };
    modifier_map Mod5 { ISO_Level3_Shift };
};

// American version of the above but with Swedish characters on
// AltGr+brackets, braces, backslash and bar.
partial alphanumeric_keys modifier_keys
xkb_symbols "us" {
    include "us"

    key <AD11> { [ bracketleft, braceleft, adiaeresis, Adiaeresis ] };
    key <AD12> { [ bracketright, braceright, aring, Aring ] };
    key <BKSL> { [ backslash, bar, odiaeresis, Odiaeresis ] };

    key <LWIN> { [ Super_L ] };
    key <LALT> { [ Meta_L ] };
    key <RALT> { [ Meta_R ] };
    key <RWIN> { [ ISO_Level3_Shift ] };

    modifier_map Mod1 { Meta_L, Meta_R };
    modifier_map Mod4 { Super_L };
    modifier_map Mod5 { ISO_Level3_Shift };
};

There are three layouts here, two versions of the Swedish keyboard layout and a straight US layout with Swedish characters on AltGr.

Note that I don't actually use “include "se(se)"” in either of the Swedish layouts. If you do you get some extra stuff including a definition of the right Alt (the RALT key) as ISO_Level3_Shift that I don't want. If you really want to include the “se” symbols and still want RALT to be an ordinary Alt you have to override it like this:

    // Override default in se(se) which has RALT as Level 3 shift.
    // Stops xkbcomp's complaints about multiple symbols.
    key <RALT> { 
      type[Group1]="TWO_LEVEL",
      type[Group2]="TWO_LEVEL",
      type[Group3]="TWO_LEVEL",
      type[Group4]="TWO_LEVEL",
      symbols[Group1] = [ Meta_R ], 
      symbols[Group2] = [ Meta_R ], 
      symbols[Group3] = [ Meta_R ], 
      symbols[Group4] = [ Meta_R ]
    };

On the first layout, “se-complete”, I define almost all the keys on the keyboard myself. The arrow keys, PgUp, PgDn et cetera are already defined in “pc”. The reason I define everything myself is that there are lots of strange stuff on the AltGr level if you include “latin”. I don't wan't that.

Physically, a Swedish keyboard is based on the ISO keyboard and the standard layout looks like this:

(Image from Wikipedia, released under Creative Commons Attribution-Share Alike 3.0 Unported.)

I'm making up for the physically missing keys on the HHKB compared to an ISO keyboard by placing apostrophe/asterisk and less/greater on the two keys above Backspace.

I don't see the point with the rarely used generic currency symbol (¤) that is normally on a shifted 4 on a modern Swedish keyboard, so I have changed that to the much more often used dollar sign. No one I know ever uses the currency symbol for anything.

I also put @ and backtick (a stand-alone grave accent) on their own key. I use them much more often than I use accented characters, so I don't want the dead_acute and dead_grave on its own key. I moved them to the AltGr level.

The layout is quite inspired by the keyboard of several old Swedish character terminals that used the Swedish version of ASCII (colloquially often “SWASCII”, defined in ISO 646-SE).

This is what my “se-complete” and “se” layouts look like on the HHKB according to xkbprint:

First level of MC's keyboard layout

Note that you have to use a Latin 1 locale for xkbprint to print out something else than ASCII. I used the “-lc en_US.ISO8859-1” option. It seems to be hardcoded to use Latin 1 so an UTF-8 locale won't work.

I couldn't find any photos of, say, a Facit terminal keyboard right now, but here's an image from a manual for the Luxor ABC series of computers from the early 1980s. Looks similar, doesn't it?

Luxor ABC keyboard.

Yes, as you can imagine, in SWASCII the “É” character was on the at sign's position and “é” was on the backtick. Incidentally, “ü” and “Ü” was on, you guessed it, “~” and “^”. This is exactly what I have on my layout.

With AltGr (or ISO_Level3_Shift) pressed my “se-complete” layout looks like this:

AltGr level of MC's Swedish layout.

There are two dead keys here: the acute/grave accent key and the diariesis/circumflex key.

Again, as you might have guessed, the braces, brackets, bar and backslash was on the Swedish “å”, “ä” and “ö” characters in the Swedish ASCII. Typically, a programmer sitting at a 7 bit ASCII terminal with a Swedish keyboard would press the Swedish keys to get the brackets et cetera. This is what I'm used to and how I grew up. Now I just have to remember to press AltGr as well.

As you can see from what I've defined I want to be able to write at least Danish, German, Icelandic and Norwegian besides Swedish.

The four characters on the “1” and “2” keys are the single and double quote Unicode characters. Some fairly often used characters that can be created by combining keys got their own keys as well, such as “É” and “Ü”.

Remaining Problems

There's one remaining problem. In my xmodmap layout, Control-å generates a Control-]. I can't figure out how to do that in XKB. Interestingly, if I use xmodmap it works, but if I dump the current configuration afterwards with xkbcomp and reload what I just dumped, it doesn't work!

Even more interesting is that pressing Control-5 actually does what I want but on the wrong key. I can't figure out why Control-5 works the way it does. Thanks, Martin, for pointing this out.

I will continue to experiment, but if anyone have figured it out please contact me!


Last updated: <2012-05-15 11:17:19 MEST>