Skip to content

Editing Layouts

reminiscience edited this page Aug 13, 2026 · 1 revision

Editing layouts

layouts.json holds the layouts themselves – every layer, every key. Changing it needs no rebuild: edit, restart AnaNeo.

Four layouts ship: Neo, NeoQwertz, Noted (six layers each) and AnNoted (21). Put your own changes in AnNoted. The other three reproduce published standards and are deliberately left alone; if you change them, you no longer have a reference to compare against.

Structure of a layout

Field Meaning
name as shown in the tray menu
dllName (optional) the native driver DLL this layout corresponds to
modifiers scan code → modifier, for every modifier key including the native ones
layers the modifier combination each layer requires
capslockableKeys scan codes affected by Capslock – normally the letter keys
map scan code → one entry per layer

Scan codes are hexadecimal. A trailing plus sets the extended bit: 36+ is the right Shift key.

dllName and extension mode

Extension mode is chosen by the native driver's DLL name: if Windows reports kbdnoted.dll, AnaNeo activates the layout carrying that dllName. AnNoted carries it, which is why typing against the native Noted driver gets you AnNoted and its 21 layers. Noted has no dllName and is available in standalone mode only.

Only one layout per dllName. With two, the last one in the file silently wins, and the mistake only shows up as the wrong layout in extension mode. A test enforces uniqueness.

layers

Each entry is the modifier state that layer requires. Layers are tested in order, and the first one whose conditions all match wins:

{ "Shift": false, "Mod3": false, "Mod4": false, "Mod5": true, "Mod6": false, "Mod7": false, "Mod8": false, "Mod9": false }

Three rules that are easy to get wrong:

Name every block modifier explicitly. A layer that does not mention a currently locked modifier is skipped entirely. Without that rule layer 1 would shadow every locked layer; with it, "applies regardless of the block" is not expressible – except through the escape hatch below.

"ignoreLocks": true exempts one layer from that rule. It is not a modifier; it is a mark. Exactly one layer uses it – layer 21, which has to stay reachable from inside every locked block. Without the mark, nothing changes.

Adding a layer means adding an entry to every line of map. Missing entries are filled in as blank keys and reported in the debug log – the layout still starts, but that layer is silent on those keys. For the shipped layouts a test demands complete lines.

map entries

{ "keysym": "aacute", "char": "á" }
{ "keysym": "Left", "vk": "VK_LEFT" }
{ "keysym": "U24E7", "char": "", "label": "" }
{ "keysym": "c", "vk": "VK_KEY_C", "mods": { "LCtrl": true } }
Field Meaning
keysym X11 keysym, named from keysymdef.h or as UXXXX for a code point. Used by compose.
char the character to produce – or
vk a Windows virtual key from the VKEY enumeration in source/mapping.d
label (optional) the on-screen label; falls back to char
mods (optional, vk only) native modifiers to force pressed (true) or released (false)

char or vk?

Keys that Windows must see as a real keypress stay vk mappings – anything that shortcuts, games or applications react to as a key code rather than as text. This is why the base layers are vk mappings even though they show letters: without it, applications would stop seeing real keypresses and Ctrl+C or Alt+Tab would deliver characters instead of codes.

Pure character output is a char mapping.

Five things that will cost you an hour

An empty cell {} becomes VK_VOID (0xFF), not 0. A test checking for 0 will never fire.

Invisible characters go in as JSON escapes" ", never typed literally. A literal no-break space is indistinguishable from an ordinary one in a diff. This is not hypothetical: a space-bar cell once carried an ordinary space while its keysym promised a hair space, and the diff showed nothing.

Do not rewrite the file with a JSON serialiser. Scripting a change by parsing and re-dumping silently turns \uXXXX escapes into real characters and destroys the formatting. Replace individual cells in the raw text instead.

A cell needs keysym and char. A keysym-only cell works when typed but counts as empty for display, so it appears on neither the layout sheet nor the on-screen keyboard – present but invisible. This was found the hard way when adding compose dead keys to layer 12.

label is the icon, not the truth. The keypad's multiply key is labelled × (U+00D7) but types * (U+002A). Read the code point on the layout sheet, never the label.

Creating a new layout

  1. Copy an existing layout and change the name.
  2. Reorder the letter lines so they follow the physical keyboard, top left to bottom right.
  3. Block-select the scan codes from an existing layout and paste them over the new one's.
  4. Block-select layers 3 and 4 from the existing layout and paste them over the new one's.
  5. Adjust modifiers and capslockableKeys.

Layers 3 and 4 then stay where they were while the other layers permute with the new letter arrangement.

This regular expression helps align the columns of a six-layer layout:

"[\dA-Fa-f]+\+?": *\[(\{.*?\}, *){5}\{

Adjust the repetition count for more layers – {20} for the 21 layers of AnNoted.

Checklist for a single remapping

  1. Find the scan code on the layout sheet: ananeo-tool sheet AnNoted, or the tray menu entry while AnaNeo is running.
  2. Edit layouts.json as text.
  3. Check what you typed, byte by byte. Editors and tooling normalise \uXXXX escapes without saying so.
  4. Look at the sheet again. The code point under the key is the truth.
  5. Type it, in both modes. Standalone and extension mode use different sending paths – a character can work in one and not the other.
  6. For Cyrillic, Greek and anything with lookalikes, check by code point, not by eye. а and a are indistinguishable on screen.

Did you just create a duplicate?

Assigning a character to a key may put it on a layer where the compose tree already reaches it. That is usually fine – the rule is that the layer carries the grip and the tree carries the derivation, so two ways to the same character are two paths, not a contradiction, as long as the compose sequence is part of a system.

To check:

ananeo-tool compose spiegel --layout AnNoted

If your new cell appears there as Einzelfall (an isolated case), its compose sequence stands alone and is a candidate for deletion. As Reihe or Muster – part of a series or a pattern – nothing needs doing. The rule never decides against the layer: it can suggest cutting a compose line, never that a key be cleared.

Clone this wiki locally