Configuration guide

Everything config.toml can say, with worked examples. For installing and using WinRemap, see the help page.

The shape of a config file

The file lives at %APPDATA%\winremap\config.toml unless you pass --config <path>. It is TOML, and it has two kinds of content: a few optional settings sections, and any number of [[keymap]] blocks.

Installed from the Microsoft Store, the file sits in the package's own folder instead — the settings window always names the folder it is really using, and opens it in Explorer for you (the difference between the two channels).

# ---- settings (all optional) ----
[macro]
delay_ms = 8

[ime_indicator]
enabled = true

# ---- keymaps: as many as you like ----
[[keymap]]
name = "global-emacs"          # a label for you and the settings window
application = ["*"]            # which apps this block applies to
exclude = ["windowsterminal.exe"]

[keymap.remap]                 # the rules themselves
"C-p" = "Up"
"C-n" = "Down"

[[keymap]]
name = "jetbrains"
application = ["phpstorm64.exe", "idea64.exe"]

[keymap.remap]
"C-h" = "Back"

Two things about TOML that catch people out here:

After editing, right-click the tray icon and pick Reload config. If the file has a mistake, WinRemap reports it and keeps running the previous config — a typo can never leave you without a keyboard setup.

Editing it without leaving WinRemap

You do not have to open the file at all. Right-click the tray icon, pick Settings, and press Edit: names, target apps, exclusions and rules become fields, keymaps can be added, deleted and reordered, and the general settings sit on sliders and checkboxes.

Not sure what to write in application? Press Capture the foreground app, bring the app you mean to the front within three seconds, and its exe name lands on the list.

Targeting applications

Every [[keymap]] starts by saying which applications it applies to.

KeyMeaning
application A list of exe names, matched case-insensitively against the foreground app — ["notepad.exe", "chrome.exe"]. Or ["*"] for every app. Mixing "*" with names is an error rather than a guess.
exclude Exe names this block does not apply to. Only allowed together with ["*"] — excluding from a list you wrote yourself would just mean writing a shorter list.
name Optional label. It appears in the settings window and in error messages, so naming blocks pays off as soon as you have more than two.

Which block wins

When more than one block could apply, an app-specific block beats a "*" block, regardless of the order they appear in the file. Within the same class, the first match wins.

[[keymap]]
application = ["*"]
[keymap.remap]
"C-h" = "Back"          # everywhere...

[[keymap]]
application = ["code.exe"]
[keymap.remap]
"C-h" = "C-h"           # ...except VS Code, where C-h stays C-h

Only one rule ever fires. If you bind the same input in two blocks that both apply, the settings window shows a column naming the other block — that clash is invisible when reading either one alone.

Finding the exe name

Right-click the tray icon, pick Show log, then switch to the app you want to target. The [window] line prints the exact string to put in application, and which of your keymaps reach it — the full path is on the detail line under it. It is always the file name only — a path does not match.

18:01:51.471 [window]   application = "notepad.exe" — matching keymaps: emacs-keys

Rules

Inside [keymap.remap], the left side is what you press and the right side is what the application receives.

Chord rules and bare-key rules

Which one you get depends on whether the left side has a modifier. The difference matters more than it looks.

Chord ruleBare-key rule
Looks like "C-h" = "Back" "CapsLock" = "LCtrl"
Matches That exact modifier set. Ctrl+Shift+H does not trigger a C-h rule. The key alone, whatever modifiers are held. Ctrl+CapsLock still becomes Ctrl+Ctrl.
Modifiers Replaced to match the output. The app receives a plain Backspace even though Ctrl is physically down. The output may carry its own: "C-i" = "S-Tab". Left alone. The output must therefore be a single key with no modifiers.
Use it for Emacs-style bindings, per-app fixes, anything where the modifier is part of what you pressed. Swapping a physical key: CapsLock→Ctrl, Muhenkan→something useful.

The two coexist. A block can swap CapsLock for LCtrl and still special-case C-CapsLock, because the chord rule is checked first.

Prefix keys (two-stroke sequences)

"A-x u" = "C-z"      # Alt+X, then U    →  undo
"A-x C-s" = "C-s"    # Alt+X, then Ctrl+S  →  save

Write two chords separated by a space. The first is swallowed and WinRemap waits — with no timeout, exactly as Emacs does. The next keystroke completes the binding.

If the second stroke is not one you defined, it is discarded rather than passed through, so a mistyped sequence never leaks a stray character into your document. The first chord must have a modifier: a bare prefix would swallow ordinary typing.

Macros

"C-t" = ["C-Right", "C-Left", "C-S-Right"]   # up to 8 commands

