Skip to main content

James Brind

Enforcing good typing habits by keyboard layout

I describe how I improved my typing form by distinguishing between left and right modifier keys in an xkb keyboard layout.

Motivation

I have been using computers from a relatively young age, and for as long as I can really remember I have been able to type fast enough without staring at the keyboard. I have never had much in the way of typing instruction - clearly, years of ingrained muscle memory is a reasonable substitute for proper technique. However, this has resulted in some rather bad keyboard habits.

One issue I had was a tendency to type combinations on the left-hand side of the keyboard one-handed using the left modifier, for example capital W or Ctrl-F. This increases strain because the hand must stretch the distance between two keys; the correct orthodox technique is to use the modifier on the opposite hand. The quickest way to eliminate this behaviour is simply to make it ineffective: my fingers will soon learn if pressing left Shift and w does nothing.

The remainder of this article describes how to enforce usage of the opposite side Shift and Ctrl keys in key chords.

Approach

My first attempt used xmodmap after this answer, and would have worked if xmodmap was not overriden by xkb. Although more powerful, xkb is really very complex.

Some authors recommend decompiling your current keyboard layout into a 2000 line configuration file, making your 10 lines of changes, and then recompiling again. You can also copy or edit in place the system keyboard layouts, in /usr/share/X11/xkb/symbols on my installation. I discounted these methods for being inelegant and non-portable respectively.

The best option is to extend a layout using a configuration snippet stored in the home directory (version controlled in my dotfiles repository), which I found here.

Separating Shift keys

The idea is to remap right Shift to a different modifier, AltGr, that would usually be used to access accented characters in a non-English alphabet. For example, AltGr-n produces ñ. We then redefine all the accented characters on the left-hand side of the keyboard to be capitalised non-accented.

From an arbitrary base directory, ~/.xkb, say, we need the following structure:

~/.xkb/
└── symbols/
    └── custom

The file ~/.xkb/symbols/custom is where our layout extension is defined. The skeleton looks like:

partial alphanumeric_keys
xkb_symbols "local" {
    // This is the base layout you're inheriting:
    include "gb(colemak)"

    //
    // Put layout modifications here
    //
};

To implement our distinct Shift keys, map right Shift to ISO_Level3_Shift, the xkb name for AltGr,

key <RTSH> { [ ISO_Level3_Shift ] };

Now we need to copy key definitions from the layout we are extending. In /usr/share/X11/xkb/symbols/us we find,

...
partial alphanumeric_keys
xkb_symbols "colemak" {
...
key <AD01> { [q, Q, adiaeresis, Adiaeresis ] };
...

The symbol <AD01> corresponds to the physical location of the q key. We can type four letters with it: q plain, Q with Shift, ä with AltGr, or Ä with Shift-AltGr. Copy this line into .xkb/symbols/custom and modify,

key <AD01> { [q, VoidSymbol, Q, Adiaeresis ] };

Now pressing left Shift-q does nothing, but right Shift-q (effectively AltGr-q) produces a capital Q. Repeat this for all keys on the left-hand side of the keyboard. As I use the GB Colemak variant, I had to get some definitions fron /usr/share/X11/xkb/symbols/gb as well.

To use the extended layout, we generate an inculde file from the ~/.xkb directory which is then compiled. I automate this with a little shell script called at startup,

#!/bin/bash
cd "$HOME/.xkb"
setxkbmap custom local -option -print \
    | xkbcomp -I. - "$DISPLAY"

Separating Ctrl keys

To make this work for Ctrl keys is a bit trickier. We map left Ctrl to Hyper (some history here) and then use sxhkd and xdotool to translate the Hyper to Ctrl only for keys on the right-hand side of the board.

First map the left Ctrl key to Hyper back in ~/.xkb/symbols/custom

key <LCTL> { [ Hyper_L, Hyper_L ] };

Then set up Hyper and Super as distinct modifiers, so we can still use the Super key,

key <SUPR> {    [ NoSymbol, Super_L ]   };
modifier_map Mod4   { <SUPR> };
key <HYPR> {    [ NoSymbol, Hyper_L ]   };
modifier_map Mod3   { <HYPR> };

My .config/sxhkd/sxhkdrc looks like this:

# Left Ctrl with any key on LHS does nothing
hyper + {q,w,f,p,g,a,r,s,t,d,z,x,c,v,b,1,2,3,4,5,6}
    :

# Left Ctrl with any key on RHS is converted to real ctrl

# Top row
hyper + {j,l,u,y,semicolon,bracketleft,bracketright}
    xdohelp {j,l,u,y,semicolon,bracketleft,bracketright}

# Middle row
hyper + {h,n,e,i,o,apostrophe,numbersign}
    xdohelp {h,n,e,i,o,apostrophe,numbersign}

# Bottom row
hyper + {k,m,comma,period,slash}
    xdohelp {k,m,comma,period,slash}

# Numbers
hyper + {7,8,9,0,minus,equal}
    xdohelp {7,8,9,0,minus,equal}

where xdohelp is a simple helper script to send the Ctrl combination that takes a key as its argument,

#!/bin/bash
xdotool keyup "$1" key --clearmodifiers Control_R+"$1"                       

Note that we need to release the original key before pressing again according to this issue.

Bonus keyboard tweaks

I map CapsLock to Escape (vi user here) using this in my custom xkb,

KEY <CAPS> { [Escape] };

I use a Microsoft Natural Ergonomic Keyboard 4000 which has nice big Alt keys under the thumbs, compared to small Ctrl keys pressed with the little finger. Swapping them has really made things more comfortable. With the Hyper modification described above, this looks like the below,

key <LALT> { [ Hyper_L, Hyper_L ] };
key <LCTL> { [ Alt_L, Alt_L ] };
key <RALT> { [ Control_R, Control_R ] };
key <RCTL> { [ Alt_R, Alt_R ] };

Heavy use is made of my function keys which generally reduces the need for chording. For example, F1-5 are for window and workspace switching, and F6-10 are used do useful things in applications.