Mendapatkan lebar terminal di C?

91

Saya telah mencari cara untuk mendapatkan lebar terminal dari dalam program C saya. Apa yang terus saya temukan adalah sesuatu di sepanjang baris:

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

Tapi setiap kali saya mencoba itu saya dapatkan

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

Apakah ini cara terbaik untuk melakukan ini, atau adakah cara yang lebih baik? Jika tidak, bagaimana saya bisa membuat ini bekerja?

EDIT: kode tetap adalah

#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
sumber
1
tidak ada jawaban yang disarankan lebih dari setengah benar.
Thomas Dickey
2
@ThomasDickey, di mana jawaban Anda?
Alexis Wilke

Jawaban:

127

Sudahkah Anda mempertimbangkan untuk menggunakan getenv () ? Ini memungkinkan Anda untuk mendapatkan variabel lingkungan sistem yang berisi kolom dan baris terminal.

Sebagai alternatif menggunakan metode Anda, jika Anda ingin melihat apa yang dilihat kernel sebagai ukuran terminal (lebih baik jika terminal diubah ukurannya), Anda perlu menggunakan TIOCGWINSZ, sebagai lawan TIOCGSIZE Anda, seperti:

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

dan kode lengkapnya:

#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
sumber
7
ya tapi istilah lebar bukanlah variabel lingkungan, statis untuk istilah tersebut.
austin
4
Itu tidak memberi Anda ukuran terminal saat ini , jika seseorang mengubah ukuran terminal selama eksekusi program.
Chris Jester-Young
ya, menambahkan bahwa :)
John T
bagaimana cara mendapatkan ukuran dalam piksel? Saya menggunakan ws_xpixeldan ws_ypixel, Tapi itu hanya mencetak nol!
Debashish
@Debbk Misalnya Linux tidak mendukung bidang tersebut sama sekali.
melpomene
16

Contoh ini agak panjang, tetapi saya yakin ini adalah cara paling portabel untuk mendeteksi dimensi terminal. Ini juga menangani acara pengubahan ukuran.

Seperti yang disarankan tim dan rlbond, saya menggunakan ncurses. Ini menjamin peningkatan besar dalam kompatibilitas terminal dibandingkan dengan variabel lingkungan membaca secara langsung.

#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
sumber
3
Tetapi apakah benar-benar aman untuk memanggil initscr dan endwin dari penangan sinyal? Mereka setidaknya tidak terdaftar di antara async-signal-safe APIs diman 7 signal
nav
1
Itu poin yang bagus @nav, saya tidak pernah memikirkan itu! Apakah solusi yang lebih baik mungkin meminta penangan sinyal menaikkan bendera, dan kemudian melakukan operasi lainnya di loop utama?
gamen
1
@gamen, ya, itu akan lebih baik;) - juga menggunakan sigaction daripada sinyal akan lebih baik juga.
Bodo Thiesen
Jadi, apakah variabel global COLS dan LINES?
einpoklum
1
@AlexisWilke: Termasuk OKdan ERR. Betapa "baik" mereka untuk membantu kita mengisi celah itu dalam hidup kita :-(
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;
}

Perlu dikompilasi dengan -ltermcap. Ada banyak informasi berguna lainnya yang bisa Anda peroleh dengan menggunakan termcap. Periksa manual termcap menggunakan info termcapuntuk lebih jelasnya.

Juliano
sumber
Anda juga dapat mengkompilasinya dengan -lcurses.
Kambus
2
Saya tahu komentar ini muncul 6 tahun setelah fakta, tapi tolong jelaskan angka ajaib 2048 ...
einpoklum
1
@einpoklum Ini hampir tiga tahun kemudian, tetapi bukankah cukup jelas bahwa 2048 hanyalah ukuran sewenang-wenang untuk buffer yang "mungkin harus cukup besar" untuk string input apa pun yang ada di sana?
Roflcopter4
2
Sebenarnya, jawaban ini membuat terlalu banyak asumsi untuk menjadi benar.
Thomas Dickey
1
Bagi siapa pun yang penasaran, ukuran buffer 2048 dijelaskan dalam dokumentasi termcap GNU di sini: gnu.org/software/termutils/manual/termcap-1.3/html_mono/… Ada juga banyak hal lain di sana, orang-orang yang membaca posting ini mungkin merasa berguna .
3

