143

In Windows 8 I used to remap my capslock key to control using the registry script

REGEDIT4

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

After having upgraded to Window 10, this does not work anymore.

How can it be done?

Dave M
  • 13,250
chtenb
  • 1,935
  • 2
  • 16
  • 17

13 Answers13

148

In case anyone needed this done via 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);

Run it as Administrator and reboot.

alper
  • 200
115

Did you remember to reboot? It works fine for me, just like in Windows 7 and 8.

Windows Registry Editor Version 5.00

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

glenviewjeff
  • 1,769
hugh
  • 1,166
108

There is now a solution directly from Microsoft for mapping caps lock to the control key called PowerToys. PowerToys does not involve using a third party tool or modifying the registry by hand (which has the potential for causing serious problems if done incorrectly). The tool in PowerToys that handles this is installed by default and called Keyboard Manager. It works exactly as expected - here is an image of the Caps Lock key mapped to the Ctrl key.

enter image description here

Snap Shot
  • 2,438
32

You can use SharpKeys to map any key to any other key in Windows 7, 8, or 10. It's much easier and cleaner to do than to modify the registry yourself.

Hope this helps.

moeabdol
  • 501
25

I use the following to send CTRL for the CAPS LOCK key, send ALT for the CTRL key, and send CAPS LOCK for the ALT key. CTRL is to the left of "A" where God intended it, ALT is below SHIFT, and the utterly useless CAPS LOCK key is safely tucked away where I have to break my wrist to hit it.

Windows Registry Editor Version 5.00

; The hex data is in five groups of four bytes: ; 00,00,00,00,\ header version (always 00000000) ; 00,00,00,00,\ header flags (always 00000000) ; 04,00,00,00,\ # of entries (3 in this case) plus a NULL terminator line. ; Entries are in 2-byte pairs: Key code to send & keyboard key to send it. ; Each entry is in "least significant byte, most significant byte" order, ; e.g. 0x1234 becomes 34,12 ; 1d,00,3a,00,\ Send LEFT CTRL (0x001d) code when user presses the CAPS LOCK key (0x003a) ; 38,00,1d,00,\ Send LEFT ALT (0x0038) code when user presses the LEFT CTRL key (0x001d) ; 3a,00,38,00,\ Send CAPS LOCK (0x003a) code when user presses the LEFT ALT key (0x0038) ; 00,00,00,00 NULL terminator

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

selurvedu
  • 117
mnemotronic
  • 392
  • 3
  • 8
8

I used to use AutoHotKey to do this.

I'd have a link in the startup directory to run a very basic ahk script:

Capslock::Ctrl

The thing is, Autohotkey isn't run as Administrator so it won't affect privileged windows, unless you use the task scheduler instead of the startup directory to run the script at login with higher privileges. The second problem is that sometimes, the script hangs when resuming sleep, so you may need to reload it, which is annoying.

AutoHotKey is better suited for more complex tasks, like writing macros.

loxaxs
  • 229
8

The inexhaustible sysinternals toolbox also provides a little program just for switching capslock with control -- Ctrl2Cap

Ctrl2cap is a kernel-mode device driver that filters the system's keyboard class driver in order to convert caps-lock characters into control characters.

It has a long history, but does work on Windows 10, including 64-bit. You run it once to install the driver.

Ed Avis
  • 488
Ernst
  • 81
  • 1
  • 1
7

If, for some reason, you don't want to run third-party tools, it's possible to do this yourself with a bit of C. Thanks to Susam Pal's brilliant answer, I put the snippet below together.

It's practically a key-logger. It listens for key presses, captures them, and constructs keyboard input with the mapping in mind. The below console app need to be running for it to work.

You will need to compile this somehow. I used msys2.org with pacman -S mingw-w64-x86_64-gcc and compiled with /mingw64/bin/gcc nocaps.c -o nocaps.exe.

#include <stdio.h>
#include <windows.h>

HHOOK hook;

#define KEYCODE_CAPSLOCK 20
#define KEYCODE_LCTRL 162

