¿Obteniendo ancho de terminal en C?

89

He estado buscando una forma de obtener el ancho de la terminal desde mi programa C. Lo que se me ocurre es algo parecido a:

#include <sys/ioctl.h>
#include <stdio.h>

int main (void)
{
    struct ttysize ts;
    ioctl(0, TIOCGSIZE, &ts);

    printf ("lines %d\n", ts.ts_lines);
    printf ("columns %d\n", ts.ts_cols);
}

Pero cada vez que lo intento consigo

austin@:~$ gcc test.c -o test
test.c: In function main’:
test.c:6: error: storage size of ts isnt known
test.c:7: error: TIOCGSIZE undeclared (first use in this function)
test.c:7: error: (Each undeclared identifier is reported only once
test.c:7: error: for each function it appears in.)

¿Es esta la mejor manera de hacer esto o hay una mejor manera? Si no, ¿cómo puedo hacer que esto funcione?

EDITAR: el código fijo es

#include <sys/ioctl.h>
#include <stdio.h>

int main (void)
{
    struct winsize w;
    ioctl(0, TIOCGWINSZ, &w);

    printf ("lines %d\n", w.ws_row);
    printf ("columns %d\n", w.ws_col);
    return 0;
}
Austin
fuente
1
ninguna de las respuestas sugeridas es más de la mitad correcta.
Thomas Dickey
2
@ThomasDickey, ¿dónde está tu respuesta entonces?
Alexis Wilke

Respuestas:

126

¿Ha considerado usar getenv () ? Le permite obtener las variables de entorno del sistema que contienen las columnas y líneas de los terminales.

Alternativamente, usando su método, si desea ver lo que el kernel ve como el tamaño de la terminal (mejor en caso de que se cambie el tamaño de la terminal), necesitaría usar TIOCGWINSZ, en lugar de su TIOCGSIZE, así:

struct winsize w;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

y el código completo:

#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>

int main (int argc, char **argv)
{
    struct winsize w;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);

    printf ("lines %d\n", w.ws_row);
    printf ("columns %d\n", w.ws_col);
    return 0;  // make sure your main returns int
}
John T
fuente
7
sí, pero el término ancho no es una variable ambiental, es estático para el término.
Austin
4
No le proporciona el tamaño actual del terminal, si alguien cambia el tamaño del terminal durante la ejecución del programa.
Chris Jester-Young
sí, estaba agregando eso :)
John T
¿Cómo obtener tamaños en píxeles? Usé ws_xpixely ws_ypixel, ¡pero solo imprime ceros!
Debashish
@Debashish depende. Por ejemplo, Linux no admite esos campos en absoluto.
melpomene
16

Este ejemplo es un poco extenso, pero creo que es la forma más portátil de detectar las dimensiones del terminal. Esto también maneja eventos de cambio de tamaño.

Como sugieren tim y rlbond, estoy usando ncurses. Garantiza una gran mejora en la compatibilidad del terminal en comparación con la lectura directa de variables de entorno.

#include <ncurses.h>
#include <string.h>
#include <signal.h>

// SIGWINCH is called when the window is resized.
void handle_winch(int sig){
  signal(SIGWINCH, SIG_IGN);

  // Reinitialize the window to update data structures.
  endwin();
  initscr();
  refresh();
  clear();

  char tmp[128];
  sprintf(tmp, "%dx%d", COLS, LINES);

  // Approximate the center
  int x = COLS / 2 - strlen(tmp) / 2;
  int y = LINES / 2 - 1;

  mvaddstr(y, x, tmp);
  refresh();

  signal(SIGWINCH, handle_winch);
}

