2. Setting/Getting the size of the terminal's window
3. Program capturing SIGWINCH
4. Program adapting its display to the window size
5. Application to a client/server application
5.1. Overview of the application
5.2. The client program
5.3. The server program
5.4. The displays from winch process
5.5. The window size management
6. Conclusion
7. References
8. About the author
SIGWINCH is a signal sent upon the resizing of a window: when the number of columns or rows changes, SIGWINCH is raised to the foreground processes attached to the terminal. The default disposition for this signal is to ignore it. But character mode applications like the editors which are sensible to the window size must handle this signal to adapt their display to the current size of the window.
iotctls are available to get and set the current window size. According to the Linux online manual (man ioctl_tty), window sizes are kept in the kernel, but not used by the kernel (except in the case of virtual consoles, where the kernel will update the window size when the size of the virtual console changes, for example, by loading a new font). The following constants and structure are defined in <sys/ioctl.h>:
- TIOCGWINSZ struct winsize *argp
Get window size.
- TIOCSWINSZ const struct winsize *argp
Set window size.
- The structure used by these ioctls is defined as:
struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; /* unused */ unsigned short ws_ypixel; /* unused */ };
In the latter structure, the interesting fields are ws_row and ws_col which respectively store the number of rows and columns.
Let's consider the following program (winch_01.c) which setups a signal handler for SIGWINCH. Each time the signal is raised, the signal handler fetches the current size of the terminal (by calling ioctl(TIOCGWINSZ)) and displays the current number of rows and columns:
#define _GNU_SOURCE #include <sys/types.h> #include <errno.h> #include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/ioctl.h> #include <termios.h> #include <string.h> #include <assert.h> // ---------------------------------------------------------------------------- // Name : sig_handler // Usage : Signal handler for SIGWINCH // Return : None // ---------------------------------------------------------------------------- static void sig_handler(int sig) { if (SIGWINCH == sig) { struct winsize winsz; ioctl(0, TIOCGWINSZ, &winsz); printf("SIGWINCH raised, window size: %d rows / %d columns\n", winsz.ws_row, winsz.ws_col); } } // sig_handler // ---------------------------------------------------------------------------- // Name : main // Usage : Program's entry point // Return : 0, if OK // !0, if error // ---------------------------------------------------------------------------- int main(void) { // Capture SIGWINCH signal(SIGWINCH, sig_handler); while (1) { pause(); } return 0; } // main |
The execution of this program displays the following when we resize the xterminal with the mouse:
$ ./winch_01 SIGWINCH raised, window size: 32 rows / 315 columns SIGWINCH raised, window size: 31 rows / 315 columns SIGWINCH raised, window size: 30 rows / 315 columns SIGWINCH raised, window size: 29 rows / 315 columns SIGWINCH raised, window size: 28 rows / 315 columns SIGWINCH raised, window size: 29 rows / 315 columns SIGWINCH raised, window size: 30 rows / 315 columns SIGWINCH raised, window size: 31 rows / 315 columns SIGWINCH raised, window size: 32 rows / 315 columns SIGWINCH raised, window size: 33 rows / 315 columns SIGWINCH raised, window size: 34 rows / 315 columns SIGWINCH raised, window size: 33 rows / 315 columns SIGWINCH raised, window size: 31 rows / 315 columns SIGWINCH raised, window size: 31 rows / 313 columns SIGWINCH raised, window size: 31 rows / 310 columns CTRL-C $ |
The preceding program is enhanced (winch_02.c) with the adding of the display_border() function which displays a frame around the window. This function is called at program's startup and after receipt of SIGWINCH to make the border fit the window changes at any time:
#define _GNU_SOURCE #include <sys/types.h> #include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/ioctl.h> #include <termios.h> #include <string.h> #include <assert.h> #include <stdlib.h> // ---------------------------------------------------------------------------- // Name : winch_display // Usage : Display buffer to reduce the number of write calls to display // the frame on the screen // ---------------------------------------------------------------------------- static char winch_display[4096]; // ---------------------------------------------------------------------------- // Name : winch_offset // Usage : Current offset in the displayu buffer // ---------------------------------------------------------------------------- static int winch_offset; // ---------------------------------------------------------------------------- // Name : winch_buffer_flush // Usage : Flush the content of the display buffer // Return : None // ---------------------------------------------------------------------------- static void winch_buffer_flush(int fd) { int rc; if (winch_offset > 0) { rc = write(fd, winch_display, winch_offset); assert(rc == winch_offset); winch_offset = 0; } } // winch_buffer_flush // ---------------------------------------------------------------------------- // Name : winch_buffer_write // Usage : Write into an internal buffer and flush it to stdout when it // is full // Return : None // ---------------------------------------------------------------------------- static void winch_buffer_write(int fd, char *data, size_t data_sz) { int i, j; for (j = 0, i = winch_offset; (j < (int)data_sz) && (i < (int)sizeof(winch_display)); i ++, j ++) { winch_display[i] = data[j]; } // New offset winch_offset = i; // If the display buffer is full ==> Flush it ! if (winch_offset == (int)sizeof(winch_display)) { winch_buffer_flush(fd); } // If there are more data to write if (j < (int)data_sz) { // Recursive call winch_buffer_write(fd, data + j, data_sz - j); } } // winch_buffer_write // ---------------------------------------------------------------------------- // Name : display_border // Usage : Display a frame around the terminal's window // Return : None // ---------------------------------------------------------------------------- static void display_border( int fd, unsigned short rows, unsigned short columns ) { unsigned short r, c; int rc; char banner[25]; static char *line = (char *)0; static size_t line_sz = 0; // Adjust the size of the line buffer // (+1 to take in account terminating '\n') if ((int)line_sz < (columns + 1)) { line = (char *)realloc(line, columns + 1); line_sz = columns + 1; } r = 0; // Print first line if (rows > 1) { unsigned short saved_rows = rows; // '\n' to go back to beginning of line after last display of 'colsXrows' winch_buffer_write(fd, "\n", 1); line[0] = '+'; for (c = 1; c < (columns - 1); c ++) { line[c] = '-'; } if (columns - 1) { line[columns - 1] = '+'; } line[columns] = '\n'; winch_buffer_write(fd, line, columns + 1); rows -= 1; if (rows >= 2) { line[0] = '|'; for (c = 1; c < (columns - 1); c ++) { line[c] = ' '; } if (columns - 1) { line[columns - 1] = '|'; } line[columns] = '\n'; // Print intermediate lines for (r = 0; r < (rows - 2); r ++) { winch_buffer_write(fd, line, columns + 1); } // End for rows -= r; } // Print last line if (rows == 2) { line[0] = '+'; for (c = 1; c < (columns - 1); c ++) { line[c] = '-'; } if (columns - 1) { line[columns - 1] = '+'; } line[columns] = '\n'; winch_buffer_write(fd, line, columns + 1); rows -= 1; } else { assert(1 == rows); } rc = snprintf(banner, sizeof(banner), "%dx%d: ", columns, saved_rows); } else { // '\n' to go back to beginning of line after // last display of 'colsXrows' rc = snprintf(banner, sizeof(banner), "\n%dx%d: ", columns, rows); } winch_buffer_write(fd, banner, rc); winch_buffer_flush(fd); } // display_border // ---------------------------------------------------------------------------- // Name : winch_winsz // Usage : Current size of the terminal // ---------------------------------------------------------------------------- static struct winsize winch_winsz; // ---------------------------------------------------------------------------- // Name : sig_handler // Usage : Signal handler for SIGWINCH // Return : None // ---------------------------------------------------------------------------- static void sig_handler(int sig) { if (SIGWINCH == sig) { ioctl(0, TIOCGWINSZ, &winch_winsz); display_border(1, winch_winsz.ws_row, winch_winsz.ws_col); } } // sig_handler // ---------------------------------------------------------------------------- // Name : main // Usage : Program's entry point // Return : 0, if OK // !0, if error // ---------------------------------------------------------------------------- int main(void) { char line[256]; size_t l; ioctl(0, TIOCGWINSZ, &winch_winsz); display_border(1, winch_winsz.ws_row, winch_winsz.ws_col); // Capture SIGWINCH signal(SIGWINCH, sig_handler); while (fgets(line, sizeof(line), stdin)) { l = strlen(line); if (l > 0) { if ('\n' == line[l - 1]) { line[l - 1] = '\0'; } if (!strcmp(line, "exit")) { break; } } display_border(1, winch_winsz.ws_row, winch_winsz.ws_col); } // End while return 0; } // main |
The execution of this program shows that the frame size is updated each time the size of the window changes (the last line displays the current number of rows and columns of the window and the "exit" keyword is accepted to quit the program properly):
$ ./winch_02 +-----------------------------------------------------------------------------------------------------------+ | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +-----------------------------------------------------------------------------------------------------------+ 109x28: |
Let's consider the following program which simulates a client
connecting to a remote server which displays the preceding frame
on the local terminal. To make it, we use the standard method
consisting to use a virtual terminal (i.e. PTY) which makes the
server believe that it is displaying its frame locally on its
host.
The following figure depicts the behavior of the application: a client named (winch_client) connects to a server (winch_server) to run the winch program seen in the previous paragraph.