LRESULT CALLBACK keyboardHook(int nCode, WPARAM wParam, LPARAM lParam)
{
    KBDLLHOOKSTRUCT *p = (KBDLLHOOKSTRUCT *) lParam;
    INPUT input = {.type = INPUT_KEYBOARD };

    printf("nCode=%d\t wParam=%d\t p->vkCode=%lu \t p->scanCode=%d\t\n", nCode, wParam, p->vkCode, p->scanCode);


    if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
        input.ki.dwFlags = KEYEVENTF_KEYUP;
    }

    if (p->vkCode == KEYCODE_CAPSLOCK && (p->flags & LLKHF_INJECTED) == 0) {
        input.ki.wVk = KEYCODE_LCTRL;
        SendInput(1, &input, sizeof (INPUT));
        return 1;
    } else if (p->vkCode == KEYCODE_LCTRL && (p->flags & LLKHF_INJECTED) == 0) {
        input.ki.wVk = KEYCODE_CAPSLOCK;
        SendInput(1, &input, sizeof (INPUT));
        return 1;
    }

    return CallNextHookEx(hook, nCode, wParam, lParam);
}

int main(int argc, char **argv)
{
    MSG messages;

    hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboardHook, NULL, 0);
    if (hook == NULL) {
        printf("Error %d\n", GetLastError());
        return 1;
    }

    printf("Mapping CAPSLOCK=>LCTRL and LCTRL=>CAPSLOCK..\n");
    while (GetMessage (&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }
    return 0;
}
kristianlm
  • 179
  • 1
  • 2
6

This is the script to swap CTRL and CAPS LOCK keys:

Windows Registry Editor Version 5.00

[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
ady
  • 161
2

You can use lswitch to remap language input to CapsLock.

Use any key to switch input languages, usage: lswitch [keycode]. Keycode is optional and defaults to context menu key. Another good candidate is a CapsLock key with a keycode of 20.

lswitch 20

Add it to autoload.

Dave M
  • 13,250
Andrew K.
  • 121
2

Several solutions. None of these require rebooting, since they use keyboard hook interface.

If you want to remap Caps Lock to Ctrl for programs running as administrator, you also need to run this program as administrator.

Uncap

Download uncap.exe from https://github.com/susam/uncap/releases , then in the terminal type uncap 20:17. It will close the terminal (cmd or powershell), but keep running in the background.

Type uncap --help for help.

dual-key-remap

Download from https://github.com/ililim/dual-key-remap/ .

Solution in PowerShell

The technique to use Add-Type to run arbitrary C# code is taken from https://www.tarlogic.com/blog/how-to-make-keylogger-in-powershell/ https://hinchley.net/articles/creating-a-key-logger-via-a-global-system-hook-using-powershell .

Just type the following in PowerShell. (To open PowerShell, press Windows+X, then select "PowerShell")

Add-Type @"
    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
public class CapsLockToCtrl {
    private const int WH_KEYBOARD_LL = 13;
    private const int WM_KEYDOWN = 0x100;
    private const int WM_KEYUP = 0x101;
    private const int VK_CAPITAL = 0x14;
    private const int VK_CONTROL = 0x11;
    private const int KEYEVENTF_KEYUP = 0x2;

    public static void Main() {
        IntPtr hookId = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookCallback, GetModuleHandle(null), 0);
        Application.Run();  // unless there's Application.Exit() call somewhere this will run indefinitely
        UnhookWindowsHookEx(hookId);
    }

    private delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);  // https://stackoverflow.com/a/3146691

    [StructLayout(LayoutKind.Sequential)]
    private struct KBDLLHOOKSTRUCT
    {
        public uint vkCode;
        public uint scanCode;
        public uint flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }

    private static IntPtr KeyboardHookCallback(int nCode, UIntPtr wParam, IntPtr lParam) {  // 
        if (nCode &gt;= 0 &amp;&amp; (wParam == (UIntPtr)WM_KEYDOWN || wParam == (UIntPtr)WM_KEYUP)) {
            KBDLLHOOKSTRUCT kbdStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
            if (kbdStruct.vkCode == VK_CAPITAL) {
                keybd_event(VK_CONTROL, 0, ((int)wParam == WM_KEYDOWN) ? 0u : KEYEVENTF_KEYUP, 0);
                return (IntPtr)1; // Block the original key press
            }
        }

        return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
    }

    [DllImport(&quot;user32.dll&quot;)]
    private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

    [DllImport(&quot;user32.dll&quot;)]
    private static extern bool UnhookWindowsHookEx(IntPtr hhk);

    [DllImport(&quot;user32.dll&quot;)]
    private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

    [DllImport(&quot;kernel32.dll&quot;)]
    private static extern IntPtr GetModuleHandle(string lpModuleName);

    [DllImport(&quot;user32.dll&quot;)]
    private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);
}

