Reading responses to ANSI escape codes

When writing an ANSI escape code to the console like this:

julia> print(stdout, "\033[14t");

One gets an answer back that is also written to the console. In this case we get

^[[4;1012;1419t

which is the width and height of my console.
Now I was wondering how I can use this to query for my console size, i.e how can I ensure that the answer is not directly written to the console but to some variable and how would I do such thing in a thread save way, so that other processes that might try to read from stdin do not interfere with my answer.

Maybe you can use displaysize?

Unfortunately that only gives me the number of rows/columns but I need the number of pixels. But I looked into the code for displaysize and it uses ioctl with TIOCGWINSZ internally (which is the second approach I am exploring right now). That call actually returns the number of pixels, but that information is then just thrown away.

Oh, then I misunderstood your question. Why do you want the pixel size of the terminal? I.e. what will you use the information for? Just out of curiosity, I don’t know of a way to retrieve that information.

It s for a package of mine that allows one to display images in a terminal when using the Kitty terminal emulator. So I thought It might be a good idea to have the ability to scale some output to the width of the terminal.

Also, there might be other information that I could get out using escape sequences, for example the name of the terminal emulator.

Bumping this… In 2024, are there any new recommendations for getting the response from ANSI escape codes?

To read, the console needs to be in raw mode otherwise it buffers the response from the query. I was able to read the response using the following:

function query_terminal(s :: String, esc :: Char) :: String
    run(`stty raw`)
    print(s)
    out :: Vector{Char} = []
    while true
        push!(out, read(stdin, 1)[begin])
        (out[end] == esc) && break
    end
    run(`stty cooked`)
    String(out)
end

Probably not the most effective way to do this, but it works.

I use a similar approach to read my local system clipboard on remote machines:

using Base64: base64decode
using REPL: Terminals

function pbpaste()
    # Put term in raw mode:
    term = Terminals.TTYTerminal("xterm", stdin, stdout, stderr)
    Terminals.raw!(term, true)
    Base.start_reading(stdin)

    # Request the system clipboard with OSC52:
    print(stdout, "\e]52;c;?\a")

    # Decode the response:
    data = readuntil(stdin, "\e\\")
    startswith(data, "\e]52;c;") || return ""
    return String(base64decode(chopprefix(data, "\e]52;c;")))
end

The raw mode part is borrowed from TerminalExtensions.jl. This is really fast (< 1 ms). It uses undocumented functions, though.

The terminal exits the raw mode when the function returns. That’s handy if something goes wrong. I can hit ^C and the terminal goes back to a functional state.

There should be probably a timeout to make it more robust but as you said, it works.