Ottenere la larghezza del terminale in C?


89

Ho cercato un modo per ottenere la larghezza del terminale dal mio programma C. Quello che continuo a pensare è qualcosa sulla falsariga di:

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

Ma ogni volta che provo, ottengo

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.)

È questo il modo migliore per farlo o esiste un modo migliore? In caso contrario, come posso farlo funzionare?

EDIT: il codice fisso è

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

1
nessuna delle risposte suggerite è più della metà corretta.
Thomas Dickey

2
@ThomasDickey, dov'è la tua risposta allora?
Alexis Wilke

Risposte:


126

Hai considerato l'utilizzo di getenv () ? Ti permette di ottenere le variabili d'ambiente del sistema che contengono le colonne e le linee dei terminali.

In alternativa, usando il tuo metodo, se vuoi vedere cosa vede il kernel come la dimensione del terminale (meglio nel caso in cui il terminale sia ridimensionato), dovresti usare TIOCGWINSZ, al contrario di TIOCGSIZE, in questo modo:

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

e il codice 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
}

7
sì, ma la larghezza del termine non è una variabile ambientale, è statica rispetto al termine.
Austin

4
Non fornisce la dimensione corrente del terminale, se qualcuno ridimensiona il terminale durante l'esecuzione del programma.
Chris Jester-Young,

sì, lo stavo aggiungendo :)
John T,

come ottenere le dimensioni in pixel? Ho usato ws_xpixele ws_ypixel, ma stampa solo zeri!
Debashish

@Debashish Depends. Ad esempio, Linux non supporta affatto quei campi.
melpomene

16

Questo esempio è un po 'lungo, ma credo che sia il modo più portatile per rilevare le dimensioni del terminale. Questo gestisce anche gli eventi di ridimensionamento.

Come suggeriscono tim e rlbond, sto usando ncurses. Garantisce un grande miglioramento nella compatibilità del terminale rispetto alla lettura diretta delle variabili d'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);
}

3
Ma è davvero sicuro chiamare initscr ed endwin da un gestore di segnali? Almeno non sono elencate tra le API async-signal-safe inman 7 signal
nav

1
Questo è un buon punto @nav, non ci ho mai pensato! Una soluzione migliore sarebbe forse quella di far alzare una bandiera dal gestore del segnale e quindi eseguire il resto delle operazioni nel ciclo principale?
gamen

1
@gamen, sì, sarebbe meglio;) - anche usare sigaction al posto del segnale sarebbe meglio.
Bodo Thiesen

Quindi sono variabili globali COLS e LINES?
einpoklum

1
@AlexisWilke: inclusi OKe ERR. Come "gentili" da parte loro ci aiutano a colmare questa lacuna nella nostra vita :-(
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;
}

Deve essere compilato con -ltermcap. Ci sono molte altre informazioni utili che puoi ottenere usando termcap. Controlla il manuale del termcap usando info termcapper maggiori dettagli.


Puoi anche compilarlo con -lcurses.
Kambus

2
So che questo commento arriva 6 anni dopo il fatto, ma per favore spiega il tuo numero magico di 2048 ...
einpoklum

1
@einpoklum Questo è ancora quasi tre anni dopo, ma non è abbastanza chiaro che 2048 è solo una dimensione arbitraria per il buffer che "dovrebbe probabilmente essere abbastanza grande" per qualunque stringa di input sta andando lì?
Roflcopter4

2
In realtà, questa risposta fa troppe supposizioni per essere corretta.
Thomas Dickey

1
Per chiunque sia curioso, la dimensione del buffer 2048 è spiegata nella documentazione GNU termcap qui: gnu.org/software/termutils/manual/termcap-1.3/html_mono/… Ci sono anche molte altre cose lì che le persone che leggono questo post potrebbero trovare utili .

3

Se hai ncurses installato e lo stai usando, puoi usare getmaxyx()per trovare le dimensioni del terminale.


2
Sì, e tieni presente che prima viene la Y e poi la X.
Daniel

0

Supponendo che tu sia su Linux, penso che tu voglia usare invece la libreria ncurses . Sono abbastanza sicuro che la roba ttysize che hai non sia in stdlib.


beh, quello che sto facendo non vale davvero la pena creare ncurses per
Austin,

Anche ncurses non è in stdlib. Entrambi sono standardizzati in POSIX, ma il ioctlmodo è più semplice e pulito, perché non è necessario inizializzare maledizioni, ecc.
Gandaro

0

Quindi non suggerendo una risposta qui, ma:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Ok, e noto che se ridimensiono il terminale GNOME, le variabili LINES e COLUMNS lo seguono.

Sembra che il terminale GNOME stia creando queste variabili di ambiente da solo?


1
E abbastanza sicuro non passa al codice C. getenv ("LINEE") restituisce NULL.
Scott Franco

Le variabili sono una cosa della shell, non una cosa del terminale.
melpomene

0

Per aggiungere una risposta più completa, quello che ho trovato che funziona per me è utilizzare la soluzione di @ John_T con alcuni bit aggiunti da Rosetta Code , insieme ad alcuni problemi di risoluzione delle dipendenze. Potrebbe essere un po 'inefficiente, ma con la programmazione intelligente puoi farlo funzionare e non aprire il file del terminale tutto il tempo.

#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 ti assicuri di non chiamare tutto ma forse ogni tanto dovresti andare bene, dovrebbe anche aggiornarsi quando l'utente ridimensiona la finestra del terminale (perché stai aprendo il file e leggendolo ogni volta).

Se non stai usando, TIOCGWINSZvedi la prima risposta su questo modulo https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Oh, e non dimenticate di free()la result.


-1

Ecco le chiamate di funzione per la variabile ambientale già suggerita:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));

11
Le variabili d'ambiente non sono affidabili. Questi valori sono impostati dalla shell, quindi non è garantita l'esistenza. Inoltre, non saranno aggiornati se l'utente modifica la dimensione del terminale.
Juliano

1
Molte shell stabiliscono un gestore per il SIGWINCHsegnale, in modo da poter mantenere aggiornate le variabili (ne hanno anche bisogno in modo che eseguano il corretto avvolgimento di riga nell'editor di input).
Barmar

5
Potrebbero farlo, ma l'ambiente di un programma non verrà aggiornato mentre è in esecuzione.
Functino

Ovviamente, è molto probabile che il codice si blocchi poiché non si verifica se getenv()restituisce NULL o meno e lo fa nel mio terminale Linux (perché quelle variabili non vengono esportate.) Inoltre, anche se la shell aggiorna quelle variabili, non vedresti il cambia mentre il programma è in esecuzione (non senza che tu abbia il tuo SIGWINCHgestore).
Alexis Wilke
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.