Otrzymujesz szerokość terminala w C?

91

Szukałem sposobu, aby uzyskać szerokość terminala z mojego programu C. Ciągle wymyślam coś w rodzaju:

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

Ale za każdym razem, gdy próbuję, otrzymuję

austin@:~$ gcc test.c -o test
test.c: In function ‘main’:
test.c:6: error: storage size of ‘ts’ isn’t 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.)

Czy to najlepszy sposób, czy jest lepszy sposób? Jeśli nie, jak mogę to uruchomić?

EDYCJA: naprawiono kod

#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
źródło
1
żadna z sugerowanych odpowiedzi nie jest poprawna więcej niż w połowie.
Thomas Dickey
2
@ThomasDickey, gdzie jest twoja odpowiedź?
Alexis Wilke

Odpowiedzi:

127

Czy rozważałeś użycie getenv () ? Pozwala na pobranie zmiennych środowiskowych systemu, które zawierają kolumny i linie terminala.

Alternatywnie, używając twojej metody, jeśli chcesz zobaczyć, co jądro widzi jako rozmiar terminala (lepiej w przypadku zmiany rozmiaru terminala), musisz użyć TIOCGWINSZ, a nie TIOCGSIZE, na przykład:

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

i pełny kod:

#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.
źródło
7
tak, ale termin szerokość nie jest zmienną środowiskową, jest statyczny względem terminu.
austin
4
Nie podaje aktualnego rozmiaru terminala, jeśli ktoś zmieni rozmiar terminala podczas wykonywania programu.
Chris Jester-Young
tak, dodawałem to :)
John T
jak uzyskać rozmiary w pikselach? Użyłem ws_xpixeli ws_ypixel, ale to po prostu drukuje zera!
Debashish
@Debashish Depends. Np. Linux w ogóle nie obsługuje tych pól.
melpomene
16

Ten przykład jest trochę obszerny, ale uważam, że jest to najbardziej przenośny sposób wykrywania wymiarów terminala. Obsługuje to również zdarzenia zmiany rozmiaru.

Jak sugerują tim i rlbond, używam ncurses. Gwarantuje dużą poprawę kompatybilności terminala w porównaniu do bezpośredniego odczytu zmiennych środowiskowych.

#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
źródło
3
Ale czy naprawdę bezpieczne jest wywoływanie initscr i endwin z programu obsługi sygnału? Przynajmniej nie są one wymienione wśród interfejsów API bezpiecznych dla sygnałów asynchronicznych wman 7 signal
nav,
1
To dobra uwaga @nav, nigdy o tym nie myślałem! Czy lepszym rozwiązaniem może być podniesienie flagi przez program obsługi sygnału, a następnie wykonanie pozostałych operacji w głównej pętli?
gamen
1
@gamen, tak, tak byłoby lepiej;) - również użycie sigaction zamiast sygnału byłoby lepsze.
Bodo Thiesen
Więc to są zmienne globalne COLS i LINES?
einpoklum
1
@AlexisWilke: Włączanie OKi ERR. Jak "mili" z ich strony, że pomogli nam wypełnić tę lukę w naszym życiu :-(
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;
}

Musi być skompilowany z -ltermcap. Istnieje wiele innych przydatnych informacji, które można uzyskać za pomocą termcap. info termcapWięcej szczegółów znajdziesz w instrukcji obsługi termcap .

Juliano
źródło
Możesz go również skompilować za pomocą -lcurses.
Kambus
2
Wiem, że ten komentarz
pojawił się
1
@einpoklum To już prawie trzy lata później, ale czy nie jest całkiem jasne, że 2048 to tylko arbitralny rozmiar bufora, który „prawdopodobnie powinien być wystarczająco duży” dla dowolnego ciągu wejściowego, który tam trafia?
Roflcopter4
2
W rzeczywistości ta odpowiedź zawiera zbyt wiele założeń, aby były poprawne.
Thomas Dickey
1
Dla każdego ciekawego, rozmiar bufora 2048 jest wyjaśniony w dokumentacji GNU termcap tutaj: gnu.org/software/termutils/manual/termcap-1.3/html_mono/ ... Jest tam również wiele innych rzeczy, które mogą być przydatne dla osób czytających ten post .
3

Jeśli masz zainstalowany ncurses i używasz go, możesz użyć, getmaxyx()aby znaleźć wymiary terminala.

rlbond
źródło
2
Tak, i zauważ, że najpierw pojawia się Y, a potem X.
Daniel
0

Zakładając, że korzystasz z Linuksa, myślę, że zamiast tego chcesz użyć biblioteki ncurses . Jestem prawie pewien, że rzeczy o rozmiarze ttysize nie znajdują się w standardowym katalogu.

tim
źródło
Cóż, to, co robię, nie jest naprawdę warte konfigurowania ncurses
austin
ncurses również nie znajduje się w standardowym lib. Oba są ustandaryzowane w POSIX, ale ioctlsposób jest prostszy i czystszy, ponieważ nie musisz inicjalizować curses itp.
Gandaro
0

Więc nie sugeruję tutaj odpowiedzi, ale:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Ok, i zauważam, że jeśli zmienię rozmiar terminala GNOME, zmienne LINES i COLUMNS podążą za tym.

Wygląda na to, że terminal GNOME sam tworzy te zmienne środowiskowe?

Scott Franco
źródło
1
I na pewno nie przechodzi do kodu C. getenv ("LINES") zwraca NULL.
Scott Franco
Zmienne są elementem powłoki, a nie terminalem.
melpomene
0

Aby dodać bardziej kompletną odpowiedź, odkryłem, że działa dla mnie, używając rozwiązania @ John_T z kilkoma bitami dodanymi z Rosetta Code , wraz z kilkoma rozwiązywaniem problemów związanych z zależnościami. Może to być trochę nieefektywne, ale dzięki inteligentnemu programowaniu możesz sprawić, że zadziała i nie będziesz otwierać pliku terminala przez cały czas.

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

Jeśli upewnisz się, że nie wywołujesz tego wszystkiego, ale może od czasu do czasu powinno być dobrze, powinno nawet zaktualizować się, gdy użytkownik zmieni rozmiar okna terminala (ponieważ otwierasz plik i czytasz go za każdym razem).

Jeśli nie używasz, TIOCGWINSZzobacz pierwszą odpowiedź w tym formularzu https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Aha, i nie zapomnij .free()result

iggy12345
źródło
-1

Oto wywołania funkcji dla już sugerowanej zmiennej środowiskowej:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));
merkuro
źródło
11
Zmienne środowiskowe nie są wiarygodne. Te wartości są ustawiane przez powłokę, więc nie ma gwarancji, że będą istnieć. Ponadto nie będą aktualne, jeśli użytkownik zmieni rozmiar terminala.
Juliano
1
Wiele powłok ustanawia procedurę obsługi dla SIGWINCHsygnału, dzięki czemu mogą aktualizować zmienne (potrzebują tego również, aby odpowiednio zawijać wiersze w edytorze wejściowym).
Barmar
5
Mogą to zrobić, ale środowisko programu nie będzie aktualizowane w trakcie jego działania.
Functino,
Oczywiście ten kod prawdopodobnie ulegnie awarii, ponieważ nie testujesz, czy getenv()zwraca NULL, czy nie, a dzieje się tak w moim terminalu Linux (ponieważ te zmienne nie są eksportowane). Również nawet jeśli powłoka aktualizuje te zmienne, nie zobaczysz zmienia się podczas działania programu (nie bez posiadania własnego programu SIGWINCHobsługi).
Alexis Wilke