Nhận chiều rộng thiết bị đầu cuối trong C?


89

Tôi đang tìm cách để lấy chiều rộng đầu cuối từ trong chương trình C của mình. Những gì tôi tiếp tục tìm ra là một cái gì đó dọc theo dòng:

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

Nhưng mỗi khi tôi cố gắng, tôi đều nhận được

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

Đây có phải là cách tốt nhất để làm điều này, hay có cách nào tốt hơn? Nếu không, làm thế nào tôi có thể làm cho nó hoạt động?

EDIT: mã cố định là

#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
không có câu trả lời gợi ý nào đúng hơn một nửa.
Thomas Dickey

2
@ThomasDickey, câu trả lời của bạn là ở đâu?
Alexis Wilke,

Câu trả lời:


126

Bạn đã cân nhắc sử dụng getenv () chưa? Nó cho phép bạn lấy các biến môi trường của hệ thống chứa các cột và dòng đầu cuối.

Ngoài ra, bằng cách sử dụng phương pháp của bạn, nếu bạn muốn xem hạt nhân nhìn thấy kích thước đầu cuối (tốt hơn trong trường hợp thiết bị đầu cuối được thay đổi kích thước), bạn sẽ cần sử dụng TIOCGWINSZ, trái ngược với TIOCGSIZE của bạn, như sau:

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

và mã đầy đủ:

#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
vâng nhưng độ rộng của thuật ngữ không phải là một biến môi trường, nó tĩnh đối với thuật ngữ.
austin

4
Nó không cung cấp cho bạn kích thước thiết bị đầu cuối hiện tại , nếu ai đó thay đổi kích thước thiết bị đầu cuối trong quá trình thực thi chương trình.
Chris Jester-Young

yeah, được bổ sung thêm rằng :)
John T

làm thế nào để lấy kích thước theo pixel? Tôi đã sử dụng ws_xpixelws_ypixel, Nhưng nó chỉ in các số không!
Debashish

@Debashish Tùy. Ví dụ: Linux hoàn toàn không hỗ trợ các trường đó.
melpomene

16

Ví dụ này hơi dài dòng, nhưng tôi tin rằng đó là cách dễ dàng nhất để phát hiện kích thước đầu cuối. Điều này cũng xử lý các sự kiện thay đổi kích thước.

Như tim và rlbond gợi ý, tôi đang sử dụng ncurses. Nó đảm bảo một sự cải thiện lớn về khả năng tương thích của thiết bị đầu cuối so với việc đọc trực tiếp các biến môi trường.

#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
Nhưng có thực sự an toàn khi gọi initscr và endwin từ trình xử lý tín hiệu không? Ít nhất chúng không được liệt kê trong số các API an toàn không có tín hiệu trongman 7 signal
nav

1
Đó là một điểm tốt @nav, tôi chưa bao giờ nghĩ đến điều đó! Một giải pháp tốt hơn có lẽ là để bộ xử lý tín hiệu giơ cờ, và sau đó thực hiện phần còn lại của các hoạt động trong vòng lặp chính?
gamen

1
@gamen, vâng, điều đó sẽ tốt hơn;) - cũng sử dụng sigaction thay vì signal cũng sẽ tốt hơn.
Bodo Thiesen,

Vậy COLS và LINES có phải là biến toàn cục không?
einpoklum

1
@AlexisWilke: Bao gồm OKERR. Làm thế nào "loại" của họ để giúp chúng tôi điền vào đó khoảng cách trong cuộc sống của chúng tôi :-(
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;
}

Cần được biên dịch với -ltermcap. Có rất nhiều thông tin hữu ích khác mà bạn có thể nhận được bằng cách sử dụng termcap. Kiểm tra hướng dẫn sử dụng termcap info termcapđể biết thêm chi tiết.


Bạn cũng có thể biên dịch nó với -lcurses.
Kambus

2
Tôi biết nhận xét này đến 6 năm sau khi thực tế, nhưng xin giải thích con số kỳ diệu của bạn 2048 ...
einpoklum

1
@einpoklum Đây là gần ba năm sau đó, nhưng không phải là khá rõ ràng rằng 2048 chỉ là một kích thước tùy ý cho bộ đệm "có lẽ phải đủ lớn" cho bất kỳ chuỗi đầu vào nào đang ở đó?
Roflcopter 4

