# dual-key-remap: one Windows key that means two different things

> dual-key-remap is a GPL-2.0 C program for Windows that remaps any key to two different outputs depending on context, most usefully CapsLock to Ctrl when held with other keys and Escape when tapped, by hooking at the lowest Windows API level. It suits someone who wants Vim style modal behaviour on Windows, and it needs administrator rights to cover elevated windows.

**ililim/dual-key-remap** — Remap any key to any other two keys on Windows 🔥. Remap CapsLock to both Ctrl and Escape! (It's like xcape for windows!)

- Repository: https://github.com/ililim/dual-key-remap
- Stars: 615 · Forks: 43
- Language: C
- License: GPL-2.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/ililim-dual-key-remap

## The problem with a modifier that is also a key

The program description says it plainly: remap any key to any other two keys on Windows, and the most common use is remapping CapsLock to Escape when pressed alone and Ctrl when pressed with other keys. The README calls it the Windows equivalent of xcape, which is the Linux tool that turns a modifier into a tap and hold key.

That use case comes from a specific irritation. CapsLock is in an awkward place. It is a modifier in the sense that many people never want it to toggle caps, and it is in a good position for a modifier because it sits under the left pinky. But it is not a Ctrl, and every keyboard shortcut that needs a modifier is on the other side of the board, so the natural fix is to make CapsLock a Ctrl, and the natural cost of that is losing the Escape key, which sits in the same spot.

The two-output design resolves that. Press CapsLock on its own and it behaves as Escape. Press it with another key and it behaves as Ctrl. One physical key, two meanings, decided by whether anything else was pressed while it was held down.

The features list names the failure modes this is designed to avoid. Other solutions can leak inputs, get stuck, or do not handle key timing edge cases, and the project's claims are fast with minimal cpu footprint, does not swallow or leak inputs, handles edge cases with correct key timings, support for remapping multiple keys simultaneously, and handles mouse and scrolling correctly.

The audience is narrow and obvious: a Windows user who wants modal keyboard behaviour, typically one who uses Vim or Emacs key bindings in an editor and wants the same muscle memory everywhere else. The implementation is written in C to run at the lowest Windows API level for speed and reliability, and the licence is GPL-2.0.

## The config file is the whole interface

There is no GUI. The entire configuration is a plain text file called config.txt that sits next to the executable, and the wiki documents the key names you can use in it.

The simplest remapping uses two keys:

```
remap_key=CAPSLOCK
to=ESCAPE
```

The two-output form is what the tool is actually for, and it uses when_alone and with_other:

```
# Open Start Menu with Ctrl+Escape chord
remap_key=CAPSLOCK
when_alone=CTRL+ESC
with_other=CTRL
```

The same pattern covers a hyper key, where RIGHT_WIN becomes a Ctrl+Alt+Shift modifier with no action when tapped, and a double-tap Escape, where when_alone=ESCAPE,ESCAPE sends Escape twice. A chord sequence, where when_alone=CTRL+KEY_K,CTRL+KEY_C, sends Ctrl+K then Ctrl+C.

The separator characters carry the semantics and they are worth internalising. A plus means the keys are pressed simultaneously as a chord, and a comma means they are sent in sequence, one after another. The README states the rule for the plus case: all keys are pressed down together and released in reverse order, like holding modifiers. The comma case is the opposite, each step is pressed and released before the next. The two can be combined, so CTRL+C,CTRL+V sends Ctrl+C and then Ctrl+V.

There is one asymmetry to know. The with_other value only supports chords with the plus sign, since modifiers need to stay held. A sequence in with_other is not a thing the tool does.

## Modifier suppression, and the F23 trick it was built for

Some keys arrive at Windows carrying modifiers you did not intend, and the tool has a specific mechanism for that.

The example is the Copilot key on newer laptops. It sends LEFT_WIN+LEFT_SHIFT+F23 as three separate events, and by the time the program intercepts F23, the modifiers have already leaked through to the application. The fix is to name the unwanted modifiers in the remap key with a minus sign:

```
# Copilot key: release Win and Shift, send Ctrl on its own
remap_key=F23-LEFT_WIN-LEFT_SHIFT
when_alone=NOOP
with_other=CTRL
```

The mechanism is a counter-press. When the remap activates, the tool sends a key up event for each suppressed key to cancel it out. The README notes this works for any key that ships with unwanted modifier side effects, so it is not a Copilot-specific patch.

The NOOP value in the when_alone line is worth noticing. It is how you say that this key has no tap behaviour at all, which is what you want for a pure modifier. The Hyper key example uses the same idea with RIGHT_WIN, where when_alone=NOOP and with_other=CTRL+ALT+SHIFT.

The limitation is stated plainly in the same paragraph. The suppression works by intercepting a synthetic key such as F23, and the modifiers that came with it have already been delivered to the application by that point. So the tool is cancelling the leak, not preventing it, and there is a brief window in which the application may already have seen the Win or Shift press.

## The tap timeout, and how the alone branch is decided

The when_alone action is suppressed if the key is held too long, and the setting is timeout_ms:

```
timeout_ms=500
remap_key=CAPSLOCK
when_alone=ESCAPE
with_other=CTRL
```

If you hold CapsLock for more than 500 ms without pressing another key, releasing it sends nothing instead of Escape. The stated purpose is to avoid accidental triggers.

The scoping rule is the detail that matters. The timeout_ms setting can be configured globally, affecting all remaps, or per-key. When it is set before any remap_key it sets the global default, and when it is set after a remap_key but before the next one it applies only to that specific key. That is a positional convention rather than a nested block, so the file has to be read in order.

The other timing rule is the one that explains the Tips and Tricks section. Dual Key Remap decides which key to send depending on whether any other keys were pressed after CapsLock was held down. So tapping CapsLock as the last part of a key sequence always sends Escape, and holding a modifier such as Shift first and then pressing CapsLock sends Shift+Escape as expected. That is how an application requiring Shift+Escape can still be driven with a remapped CapsLock.

Mouse interaction follows the same rule. Mouse clicks and scrolling trigger the with_other behaviour when a dual key is held down, so holding CapsLock and clicking sends Ctrl+Click, and the README states this works with all mouse buttons and scrolling.

## Installing it, the tray icon and the scheduled task

The installation is four steps. Download and unzip the latest release. Put both dual-key-remap.exe and config.txt in a permanent directory of your choice, with C:\Program Files\dual-key-remap given as an example. Download add-to-startup.bat into the same folder and run it as Administrator so the program starts at login. Then edit config.txt if you want and run dual-key-remap.exe.

The batch script does more than add a startup entry, and this is the part that has consequences. If launched normally, Dual Key Remap will not be able to rebind your key inputs while you are viewing escalated or administrator applications such as Task Manager. The add-to-startup.bat script handles this by creating a scheduled task that runs with admin privileges. If you launch manually, you need to run the program as administrator yourself.

That is a genuine trade-off rather than a wart. A key remapper has to sit at a level where it can intercept input for every window, including elevated ones, and the only way to do that on Windows is to run elevated. The cost is that a program which sees every keystroke is running as administrator on your machine, and it is a GPL-2.0 program with a config file and a tray icon.

Uninstallation is correspondingly thorough. Right-click the tray icon and select Exit, then remove the scheduled task with the schtasks delete command naming the DualKeyRemap task, and delete the folder holding the executable and the config.

Debug mode is enabled by setting debug=1 in config.txt or by setting the DEBUG environment variable, and it shows detailed logging of all key events and remapping actions. For a program that sits between your keyboard and every application, that log is how you find out what it did with a key press you did not expect.

## The login screen gap and the Sharp Keys workaround

There is a limitation the documentation is upfront about: Dual Key Remap will not rebind any keys until the application is running, which means that on the login screen and until Windows has fully loaded, your keys are not rebound yet.

The proposed workaround involves another tool. Using a utility like Sharp Keys you can rebind keys in the Windows registry, and while that method only supports simple key-to-key rebinding, those rebindings persist on the Windows login screen.

The suggested sequence is a chain of two tools. Rebind CapsLock to Escape in Sharp Keys. Rebind Escape to Ctrl and Escape in Dual Key Remap. The result is that CapsLock works as Escape during login and early startup, and once Windows is fully loaded the dual key functionality takes over.

It is a clever solution, and it also shows the cost of the architecture. The program's interception cannot exist before it starts, and the registry is the only mechanism Windows offers for a key mapping that applies that early. A team standardising this across many machines has to run Sharp Keys, edit the registry, and keep the two configurations in sync, and Sharp Keys is a legacy utility whose own maintenance status is not discussed here.

For a single user on one machine, the friction is tolerable. For an organisation rolling this out, it is a sign that the tool is a personal utility rather than something designed to be deployed at scale.

## The C source, the test harness and how it is built

The repository layout shows a small, hand-written Windows program in C, and the file names map closely onto the features.

There is dual-key-remap.c as the main program, keys.c and keys.def for the key tables, remap.c for the remapping logic, config.c for the configuration parser, and tray.c for the system tray icon. input.h and resource.h sit alongside, and resources.rc is the Windows resource script. The assets/ directory holds the icon and the application manifest is app.manifest.

The lua/ directory is unexpected and interesting, and the documentation does not explain it in the visible copy.

The build is a plain Makefile driving the Microsoft toolchain, and the targets are readable:

```make
build:
	rc resources.rc
	cl .\dual-key-remap.c resources.res /link user32.lib shell32.lib /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup
```

The link line is the interesting part. Linking user32.lib and shell32.lib and setting the subsystem to WINDOWS with the mainCRTStartup entry point is how you build a Windows GUI program that has no console window, which is what a background tray application needs. The tests target compiles a separate tests.c and runs the resulting executable, and there are fixture files in a fixtures/ directory alongside test_keys.c and a run-tests.bat script.

The build prerequisites are Visual Studio's build tools, and the release target runs a PowerShell script, release.ps1, to package it. There is also a CHANGELOG.md, a CLAUDE.md file, and a config.example.txt, which is the shipped sample configuration.

The version history is steady and recent: v0.12 from 2026-08-12, v0.11 from 2026-03-20 and v0.10 from 2026-02-10, with the last push on 2026-08-12.

## Conclusion

dual-key-remap fits a Windows user who has decided CapsLock should be a modifier rather than a CapsLock, and who wants the tap and hold distinction handled correctly instead of by a shell script that sometimes leaves a key stuck. It is the wrong choice for a Linux or macOS user, for anyone who wants a full remapping layer with layers and per-application profiles, and for anyone unwilling to run a program with administrator rights in a tray. Before adopting it, read the timing rules in the configuration section so you understand when the alone action fires, install it through the scheduled task script so elevated windows are covered, and set timeout_ms to a value that matches how fast you actually press the key.

## FAQ

### How do I remap CapsLock to both Ctrl and Escape on Windows?

Set remap_key=CAPSLOCK with when_alone=ESCAPE and with_other=CTRL in config.txt. The key is then Escape when tapped and Ctrl when pressed together with other keys, with a plus sign meaning a chord pressed together and a comma meaning keys sent in sequence.

### How do I install dual-key-remap?

Download and unzip the latest release, put dual-key-remap.exe and config.txt in a permanent directory, download add-to-startup.bat into the same folder and run it as Administrator to start the program at login, then optionally edit config.txt and run dual-key-remap.exe.

### Why does dual-key-remap not work in Task Manager?

Launched normally it cannot rebind input while you are viewing escalated or administrator applications such as Task Manager. The add-to-startup.bat script handles this by creating a scheduled task that runs with admin privileges, and if you launch manually you need to run the program as administrator yourself.

### What does the timeout_ms setting do in dual-key-remap?

It suppresses the when_alone action if the key is held for longer than the timeout, so holding CapsLock past 500 ms and releasing it sends nothing rather than Escape. The setting is positional: before any remap_key it is the global default, and after a remap_key it applies only to that key.

### How do I remove dual-key-remap from my system?

Right-click the tray icon and select Exit, then delete the scheduled task with schtasks /delete /tn "DualKeyRemap" /f, and remove the executable and config.txt from the directory you put them in.

## Sources

- [ililim/dual-key-remap on GitHub](https://github.com/ililim/dual-key-remap)
- [Issues](https://github.com/ililim/dual-key-remap/issues)
- [License: GPL-2.0](https://github.com/ililim/dual-key-remap/blob/main/LICENSE)
- [README](https://github.com/ililim/dual-key-remap/blob/main/README.md)
- [Releases](https://github.com/ililim/dual-key-remap/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ililim-dual-key-remap
