My custom keyboard layout setup

I have a Thinkpad and I also have two external keyboards I occasionally use with it: one at home when I decide to work at my desk with a bigger monitor, another one at a shared office I rent. They have quite different physical layouts: the one I use at home is a Thinkpad keyboard which roughly mirrors the layout of Thinkpad X230, the other one is Sun Type 6. I want to be able to use any of them while having my own custom keyboard layout too.

Before I started customising layouts, I typically has three layouts I’d normally use: US English, Russian and Belarusian. Three layouts are not a very optimal setup, especially if there’s no LRU/MRU ordering. Also, Russian and Belarusian are almost the same except a couple of letters, which makes it a bit inconvenient having to switch between them; also typing in a wrong one cannot immediately be seen until you type a letter which is missing in the current layout.

Hence, my first step was unifying Belarusian and Russian adding the missing letters to the third level (AltGr) of each other. I initially tried it with just one layout, but it made typing in one of the two languages for longer periods of time more difficult.

The second step I undertook was stateless switching. I found it much easier to always switch to a Latin-based layout as the first thing before typing anything, switching to a Cyrillic-based layout as I need to. I configured a hotkey to switch to the first layout (English), another one to switch to the last layout (Russian), and yet two to switch to the next or previous (so that I could cycle between layouts if I really needed to).

Finally, I heavily customised the English layout, adding all sorts of special symbols and letters for various Slavic languages (initially those were Latin alphabet for Belarusian and Polish, then I gradually replaced most of those with Slovak letters since I type in Slovak much more that in either Polish or Belarusian). Currently my Latin layout is based on the Belarusian Latin alphabet replacing quite a lot of letters, so I probably should switch to a different layout as the basis for mine anyway.

My setup consists of the following ~/.xkb hierarchy of directories and files:

~/.xkb
├── compat
│   └── custom
├── geometry
│   └── custom
├── keycodes
│   └── custom
└── symbols
    └── custom

Over the time as I experimented with settings half of the files became empty except these.

keycodes/custom:

xkb_keycodes "sun" {
    // include "sun(type6_usb)"
    <LSGT> = 94;
    <LALT> = 133;
    <RWIN> = 105;
    <LWIN> = 64;
    <RALT> = 134;
    <RCTL> = 108;
    <COMP> = 135;

    //indicator 5 = "Compose";
};

I found the swapped Alt and Meta on the Sun keyboard too difficult to get used when I frequently switch keyboards, so I swapped them to match my other two keyboards. I tried to make the Compose indicator work, but to no avail.

symbols/custom:

partial alphanumeric_keys

