Changing Keyboard Layouts with XKB in X11 and Wayland

Introduction

A black 60% keyboard with no labels on keys

I use my own key layouts for the keyboards I use, mainly Happy Hacking Keyboard Pro 2 and Thinkpad keyboards.

Both Wayland and X11 makes it possible to define your own keyboard layouts. These days it's done with the X keyboard extension (XKB). Yes, even in Wayland.

The goal here is to use a Swedish standard layout but with a few important changes:

  1. Control where Caps Lock usually is.
  2. ESC or possibly "<" and ">" to the left of "1".
  3. "$" on shifted "4", not the universal currency sign.
  4. }{|][\ on AltGr+åäöÅÄÖ. Immediately obvious to Swedish programmers of a certain age.
  5. Preferably two real Alt/Meta keys and still having AltGr.
  6. "@", "`", "~", and "^" easily available and not as dead keys.

On the HHKB I have some other changes to the AltGr layer, too, for ease of writing at least German and Nordic languages.

HHKB layout

Complete layout for HHKB Pro 2 using the XKB “hhk(win2)” variant of the “hhk” geometry.

HHKB has hardware switches on the keyboard to change its behaviour. I keep my HHKB switches set to 01 1010. 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 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 01 1010:

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.

First level:

First level of MC's keyboard layout

There are no dead keays on the first level unlike the standard Swedish keyboard.

(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.)

With AltGr (or ISO_Level3_Shift) pressed:

AltGr level of MC's Swedish layout.

There are two dead keys here: the acute/grave accent key and the diariesis/circumflex key to make it possible to combine and give characters like "ï".

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 “Ü”.

Here's the XKB symbols file for this layout, hhkb:

// A complete Swedish map for use with HHKB Pro 2 with SW 10 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 ] };
    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 };
};

American/Swedish HHKB layout

Ordinary ANSI keyboard on the HHKB but with Swedish characters on }{|][\ when AltGr is pressed, hhkb-us:

// 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 };
};

Thinkpad layout

Here's a Thinkpad keyboard layout, thinkpad, that works well on both ISO (L-shaped Enter, extra key to the left of "Z") and ANSI (horisontal Enter, one less key) keyboards. It works on both Thinkpad laptop keyboards and the standalone Thinkpad keyboards, including the wireless Trackpoint II.

It uses PrtSc (Print Screen) as AltGr and gives me two real Alt keys. It has brackets, braces and stuff on the keys with Swedish characters

