DECRQM: How to issue requests with DEC Request Mode and get responses back
March 04, 2025
Anyone who has gotten into the weeds regarding terminal emulators knows about ANSI escape sequences. These are useful for, say, printing out bold text from your program on the terminal.
However, escape sequences are not only for controlling output. Escape sequences via STDIN
and STDOUT
form a communications channel between your program and the terminal it's running in. You can use this to get the cursor position, for example.
One thing that took me a minute to connect the dots on, however, was that the technical documentation for VT100 etc. uses different terminology for escape sequences. The important thing to know is that CSI
is intended to indicate the sequence \e[
and that the spaces are typographical only and shouldn't be in your escape sequence.
As a result, you can use echo -ne "\e[?2027\$p"
in WezTerm (for example) to see that Mode 2027 is set (3
). (Remember that in the shell, the $
character indicates a variable substitution, so we have to escape it with a backslash in our example.)
The result is communicated back via STDIN
, which in this case is treated like user input and echoed on the command line. However, within your program, you should be able to capture this without it being visible to the user.
One complicating factor is that Apple's Terminal.app
doesn't seem to support the same set of escape sequences that most terminal emulators do. This is, of course, frustrating.
When running on a local machine, you can detect Terminal.app
by the presence of the TERM_PROGRAM
environment variable, which will be set to Apple_Terminal
. But what if you're running over a secure shell? That's a bit more complicated.
One solution I have in mind is that, if your program is running over ssh
(which you can detect by the presence of the SSH_CLIENT
and/or SSH_CONNECTION
environment variables), you can display brief startup message (e.g., Initializing program...
). However, we can in fact exploit Terminal.app
's oddities by issuing a cursor save/restore sequence that it doesn't support. So we would, in fact, print \e[sInitializing program...\e[u\e[6n
. If \e[s
and \e[u
work, as in most terminals, then the second part of the response should be a 1
for the first column. However, if we get 24
, then we are probably running on Terminal.app
and can make the assumptions we need to.