xkb_symbols "andrewsh" {

    key <LWIN> {
        //type[group1]= "FOUR_LEVEL",
        //symbols[Group1] = [ Super_L ],
        symbols[Group1] = [ ISO_First_Group, ISO_Last_Group ]
        //actions[Group1] = [ LockGroup(group=1), LockGroup(group=2), LockGroup(group=+1), LockGroup(group=-1) ]
    };

    modifier_map Mod4   { Super_L };

    key <MENU> {         [  ISO_Next_Group,  ISO_Prev_Group ] };

    key <AE11> {
        symbols[Group1]= [           minus,      underscore,       emdash, endash ],
        symbols[Group2]= [           minus,      underscore,       emdash, endash ],
        symbols[Group3]= [           minus,      underscore,       emdash, endash ]
    };

    key <BKSL> {
        type[group1]= "FOUR_LEVEL",
        symbols[Group1]= [       backslash,             bar,      dead_grave,      dead_breve ],
        symbols[Group2]= [           slash,       backslash ],
        symbols[Group3]= [           slash,             bar ]
    };

    key <TLDE> {
        type[group1]= "FOUR_LEVEL",
        type[group2]= "FOUR_LEVEL",
        type[group3]= "FOUR_LEVEL",
        symbols[Group1]= [           grave,      asciitilde,           U0301,           U0301 ],
        symbols[Group2]= [     Cyrillic_io,     Cyrillic_IO,           U0301,           U0301 ],
        symbols[Group3]= [     Cyrillic_io,     Cyrillic_IO,           U0301,           U0301 ]
    };

    key <AE04> {
        type[group1]= "FOUR_LEVEL",
        symbols[Group1]= [               4,          dollar,          ssharp,        EuroSign ],
        symbols[Group2]= [               4,       semicolon ],
        symbols[Group3]= [               4,       semicolon ]
    };
    key <AE05> {
        type[group1]= "FOUR_LEVEL",
        symbols[Group1]= [               5,         percent,         onehalf,      onequarter ],
        symbols[Group2]= [               5,         percent ],
        symbols[Group3]= [               5,         percent ]
    };

    key <AD01> {
        type[group1]= "FOUR_LEVEL_SEMIALPHABETIC",
        type[group2]= "FOUR_LEVEL_ALPHABETIC",
        type[group3]= "FOUR_LEVEL_ALPHABETIC",
        symbols[Group1]= [               q,               Q,              at,           U00A7 ],
        symbols[Group2]= [ Cyrillic_shorti, Cyrillic_SHORTI,    Ukrainian_yi,    Ukrainian_YI ],
        symbols[Group3]= [ Cyrillic_shorti, Cyrillic_SHORTI,    Ukrainian_yi,    Ukrainian_YI ]
    };

    key <AD05> {
        type= "FOUR_LEVEL_ALPHABETIC",
        symbols[Group1]= [               t,               T,          tcaron,          Tcaron ],
        symbols[Group2]= [     Cyrillic_ie,     Cyrillic_IE,    Ukrainian_ie,    Ukrainian_IE ],
        symbols[Group3]= [     Cyrillic_ie,     Cyrillic_IE,    Ukrainian_ie,    Ukrainian_IE ]
    };



    key <AD09> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "FOUR_LEVEL_ALPHABETIC",
        type[group3]= "FOUR_LEVEL_ALPHABETIC",
        symbols[Group1]= [               o,               O,          oslash,          Oslash ],
        symbols[Group2]= [  Cyrillic_shcha,  Cyrillic_SHCHA, Byelorussian_shortu, Byelorussian_SHORTU ],
        symbols[Group3]= [ Byelorussian_shortu, Byelorussian_SHORTU, Cyrillic_shcha,  Cyrillic_SHCHA  ]
    };

    key <AD12> {
        type[group1]= "FOUR_LEVEL",
        type[group2]= "FOUR_LEVEL_ALPHABETIC",
        type[group3]= "FOUR_LEVEL_ALPHABETIC",
        symbols[Group1]= [    bracketright,      braceright,      dead_tilde,     dead_macron ],
        symbols[Group2]= [ Cyrillic_hardsign, Cyrillic_HARDSIGN,  apostrophe,      apostrophe ],
        symbols[Group3]= [      apostrophe,  apostrophe, Cyrillic_hardsign, Cyrillic_HARDSIGN ]
    };

    key <AC02> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               s,               S,          scaron,          Scaron ],
        symbols[Group2]= [   Cyrillic_yeru,   Cyrillic_YERU ],
        symbols[Group3]= [   Cyrillic_yeru,   Cyrillic_YERU ]
    };

    key <AC03> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               d,               D,          dcaron,          Dcaron ],
        symbols[Group2]= [     Cyrillic_ve,     Cyrillic_VE ],
        symbols[Group3]= [     Cyrillic_ve,     Cyrillic_VE ]
    };

    key <AC09> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               l,               L,          lcaron,          Lcaron ],
        symbols[Group2]= [     Cyrillic_de,     Cyrillic_DE ],
        symbols[Group3]= [     Cyrillic_de,     Cyrillic_DE ]
    };

    key <AB01> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               z,               Z,          zcaron,          Zcaron ],
        symbols[Group2]= [     Cyrillic_ya,     Cyrillic_YA ],
        symbols[Group3]= [     Cyrillic_ya,     Cyrillic_YA ]
    };

    key <AB03> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               c,               C,          ccaron,          Ccaron ],
        symbols[Group2]= [     Cyrillic_es,     Cyrillic_ES ],
        symbols[Group3]= [     Cyrillic_es,     Cyrillic_ES ]
    };

    key <AB06> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               n,               N,          ncaron,          Ncaron ],
        symbols[Group2]= [     Cyrillic_te,     Cyrillic_TE ],
        symbols[Group3]= [     Cyrillic_te,     Cyrillic_TE ]
    };

    key <AB08> {
        type[group1]= "FOUR_LEVEL",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [           comma,            less,           U2212,        multiply ],
        symbols[Group2]= [     Cyrillic_be,     Cyrillic_BE ],
        symbols[Group3]= [     Cyrillic_be,     Cyrillic_BE ]
    };


    key <AB05> {
        type[group1]= "FOUR_LEVEL_SEMIALPHABETIC",
        type[group2]= "FOUR_LEVEL_ALPHABETIC",
        type[group3]= "FOUR_LEVEL_ALPHABETIC",
        symbols[Group1]= [               b,               B,                U2033,                U2032 ],
        symbols[Group2]= [      Cyrillic_i,      Cyrillic_I,          Ukrainian_i,          Ukrainian_I ],
        symbols[Group3]= [     Ukrainian_i,     Ukrainian_I,           Cyrillic_i,           Cyrillic_I ]
    };

    key <AD03> {
        type[group1]= "FOUR_LEVEL_ALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               e,               E,           schwa,           U025B ],
        symbols[Group2]= [      Cyrillic_u,      Cyrillic_U ],
        symbols[Group3]= [      Cyrillic_u,      Cyrillic_U ]
    };

    key <AB02> {
        type[group1]= "FOUR_LEVEL_SEMIALPHABETIC",
        type[group2]= "ALPHABETIC",
        type[group3]= "ALPHABETIC",
        symbols[Group1]= [               x,               X,           U2A7D,           U2A7E ],
        symbols[Group2]= [    Cyrillic_che,    Cyrillic_CHE ],
        symbols[Group3]= [    Cyrillic_che,    Cyrillic_CHE ]
    };



    key <LSGT> {
        type[group1]= "FOUR_LEVEL",
        type[group2]= "FOUR_LEVEL",
        type[group3]= "FOUR_LEVEL",
#         symbols[Group1]= [           U201D,           U201C,  guillemotright,   guillemotleft ],
#         symbols[Group2]= [  guillemotright,   guillemotleft,           U201D,           U201C ],
#         symbols[Group3]= [  guillemotright,   guillemotleft,           U201D,           U201C ]
        symbols[Group1]= [           U2019,           U2018,           U201D,           U201C ],
        symbols[Group2]= [  guillemotright,   guillemotleft,           U201C,           U201E ],
        symbols[Group3]= [  guillemotright,   guillemotleft,           U201C,           U201E ]
    };

    key <LEFT> {
        type= "FOUR_LEVEL_ALPHABETIC",
        [            Left,            Left,           U2190,           U21D0 ]
    };
    key <RGHT> {
        type= "FOUR_LEVEL_ALPHABETIC",
        [           Right,           Right,           U2192,           U21D2 ]
    };
    key <UP> {
        type= "FOUR_LEVEL_ALPHABETIC",
        [              Up,              Up,           U2191,           U21D1 ]
    };
    key <DOWN> {
        type= "FOUR_LEVEL_ALPHABETIC",
        [            Down,            Down,           U2193,           U21D3 ]
    };
    key <SPCE> {
        type[Group1]="FOUR_LEVEL",
        symbols[Group1]= [ space, space, Multi_key, nobreakspace ]
    };

};

