7 minutes
Written: 2020-12-05 22:05 +0000
Updated: 2024-08-06 00:52 +0000
Remapping Keys with XKB and KLFC
This post is part of the Colemak necessities and Keboard management series.
An introduction to hacking keyboard layouts with X keyboard extension (XKB) and
klfc
, focused on Colemak and vim bindings
Background
In-spite of maximizing ergonomic bindings for most common software (e.g. Vimium, doom-emacs), every operation with the arrow keys still trouble me. Here I will lay out my experiments transitioning to a stable, uniquely defined setup with the X keyboard extension.
Keyboard Basics
Some terms to keep in mind for this post are1:
- Dead Keys
- These don’t actually output anything, but modify the next key pressed. Like applying an umlaut on the subsequent letter.
- Lock Keys
- State modifiers which are toggled, like Caps Lock
- Compose Key
- A key which interprets a series of subsequent key strokes. A dead key on steroids.
Also the different levels (from here) are concisely defined in the following table.
Level | Modifier | Keys |
---|---|---|
1 | None | Lowercase letters, numbers other symbols |
2 | Shift | Uppercase letters, symbols placed above numbers |
3 | AltGr | Accented characters, symbols, some dead keys |
4 | Shift+AltGr | More dead keys and symbols |
5 | Extend | User-defined |
6 | Shift+Extend | User-defined |
Modification Strategies
Common approaches to quick remapping of keys involves xmodmap
, which does not persist between reboots. Manually recreating or spinning off of XKB configuration files was also not very appealing.
KLFC
A more elegant approach is by using the excellent klfc Haskell binary. To install this from source:
1git clone https://github.com/39aldo39/klfc
2cd klfc
3# Kanged from the AUR https://aur.archlinux.org/packages/klfc/
4cabal v1-update
5cabal v1-install --only-dependencies --ghc-options=-dynamic --force-reinstalls
6cabal v1-configure --prefix=/usr --ghc-options=-dynamic
7cabal v1-build
The output binary is in ./dist/build/klfc/klfc
.
Note that the set of keys mapped by the json
files are relative to the QWERTY layout, that is:
This means that we have to ensure that the keys are mapped relative to QWERTY as well, not relative to the modified base layout.
Remapping
Some goals were:
- Programming (particularly in
python
andlisp
) put a lot of stress on the right hand pinky for Colemak users2 - VIM keys should be global but toggled with a lock
My primary use case is currently my ThinkPad X380, which comes with a basic QWERTY contracted layout as shown in Fig. 1.
Colemak - Layers 1 and 2
The first mapping is a basic Colemak setup as shown in Fig. fig:colemak.
It wouldn’t make much sense to remap the first two layers. We can use the json
from the examples of the klfc
repository.
1// Base Colemak layout
2// https://colemak.com
3{
4 "fullName": "Colemak",
5 "name": "colemak",
6 "localeId": "00000409",
7 "copyright": "Public Domain",
8 "company": "2006-01-01 Shai Coleman",
9 "version": "1.0",
10 "shiftlevels": [ "None", "Shift" ],
11 "singletonKeys": [
12 [ "CapsLock", "Backspace" ]
13 ],
14 "keys": [
15 { "pos": "~", "letters": [ "`", "~" ] },
16 { "pos": "1", "letters": [ "1", "!" ] },
17 { "pos": "2", "letters": [ "2", "@" ] },
18 { "pos": "3", "letters": [ "3", "#" ] },
19 { "pos": "4", "letters": [ "4", "$" ] },
20 { "pos": "5", "letters": [ "5", "%" ] },
21 { "pos": "6", "letters": [ "6", "^" ] },
22 { "pos": "7", "letters": [ "7", "&" ] },
23 { "pos": "8", "letters": [ "8", "*" ] },
24 { "pos": "9", "letters": [ "9", "(" ] },
25 { "pos": "0", "letters": [ "0", ")" ] },
26 { "pos": "-", "letters": [ "-", "_" ] },
27 { "pos": "+", "letters": [ "=", "+" ] },
28 { "pos": "Q", "letters": [ "q", "Q" ] },
29 { "pos": "W", "letters": [ "w", "W" ] },
30 { "pos": "E", "letters": [ "f", "F" ] },
31 { "pos": "R", "letters": [ "p", "P" ] },
32 { "pos": "T", "letters": [ "g", "G" ] },
33 { "pos": "Y", "letters": [ "j", "J" ] },
34 { "pos": "U", "letters": [ "l", "L" ] },
35 { "pos": "I", "letters": [ "u", "U" ] },
36 { "pos": "O", "letters": [ "y", "Y" ] },
37 { "pos": "P", "letters": [ ";", ":" ] },
38 { "pos": "[", "letters": [ "[", "{" ] },
39 { "pos": "]", "letters": [ "]", "}" ] },
40 { "pos": "\\", "letters": [ "\\", "|" ] },
41 { "pos": "A", "letters": [ "a", "A" ] },
42 { "pos": "S", "letters": [ "r", "R" ] },
43 { "pos": "D", "letters": [ "s", "S" ] },
44 { "pos": "F", "letters": [ "t", "T" ] },
45 { "pos": "G", "letters": [ "d", "D" ] },
46 { "pos": "H", "letters": [ "h", "H" ] },
47 { "pos": "J", "letters": [ "n", "N" ] },
48 { "pos": "K", "letters": [ "e", "E" ] },
49 { "pos": "L", "letters": [ "i", "I" ] },
50 { "pos": ";", "letters": [ "o", "O" ] },
51 { "pos": "'", "letters": [ "'", "\"" ] },
52 { "pos": "Z", "letters": [ "z", "Z" ] },
53 { "pos": "X", "letters": [ "x", "X" ] },
54 { "pos": "C", "letters": [ "c", "C" ] },
55 { "pos": "V", "letters": [ "v", "V" ] },
56 { "pos": "B", "letters": [ "b", "B" ] },
57 { "pos": "N", "letters": [ "k", "K" ] },
58 { "pos": "M", "letters": [ "m", "M" ] },
59 { "pos": ",", "letters": [ ",", "<" ] },
60 { "pos": ".", "letters": [ ".", ">" ] },
61 { "pos": "/", "letters": [ "/", "?" ] }
62 ],
63 "variants": [
64 {
65 "name": "mod-dh",
66 "shiftlevels": [ "None", "Shift" ],
67 "keys": [
68 { "pos": "V", "letters": [ "d", "D" ] },
69 { "pos": "B", "letters": [ "v", "V" ] },
70 { "pos": "G", "letters": [ "g", "G" ] },
71 { "pos": "T", "letters": [ "b", "B" ] },
72 { "pos": "H", "letters": [ "k", "K" ] },
73 { "pos": "N", "letters": [ "m", "M" ] },
74 { "pos": "M", "letters": [ "h", "H" ] }
75 ]
76 }
77 ]
78}
VIM Extensions
The additions are primarily through the Extend Layer3 (Fig. 3), with a Shift addition (Fig. 4) and more keys with AltGr (Fig. 5).
As mentioned before, we have to continue mapping relative to QWERTY, so these mappings can be used by QWERTY users as well. We will essentially use the ISO_5
shift key.
- Maps basic
vim
movement
- Relatively empty, just has a bracket
- Includes deletions
These are defined in a single json
as shown.
1{
2 "filter": "no klc,keylayout",
3 "singletonKeys": [
4 [ "CapsLock", "Extend" ],
5 [ "Alt_L", "AltGr" ]
6 ],
7 "shiftlevels": [ "Extend", "Shift+Extend", "AltGr+Extend" ],
8 "keys": [
9 { "pos": "H", "letters": [ "Left", "", "Backspace" ] },
10 { "pos": "J", "letters": [ "Down" ] },
11 { "pos": "K", "letters": [ "Up" ] },
12 { "pos": "L", "letters": [ "Right", "", "Delete" ] },
13 { "pos": ";", "letters": [ "Enter" ] },
14 { "pos": "N", "letters": [ "(", "[", "{" ] },
15 { "pos": "V", "letters": [ ")", "]" , "}"] }
16 ]
17}
- The key idea is to have the
AltGr
keys placed symmetrically - One major issue is that
Backspace
has gone from a single stroke ofCapsLock
to a three-key-combo- This is the least intuitive, and might need to be changed
- The third level (
Extend+AltGr
) is more accessible than the second in this layout
Usage
To generate the files needed to load the new layout:
1klfc colemak.json extendVIM.json -o coleVIM
2cd coleVIM/xkb
3./run-session.sh # to try them out
4./install-system.sh && ./scripts/install-xcompose.sh # to install them
Conclusions
The layout takes a bit of time to get used to, but it is a lot more transparent in the end compared to manually remapping to Colemak’s NEIO instead of HJKL for movement. It is both persistent and easily extended, though it is likely that more needs to be done. Perhaps some metrics 4 might be collected as well.
For more details the Wikipedia article on Keyboard Layouts is useful or this file on the tmk keyboard ↩︎
Colemak, unlike Dvorak, prioritises finger rolls over alternating hands ↩︎
DreymaR’s Extend mappings might be good for QWERTY people ↩︎
The metric collection of Michael White or the CARPALX metrics ↩︎
Series info
Colemak necessities series
- Switching to Colemak
- Refactoring Dotfiles For Colemak
- Remapping Keys with XKB and KLFC <-- You are here!
- Remapping Keys for ColemakVIM on MacOS
- Icelandic with Compose Keys on Linux
Series info
Keboard management series
- Remapping Keys with XKB and KLFC <-- You are here!
- Remapping Keys for ColemakVIM on MacOS
- Icelandic with Compose Keys on Linux