1

When you write \e[6m (the "tell-me-where-I-am" code) to a compliant terminal, it will write back \e[<ROW>;<COLUMN>R, eg \e75;1R.

NOTE: all the \es here are the escape code, not literal "slash ee"s, so what's actually written back is garbled if not captured.

In bash, you can read & output this via:

#!/bin/bash

echo -en "\E[6n";
read -sdR CURTEXT;
CURPOS=${CURTEXT#*[};
echo $CURPOS;

Or the one-line CLI command echo -en "\E[6n"; read -sdR ROW_COL_RAW; ROW_COL=${ROW_COL_RAW#*[}; printf "%b\n" $ROW_COL;.

NOTE: I got this from https://stackoverflow.com/a/6003016/3969962.

While bash can obtain this text via read, I can't figure out how to obtain it from C++. The escaped coordinate text is still being written by the terminal itself, but I can't read these characters from std::cin, even by fread().


  1. How, and where, is this text actually being written (by the terminal iteslf)?
  2. Is there some way I can read this data in C++ (eg, to a std::string)? (hopefully portably and without race conditions).

My goal is to determine the current tab width so that output can be nicely displayed. I got the terminal dimensions via ioctl(0, TIOCGWINSZ, &win_size).

xaxazak
  • 748
  • 4
  • 16
  • Consider using **ncurses** which already handles working with the terminal. Or be prepared to program a subset of ncurses that fulfills your particular use cases. – Eljay Mar 08 '23 at 18:52
  • @Eljay I tried ncurses but I found it ignored pre-existing user-set tab size (it returned 8 after using "tabs 2"). If its possible to *temporarily* use ncurses to somehow obtain the actual user tab size, I'd be keen to do so. Also, this is an attachment to an existing library that writes a lot to terminal. – xaxazak Mar 08 '23 at 18:56
  • It can be tricky. I have my terminal's tabs set to columns 16 and 40. – Eljay Mar 08 '23 at 19:52
  • @Eljay I ... did not know you could do that. And that would completely break what I'm trying to do. Of course, pure spaces works perfectly but then I'm forcing my indent preference. Unless I find a better answer then, for now, I think I'll just assume the terminfo `init_tabs` value (or perhaps just assume 8) and provide an option to change this. – xaxazak Mar 08 '23 at 21:15
  • 1
    The problem is that std::cin is line buffered and requires a newline before anything can be read. If you turn off line buffering and echoing input characters then it will work fine. If that's what you want to do let me know and I will write an answer. – Jerry Jeremiah Mar 08 '23 at 22:10

1 Answers1

1
  1. How, and where, is this text actually being written (by the terminal iteslf)?

I believe it's written by the terminal mimicking user input, exactly in the same place where it goes if input by the user. If you press enter after it's shown in the terminal, this should appear in the fread()/cin of your program.

  1. Is there some way I can read this data in C++ (eg, to a std::string)? (hopefully portably and without race conditions).

According to the other question you posted, you can avoid writing to the terminal by setting it to raw mode. Do this from your program and it should work. Just be careful when handling the received data because it has non printable characters. I think the following code does what you want. set_raw_tty() and its helper functions are based on this reference and are posted at the end of this answer to make it clearer:

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#include <iostream>
#include <string>

void tty_atexit(void);

static struct termios orig_termios;
static int ttyfd = STDIN_FILENO;
void fatal(char *mess);
void tty_atexit(void);
int tty_reset(void);
int tty_raw(void);
void set_raw_tty();

int main()
{
    set_raw_tty();

    std::cout << "\E[6n";

    std::string s;

    char c = 0;
    int ret = -1;

    ret = fread(&c, 1, 1, stdin);
    while (ret == 1) {
        ret = fread(&c, 1, 1, stdin);
        if (ret != 1 || c == 'R')
            break;
        s += c;
    }

    std::cout << "s = "<< s << "\r\n" << std::flush;
}

Testing:

$ ./a.out 
s = [2;1
$ ./a.out 
s = [4;1
$ 
$ 
$ 
$ ./a.out 
s = [9;1

The rest of the code to be added after main function:

void set_raw_tty()
{
    if (tcgetattr(ttyfd,&orig_termios) < 0) {
        printf ("%s %d: error here\n", __FUNCTION__, __LINE__);
        exit(1);
    }
    if (atexit(tty_atexit) != 0) {
        printf ("%s %d: error here\n", __FUNCTION__, __LINE__);
        exit(1);
    }
    if (tty_raw() != 0) {
        printf ("%s %d: error here\n", __FUNCTION__, __LINE__);
        exit(1);
    }
}

void tty_atexit(void)
{
    tty_reset();
}

int tty_raw(void)
{
    struct termios raw;
    raw = orig_termios;
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    raw.c_oflag &= ~(OPOST);
    raw.c_cflag |= (CS8);
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    raw.c_cc[VMIN] = 0; raw.c_cc[VTIME] = 8;
    return tcsetattr(ttyfd,TCSAFLUSH,&raw) < 0;
}

int tty_reset(void)
{
    if (tcsetattr(ttyfd,TCSAFLUSH,&orig_termios) < 0)
        return -1;
    return 0;
}
Jardel Lucca
  • 1,115
  • 3
  • 10
  • 19
  • 1
    Works for me, thanks, and backing up the original settings is great. Although it does have a surprisingly noticeable delay. I'm also confused about the repeated `raw.c_cc[VMIN] = ` (and same for `VTIME`) - only the last line seems to have any effect? – xaxazak Mar 09 '23 at 05:45
  • @xaxazak you can get rid of the delay if you read stdin one byte at a time and break when `R` arrives. I added this to the answer. Excellent point about this `raw.c_cc` part of the code, I have not idea. Just used it as a black box. I think you should tag the other guy in the comments sections who seems to know better what's going on and he potentially could write a more definitive answer. Perhaps the code here could help somehow. I'll have a look at this `raw.c_cc` at least to learn that myself as it seems puzzling. – Jardel Lucca Mar 09 '23 at 13:30
  • Oh yeah answering the last question... I did some quick experimental tweaking and it seems you can pull out the `raw.c_cc` lines leaving only the last one as you suggested. Perhaps the original author of that part of the code left those extra lines as some sort of placeholder or code example. I'm removing from the answer to simplify it. – Jardel Lucca Mar 09 '23 at 14:40