Jika Anda telah menginstal ncurses dan menggunakannya, Anda dapat menggunakan getmaxyx()untuk mencari dimensi terminal.

rlbond.dll
sumber
2
Ya, dan perhatikan bahwa Y datang lebih dulu dan kemudian X.
Daniel
0

Dengan asumsi Anda menggunakan Linux, saya rasa Anda ingin menggunakan pustaka ncurses sebagai gantinya. Saya cukup yakin barang ttysize yang Anda miliki tidak ada di stdlib.

tim
sumber
baik, apa yang saya lakukan tidak benar-benar layak untuk dipasang ncurses
austin
ncurses juga tidak di stdlib. Keduanya distandarisasi di POSIX, tetapi ioctlcaranya lebih sederhana dan lebih bersih, karena Anda tidak perlu menginisialisasi kutukan, dll.
Gandaro
0

Jadi tidak menyarankan jawaban di sini, tetapi:

linux-pc:~/scratch$ echo $LINES

49

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

linux-pc:~/scratch$

Ok, dan saya perhatikan bahwa jika saya mengubah ukuran terminal GNOME, variabel LINES dan COLUMNS mengikutinya.

Agaknya terminal GNOME membuat variabel lingkungan ini sendiri?

Scott Franco
sumber
1
Dan cukup yakin itu tidak diturunkan ke kode C. getenv ("LINES") mengembalikan NULL.
Scott Franco
Variabel adalah benda shell, bukan hal terminal.
melpomene
0

Untuk menambahkan jawaban yang lebih lengkap, apa yang menurut saya berhasil untuk saya adalah menggunakan solusi @ John_T dengan beberapa bit yang ditambahkan dari Rosetta Code , bersama dengan beberapa pemecahan masalah yang mencari tahu dependensi. Ini mungkin sedikit tidak efisien, tetapi dengan pemrograman cerdas Anda dapat membuatnya bekerja dan tidak membuka file terminal Anda sepanjang waktu.

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

Jika Anda memastikan untuk tidak memanggil semuanya tetapi mungkin sesekali Anda akan baik-baik saja, itu bahkan harus diperbarui ketika pengguna mengubah ukuran jendela terminal (karena Anda membuka file dan membacanya setiap saat).

Jika Anda tidak menggunakan, TIOCGWINSZlihat jawaban pertama di formulir ini https://www.linuxquestions.org/questions/programming-9/get-width-height-of-a-terminal-window-in-c-810739/ .

Oh, dan jangan lupa untuk free()yang result.

iggy12345
sumber
-1

Berikut adalah panggilan fungsi untuk variabel lingkungan yang sudah disarankan:

int lines = atoi(getenv("LINES"));
int columns = atoi(getenv("COLUMNS"));
merkuro
sumber
11
Variabel lingkungan tidak dapat diandalkan. Nilai-nilai ini ditetapkan oleh shell, sehingga tidak dijamin ada. Juga, mereka tidak akan diperbarui jika pengguna mengubah ukuran terminal.
Juliano
1
Banyak shell membuat penangan untuk SIGWINCHsinyal, sehingga mereka dapat menjaga variabel tetap mutakhir (mereka juga membutuhkannya sehingga mereka akan melakukan penggabungan baris yang tepat di editor input).
Barmar
5
Mereka mungkin melakukannya, tetapi lingkungan program tidak akan diperbarui saat dijalankan.
Functino
Tentu saja, kode itu sangat mungkin macet karena Anda tidak menguji apakah getenv()mengembalikan NULL atau tidak dan itu terjadi di terminal Linux saya (karena variabel-variabel itu tidak diekspor.) Juga bahkan jika shell memperbarui variabel-variabel itu, Anda tidak akan melihat berubah saat program Anda berjalan (bukan tanpa Anda memiliki SIGWINCHpenangan sendiri ).
Alexis Wilke