An array output taps each command once, in order. The whole run is injected as a single batch, so your own typing cannot interleave with it. A macro fires once per press — holding the key does not repeat it.

If an app drops some of the strokes (the WinUI Notepad does), see [macro] delay_ms below.

Settings sections

[macro]

[macro]
delay_ms = 8            # 0–15, default 0

record_start = "S-F10"  # macro recording — see the help page
# record_stop = "S-F11" # omit it and the start key stops it too
record_play  = "F10"
KeyMeaning
delay_ms Pause between the commands of a macro, in milliseconds (0–15). Leave it at 0 unless an app is dropping strokes; 8 is enough for the WinUI Notepad. It paces replayed recordings too. The --macro-delay command-line flag overrides it for experiments.
record_start
record_stop
record_play
Keys for macro recording. Recording is off until you name record_start and record_play; record_stop defaults to the start key, which makes it a toggle. These keys are taken before any keymap lookup, so binding one of them in [keymap.remap] as well is an error.

[ime_indicator]

[ime_indicator]
enabled = true                # default: false
# trigger_keys = ["C-Space"]  # if you toggle the IME with Ctrl+Space
# duration_ms = 800           # 100–5000
# size = 96                   # 32–256
# opacity = 200               # 0–255
# show_app_name = true        # exe name under the glyph

Flashes a translucent 「あ」 panel at the centre of the active window when the IME turns on. Standard IME keys are detected already; add trigger_keys for a custom toggle such as the Windows 11 IME's Ctrl+Space option. Display only — WinRemap never switches the IME.

Key notation

Rules use the Emacs-style notation familiar from Keyhac/fakeymacs: modifier prefixes plus a key name, e.g. C-h, A-S-F5, W-Space. Everything is case-insensitive, and prefix order does not matter — C-S-h and S-C-h are the same chord.

