Obtendo a largura do terminal em C?

89

Estou procurando uma maneira de obter a largura do terminal de dentro do meu programa C. O que eu continuo descobrindo é algo como:

#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);
}

Mas toda vez que tento eu 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.)

Esta é a melhor maneira de fazer isso ou existe uma maneira melhor? Se não, como posso fazer isso funcionar?

EDIT: código fixo é

#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
fonte
1
nenhuma das respostas sugeridas está mais da metade correta.
Thomas Dickey de
2
@ThomasDickey, onde está sua resposta então?
Alexis Wilke

Respostas:

126

Você já pensou em usar getenv () ? Ele permite que você obtenha as variáveis ​​de ambiente do sistema que contêm as colunas e linhas dos terminais.

Como alternativa, usando seu método, se você quiser ver o que o kernel vê como o tamanho do terminal (melhor no caso de o terminal ser redimensionado), você precisará usar TIOCGWINSZ, ao contrário de TIOCGSIZE, assim:

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

e o 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
fonte
7
sim, mas o termo largura não é uma variável ambiental, é estático para o termo.
Austin
4
Ele não fornece o tamanho atual do terminal, se alguém redimensionar o terminal durante a execução do programa.
Chris Jester-Young
sim, estava adicionando :)
John T
como obter tamanhos em pixels? Usei ws_xpixele ws_ypixel, mas só imprime zeros!
Debashish de
@Debashish Depends. Por exemplo, o Linux não oferece suporte a esses campos.
melpomene
16

Este exemplo é um pouco extenso, mas acredito que seja a maneira mais portátil de detectar as dimensões do terminal. Isso também lida com eventos de redimensionamento.

Como tim e rlbond sugerem, estou usando ncurses. Isso garante uma grande melhoria na compatibilidade do terminal em comparação com a leitura direta de variáveis ​​de ambiente.

#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
fonte
3
Mas é realmente seguro chamar initscr e endwin de um manipulador de sinal? Eles pelo menos não estão listados entre as APIs assíncronas de sinal seguro emman 7 signal
nav
1
Esse é um bom ponto, @nav, nunca pensei nisso! Uma solução melhor seria, talvez, fazer com que o manipulador de sinais levante um sinalizador e, em seguida, execute o resto das operações no loop principal?
gamen
1
@gamen, sim, seria melhor;) - também usar sigaction em vez de signal seria melhor.
Bodo Thiesen
Então, são variáveis ​​globais COLS e LINES?
einpoklum
1
@AlexisWilke: Incluindo OKe ERR. Quão "gentis" eles nos ajudaram a preencher essa lacuna em nossas 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;
}

Precisa ser compilado com -ltermcap. Existem muitas outras informações úteis que você pode obter usando o termcap. Verifique o manual do termcap usando info termcappara mais detalhes.

Juliano
fonte
Você pode compilá-lo com -lcurses também.
Kambus
2
Eu sei que este comentário vem 6 anos depois do fato, mas por favor, explique seu número mágico de 2048 ...
einpoklum
1
@einpoklum Já se passaram quase três anos, mas não está bastante claro que 2048 é apenas um tamanho arbitrário para o buffer que "provavelmente deve ser grande o suficiente" para qualquer string de entrada que esteja lá?
Roflcopter4
2
Na verdade, essa resposta faz muitas suposições para ser correta.
Thomas Dickey
1
Para qualquer pessoa curiosa, o tamanho do buffer de 2048 é explicado na documentação do GNU termcap aqui: gnu.org/software/termutils/manual/termcap-1.3/html_mono/… Há também muitas outras coisas aí que as pessoas lendo este post podem achar útil .
3

Se você tiver ncurses instalado e estiver usando, você pode usar getmaxyx()para encontrar as dimensões do terminal.

rlbond
fonte
2
Sim, e observe que o Y vem primeiro e depois o X.
Daniel
0

Supondo que você esteja no Linux, acho que você deseja usar a biblioteca ncurses . Tenho certeza de que o ttysize que você tem não está no stdlib.

tim
fonte
bem, o que estou fazendo realmente não vale a pena configurar ncurses para
austin
ncurses também não está em stdlib. Ambos são padronizados em POSIX, mas o ioctljeito é mais simples e limpo, porque você não precisa inicializar curses, etc.
Gandaro
0

Portanto, não estou sugerindo uma resposta aqui, mas:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Ok, e notei que se eu redimensionar o terminal GNOME, as variáveis ​​LINES e COLUMNS seguem isso.

Parece que o terminal GNOME está criando essas variáveis ​​de ambiente sozinho?

Scott Franco
fonte
1
E com certeza ele não passa para o código C. getenv ("LINES") retorna NULL.
Scott Franco,
As variáveis ​​são uma coisa de shell, não uma coisa terminal.
melpomene
0

Para adicionar uma resposta mais completa, o que descobri que funciona para mim é usar a solução de @John_T com alguns bits adicionados do Rosetta Code , junto com alguma solução de problemas para descobrir dependências. Pode ser um pouco ineficiente, mas com a programação inteligente você pode fazer funcionar e não abrir o arquivo do terminal o tempo todo.

#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;
}

Se você se certificar de não chamar tudo, mas talvez de vez em quando você fique bem, ele deve até atualizar quando o usuário redimensiona a janela do terminal (porque você está abrindo o arquivo e lendo-o a cada ).

Se você não estiver usando, TIOCGWINSZveja a primeira resposta neste formulário https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Ah, e não se esqueça free()do result.

iggy12345
fonte
-1

Aqui estão as chamadas de função para a variável ambiental já sugerida:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));
merkuro
fonte
11
Variáveis ​​de ambiente não são confiáveis. Esses valores são definidos pelo shell, portanto, não é garantido que existam. Além disso, eles não estarão atualizados se o usuário alterar o tamanho do terminal.
Juliano
1
Muitos shells estabelecem um manipulador para o SIGWINCHsinal, para que possam manter as variáveis ​​atualizadas (eles também precisam disso para fazer a quebra de linha apropriada no editor de entrada).
Barmar
5
Eles podem muito bem fazer isso, mas o ambiente de um programa não será atualizado durante a execução.
Functino
Claro, esse código é muito provável de travar, já que você não testa se getenv()retorna NULL ou não e isso acontece no meu terminal Linux (porque essas variáveis ​​não são exportadas.) Além disso, mesmo se o shell atualizar essas variáveis, você não verá o muda enquanto seu programa está sendo executado (não sem você ter seu próprio SIGWINCHmanipulador).
Alexis Wilke