I tried to make it possible to use the Win key as both Super (in combination with other keys) and as a language switcher, but that didn’t work (see the commented out lines).

Here are some more of my experiments from that file which didn’t work are not currently in use:

partial modifier_keys
xkb_symbols "sun" {

    key <LALT> {
        type[group1]= "FOUR_LEVEL",
        symbols[Group1] = [ Super_L ],
        actions[Group1] = [ LockGroup(group=1), LockGroup(group=2), LockGroup(group=+1), LockGroup(group=-1) ]
    };

    modifier_map Mod4   { Super_L };

    key  <LWIN> {         [        NoSymbol,           Alt_L ] };

    key <RWIN> {
        type= "ONE_LEVEL",
        symbols[Group1]= [ ISO_Level3_Shift ]
    };

    key <RALT> {         [       Control_R ] };

};

partial modifier_keys
xkb_symbols "win_lang_super" {
    key <LWIN> {
        type[group1]= "FOUR_LEVEL",
        symbols[Group1] = [ Super_L ],
        actions[Group1] = [ LockGroup(group=1), LockGroup(group=2), LockGroup(group=+1), LockGroup(group=-1) ]
    };

    modifier_map Mod4   { Super_L };
};

partial modifier_keys
xkb_symbols "alt_lang_super" {
    key <LALT> {
        type[group1]= "FOUR_LEVEL",
        symbols[Group1] = [ Super_L ],
        actions[Group1] = [ LockGroup(group=1), LockGroup(group=2), LockGroup(group=+1), LockGroup(group=-1) ]
    };

    modifier_map Mod4   { Super_L };
};

