Obtenir la largeur du terminal en C?

89

J'ai cherché un moyen d'obtenir la largeur du terminal à partir de mon programme C. Ce que je n'arrête pas de proposer, c'est quelque chose du genre:

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

Mais à chaque fois que j'essaye d'avoir

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

Est-ce la meilleure façon de procéder ou y a-t-il une meilleure façon de procéder? Sinon, comment puis-je faire fonctionner cela?

EDIT: le code fixe est

#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
la source
1
aucune des réponses suggérées n'est à moitié correcte.
Thomas Dickey
2
@ThomasDickey, où est ta réponse alors?
Alexis Wilke

Réponses:

126

Avez-vous envisagé d'utiliser getenv () ? Il vous permet d'obtenir les variables d'environnement du système qui contiennent les colonnes et les lignes des terminaux.

Alternativement en utilisant votre méthode, si vous voulez voir ce que le noyau considère comme la taille du terminal (mieux si le terminal est redimensionné), vous devrez utiliser TIOCGWINSZ, par opposition à votre TIOCGSIZE, comme ceci:

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

et le code complet:

#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
la source
7
oui mais le terme largeur n'est pas une variable environnementale, il est statique par rapport au terme.
austin
4
Il ne vous fournit pas la taille actuelle du terminal, si quelqu'un redimensionne le terminal pendant l'exécution du programme.
Chris Jester-Young
ouais, ajoutait ça :)
John T
comment obtenir des tailles en pixels? J'ai utilisé ws_xpixelet ws_ypixel, mais il imprime juste des zéros!
Débattre
@Debashish dépend. Par exemple, Linux ne prend pas du tout en charge ces champs.
melpomene
16

Cet exemple est un peu long, mais je pense que c'est le moyen le plus portable de détecter les dimensions du terminal. Cela gère également les événements de redimensionnement.

Comme le suggèrent tim et rlbond, j'utilise ncurses. Il garantit une grande amélioration de la compatibilité du terminal par rapport à la lecture directe des variables d'environnement.

#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
la source
3
Mais est-il vraiment sûr d'appeler initscr et endwin depuis un gestionnaire de signaux? Ils ne sont au moins pas répertoriés parmi les API de sécurité du signal asynchrone dansman 7 signal
nav
1
C'est un bon point @nav, je n'y ai jamais pensé! Une meilleure solution serait peut-être de demander au gestionnaire de signaux de lever un drapeau, puis d'effectuer le reste des opérations dans la boucle principale?
gamen
1
@gamen, oui, ce serait mieux;) - utiliser également sigaction au lieu de signal serait mieux aussi.
Bodo Thiesen
Alors, les variables globales COLS et LINES?
einpoklum
1
@AlexisWilke: Y compris OKet ERR. Comment "gentils" de leur part pour nous aider à combler cette lacune dans nos vies :-(
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;
}

Doit être compilé avec -ltermcap. Il y a beaucoup d'autres informations utiles que vous pouvez obtenir en utilisant termcap. Consultez le manuel de termcap info termcappour plus de détails.

Juliano
la source
Vous pouvez également le compiler avec -lcurses.
Kambus
2
Je sais que ce commentaire arrive 6 ans après les faits, mais veuillez expliquer votre nombre magique de 2048 ...
einpoklum
1
@einpoklum C'est presque trois ans plus tard encore, mais n'est-il pas assez clair que 2048 est juste une taille arbitraire pour le tampon qui "devrait probablement être assez grand" pour la chaîne d'entrée qui y est envoyée?
Roflcopter4
2
En fait, cette réponse fait trop d'hypothèses pour être correcte.
Thomas Dickey
1
Pour tous les curieux, la taille du tampon 2048 est expliquée dans la documentation termcap GNU ici: gnu.org/software/termutils/manual/termcap-1.3/html_mono/... Il y a aussi beaucoup d'autres choses là - bas des gens qui lisent ce post peut trouver utile .
3

Si vous avez installé ncurses et que vous l'utilisez, vous pouvez utiliser getmaxyx()pour trouver les dimensions du terminal.

rlbond
la source
2
Oui, et notez que le Y vient en premier, puis le X.
Daniel
0

En supposant que vous soyez sous Linux, je pense que vous souhaitez utiliser la bibliothèque ncurses à la place. Je suis à peu près sûr que le contenu de ttysize que vous avez n'est pas dans stdlib.

Tim
la source
eh bien, ce que je fais ne vaut pas vraiment la peine d'installer ncurses pour
austin
ncurses n'est pas non plus dans stdlib. Les deux sont standardisés dans POSIX, mais la ioctlmanière est plus simple et plus propre, car vous n'avez pas à initialiser les malédictions, etc.
Gandaro
0

Donc, je ne suggère pas de réponse ici, mais:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Ok, et je remarque que si je redimensionne le terminal GNOME, les variables LINES et COLUMNS suivent cela.

Il semble que le terminal GNOME crée lui-même ces variables d'environnement?

Scott Franco
la source
1
Et bien sûr, il ne passe pas au code C. getenv ("LINES") renvoie NULL.
Scott Franco
Les variables sont une chose de shell, pas une chose de terminal.
melpomene
0

Pour ajouter une réponse plus complète, ce que j'ai trouvé qui fonctionne pour moi, c'est d'utiliser la solution de @ John_T avec quelques bits ajoutés à partir de Rosetta Code , ainsi que du dépannage pour déterminer les dépendances. Cela peut être un peu inefficace, mais avec une programmation intelligente, vous pouvez le faire fonctionner et ne pas ouvrir votre fichier de terminal tout le temps.

#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 vous vous assurez de ne pas tout appeler, mais peut-être de temps en temps, cela devrait aller, il devrait même être mis à jour lorsque l'utilisateur redimensionne la fenêtre du terminal (car vous ouvrez le fichier et le lisez à chaque fois).

Si vous n'utilisez pas, TIOCGWINSZvoyez la première réponse sur ce formulaire https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Oh, et n'oubliez pas free()le result.

iggy12345
la source
-1

Voici les appels de fonction pour la variable environnementale déjà suggérée:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));
merkuro
la source
11
Les variables d'environnement ne sont pas fiables. Ces valeurs sont définies par le shell, elles ne sont donc pas garanties d'exister. De plus, ils ne seront pas à jour si l'utilisateur modifie la taille du terminal.
Juliano
1
De nombreux shells établissent un gestionnaire pour le SIGWINCHsignal, afin qu'ils puissent garder les variables à jour (ils en ont également besoin afin de faire un wrapping de ligne approprié dans l'éditeur d'entrée).
Barmar
5
Ils peuvent bien le faire, mais l'environnement d'un programme ne sera pas mis à jour pendant son exécution.
Functino
Bien sûr, ce code est très susceptible de planter puisque vous ne testez pas si getenv()renvoie NULL ou non et c'est le cas dans mon terminal Linux (car ces variables ne sont pas exportées.) De plus, même si le shell met à jour ces variables, vous ne verriez pas le change pendant que votre programme est en cours d'exécution (pas sans que vous ayez votre propre SIGWINCHgestionnaire).
Alexis Wilke