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.