Terminalbreite in C ermitteln?

89

Ich habe nach einer Möglichkeit gesucht, die Terminalbreite aus meinem C-Programm heraus zu ermitteln. Was mir immer wieder einfällt, ist etwas in der Art von:

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

Aber jedes Mal, wenn ich das versuche, bekomme ich

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

Ist dies der beste Weg, oder gibt es einen besseren Weg? Wenn nicht, wie kann ich das zum Laufen bringen?

EDIT: fester Code ist

#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
quelle
1
Keine der vorgeschlagenen Antworten ist mehr als halb richtig.
Thomas Dickey
2
@ ThomasDickey, wo ist deine Antwort dann?
Alexis Wilke

Antworten:

126

Haben Sie überlegt, getenv () zu verwenden ? Hiermit können Sie die Umgebungsvariablen des Systems abrufen, die die Spalten und Zeilen der Terminals enthalten.

Wenn Sie alternativ Ihre Methode verwenden möchten, um zu sehen, was der Kernel als Terminalgröße sieht (besser, wenn die Größe des Terminals geändert wird), müssen Sie TIOCGWINSZ im Gegensatz zu Ihrer TIOCGSIZE verwenden, wie folgt:

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

und der vollständige Code:

#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.
quelle
7
Ja, aber der Begriff Breite ist keine Umgebungsvariable, sondern statisch zum Begriff.
Austin
4
Sie erhalten nicht die aktuelle Terminalgröße, wenn während der Programmausführung die Größe des Terminals geändert wird.
Chris Jester-Young
Ja, fügte das hinzu :)
John T
Wie bekomme ich Größen in Pixel? Ich habe ws_xpixelund verwendet ws_ypixel, aber es werden nur Nullen gedruckt!
Debashish
@ Debashish Kommt darauf an. Zum Beispiel unterstützt Linux diese Felder überhaupt nicht.
Melpomene
16

Dieses Beispiel ist etwas langwierig, aber ich glaube, es ist die tragbarste Methode zum Erkennen der Terminalabmessungen. Dies behandelt auch Größenänderungsereignisse.

Wie tim und rlbond vorschlagen, benutze ich ncurses. Es garantiert eine große Verbesserung der Terminalkompatibilität im Vergleich zum direkten Lesen von Umgebungsvariablen.

#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
quelle
3
Aber ist es wirklich sicher, initscr und endwin von einem Signalhandler aus aufzurufen? Sie sind zumindest nicht unter den async-signal-sicheren APIs inman 7 signal
nav
1
Das ist ein guter Punkt @nav, daran habe ich noch nie gedacht! Wäre es vielleicht eine bessere Lösung, wenn der Signalhandler ein Flag hisst und dann den Rest der Operationen in der Hauptschleife ausführt?
Gamen
1
@gamen, ja, das wäre besser;) - auch die Verwendung von Sigaction anstelle von Signal wäre besser.
Bodo Thiesen
Sind also COLS und LINES globale Variablen?
Einpoklum
1
@AlexisWilke: Einschließlich OKund ERR. Wie "nett" von ihnen, uns zu helfen, diese Lücke in unserem Leben zu schließen :-(
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;
}

Muss kompiliert werden mit -ltermcap. Es gibt viele andere nützliche Informationen, die Sie mit termcap erhalten können. info termcapWeitere Informationen finden Sie im termcap-Handbuch unter .

Juliano
quelle
Sie können es auch mit -lcurses kompilieren.
Kambus
Ich kann termcap nicht in Ubuntu 14.04 aufnehmen und kann es auch nicht in den Repositories finden. : /
Eric Sebasta
2
Ich weiß, dass dieser Kommentar 6 Jahre nach der Tat kommt, aber bitte erklären Sie Ihre magische Zahl von 2048 ...
einpoklum
1
@einpoklum Dies ist noch fast drei Jahre später, aber ist es nicht ziemlich klar, dass 2048 nur eine beliebige Größe für den Puffer ist, die "wahrscheinlich groß genug sein sollte" für jede Eingabezeichenfolge, die dort abläuft?
Roflcopter4
2
Tatsächlich macht diese Antwort zu viele Annahmen, um richtig zu sein.
Thomas Dickey
3

