If you use vim or vim-style keybindings, it’s really convenient setting up left caps-lock to function as escape when tapped, but ctrl when chorded, i.e. held together with other keys.

On Windows, most solutions that do this are fully AutoHotKey-based.

However, as a heavy user of Emacs with evil-mode, but with enough ctrl-c chording for conventional Emacs bindings to confuse any keymapping solution, these solutions often broke.

The slightly more robust solution I settled on uses the built-in Windows remapping functionality to remap caps-lock to control (which is active from boot, and for the whole machine), and then a subset of the AutoHotKey approach only to do the ctrl (which is actually caps lock!) to escape translation.

Built-in Windows remap caps-lock to control

First we swap Caps-Lock and Control.

Either run this as admin in powershell:

$hexified = "00,00,00,00,00,00,00,00,02,00,00,00,1d,00,3a,00,00,00,00,00".Split(',') | % { "0x$_"};
$kbLayout = 'HKLM:\System\CurrentControlSet\Control\Keyboard Layout';
New-ItemProperty -Path $kbLayout -Name "Scancode Map" -PropertyType Binary -Value ([byte[]]$hexified);
 

… or apply this .reg file:

REGEDIT4
 
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout]
"Scancode Map"=hex:00,00,00,00,00,00,00,00,03,00,00,00,1d,00,3a,00,3a,00,1d,00,00,00,00,00

Reboot to apply. Enjoy caps-lock as control.

AutoHotKey for Esc

Then we map Control (which is physically Caps-Lock) to Esc, but only when tapped by itself.

Ensure that you have AHK installed, with the below script running.

;; based on https://github.com/fenwar/ahk-caps-ctrl-esc/blob/master/AutoHotkey.ahk
;; modified because I already map capslock to ctrl using windows registry
;; here I'm ONLY adding behaviour that LControl tap results in esc
*LControl::
    Send {Blind}{LControl down}
    return
 
*LControl up::
    Send {Blind}{LControl Up}
    ;; send Esc only when it was LControl by itself
    if A_PRIORKEY = LControl
    {
        Send {Esc}
    }
    Return

Right click on the relevant AHK icon and select reload.