x keyboard extension (aka: xkb), which was made several decades ago, is still in active use today; here we show how to use xkb to alter system keyboard layout and create our own; in this example, we want to switch comma and period, for no reason but a demo;

load, change, save

xkb comes with a tool called xkbcomp, which is used to compile xkb keymap descriptions; a great feature of this “compiler” is that it can talk to the “live” x server to load and save keymap; this makes our customization an easy 3-step process:

  1. load keymap from x server;

    xkbcomp "$DISPLAY" custom.xkb
    
  2. change keymap;

    vim custom.xkb
    

    assuming we are using layout us; we look for section xkb_symbols then we can find these two lines:

    key <AB08> {         [           comma,            less ] };
    key <AB09> {         [          period,         greater ] };
    

    as you can guess, to switch these two keys, we simply switch their values:

    key <AB08> {         [          period,         greater ] };
    key <AB09> {         [           comma,            less ] };
    
  3. save keymap to x server;

    xkbcomp custom.xkb "$DISPLAY"
    

this is pretty easy and works well;

change partials

the above approach involves a monolithic keymap file, which may seem daunting; we can do it a bit more elegant, by editing partial files; to do this, we step into /usr/share/X11/xkb and we find subdirs here such as keycodes, symbols, types, etc.; we are still interested symbols files so we look into symbols subdir; for simplicity we open symbols/us, which contains this part:

default partial alphanumeric_keys modifier_keys
xkb_symbols "basic" {

    name[Group1]= "English (US)";

    key <TLDE> {    [   grave,          asciitilde      ]   };
    key <AE01> {    [   1,              exclam          ]   };
    key <AE02> {    [   2,              at              ]   };
    key <AE03> {    [   3,              numbersign      ]   };
    key <AE04> {    [   4,              dollar          ]   };
    key <AE05> {    [   5,              percent         ]   };
    key <AE06> {    [   6,              asciicircum     ]   };
    key <AE07> {    [   7,              ampersand       ]   };
    key <AE08> {    [   8,              asterisk        ]   };
    key <AE09> {    [   9,              parenleft       ]   };
    key <AE10> {    [   0,              parenright      ]   };
    key <AE11> {    [   minus,          underscore      ]   };
    key <AE12> {    [   equal,          plus            ]   };

    key <AD01> {    [   q,              Q               ]   };
    key <AD02> {    [   w,              W               ]   };
    key <AD03> {    [   e,              E               ]   };
    key <AD04> {    [   r,              R               ]   };
    key <AD05> {    [   t,              T               ]   };
    key <AD06> {    [   y,              Y               ]   };
    key <AD07> {    [   u,              U               ]   };
    key <AD08> {    [   i,              I               ]   };
    key <AD09> {    [   o,              O               ]   };
    key <AD10> {    [   p,              P               ]   };
    key <AD11> {    [   bracketleft,    braceleft       ]   };
    key <AD12> {    [   bracketright,   braceright      ]   };

    key <AC01> {    [   a,              A               ]   };
    key <AC02> {    [   s,              S               ]   };
    key <AC03> {    [   d,              D               ]   };
    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> {    [   semicolon,      colon           ]   };
    key <AC11> {    [   apostrophe,     quotedbl        ]   };

    key <AB01> {    [   z,              Z               ]   };
    key <AB02> {    [   x,              X               ]   };
    key <AB03> {    [   c,              C               ]   };
    key <AB04> {    [   v,              V               ]   };
    key <AB05> {    [   b,              B               ]   };
    key <AB06> {    [   n,              N               ]   };
    key <AB07> {    [   m,              M               ]   };
    key <AB08> {    [   comma,          less            ]   };
    key <AB09> {    [   period,         greater         ]   };
    key <AB10> {    [   slash,          question        ]   };

    key <BKSL> {    [   backslash,      bar             ]   };
};

it is straightforward to change this file and save as a custom copy; some articles may then tell you to add this custom copy to rules by editing some file; but we will not go that way here, cuz that needs to write under /usr/share, which should not be touched by normal users; instead, we do these steps:

  1. save our modified symbols file in a dir that can be written by normal user:

    mkdir -p "${xkb}/symbols"
    echo "$symbols" > "${xkb}/symbols/custom"
    
  2. print current keymap using setxkbmap:

    setxkbmap -print > "${xkb}/custom.xkb"
    

    compare this with:

    xkbcomp "$DISPLAY" "${xkb}/custom.xkb"
    

    they both generate a keymap file, but setxkbmap generates it succinct:

    xkb_keymap {
        ...
        xkb_symbols   { include "pc+us"         };
        ...
    };
    
  3. customize curent keymap:

    sed -i "s/+us/+custom/" "${xkb}/custom.xkb"
    
  4. call xkbcomp to apply our change on x display:

    xkbcomp -I"$xkb" "${xkb}/custom.xkb" "$DISPLAY"
    

    we need -I because our $xkb is not a standard dir searched by xkbcomp;

when our work is done, we probably want to know how our keymap looks like; for this we can use xkbprint:

xkbprint -color "$DISPLAY"

this outputs a .ps file, which we can pipe to get a .pdf file:

xkbprint -color "$DISPLAY" - | ps2pdf - custom.pdf

now comes the problem: xkbprint may not be available in your linux distro; for example, it has been retired in fedora; seriously, i could not believe it, but we are gonna help ourselves; on fedora, since xkbprint is no longer available, i pulled the xkbcomp srpm, changed all names and versions to those of xkbprint, fixed some obvious bugs in spec file, then fetched the xkbprint source tarball from here, finally it built into a binary;

references

this is a huge topic, many not covered here; but we got these great articles:

it seems xkb configuration can only read from /usr/share, not /usr/local/share or other dirs; this sucks, really;