int main(int argc, char *argv[]){
  initscr();
  // COLS/LINES are now set

  signal(SIGWINCH, handle_winch);

  while(getch() != 27){
    /* Nada */
  }

  endwin();

  return(0);
}
gamen
fuente
3
Pero, ¿es realmente seguro llamar a initscr y endwin desde un controlador de señales? Al menos no figuran entre las API seguras de señal asíncrona enman 7 signal
nav
1
Ese es un buen punto @nav, ¡ nunca había pensado en eso! ¿Quizás una mejor solución sería que el manejador de señales levantara una bandera y luego realizara el resto de las operaciones en el bucle principal?
gamen
1
@gamen, sí, eso sería mejor;) - también sería mejor usar sigaction en lugar de señal.
Bodo Thiesen
Entonces, ¿son COLS y LINES variables globales?
einpoklum
1
@AlexisWilke: Incluyendo OKy ERR. Qué "amables" de ellos para ayudarnos a llenar ese vacío en nuestras vidas :-(
einpoklum
12
#include <stdio.h>
#include <stdlib.h>
#include <termcap.h>
#include <error.h>

static char termbuf[2048];

int main(void)
{
    char *termtype = getenv("TERM");

    if (tgetent(termbuf, termtype) < 0) {
        error(EXIT_FAILURE, 0, "Could not access the termcap data base.\n");
    }

    int lines = tgetnum("li");
    int columns = tgetnum("co");
    printf("lines = %d; columns = %d.\n", lines, columns);
    return 0;
}

Necesita ser compilado -ltermcap. Hay mucha otra información útil que puede obtener usando termcap. Consulte el manual de termcap utilizando info termcappara obtener más detalles.

Juliano
fuente
También puede compilarlo con -lcurses.
Kambus
2
Sé que este comentario llega 6 años después del hecho, pero por favor, explique su número mágico de 2048 ...
einpoklum
1
@einpoklum Esto es casi tres años después, pero ¿no está bastante claro que 2048 es solo un tamaño arbitrario para el búfer que "probablemente debería ser lo suficientemente grande" para cualquier cadena de entrada que vaya allí?
Roflcopter4
2
En realidad, esta respuesta hace demasiadas suposiciones para ser correcta.
Thomas Dickey
1
Para cualquier persona curiosa, el tamaño del búfer de 2048 se explica en la documentación de GNU termcap aquí: gnu.org/software/termutils/manual/termcap-1.3/html_mono/… También hay muchas otras cosas allí que las personas que leen esta publicación pueden encontrar útiles .
3

Si tiene ncurses instalado y lo está usando, puede usar getmaxyx()para encontrar las dimensiones del terminal.

rlbond
fuente
2
Sí, y tenga en cuenta que la Y viene primero y luego la X.
Daniel
0

Suponiendo que está en Linux, creo que desea usar la biblioteca ncurses en su lugar. Estoy bastante seguro de que las cosas ttysize que tienes no están en stdlib.

tim
fuente
bueno, lo que estoy haciendo realmente no vale la pena configurar ncurses para
austin
ncurses tampoco está en stdlib. Ambos están estandarizados en POSIX, pero la ioctlforma es más simple y limpia, porque no tienes que inicializar maldiciones, etc.
Gandaro
0

Así que no sugiero una respuesta aquí, pero:

linux-pc:~/scratch$ echo $LINES

49

linux-pc:~/scratch$ printenv | grep LINES

linux-pc:~/scratch$

Ok, y noto que si cambio el tamaño del terminal GNOME, las variables LINES y COLUMNS siguen a eso.

¿Parece que el terminal GNOME está creando estas variables de entorno por sí mismo?

Scott Franco
fuente
1
Y, efectivamente, no se transmite al código C. getenv ("LINES") devuelve NULL.
Scott Franco
Las variables son una cosa de shell, no una cosa terminal.
melpomene
0

Para agregar una respuesta más completa, lo que encontré que me funciona es usar la solución de @ John_T con algunos bits agregados de Rosetta Code , junto con algunos problemas de resolución de dependencias. Puede ser un poco ineficiente, pero con la programación inteligente puede hacer que funcione y no tener que abrir el archivo de su terminal todo el tiempo.

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h> // ioctl, TIOCGWINSZ
#include <err.h>       // err
#include <fcntl.h>     // open
#include <unistd.h>    // close
#include <termios.h>   // don't remember, but it's needed

size_t* get_screen_size()
{
  size_t* result = malloc(sizeof(size_t) * 2);
  if(!result) err(1, "Memory Error");

  struct winsize ws;
  int fd;

  fd = open("/dev/tty", 0_RDWR);
  if(fd < 0 || ioctl(fd, TIOCGWINSZ, &ws) < 0) err(8, "/dev/tty");

  result[0] = ws.ws_row;
  result[1] = ws.ws_col;

  close(fd);

  return result;
}

Si se asegura de no llamarlo todo, pero tal vez de vez en cuando debería estar bien, incluso debería actualizarse cuando el usuario cambie el tamaño de la ventana del terminal (porque está abriendo el archivo y leyéndolo todo el tiempo).

Si no está utilizando, TIOCGWINSZvea la primera respuesta en este formulario https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Ah, y no te olvides free()del result.

iggy12345
fuente
-1

Aquí están las llamadas de función para la variable ambiental ya sugerida:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));
merkuro
fuente
11
Las variables de entorno no son confiables. Estos valores los establece el shell, por lo que no se garantiza que existan. Además, no estarán actualizados si el usuario cambia el tamaño del terminal.
Juliano
1
Muchos shells establecen un manejador para la SIGWINCHseñal, de modo que puedan mantener las variables actualizadas (también lo necesitan para que realicen un ajuste de línea adecuado en el editor de entrada).
Barmar
5
Es posible que lo hagan, pero el entorno de un programa no se actualizará mientras se ejecuta.
Functino
Por supuesto, es muy probable que ese código se bloquee ya que no prueba si getenv()devuelve NULL o no y lo hace en mi terminal de Linux (porque esas variables no se exportan). Además, incluso si el shell actualiza esas variables, no verá el cambia mientras su programa se está ejecutando (no sin que usted tenga su propio SIGWINCHcontrolador).
Alexis Wilke