You can choose if you want the key to the left of "1" <TLDE> to be less than/greater than (typically useful on an ANSI keyboard which doesn't have that key to the left of "Z") or ESC.

// MC's Swedish keymap for use with Thinkpad keyboards
default partial alphanumeric_keys modifier_keys
xkb_symbols "se" {
    include "latin(type2)"
    include "se(se)"

    // For ISO:
    //key <TLDE> { [ Escape ] };
    // For ANSI:
    key <TLDE> { [ less, greater ] };

    key <AE04> { [ 4, dollar ] };
    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 <AE12> { [ grave, at, dead_acute, dead_grave ] };

    key <LWIN> { [ Hyper_L ] };
    key <LALT> { [ Meta_L ] };

    key <I0C> { [ XF86AudioMute ] };
    key <I2E> { [ XF86AudioLowerVolume ] };
    key <I30> { [ XF86AudioRaiseVolume ] };

    key <RALT> { [ Meta_R ] };
    key <PRSC> { [ ISO_Level3_Shift ] };

    replace key <CAPS> { [ Control_L, Control_L ] };
    modifier_map Control { <CAPS>, <LCTL> };

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

Here's a US layout, thinkpad-us with the Swedish characters on brackets, et cetera, using PrtSc as AltGr. You might want to switch between this one and the above if you're a programmer.

// MC's US keymap for use with Thinkpad keyboards
default 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> { [ Hyper_L ] };
    key <LALT> { [ Meta_L ] };

    key <I0C> { [ XF86AudioMute ] };
    key <I2E> { [ XF86AudioLowerVolume ] };
    key <I30> { [ XF86AudioRaiseVolume ] };

    key <RWIN> { [ Meta_R ] };
    key <PRSC> { [ ISO_Level3_Shift ] };

    replace key <CAPS> { [ Control_L, Control_L ] };
    modifier_map Control { <CAPS>, <LCTL> };

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

X11 config

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 privileges to edit the files. They might also be overwritten on your next update. The system XKB files are usually in the /usr/share/X11/xkb/ directory. Look, for instance, in /usr/share/X11/xkb/symbols/se for the Swedish layouts.

However, you can use your own layout configuration in your ordinary home directory and not have to be root to apply them.

Say you put the files in ~/.xkb, then you need a file defining the layout, say hhkb.xkb:

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 ~/.xkb/symbols/hhkb. You can see it at the top of the page.

Similarly for a Thinkpad keyboard:

xkb_keymap {
	xkb_keycodes  { include "xfree86+aliases(qwerty)"	};
	xkb_types     { include "complete"	};
	xkb_compat    { include "complete"	};
	xkb_symbols   { include "pc+se(se)+thinkpad(se)"	};
	xkb_geometry  { include "pc(pc105)"	};
};

NOTE! If you're using a more modern X.org 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/.xkb $HOME/.xkb/hhkb.xkb $DISPLAY

NOTE! There must not be any space character after “-I” pointing to the directory where you keep the files. Also note that if $DISPLAY isn't set and there is an .xkm file, xkbcomp will decompile the .xkm and replace your precious .xkb file!

You can put this command in your .xinitrc (if started with startx) or .xsession (XDM) or whatever starts your X server.

For many years I used xmodmap instead of XKB. Some of my xmodmap files are available here:

https://hack.org/mc/files/

Note that they are X server dependent.

I haven't used X11 for a while now, but still use the XKB layouts mentioned here. I blogged about even turning off Xwayland in "No more X11" a few years ago.

Wayland

Wayland compositors such as Sway and River also use the XKB format to define layouts. This is probably true for all the compositors that use wlroots and probably for Gnome and KDE as well, but I don't know how to use it there.

For River you can configure keyboard layout and key rate like this, typically in ~/.config/river/init:

riverctl keyboard-layout -options grp:alt_space_toggle hhkb,hhkb-us
riverctl set-repeat 40 240

In Sway you add something like this to ~/.config/sway/config:

input * {
      xkb_layout "hhkb,hhkb-us"
      xkb_options "grp:alt_space_toggle"
      repeat_delay 240
      repeat_rate 40
      pointer_accel 0.5
      accel_profile flat
}

For all who use libxkbcommon it's also possible to use environment variables:

export XKB_DEFAULT_LAYOUT="hhkb,hhkb-us"
export XKB_DEFAULT_OPTIONS="grp:alt_space_toggle"

Keep your keyboard definitions with those names (hhkb, hhkb-us, et cetera) in the ~/.xkb/symbols directory. In the example above you can switch between these two layouts with Alt-space.

Swedish keyboards and my layout

Physically, a Swedish keyboard is based on the ISO keyboard and the typical layout these days 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).

The braces, brackets, bar and backslash used to be on the Swedish “å”, “ä” and “ö” characters in the Swedish version of ASCII. Typically, a programmer sitting at a 7 bit ASCII terminal with a Swedish keyboard would press keys with the Swedish characters 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.

Changes from US ASCII:

In the special version ISO 646-SE2 extended for names these were also changed:

Here's a page from a booklet about the Luxor ABC 1600 workstation, a computer I had a for a while as a teenager. The keyboard looks similar to the first layer of my HHKB, doesn't it? I was even more used to the Facit terminals (Twist and 4431) but couldn't find any good photos of the keyboard layout of those. Also, check out the mouse! Same sort of mouse as the ETH Zürich Ceres workstations!

Brown Luxor ABC 1600 keyboard with white keys.

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 neither in X11 nor Wayland. At least in Wayland Control-AltGr-å works and generates Control-] like Goddess intended.

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: <2024-02-29 22:02:17 MET>