PrefixModifier
C-Ctrl (either side)
A-Alt (either side)
S-Shift (either side)
W-Win (either side)
GroupKey names
Letters / digitsaz, 09
FunctionF1F24
EditingBack (Backspace, BS), Tab, Enter (Return), Esc (Escape), Space, Insert (Ins), Delete (Del)
NavigationPageUp (PgUp), PageDown (PgDn), Home, End, Left, Up, Right, Down
LockCapsLock
SymbolsThe character printed on the key (;, /, -, [, …). Aliases: Oem1Oem8, Oem102, OemPlus, OemComma, OemMinus, OemPeriod
Modifiers as output keysLCtrl, RCtrl, LShift, RShift, LAlt, RAlt, LWin, RWin, Apps (Menu) — output side only

Not supported: modifier keys on the input side of a rule.

Symbol keys follow your keyboard

Write a symbol key as the character printed on it. "C-;" = "Enter" means the key engraved ;.

Which key that is genuinely differs between keyboards: one and the same key prints ; on a US layout and : on a Japanese one. WinRemap does not assume — it asks Windows when it loads your config. So the same file picks the key that matches what is engraved on the keyboard in front of you.

A character that needs Shift is written with it.

On a US keyboard @ is Shift+2, so the rule is "C-S-2", not "C-@". WinRemap will not quietly add the Shift for you; it says so, with the spelling to use:

`@` needs Shift on this keyboard; write `S-2` instead

On a Japanese keyboard @ is a key of its own, so "C-@" is exactly right there. Different layout, different spelling — because the keyboards really are different, and hiding that would cost you more than knowing it.

To carry one config between machines with different keyboards, use the aliases: "C-OemMinus" names the same key on any layout. They are also the only way to reach a key with nothing printed on it (Oem8), or to pick between two keys that print the same character — a US layout has \ on two of them.

Changed keyboards? Choose Reload config from the tray menu; the question is asked again each time the config is read. The key-notation panel in the settings window () lists the symbol keys of the keyboard you have attached right now.

Worked examples

Each of these is a complete, self-contained block you can paste into your config.

CapsLock as another Ctrl

[[keymap]]
name = "capslock"
application = ["*"]

[keymap.remap]
"CapsLock" = "LCtrl"

A bare-key rule, so it holds whatever else you are pressing. CapsLock stops toggling caps entirely — it is Ctrl now, including when held for a chord.

Ctrl+H as Backspace, except where the terminal needs it

[[keymap]]
name = "backspace"
application = ["*"]
exclude = ["windowsterminal.exe", "mintty.exe"]  # these send their own 0x08

[keymap.remap]
"C-h" = "Back"

The problem WinRemap was written for. In an editor Ctrl+H usually opens Find & Replace; here it deletes one character instead. Terminals are excluded because they already do the right thing.

Emacs cursor movement everywhere

[[keymap]]
name = "emacs-motion"
application = ["*"]
exclude = ["windowsterminal.exe", "emacs.exe"]

[keymap.remap]
"C-p" = "Up"
"C-n" = "Down"
"C-b" = "Left"
"C-f" = "Right"
"C-a" = "Home"
"C-e" = "End"
"C-d" = "Delete"
"C-k" = ["S-End", "C-x"]     # kill to end of line, into the clipboard
"C-y" = "C-v"                # yank

Note C-k: a macro, because "kill line" is two operations — select to the end of the line, then cut. Exclude apps that already speak Emacs, or you will fight them.

One app that needs the opposite

[[keymap]]
name = "vscode"
application = ["code.exe"]

[keymap.remap]
"C-p" = "C-p"     # VS Code's quick-open: hand it back untouched

An app-specific block beats the "*" block above without needing to be written after it. Mapping a chord to itself is the idiom for "this app is an exception".

Emacs-style prefix commands

[[keymap]]
name = "prefix"
application = ["*"]

[keymap.remap]
"A-x u" = "C-z"        # Alt+X U  →  undo
"A-x s" = "C-s"        # Alt+X S  →  save
"A-x C-c" = "A-F4"     # Alt+X Ctrl+C  →  close the window

Anything you press after Alt+X that is not listed is discarded, so a slip never types into your document.

Selecting a word with one key

[[keymap]]
name = "select-word"
application = ["*"]

[keymap.remap]
"C-t" = ["C-Right", "C-Left", "C-S-Right"]

Jump past the word, back to its start, then select to its end — which lands the caret on a whole word no matter where in it you started. Up to 8 commands per macro.

Recording a one-off repetitive edit

[macro]
delay_ms = 8
record_start = "S-F10"   # Shift+F10 starts, and stops
record_play  = "F10"     # F10 replays

For edits not worth a permanent rule: press Shift+F10, do the work once, press it again, then press F10 down the rest of the file. The recording holds 20 commands, lives in memory only, and is gone when WinRemap exits. See macro recording for what it does and does not capture.

A starting file

Two examples ship with WinRemap and are worth reading end to end: minimal.toml (one rule, for checking that WinRemap is alive) and emacs.toml (a full fakeymacs-style set).

When the config is wrong

Every problem is reported at once, each with a line number, so one pass through the file fixes them all. A failed reload keeps the previous working config.

What it saysWhat it means
unknown key `Bogus` The name is not in the key notation table. If it is a symbol, your keyboard does not print it on any key — check the panel in the settings window for the ones it does.
`@` needs Shift on this keyboard; write `S-2` instead That character is on a key's shifted face. Write it with the Shift, exactly as the message spells it (symbol keys).
modifier key `LCtrl` cannot be a remap input A modifier alone cannot be the left side of a rule. It can be an output.
a bare-key rule's target may not have modifiers The left side had no modifier, so this is a bare-key rule, and those leave the modifier state alone. Add a modifier to the left side to make it a chord rule.
duplicates an earlier rule for the same key Two rules in one block resolve to the same chord — "C-h" and "c-H" are the same thing.
is already used as a sequence prefix The same chord is both a plain rule and the first stroke of a sequence. WinRemap cannot tell whether to fire it or wait for a second key.
`exclude` requires application = ["*"] You listed apps and then excluded some. Just write the shorter list.
is also remapped in `…`; recording keys are always taken first A recording key is also bound in a keymap. That rule could never fire, and nothing in the file would show it — so it is rejected instead.

Still stuck? The tray's Show log window prints what WinRemap does with each keystroke, and the Settings window shows the config actually in effect — which is the fastest way to see that a reload did not happen.

Reading the log

Every line carries the time to the millisecond and a tag saying which stream it belongs to. [decided] is one line per key; tick Every event for the whole stream — [input] for the presses and releases that arrived, [injected] for everything WinRemap sent in reply. Everything is recorded either way, so ticking the box explains the keys you already pressed.

18:01:51.517 [input]     n ↓
             [decided]   C-n → remapped to Down
             [injected]    LCtrl ↑ (modifier adjust)
             [injected]    Down ↓ (remap)

Where a key or a chord carries an ASCII control code, the log says so: C-h (BS 0x08), Enter (CR 0x0D). Letters and digits never show a code — WinRemap logs keys, not what you typed.

Ctrl+H, Backspace and terminals. A terminal sends DEL 0x7f for the Backspace key and BS 0x08 for Ctrl+H, and an application may bind the two to different things — which is the problem WinRemap was written for. Remapping C-h to Back makes your terminal send 0x7f for both. The log shows BS 0x08 on both sides because that is the code Windows itself gives the Backspace key; the 0x7f is the terminal's own doing, further down the line.