Last update: 07-Aug-2019
Author: R. Koucha
Playing with SIGWINCH
1. Introduction
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
1. Introduction

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.

2. Setting/Getting the size of the terminal's 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>:

              Get window size.

              Set window size.

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.

3. Program capturing SIGWINCH

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


// ----------------------------------------------------------------------------
// 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
$
4. Program adapting its display to the window size

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


// ----------------------------------------------------------------------------
// 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: 
5. Application to a client/server application

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.

5.1. Overview of the application

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.

figure_1
5.2. The client program

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:

5.3. The server program

The server program consists to bind on a given IP port and wait for connections from the clients:

figure_2

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

figure_3

Then, the server forks a child process:

figure_4


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:

figure_5

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

figure_6
5.4. The displays from winch process

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:

figure_7
5.5. The window size management

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.

figure_8

6. Conclusion

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.

7. References
8. About the author

The author is an engineer in computer sciences located in France. He can be contacted here.


Annex 1: Finished state machines (FSM)
The server process is internally driven with FSMs to simplify the algorithmic: there is one FSM to manage the server part and one FSM per-connection (client part).
Annex 1.1: Server's FSM
figure_srv_fsm
Annex 1.2: Client's FSM
figure_client_fsm