Reading virtual terminal contents is AFAIK impossible. Size and current cursor position, however, is rather easy. Using the ncurses library:
Code:
#include <ncurses.h>
#include <stdio.h>
int main(void)
{
int rows, cols, row, col;
WINDOW *w = initscr();
getmaxyx(w, rows, cols);
getyx(w, row, col);
endwin();
printf("Row %d of %d, column %d of %d.\n",
1+row, rows, 1+col, cols);
return 0;
}
If you do not want to add another library dependence, you can use the Linux-specific lower-level interfaces. See
man tty_ioctl and
man console_ioctl.
TIOCGWINSZ ioctl to the controlling terminal (stdout or stderr, whichever
is a tty) will fill in a struct winsize, which contains the number of rows and columns in the terminal at that point. Install a SIGWINCH signal handler which does that ioctl again, if you want to be notified of changes.
ANSI escape sequence
\033[6n (4 bytes) to the controlling terminal should inject the cursor position as
\033[row;colR into standard input. In order to hide that sequence from the terminal, you'll need to manipulate the terminal attributes.
Here is a program which seems to work. I just threw it together, so there may be bugs lurking in it.
Code:
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <stdio.h>
int terminal_specs(int *const cols, int *const rows,
int *const col, int *const row)
{
int in, out, result;
char buffer[16];
struct winsize ws;
struct termios t1, t2;
/* Initialize zeros (unknown) */
if (cols) *cols = 0;
if (rows) *rows = 0;
if (col) *col = 0;
if (row) *row = 0;
/* Open controlling tty for input. */
do {
in = open("/dev/tty", O_RDONLY | O_NONBLOCK);
} while (in == -1 && errno == EINTR);
if (in == -1)
return errno;
/* Reopen TTY for output. */
do {
out = open("/dev/tty", O_WRONLY);
} while (out == -1 && errno == EINTR);
if (out == -1) {
int const saved_errno = errno;
do {
result = close(in);
} while (result == -1 && errno == EINTR);
return saved_errno;
}
/* Determine terminal size. */
if (!ioctl(out, TIOCGWINSZ, &ws)) {
if (cols) *cols = ws.ws_col;
if (rows) *rows = ws.ws_row;
}
/* Save current terminal settings. */
tcgetattr(out, &t1);
tcgetattr(out, &t2);
/* Disable echo and signals for now. */
t2.c_iflag &= ~( IGNBRK | IGNPAR );
t2.c_oflag &= ~( OFILL );
t2.c_oflag |= ONOCR;
t2.c_lflag &= ~( ISIG | ICANON | ECHO );
tcsetattr(out, TCSANOW, &t2);
/* Query cursor location. */
if (row || col) {
if (write(out, "\033[6n", 4) == 4) {
do {
result = read(in, buffer, sizeof(buffer) - 1);
} while (result == -1 && (errno == EINTR || errno == EWOULDBLOCK));
if (result > 0 && buffer[result - 1] == 'R') {
char *p = buffer;
int nr = 0;
int nc = 0;
buffer[result - 1] = 0;
/* parse [^0-9]*([0-9]*)[^0-9]*([0-9]*) */
while (*p && !(*p >= '0' && *p <= '9')) p++;
while (*p >= '0' && *p <= '9')
nr = nr * 10 + (*(p++) - '0');
while (*p && !(*p >= '0' && *p <= '9')) p++;
while (*p >= '0' && *p <= '9')
nc = nc * 10 + (*(p++) - '0');
if (row) *row = nr;
if (col) *col = nc;
}
}
}
/* Restore terminal settings. */
tcsetattr(out, TCSANOW, &t1);
/* Close descriptors. */
do {
result = close(in);
} while (result == -1 && errno == EINTR);
do {
result = close(out);
} while (result == -1 && errno == EINTR);
return 0;
}
int main(void)
{
int cols, rows, col, row;
terminal_specs(&cols, &rows, &col, &row);
if (cols > 0 && rows > 0 && col > 0 && row > 0)
printf("Row %d of %d, column %d of %d\n", row, rows, col, cols);
else
if (cols > 0 && rows > 0)
printf("%d rows, %d columns\n", rows, cols);
else
if (col > 0 && row > 0)
printf("Row %d, column %d\n", row, col);
return 0;
}
Note that I wrote the
terminal_specs() function so that it does not need stdio at all. (I could have used sscanf(), but this way it's independent.)
If you call
terminal_specs() first, before reading from standard input, standard input should be kept untouched.
Hope this helps.