VTM key codes - the lookup table¶
What this is. What VTINBT hands back for each key, measured on D100 on 2026-08-25 with
SINTRAN/XMSG/SINTRAN-CHAT/KEYPROB.PLNC. No ND document describes any of this - VTM's manual
number is literally "Internal" (ND-20034-1-EN:1248) - so this page is the only record there is.
Read VTM-API-REFERENCE.md first for how to call VTINBT. Two traps there will make it look
as though VTM decodes nothing: type it as three values IN and one OUT, and pass VARIABLES rather
than literals.
1. How this was measured, so it can be repeated¶
1. Set the line's terminal type @SET-TERMINAL-TYPE ,6 (or ,53 for a TDV)
2. Open RetroTerm in the matching emulation - VT100 or TDV2200
3. @KEYPROB
4. Send each key FOLLOWED BY a full stop as a marker
5. Q to stop; the codes print on the way out, in order
THE MARKER IS WHAT MAKES IT TRUSTWORTHY. A full stop is code 46, so a correct run reads
key 46 key 46 .... If the count is wrong, the reading is wrong - and it caught a real fault
here, see ESC[42_ below. Send at most five keys per run and check the count; a twenty-key
sweep drifted silently and produced a table that was wrong from the middle onwards.
Anchor every sweep on a key you already know. ESC[46_ is HJELP and must give 191; ESC[30_
is ANGRE and must give 216. If an anchor lands in the wrong place, discard the run.
2. DEC VT100 - terminal type 6¶
Cursor and editing keys¶
| Key | what the terminal sends | VTM code | seen |
|---|---|---|---|
| cursor UP | ESC [ A (ANSI normal mode) |
28 | 3 runs |
| cursor DOWN | ESC [ B |
11 | 2 runs |
| cursor RIGHT | ESC [ C |
24 | 1 |
| cursor LEFT | ESC [ D |
8 | 1 |
| FIND | ESC [ 1 ~ |
130 | 1 |
| INSERT HERE | ESC [ 2 ~ |
133 | 1 |
| REMOVE | ESC [ 3 ~ |
129 | 1 |
| SELECT | ESC [ 4 ~ |
160 | 1 |
| PREV SCREEN (page up) | ESC [ 5 ~ |
201 | 4 runs |
| NEXT SCREEN (page down) | ESC [ 6 ~ |
197 | 3 runs |
THE KEY NAMES ABOVE ARE DEC'S, CHECKED AGAINST THE VT220 MANUAL (vt100.net, VT220 Programmer Reference, chapter 3, table 3-1). An earlier version of this page called them HOME, DELETE and END. Those are xterm's names, not DEC's, and the distinction is not pedantry - a VT220 keyboard has keys physically labelled FIND, SELECT and REMOVE, and anyone looking for HOME on one will not find it.
AND THESE ARE VT220 KEYS, NOT VT100 KEYS. The same manual says plainly: "In VT100 or VT52 modes the editing keys do not generate codes." A real DEC VT100 has no editing keypad at all and can never send any of these six sequences.
That makes the measurement above interesting rather than wrong: SINTRAN terminal type 6 is "DEC VT100 (80 columns)", and VTM decoded all six anyway. So VTM's type-6 table accepts sequences a genuine VT100 could not produce. Useful in practice - a modern emulator sends them and they work - but it means the type-6 entry is not a strict VT100. SINTRAN's own list has separate entries for the VT220 at types 131 and 132; whether those decode differently has NOT been tested.
Ordinary and control keys¶
| Key | byte sent | VTM code | note |
|---|---|---|---|
| any printable | its ASCII | the same | parity already stripped - A is 65, not 193 |
| TAB | 9 | 9 | passes through |
| RETURN | 13 | 13 | passes through |
| Ctrl-A | 1 | 1 | passes through |
| Ctrl-C | 3 | 3 | passes through - usable as a quit key |
| Ctrl-Z | 26 | 26 | passes through |
| Ctrl-H / Backspace | 8 | 127 | TRANSLATED, see below |
Ctrl-H DOES NOT COLLIDE WITH LEFT ARROW, and that is worth knowing because it looks as though it must. Left arrow reports 8. Backspace also sends byte 8 - but VTM turns it into 127. So the two are distinguishable, and a program that treats 8 as "left arrow" is correct.
ALT IS NOT USABLE¶
ESC + a came back as two codes, 27 then 97 - VTM does not recognise it and the bytes fall
straight through. Do not design around Alt. Whatever it is on the user's keyboard, VTM will
not tell you about it.
The ESC O x family - what it actually is, and why it is NOT settled¶
ESC O is SS3, and in ANSI application mode it prefixes the auxiliary keypad keys
(VT220 manual, table 3-3):
| sequence | key |
|---|---|
ESC O P |
PF1 |
ESC O Q |
PF2 |
ESC O R |
PF3 |
ESC O S |
PF4 |
These are the numeric keypad's PF keys, NOT the function keys F1 to F4 - another label this page had wrong. On a VT220 the keys marked F1 to F5 are local (Hold Screen, Print Screen, Set-Up, Data/Talk, Break) and send nothing at all.
ESC O also prefixes the CURSOR keys when the terminal is in application mode - SS3 A/B/C/D
rather than CSI A/B/C/D. Since VTM sends ESC = (keypad application mode) during start-up,
which of the two forms a given terminal actually sends is a live question and not one this page
can answer yet.
Both sequences really are emitted by the terminal. RetroTerm maps VK_F1..VK_F4 to
ESC O P..ESC O S unconditionally, in both its VT100 and VT220 mappers - so what reached VTM
was well formed, and the asymmetry is in VTM's decoder, not in what was sent. DECKPAM does
not enter into it either: the application-keypad mapping covers only ESC O p..ESC O y and
ESC O j/k/l/m/n/o, so ESC = changed nothing about the uppercase forms.
The naming caveat worth carrying: on real DEC hardware ESC O P..S are the KEYPAD's
PF1..PF4, not the top-row function keys. Modern emulators map the F1..F4 keys onto them by
convention, so "F1" on a PC keyboard and "PF1" on a VT100 keypad are the same four bytes. If VTM
tells them apart it does so by context, not by the sequence.
Measured, and inconsistent: ESC O P gave 29, but ESC O Q returned TWO codes in the same
run. One clean per-key run is needed before any of it is written down. Left here as a warning,
not as data.
3. Tandberg TDV 2200/9 ND-NOTIS - terminal type 53¶
Every special key on this terminal sends ESC [ <n> _, so the whole keyboard can be swept by
varying n rather than by finding a name for each key.
n |
VTM code | key, where known |
|---|---|---|
| 30 | 216 | ANGRE (undo) |
| 31 | 209 | |
| 32 | 139 | |
| 33 | 152 | |
| 34 | 194 | |
| 35 | 146 | |
| 36 | 206 | |
| 37 | 134 | |
| 38 | 25 | |
| 39 | 214 | |
| 40 | 9 | |
| 41 | 203 | |
| 42 | 174 | FUNK (G51, function-shift) - A PREFIX, see below |
| 43 | 159 | SHIFT-FUNK |
| 44 | 161 | SKRIV (G52, "write"/print) |
| 45 | 162 | SHIFT-SKRIV |
| 46 | 191 | HJELP (G53, help) |
| 47 | 165 | SHIFT-HJELP |
| 48 | 163 | SLUTT (G54, "end") |
| 49 | 164 | SHIFT-SLUTT |
| 50 | 132 | F1 |
| 51 | 193 | SHIFT-F1 |
| 52 | 140 | F2 |
| 55 | 149 | F3 |
| 58 | 171 | F4 |
| 60 | 217 | F5 |
| 62 | 204 | F6 |
| 64 | 220 | F7 |
| 66 | 221 | F8 |
A KEY HAS TWO NAMES: ITS LEGEND AND ITS GRID POSITION¶
This confused me for half an hour and it is not a defect. HJELP and G53 send the same
ESC[46_ and give the same 191 because they are the same key: HJELP is the legend printed
on it, G53 is where it sits on the keyboard grid. Same for F1 and F51, F2 and F52,
F3 and F53, F4 and F54 - one key, two names, identical bytes.
And that is why the F-names stop at F54. The F5..F8 legends sit on grid row E, not row F,
so they are E51..E54. There is no F55. The table is not half filled in; the F row simply has
four keys on it.
Confirmed by the RetroTerm session on 2026-08-25 from its own key registry - Reg("F51", "F1")
and Reg("E51", "F5"). Do not report the duplication as a bug; it was already reported and it
is correct behaviour. What WAS wrong was RetroTerm's parameter text saying "F1..F52", which
mixes the two naming systems into a range that matches neither. That has been corrected.
SHIFT IS IN THE SEQUENCE NUMBER, NOT A MODIFIER¶
F1 is ESC[50_ and SHIFT-F1 is ESC[51_ - a different key as far as the wire is concerned, and a
different code (132 against 193). So the sweep above already covers the shifted keys; they are
simply other values of n. There is no modifier bit to combine with anything.
The F-key numbers are not evenly spaced - 50, 52, 55, 58, 60, 62, 64, 66 - and the codes they produce are not ordered at all (132, 140, 149, 171, 217, 204, 220, 221). It is a lookup, not a formula. Do not try to compute one.
ESC[42_ SWALLOWS THE FOLLOWING BYTE - AND THAT IS CORRECT¶
MEASURED twice, on purpose. Sent alone with one marker after it, the marker disappeared entirely. Sent with THREE markers, only TWO came back:
ESC[42_ . . . -> 174, 46, 46
It is the FUNK key - grid G51, legend FUNK, short for funksjon. On a Tandberg keyboard
that is a function-shift: you hold FUNK and press another key, and the pair selects a
function. It is a PREFIX and produces nothing on its own.
So VTM is doing exactly the right thing - it swallows the next byte because it is waiting for the key FUNK modifies. This is not a defect in VTM and not a defect in the terminal. It is, however, a real hazard for a program that reads keys one at a time: press FUNK by accident and the NEXT keystroke vanishes.
Identified from RetroTerm's own key registry, 2026-08-25. It is also what silently corrupted a twenty-key sweep here, which is why the method now uses a marker after every key and checks the count.
3b. THE EMULATOR AND THE HOST AGREE - checked end to end¶
The same key sent two different ways gives the same code. MEASURED 2026-08-25: each key sent
once through RetroTerm's own sendkey (which looks the sequence up in its key registry) and once
as raw bytes with sendraw:
| key | via sendkey |
via sendraw |
agree? |
|---|---|---|---|
| HJELP | 191 | ESC[46_ -> 191 |
yes |
| ANGRE | 216 | ESC[30_ -> 216 |
yes |
| F3 | 149 | ESC[55_ -> 149 |
yes |
That is worth doing because the two paths prove different things. sendraw only tests VTM's
decoder - it says nothing about whether the emulator would ever produce those bytes. sendkey
tests the emulator's key registry as well, end to end from a keyboard legend to a code inside
a PLANC program on the ND.
They agree, which is the first end-to-end check of that registry against a real host. If the two ever disagree for a key, the fault is on the emulator side and worth reporting there.
4. THE CODES ARE NOT THE SAME ACROSS TERMINALS - do not assume they are¶
It is tempting to think VTM normalises every terminal onto one logical key set. That is NOT established, and the evidence so far is against it. None of the 29 TDV codes measured is 201, the VT100's PAGE UP. The sweeps do not overlap in an obvious way at all.
Two honest qualifications:
- the TDV sweep covered
n= 30..52 and a handful of F-keys, NOT the whole range, so a match could be sitting at annnobody has tried; - a TDV has keys a VT100 does not have and the reverse, so a complete mapping cannot exist anyway.
Until somebody sweeps both terminals exhaustively, treat the codes as PER TERMINAL TYPE. A program that must work on both should read its key bindings from a table chosen by terminal type, not hard-code one number per logical key.
And VTM only decodes the terminal it has been told it is talking to. The VT100 ESC [ 5 ~
sent to a line set to type 53 came back as four raw bytes - 27, 91, 53, 126 - undecoded. Two
consequences:
- never treat a stray 27 as a key; it may be the head of a sequence VTM did not recognise and the rest is arriving as separate calls;
- a bare ESC is not a dependable quit key. On a VT100 line, pressing ESC alone did not produce
27 at all.
CHATUIbound its exit to it, became impossible to leave, and its terminal had to be freed withSTOP-TERMINALfrom another session. Use a typed command such as/exit.
5. Two practical notes¶
The rubbish at start-up is VTM's terminal-type negotiation. A program that takes the keyboard
straight after blankscreen reads about eight junk bytes - 63 63 128 103 0 29 63 63 - and they
land in whatever it thinks is its input. On a line whose type was already set with
SET-TERMINAL-TYPE, none of them appear. Either set the type in advance or drain the input
before taking the keyboard.
RetroTerm keeps the two emulations properly separate, so a VT100 session cannot accidentally
send TDV keys - terminal_sendkey refuses outright:
SENDKEY needs a TDV emulator (current: VT100Emulator) - use SENDRAW for other terminals
That was checked deliberately, because if the emulator had been sending TDV sequences on a VT100 line every VT100 figure above would have been meaningless.
See also¶
- VTM-API-REFERENCE.md -
VTINBTand the other 36 routines - PLANC-INTERACTIVE-SCREEN-PATTERNS.md - the polling loop
SINTRAN/XMSG/SINTRAN-CHAT/KEYPROB.PLNC- the probe, and the way to add to this table