"@ -ReferencedAssemblies System.Windows.Forms

Keep the window open (possibly minimized), otherwise it will stop working.

Or, minified version if you prefer. (in case you cannot copy-paste/have Internet access and have to type in the code manually)

Add-Type @"
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class C {
    public static void Main() {
        SetWindowsHookEx(13, B, GetModuleHandle(null), 0); Application.Run();
    }
    delegate IntPtr L(int n, UIntPtr w, IntPtr l);
    private static IntPtr B(int n, UIntPtr w, IntPtr l) {
        if ((int)w<258&&Marshal.ReadInt32(l)==20){ keybd_event(17, 0, (int)w==256?0u:2, 0); return (IntPtr) 1; }
        return CallNextHookEx((IntPtr)0, n, w, l);
    }
    [DllImport("user32.dll")] static extern IntPtr SetWindowsHookEx(int i, L f, IntPtr h, uint d);
    [DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr i, int n, UIntPtr w, IntPtr l);
    [DllImport("kernel32.dll")] static extern IntPtr GetModuleHandle(string n);
    [DllImport("user32.dll")] static extern void keybd_event(byte v, byte s, uint f, uint e);
}
"@ -ReferencedAssemblies System.Windows.Forms
[C]::Main()

Solution in C#

Save the following into a file named a.cs.

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class CapsLockToCtrl { private const int WH_KEYBOARD_LL = 13; private const int WM_KEYDOWN = 0x100; private const int WM_KEYUP = 0x101; private const int VK_CAPITAL = 0x14; private const int VK_CONTROL = 0x11; private const int KEYEVENTF_KEYUP = 0x2;

public static void Main() {
    IntPtr hookId = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookCallback, GetModuleHandle(null), 0);
    Application.Run();  // unless there's Application.Exit() call somewhere this will run indefinitely
    UnhookWindowsHookEx(hookId);
}

private delegate IntPtr LowLevelKeyboardProc(int nCode, UIntPtr wParam, IntPtr lParam);  // https://stackoverflow.com/a/3146691

[StructLayout(LayoutKind.Sequential)]
private struct KBDLLHOOKSTRUCT
{
    public uint vkCode;
    public uint scanCode;
    public uint flags;
    public uint time;
    public IntPtr dwExtraInfo;
}

private static IntPtr KeyboardHookCallback(int nCode, UIntPtr wParam, IntPtr lParam) {  // 
    if (nCode &gt;= 0 &amp;&amp; (wParam == (UIntPtr)WM_KEYDOWN || wParam == (UIntPtr)WM_KEYUP)) {
        KBDLLHOOKSTRUCT kbdStruct = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
        if (kbdStruct.vkCode == VK_CAPITAL) {
            keybd_event(VK_CONTROL, 0, ((int)wParam == WM_KEYDOWN) ? 0u : KEYEVENTF_KEYUP, 0);
            return (IntPtr)1; // Block the original key press
        }
    }

    return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}

[DllImport(&quot;user32.dll&quot;)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport(&quot;user32.dll&quot;)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport(&quot;user32.dll&quot;)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, UIntPtr wParam, IntPtr lParam);

[DllImport(&quot;kernel32.dll&quot;)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

[DllImport(&quot;user32.dll&quot;)]
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);

}

Then in a terminal type:

"C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe" a.cs
a.exe