2
Trên thực tế, câu trả lời này tạo ra quá nhiều giả thiết là đúng.
Thomas Dickey

1
Đối với bất kỳ ai tò mò, kích thước bộ đệm 2048 được giải thích trong tài liệu về bản tóm tắt của GNU tại đây: gnu.org/software/termutils/manual/termcap-1.3/html_mono/… Ngoài ra còn có rất nhiều thứ khác mà mọi người đọc bài đăng này có thể thấy hữu ích .

3

Nếu bạn đã cài đặt ncurses và đang sử dụng nó, bạn có thể sử dụng getmaxyx()để tìm kích thước của thiết bị đầu cuối.


2
Có, và hãy lưu ý rằng chữ Y đi trước rồi đến chữ X.
Daniel

0

Giả sử bạn đang sử dụng Linux, tôi nghĩ bạn muốn sử dụng thư viện ncurses để thay thế. Tôi khá chắc chắn những thứ ttysize bạn có không có trong stdlib.


tốt, những gì tôi đang làm là không thực sự đáng để thiết lập ncurses cho
austin

ncurses cũng không có trong stdlib. Cả hai đều được chuẩn hóa trong POSIX, nhưng ioctlcách đơn giản và gọn gàng hơn, vì bạn không phải khởi tạo các lời nguyền, v.v.
Gandaro

0

Vì vậy, không đề xuất câu trả lời ở đây, nhưng:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Ok, và tôi nhận thấy rằng nếu tôi thay đổi kích thước của thiết bị đầu cuối GNOME, thì các biến LINES và COLUMNS sẽ tuân theo điều đó.

Kinda có vẻ như thiết bị đầu cuối GNOME đang tự tạo các biến môi trường này?


1
Và chắc chắn nó không chuyển xuống mã C. getenv ("LINES") trả về NULL.
Scott Franco

Các biến là một thứ vỏ, không phải là một thứ cuối cùng.
melpomene

0

Để thêm câu trả lời đầy đủ hơn, những gì tôi thấy phù hợp với mình là sử dụng giải pháp của @ John_T với một số bit được thêm vào từ Mã Rosetta , cùng với một số khắc phục sự cố để tìm ra sự phụ thuộc. Nó có thể hơi kém hiệu quả, nhưng với lập trình thông minh, bạn có thể làm cho nó hoạt động và không phải mở tệp đầu cuối của bạn mọi lúc.

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

Nếu bạn đảm bảo không gọi hết nhưng có thể thỉnh thoảng bạn vẫn thấy ổn, nó thậm chí sẽ cập nhật khi người dùng thay đổi kích thước cửa sổ đầu cuối (vì bạn đang mở tệp và đọc nó mọi lúc).

Nếu bạn không sử dụng, TIOCGWINSZhãy xem câu trả lời đầu tiên trên biểu mẫu này https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Oh, và đừng quên free()các result.


-1

Dưới đây là các lệnh gọi hàm cho biến môi trường đã được đề xuất:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));

11
Các biến môi trường không đáng tin cậy. Các giá trị này được thiết lập bởi shell, vì vậy chúng không được đảm bảo tồn tại. Ngoài ra, chúng sẽ không được cập nhật nếu người dùng thay đổi kích thước thiết bị đầu cuối.
Juliano

1
Nhiều shell thiết lập một trình xử lý cho SIGWINCHtín hiệu, vì vậy chúng có thể cập nhật các biến (chúng cũng cần nó để chúng thực hiện việc gói dòng thích hợp trong trình soạn thảo đầu vào).
Barmar

5
Họ có thể làm tốt điều đó, nhưng môi trường của chương trình sẽ không được cập nhật khi nó đang chạy.
Functino

Tất nhiên, mã đó rất có khả năng bị lỗi vì bạn không kiểm tra xem có getenv()trả về NULL hay không và nó có trong terminal Linux của tôi (vì các biến đó không được xuất.) Ngoài ra, ngay cả khi shell cập nhật các biến đó, bạn sẽ không thấy thay đổi trong khi chương trình của bạn đang chạy (không phải bạn không có SIGWINCHtrình xử lý của riêng mình ).
Alexis Wilke,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.