Отримання ширини терміналу в С?


89

Я шукав спосіб отримати ширину терміналу в моїй програмі 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 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.)

Це найкращий спосіб зробити це, чи є кращий спосіб? Якщо ні, як я можу змусити це працювати?

EDIT: фіксований код -

#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

Відповіді:


126

Ви розглядали можливість використання 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
Він не надає вам поточного розміру терміналу, якщо хтось змінює розмір терміналу під час виконання програми.
Кріс Джестер-Янг

так, додав це :)
Джон Т

як отримати розміри в пікселях? Я використовував ws_xpixelі ws_ypixel, Але це просто друкує нулі!
Дебашіш

@Debashish Залежить. Наприклад, Linux взагалі не підтримує ці поля.
Мельпомена

16

Цей приклад трохи довгий, але я вважаю, що це найбільш портативний спосіб виявлення розмірів терміналу. Це також обробляє події зміни розміру.

Як підказують tim і 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, так, це було б краще;) - також також було б краще використовувати сигакцію замість сигналу.
Бодо Тісен

Тож чи є 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. Перегляньте посібник із використання термінів, info termcapщоб отримати докладнішу інформацію.


Ви також можете скомпілювати його з -lcurses.
Камбус

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 ("РЯДКИ") повертає NULL.
Скотт Франко

Змінні - це річ оболонки, а не термінальна річ.
Мельпомена

0

Щоб додати більш повну відповідь, я знайшов, що працює для мене, це використовувати рішення @ John_T з деякими бітами, доданими з Розеттського коду , разом із деякими способами усунення несправностей для з’ясування залежностей. Це може бути трохи неефективно, але за допомогою розумного програмування ви можете змусити його працювати і не постійно відкривати файл терміналу.

#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
Змінні середовища не є надійними. Ці значення встановлюються оболонкою, тому вони не гарантують існування. Крім того, вони не будуть оновлені, якщо користувач змінить розмір терміналу.
Джуліано

1
Багато оболонок встановлюють обробник для SIGWINCHсигналу, тому вони можуть постійно оновлювати змінні (їм це також потрібно, щоб вони виконали належне перенесення рядків у редактор введення).
Barmar

5
Вони цілком можуть це зробити, але середовище програми не оновлюватиметься під час роботи.
Functino

Звичайно, цей код дуже ймовірно вийде з ладу, оскільки ви не перевіряєте, чи getenv()повертає NULL чи ні, і це відбувається в моєму терміналі Linux (оскільки ці змінні не експортуються.) Також навіть якщо оболонка оновлює ці змінні, ви не побачите зміни під час роботи вашої програми (не без того, щоб у вас був власний SIGWINCHобробник).
Alexis Wilke
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.