The caveat with having to keep the window open, and using Ctrl+C instead of Caps+C to terminate the program, applies.

Solution in C

This will only work if there's a C compiler installed.

Quite similar to the answer above https://superuser.com/a/1490007/577463 , but minified in case you cannot copy-paste/have Internet access and have to type in the code manually. The difference is that Ctrl is not mapped back to Caps Lock.

Save the following to a file named for example a.c.

#include<windows.h>
LRESULT f(int n,WPARAM w,LPARAM l){
    return n>=0&&w<258&&*(int*)l==20 ? keybd_event(17,0,w%2*2,0),1: CallNextHookEx(0,n,w,l); }
int main(){
    SetWindowsHookEx(13,f,GetModuleHandle(0),0);
    MSG m;while(GetMessage(&m,0,0,0)) TranslateMessage(&m),DispatchMessage(&m); }

Then type in the terminal gcc a.c -o a, press enter, then type a, press enter.

Note that if you use Caps+C to terminate the program a, you can, but then the Ctrl key will not be released. Use Ctrl+C to terminate it instead.

Unminified version

#include <Windows.h>

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode >= 0 && (wParam == WM_KEYDOWN || wParam == WM_KEYUP)) { KBDLLHOOKSTRUCT* kbdStruct = (KBDLLHOOKSTRUCT*)lParam; if (kbdStruct->vkCode == VK_CAPITAL) { // keybd_event(VK_CONTROL, 0, wParam == WM_KEYUP ? KEYEVENTF_KEYUP : 0, 0); INPUT input = {.type = INPUT_KEYBOARD, .ki = { .wVk = VK_CONTROL, .dwFlags = wParam == WM_KEYUP ? KEYEVENTF_KEYUP : 0 }}; SendInput(1, &input, sizeof(INPUT)); return 1; // Block the original key press } } return CallNextHookEx(NULL, nCode, wParam, lParam); }

int main() { HHOOK hookId = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0); MSG msg; while (GetMessage(&msg, NULL, 0, 0) != 0) { TranslateMessage(&msg); DispatchMessage(&msg); } UnhookWindowsHookEx(hookId); return 0; }

0

I put together a cmd batch file, install-keyboard.bat (latest version in https://gitlab.com/0mid/dotfiles/-/blob/master/install-keyboard.bat), to map the CapsLock and the right "Windows key" both to Left Ctrl (LCtrl).

Hopefully, the variable names for the key codes and clarifying comments (incorporating those from @mnemotronic here and the 'official' docs, in addition to mine) make this script useful for (and extensible by) others who are forced to use this proprietary operating system (Windows).

This batch file needs to be run in an Admin cmd, and a sign-out sign-in (NOT a restart) is needed for the new mappings to take effect. There doesn't seem to be an equivalent key per user (HKCU), so this mapping affects the whole local machine (HKLM).

I'm having to do this because AutoHotKey is working unreliably (otherwise it is great, free software, https://www.gnu.org/philosophy/free-sw.html, and doesn't need Admin to 'install', use or take effect either). In particular, although, in AutoHotKey, I mapped the "Windows key" to Ctrl, Windows+d (expected to be Ctrl-d) keeps being 'intercepted' by Windows first instead, causing its multiple desktop to be displayed (which I couldn't find a way to disable), or worse a broken keyboard state to happen in which most keys won't even function.

@echo off
setlocal

net session >nul 2>&1 || (echo This script requires Admin.&goto :eof)

rem Unfortunately, as the key "Keyboard Layout" HAS TO be written rem under HKEY_LOCAL_MACHINE..., this needs Admin.

rem Also unfortunately, as the mappings are apparently read by the rem keyboard driver at session start-up, once the mapping is stored in rem the registry, a log out/log in was needed for the mapping to take rem effect. Restarting explorer.exe did NOT do it. Microsoft docs rem below says a restart is needed, which wasn't.

rem From rem https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/keyboard-and-mouse-class-drivers#scan-code-mapper-for-keyboards:

rem ---

rem Windows 2000 and Windows XP include a new Scan Code Mapper, which rem provides a method that allows for mapping of scan codes. The scan rem code mappings for Windows are stored in the following registry rem key: syntax

rem HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout

rem Note There is also a Keyboard Layouts key (notice the plural form) rem under the Control key, but that key should not be modified.

rem In the Keyboard Layout key, the Scancode Map value must be added. rem This value is of type REG_BINARY (little Endian format) and has rem the data format specified in the following table.

rem |Start offset (in bytes) | Size (in bytes) | Data | rem |0 | 4 | Header: Version Information | rem |4 | 4 | Header: Flags | rem |8 | 4 | Header: Number of Mappings | rem |12 | 4 | Individual Mapping | rem |... | ... | ... | rem |Last 4 bytes | 4 | Null Terminator (0x00000000) |

rem The first and second DWORDS store header information and should be rem set to all zeroes for the current version of the Scan Code Mapper. rem The third DWORD entry holds a count of the total number of rem mappings that follow, including the null terminating mapping. The rem minimum count would therefore be 1 (no mappings specified). The rem individual mappings follow the header. Each mapping is one DWORD rem in length and is divided into two WORD length fields. Each WORD rem field stores the scan code for a key to be mapped.

rem Note that if the mapping of a scan code is necessary on a rem keypress, the step is performed in user mode just before the scan rem code is converted to a virtual key. Doing this conversion in user rem mode can present certain limitations, such as mapping not working rem correctly when running under Terminal Services.

rem To remove these mappings, remove the Scancode Map registry value rem and reboot.

rem ---

rem The hex data is in five groups of four bytes: rem 00,00,00,00,\ header version (always 00000000) rem 00,00,00,00,\ header flags (always 00000000) rem 03,00,00,00,\ # of entries (2 in this case) plus a NULL terminator line. rem Entries are in 2-byte pairs: Key code to send & keyboard key to send it. rem Each entry is in "least significant byte, most significant byte" order, rem e.g. 0x1234 becomes 34,12 rem 1d,00,3a,00,\ Send LEFT CTRL (0x001d) code when user presses the CAPS LOCK key (0x003a) rem 1d,00,5c,e0,\ Send LEFT CTRL (0x001d) code when user presses the right Windows key (0xe05c) rem 00,00,00,00 NULL terminator

set "CapsLock=3a,00" set "LCtrl=1d,00" set "RCtrl=1d,e0" set "LAlt=38,00" set "RAlt=38,e0" set "LWin=5b,e0" set "RWin=5c,e0" set "Menu=5d,e0"

set "headerVersion=00,00,00,00" set "headerFlags=00,00,00,00" set "numEntries=03,00,00,00" set "mapping1=%LCtrl%,%CapsLock%" set "mapping2=%LCtrl%,%RWin%" set "nullTerminator=00,00,00,00" set "data=%headerVersion%%headerFlags%%numEntries%%mapping1%%mapping2%%nullTerminator%" set "dataNoComma=%data:,=%"

set "key=HKLM\SYSTEM\CurrentControlSet\Control\Keyboard Layout" reg add "%key%" /f /v "Scancode Map" /t REG_BINARY /d %dataNoComma%

echo Sign out and sign in for the new key mappings to take effect.

0mid
  • 177
-1

I would like to share my AutoHotKey solution on Windows 10:

Loop, %0%  ; For each parameter:
  {
    param := %A_Index%  ; Fetch the contents of the variable whose name is contained in A_Index.
    params .= A_Space . param
  }
ShellExecute := A_IsUnicode ? "shell32\ShellExecute":"shell32\ShellExecuteA"

if not A_IsAdmin { If A_IsCompiled DllCall(ShellExecute, uint, 0, str, "RunAs", str, A_ScriptFullPath, str, params , str, A_WorkingDir, int, 1) Else DllCall(ShellExecute, uint, 0, str, "RunAs", str, A_AhkPath, str, """" . A_ScriptFullPath . """" . A_Space . params, str, A_WorkingDir, int, 1) ExitApp }

+Capslock::Capslock ; make shift+Caps-Lock the Caps Lock toggle Capslock::Control ; make Caps Lock the control button