The client program registers a SIGWINCH handler and connects to the server identified by the address and the port number passed as parameters. Then it enters its main loop consisting to listen to 3 events and to trigger a specific action for each of them:
- SIGWINCH signal: send the current window size to the
server
- Input from stdin: if it is the "exit" keyword, terminate the program
- Message from winch_server: display the data on screen
(this is the frame displayed by the winch program
running remotely)
The server program consists to bind on a given IP port and wait for connections from the clients:

When a client is connected, a pseudo-terminal (PTY) is created (cf. References for more information about the PTY):

Then, the server forks a child process:

The standard input and outputs of the latter (i.e. child process) are redirected to the PTY and a call to setsid() and ioctl(TIOCSCTTY) are done to make the PTY the controlling terminal of the child process. This step is very important because SIGWINCH is sent to the foreground processes of a controlling terminal. Hence the last two calls ! Future updates of the size of the PTY on the master side trigger a SIGWINCH signal in the child process to make it update the displayed frame:

Then the child process executes the winch program which believes that it is running with a local terminal and displays its frame as usual:

The display of the frame is done by winch by writing to its standard output. As the latter is redirected to the slave side of the PTY, the data appears on the master side. The server reads these data on send them as a message over the network to the connected client:

The update of the size of the terminal on the client side, makes it receives a SIGWINCH. Its signal handler reads the new size of the terminal with a call to ioctl(TIOCGWINSZ) on its standard input (local terminal) and send the dimension of the window as a message to the server. The latter calls ioctl(TIOCSWINSZ) onto the master side of the PTY to update the size of the pseudo-terminal accordingly. This ioctl also triggers a SIGWINCH event on the slave side of the PTY which is captured by the winch process. The latter updates the displayed frame accordingly with the size provided by a call to ioctl(TIOCGWINSZ) on the slave side.

This papers gave some tricks to manage the SIGWINCH
signal in character mode applications where the change of the size of the window
matters.
The source code of the preceding examples is available as a compressed file (winch.tgz). The archive provides a README file for instructions about the build and the execution of the examples.
The author is an engineer in computer sciences located in France. He can be contacted here.

