Получение ширины терминала в C?

91

Я искал способ получить ширину терминала из моей программы C. Я продолжаю придумывать что-то вроде:

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

Но каждый раз, когда я пытаюсь это сделать, я получаю

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

Это лучший способ сделать это или есть лучший способ? Если нет, как я могу заставить это работать?

РЕДАКТИРОВАТЬ: фиксированный код

#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
ни один из предложенных ответов не более чем наполовину правильный.
Thomas Dickey
2
@ThomasDickey, а где тогда твой ответ?
Alexis Wilke

Ответы:

127

Вы рассматривали возможность использования getenv () ? Это позволяет вам получить системные переменные среды, которые содержат столбцы и строки терминалов.

В качестве альтернативы, используя ваш метод, если вы хотите увидеть, что ядро ​​видит в качестве размера терминала (лучше, если размер терминала изменен), вам нужно будет использовать TIOCGWINSZ, а не ваш TIOCGSIZE, например:

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

и полный код:

#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
да, но ширина термина не является переменной окружения, она статична для термина.
Остин
4
Он не предоставляет вам текущий размер терминала, если кто-то изменит размер терминала во время выполнения программы.
Крис Джестер-Янг,
да, добавлял это :)
John T
как получить размеры в пикселях? Я использовал ws_xpixelи ws_ypixel, но он просто печатает нули!
Debashish
@Debashish Зависит. Например, Linux вообще не поддерживает эти поля.
melpomene 05
16

Этот пример немного длинноват, но я считаю, что это наиболее удобный способ определения размеров терминала. Это также обрабатывает события изменения размера.

Как предлагают тим и rlbond, я использую ncurses. Это гарантирует значительное улучшение совместимости терминала по сравнению с прямым чтением переменных среды.

#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
Но действительно ли безопасно вызывать initscr и endwin из обработчика сигналов? По крайней мере, они не указаны в списке API, безопасных для асинхронных сигналов, вman 7 signal
nav
1
Хороший момент @nav, я никогда об этом не думал! Может быть, лучшим решением было бы, чтобы обработчик сигналов поднимал флаг, а затем выполнял остальные операции в основном цикле?
gamen
1
@gamen, да, так было бы лучше;) - тоже было бы лучше использовать sigaction вместо signal.
Bodo Thiesen
Значит, COLS и LINES глобальные переменные?
einpoklum
1
@AlexisWilke: Включая OKи ERR. Как «добрые» с их стороны помочь нам заполнить этот пробел в нашей жизни :-(
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;
}

Необходимо компилировать с -ltermcap. Есть много другой полезной информации, которую вы можете получить с помощью termcap. Обратитесь к руководству по termcap info termcapдля получения более подробной информации.

Джулиано
источник
Вы также можете скомпилировать его с помощью -lcurses.
Kambus
2
Я знаю, что этот комментарий прозвучал через 6 лет после того, как это произошло, но, пожалуйста, объясните свое магическое число 2048 ...
einpoklum
1
@einpoklum Это уже почти три года спустя, но разве не совсем ясно, что 2048 - это всего лишь произвольный размер буфера, который «вероятно, должен быть достаточно большим» для любой входной строки, идущей туда?
Roflcopter4
2
На самом деле, этот ответ делает слишком много предположений, чтобы быть правильным.
Томас Дики
1
Для всех, кому интересно, размер буфера 2048 объясняется в документации GNU termcap здесь: gnu.org/software/termutils/manual/termcap-1.3/html_mono/ ... Там также есть много других вещей, которые люди, читающие этот пост, могут найти полезными .
3

Если у вас установлен ncurses и вы его используете, вы можете использовать его getmaxyx()для определения размеров терминала.

rlbond
источник
2
Да, и обратите внимание, что сначала идет Y, а затем X.
Даниэль
0

Предполагая, что вы работаете в Linux, я думаю, вы захотите вместо этого использовать библиотеку ncurses . Я почти уверен, что у вас есть материал ttysize, которого нет в stdlib.

Тим
источник
ну, то, что я делаю, на самом деле не стоит того, чтобы настраивать ncurses
Остин
ncurses также отсутствует в stdlib. Оба стандартизированы в POSIX, но ioctlспособ проще и чище, потому что вам не нужно инициализировать проклятия и т. Д.
Гандаро
0

Так что не предлагая здесь ответа, но:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Хорошо, и я заметил, что если я изменю размер терминала GNOME, переменные LINES и COLUMNS последуют за этим.

Похоже, что терминал GNOME сам создает эти переменные среды?

Скотт Франко
источник
1
И конечно же, это не передается коду C. getenv ("LINES") возвращает NULL.
Скотт Франко
Переменные - это оболочка, а не терминал.
melpomene 05
0

Чтобы добавить более полный ответ, я обнаружил, что для меня работает решение @ John_T с некоторыми битами, добавленными из кода Rosetta , а также с некоторыми устранениями неполадок, выясняющих зависимости. Это может быть немного неэффективно, но с умным программированием вы можете заставить его работать и не открывать файл терминала все время.

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

Если вы убедитесь, что не вызываете его все, но, возможно, время от времени все будет в порядке, он должен даже обновляться, когда пользователь изменяет размер окна терминала (потому что вы открываете файл и читаете его каждый раз).

Если вы не используете, TIOCGWINSZсм. Первый ответ в этой форме https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

О, и не забывайте free()о result.

iggy12345
источник
-1

Вот вызовы функций для уже предложенной переменной окружения:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));
Меркуро
источник
11
Переменные среды ненадежны. Эти значения устанавливаются оболочкой, поэтому не гарантируется их существование. Кроме того, они не будут обновлены, если пользователь изменит размер терминала.
Juliano
1
Многие оболочки устанавливают обработчик для SIGWINCHсигнала, чтобы они могли поддерживать переменные в актуальном состоянии (они также нуждаются в этом, чтобы они выполняли надлежащий перенос строк в редакторе ввода).
Barmar 08
5
Они вполне могут это сделать, но среда программы не будет обновляться по мере ее выполнения.
Functino
Конечно, этот код с большой вероятностью выйдет из строя, поскольку вы не проверяете, getenv()возвращает ли NULL или нет, и это происходит в моем терминале Linux (потому что эти переменные не экспортируются). Также, даже если оболочка обновляет эти переменные, вы не увидите изменяется во время работы вашей программы (не без вашего собственного SIGWINCHобработчика).
Alexis Wilke