I combine all of the above settings using this file, ~/.xkb_keymap:

xkb_keymap {
    xkb_keycodes  { include "evdev+aliases(qwerty)" };
    xkb_types     { include "complete"  };
    xkb_compat    { include "complete+custom(andrewsh)"  };
    xkb_symbols   { include "pc+by(latin)+ru(winkeys):2+by:3+inet(evdev)+custom(andrewsh)"  };
    xkb_geometry  { include "pc(pc105)" };
};

and a script:

xkbcomp -I$HOME/.xkb ~/.xkb_keymap $DISPLAY
xmodmap ~/.xmodmaprc

sun_id=$(xinput list | grep "HID 0430:0005" | grep -o 'id=[0-9]*' | cut -d = -f 2)

if [ -n "$sun_id" ]
then
    xkbcomp -I$HOME/.xkb ~/.xkb_keymap_sun $DISPLAY
fi

xmodmap is not really used anymore, it only has one key mapping for the suspend key. For the Sun keyboard, ~/.xkb_keymap_sun file is used:

xkb_keymap {
    xkb_keycodes  { include "evdev+aliases(qwerty)+custom(sun)" };
    xkb_types     { include "complete"  };
    xkb_compat    { include "complete+ledcompose(compose)+custom(andrewsh)"  };
    xkb_symbols   { include "pc+by(latin)+ru(winkeys):2+by:3+inet(evdev)+custom(andrewsh)+compose(menu)"  };
    xkb_geometry  { include "pc(pc105)" };
};

I’m using inputplug to run those scripts automatically when keyboards are connected or disconnected.

Since I use Unity, I had to tell gnome-settings-daemon to stop interfering with the keyboard layout by setting org.gnome.settings-daemon.plugins.keyboard active to false. That, however, doesn’t work with GNOME anymore: in a GNOME session, the keyboard layout loads, but none of the layout switching hotkeys work.

Trying to make this work in GNOME, I followed what Peter Hutterer described, only to find out it will work in Wayland only. Anyway, here’s what I did.

First, I’ve created these files:

~/.xkb
└── rules
    ├── evdev
    └── evdev.xml

rules/evdev:

! option                =       symbols
  custom:andrewsh       =       +custom(andrewsh)

! include %S/evdev

rules/evdev.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xkbConfigRegistry SYSTEM "xkb.dtd">
<xkbConfigRegistry version="1.1">
  <optionList>
    <group allowMultipleSelection="true">
      <configItem>
        <name>custom</name>
        <description>Custom options</description>
      </configItem>
      <option>
      <configItem>
        <name>custom:andrewsh</name>
        <description>andrewsh’s custom stuff</description>
      </configItem>
      </option>
  </optionList>
</xkbConfigRegistry>

Then I symlinked ~/.xkb to ~/.config/xkb where it is expected to be found by libxkbcommon.