Re-key-remapping
Richard Mortier · 3 min read · July 26, 2025 · #tech #nixos #linux #dotfiles #configAs all right-minded people must, I remap the historic, redundant, absurd CAPSLOCK
key1 to be a more conveniently accessible current, useful, sleek CTRL
(control) key on any given keyboard. I also do some other minor mods to assist retention of the little sanity I still have – as I get older and more decrepit, it gets harder for me to handle change. Deal with it.
I used to do this on my Macbook using a tool called Karabiner Elements. But now I’m on Linux, that’s not a thing. So I use the excellent keyd
instead. The UK Thinkpad keyboard being a lot less daft in design than the UK Macbook keyboard, the modifications are also much smaller, which is nice.
However the canonical First Modification, CAPSLOCK
to CONTROL
remains the key one. For added convenience, given I rarely need to “tap” CTRL
I also make a tap on the CAPSLOCK
key into an ESC
(escape). And there the fun starts.
It turns out the keyd
recommended way to achieve this is with the configuration stanza
[main]
, esc)
capslock
…which is mostly fine and has worked well for me for some time. But I realised I was getting increasingly irritated that I seemed to get spurious ESC
key presses when using CAPSLOCK
as CTRL
. sudo keyd monitor
and it became clear that the semantics of the overload()
function weren’t quite as I thought. From man keyd
,
overload(<layer>, <action>)
Activates the given layer while held and executes <action> on tap.
I had assumed this meant that <action>
did not execute if the assigned key was held, but it doesn’t quite: it seems that a tap is deemed to have occurred if the key is not chorded. So sometimes, being a bit slow, when I hold CAPSLOCK
thinking I want to chord and thus have it treated as CTRL
but then realise I don’t and I just let go, I still get an ESC
generated – even if it was a second or two that I was holding it down for.2
So I read a bit more of the manpage can came across the timeout
function
timeout(<action 1>, <timeout>, <action 2>)
If the key is held in isolation for more than <timeout> ms, activate the
second action, if the key is held for less than <timeout> ms or another key
is struck before <timeout> ms expires, execute the first action.
E.g.
timeout(a, 500, layer(control))
Will cause the assigned key to behave as control if it is held for more than
500 ms.
…used as capslock = timeout(esc, 250, layer(control))
but that turns out also not to be quite right. In the end, it looks like the right answer is overloadt2
:
overloadt(<layer>, <action>, <timeout>)
Identical to overload, but only activates the layer if the bound key is held
for <timeout> milliseconds. This is mainly useful for overloading keys which
are commonly struck in sequence (e.g letter keys).
Note that this will add a visual delay when typing, since overlapping keys
will be queued until the timeout expires or the bound key is released.
overloadt2(<layer>, <action>, <timeout>)
Identical to overloadt, but additionally resolves as a hold in the event of
an intervening key tap.
The end result is the following stanza in my NixOS configuration:
keyd = {
enable = true;
keyboards.default = {
ids = [ "*" ];
settings = {
main = {
# capslock -> (held) ctrl, (tap) ESC
capslock = "overloadt2(control, esc, 150)";
rightalt = "leftalt";
};
shift = {
grave = "G-4"; # S-` -> €
};
};
};
};
The timeout
of 150ms feels “about right” at this point – 100ms was borderline not enough, and 200ms was definitely annoyingly long.
Not to be confused with the elegant, historic, radical RUN/STOP
key of course. C64 FTW!
Honestly, sometimes I really am that slow. I’m old see?