In the past 2 years or so, I have been using my caps lock key as a separate ctrl key on my desktop keyboard. This is very easy to do in X11 with a setxkmap command. However, with my laptop, I try to run without X as much as possible. (Ive found it make a nice, distraction free environment, and it seems to be pretty good for battery life)

Obviously, without X, we cannot use setxkmap. In order to do this without the tools in setxkbmap, we will have to edit the keymap used by the vitual console and set it as the keymap using localectl.

Now, according to the Arch Wiki, we should be able to create a file containing

keycode 58 = Control

And be done with it.

However, if we do this, we will notice a somewhat odd bug. When we hold down caps lock and press another key, the kernel starts sending control- keycodes. However, when we release caps lock, the kernel continues to send control- keycodes. From what I can tell, the only way to 'release' control is to reboot.

In order to figure out why this is happening, we read the man page 'man keymaps'.

✏️

You should be very careful when binding the modifier keys, otherwise you can end up with an unusable keyboard mapping. If you for example define a key to have Control in its first column and leave the rest of the columns to be VoidSymbols, you’re in trouble. This is because pressing the key puts Control modifier in effect and the following actions are looked up from the fifth column (see the table above). So, when you release the key, the action from the fifth column is taken. It has VoidSymbol in it, so nothing happens. This means that the Control modifier is still in effect, although you have released the key. Re-pressing and releasing the key has no effect.

So what is happening seems to be that when we press caps lock, it looks for what keycode to send when no modifier keys are pressed. Finding Control in the first column (the only column we specified), it activates the control modifier. When we release caps lock, it looks for the key to [un]press when Caps lock is released, and finds nothing. This means that control is now stuck on.

But wait, if we read a bit further in the man page, we find that this shouldn’t be happening!

For added convenience, you can usually get off with still more terse definitions.
If you enter a key definition line with only and exactly one action code after  the  equals  sign, it has a special meaning.
If the code (numeric or symbolic) is not an ASCII letter, it means the code is implicitly replicated through all columns being defined.

Shouldn’t this mean that our 'keycode 58 = Control' should be interpreted as 'keycode 58 = Control Control Control (and so on)'? Well, it should! However, there seems to be a bug in 'loadkeys', as the above only works when defining a complete keymap, not when overriding parts of default.map. This means, that in order to correctly modify the keymap, we either have to define all columns manually, or we have to copy the default keymap, edit it, and load it as a complete keymap.

Keymap patch

To continue overriding the default keymap, you can simply manually repeat the control command. Now, technically, there are 256 columns in the keymap file, but, at least for latin keyboards, only the first 16 are used. As such, our keymap patch looks like:

keycode 58 = Control Control Control Control Control Control Control Control Control Control Control Control Control Control Control Control

Now just put it in '/usr/share/kbd/keymaps/', and set it as your keymap with 'sudo localectl set-keymap [filename without .map extention]'.

Full keymap

In order to create a new full keymap, copy the keymap you want to edit from '/usr/share/kbd/keymaps/i386/[couple more folders here]' to somewhere in 'usr/share/kbd/keymaps/' and unzip it with 'sudo gzip -d [filename]'. Edit it with sudoedit and replace

keycode 58 = Caps_Lock

With

keycode 58 = Control

Then you can (optionally) re-zip it with 'sudo gzip [filename]' and set it as your keymap with 'sudo localectl set-keymap [filename without .map extention]'.

There we go! Our caps lock key is now a second control key! Note that localectl does not seem to propogate our change to X11, unfortuanately.

To do this in X as well, create a file in /etc/X11/xorg.conf.d/, named something along the lines of 90-keyboard.conf with the config:

1
2
3
4
5
Section "InputClass"
        Identifier "system-keyboard"
        MatchIsKeyboard "on"
        Option "XkbOptions" "ctrl:nocaps"
EndSection

Some people may suggest setxkbmap as a way to do this. However, unfortuantely, setxkbmap tends to be overwritten when a usb input device is plugged in or unplugged, and xorg.conf.d is the only persistent way to set it.