Wenn Sie ncurses installiert haben und verwenden, können getmaxyx()Sie die Abmessungen des Terminals ermitteln.

rlbond
quelle
2
Ja, und beachten Sie, dass das Y zuerst kommt und dann das X.
Daniel
0

Angenommen, Sie arbeiten unter Linux, möchten Sie stattdessen die ncurses- Bibliothek verwenden. Ich bin mir ziemlich sicher, dass das ttysize-Zeug, das Sie haben, nicht in stdlib ist.

tim
quelle
Nun, was ich tue, ist es nicht wirklich wert, Flüche einzurichten
Austin
ncurses ist auch nicht in stdlib. Beide sind in POSIX standardisiert, aber der ioctlWeg ist einfacher und sauberer, da Sie keine Flüche usw. initialisieren müssen
Gandaro
0

Schlagen Sie hier also keine Antwort vor, sondern:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Ok, und ich stelle fest, dass die Variablen LINES und COLUMNS dem folgen, wenn ich die Größe des GNOME-Terminals verändere.

Irgendwie scheint das GNOME-Terminal diese Umgebungsvariablen selbst zu erstellen?

Scott Franco
quelle
1
Und sicher wird es nicht an C-Code weitergegeben. getenv ("LINES") gibt NULL zurück.
Scott Franco
Variablen sind eine Shell-Sache, keine Terminal-Sache.
Melpomene
0

Um eine vollständigere Antwort hinzuzufügen, habe ich festgestellt, dass die Lösung von @ John_T mit einigen aus Rosetta Code hinzugefügten Bits zusammen mit einigen Fehlern bei der Ermittlung von Abhängigkeiten funktioniert . Es mag ein bisschen ineffizient sein, aber mit intelligenter Programmierung können Sie es zum Laufen bringen und Ihre Terminaldatei nicht ständig öffnen.

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

Wenn Sie sicherstellen, dass Sie nicht alles aufrufen, aber vielleicht hin und wieder, sollte es Ihnen gut gehen, sollte es sogar aktualisiert werden, wenn der Benutzer die Größe des Terminalfensters ändert (weil Sie die Datei jedes Mal öffnen und lesen ).

Wenn Sie nicht verwenden, TIOCGWINSZlesen Sie die erste Antwort auf diesem Formular https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Oh, und vergiss free()das nicht result.

iggy12345
quelle
-1

Hier sind die Funktionsaufrufe für die bereits vorgeschlagene Umgebungsvariable:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));
Merkuro
quelle
11
Umgebungsvariablen sind nicht zuverlässig. Diese Werte werden von der Shell festgelegt, sodass ihre Existenz nicht garantiert werden kann. Außerdem sind sie nicht auf dem neuesten Stand, wenn der Benutzer die Terminalgröße ändert.
Juliano
1
Viele Shells richten einen Handler für das SIGWINCHSignal ein, damit sie die Variablen auf dem neuesten Stand halten können (sie benötigen ihn auch, damit sie den richtigen Zeilenumbruch im Eingabeditor ausführen).
Barmar
5
Möglicherweise tun sie das, aber die Umgebung eines Programms wird während der Ausführung nicht aktualisiert.
Functino
Natürlich stürzt dieser Code sehr wahrscheinlich ab, da Sie nicht testen, ob getenv()NULL zurückgegeben wird oder nicht, und dies in meinem Linux-Terminal (da diese Variablen nicht exportiert werden). Auch wenn die Shell diese Variablen aktualisiert, wird das nicht angezeigt Änderungen, während Ihr Programm ausgeführt wird (nicht ohne dass Sie einen eigenen SIGWINCHHandler haben).
Alexis Wilke