Получение ширины терминала в 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()для определения размеров терминала.


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.


-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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.