Terminal Window Size SIGWINCH · TIOCGWINSZ · winsize struct

Terminal Window Size
Chapter 62.9 — SIGWINCH · TIOCGWINSZ · winsize struct | EmbeddedPathashala
Signal
SIGWINCH
ioctl
TIOCGWINSZ / TIOCSWINSZ
Header
<sys/ioctl.h>

Why Does Window Size Matter?

When you run a text editor like vim or a terminal application like top, it draws content that fills your terminal window exactly. If you resize the window, the application needs to know the new dimensions so it can redraw itself correctly.

Linux provides two mechanisms for this:

  • SIGWINCH signal — the kernel automatically notifies the foreground process group whenever the terminal window size changes.
  • ioctl(TIOCGWINSZ) — a process can query the current window dimensions at any time.

Together, these allow an application to respond dynamically to window resizes, giving the user a smooth, correctly-laid-out experience.

Key Terms

SIGWINCH TIOCGWINSZ TIOCSWINSZ struct winsize ws_row ws_col ws_xpixel ws_ypixel ioctl sigaction foreground process group pause()

🔄 How the Window Size Mechanism Works

The kernel tracks the terminal window size in a winsize structure attached to each terminal. When the terminal emulator (like xterm or gnome-terminal) is resized by the user, it updates this kernel structure and sends a SIGWINCH signal to the foreground process group.

User resizes
terminal window
Terminal emulator
(xterm / gnome-terminal)
updates kernel winsize
Kernel sends
SIGWINCH
to foreground
process group
Your app catches
SIGWINCH, calls
ioctl(TIOCGWINSZ)
and redraws

Default behavior of SIGWINCH: If your program does not install a handler, SIGWINCH is silently ignored. Only programs that care about window size (editors, TUI apps) need to handle it.

📈 The winsize Structure

The window dimensions are returned in a struct winsize defined in <sys/ioctl.h>:

#include <sys/ioctl.h>

struct winsize {
    unsigned short ws_row;    /* Number of character rows */
    unsigned short ws_col;    /* Number of character columns */
    unsigned short ws_xpixel; /* Terminal width in pixels  (unused on Linux) */
    unsigned short ws_ypixel; /* Terminal height in pixels (unused on Linux) */
};
Field Meaning Used on Linux?
ws_row Number of character rows (lines) in the terminal ✅ Yes
ws_col Number of character columns (characters per line) ✅ Yes
ws_xpixel Horizontal pixel size of terminal ❌ Not used
ws_ypixel Vertical pixel size of terminal ❌ Not used

Linux only populates ws_row and ws_col. The pixel fields are always zero. If you need pixel size, query the X display system directly instead.

Terminal Grid Concept
c
o
l
u
m
n
s
← ws_col
↑ ws_row (3 shown)
Each cell = 1 character position

🔍 Reading Window Size: ioctl(TIOCGWINSZ)

To query the current terminal window dimensions, use ioctl() with the TIOCGWINSZ (Terminal IO Control Get WINdow SiZe) request:

#include <sys/ioctl.h>

struct winsize ws;

if (ioctl(fd, TIOCGWINSZ, &ws) == -1) {
    perror("ioctl TIOCGWINSZ");
    exit(EXIT_FAILURE);
}

printf("Rows: %d, Columns: %d\n", ws.ws_row, ws.ws_col);

fd can be any file descriptor that refers to a terminal — STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, or a file descriptor from open("/dev/tty", ...).

🔔 Handling SIGWINCH — The Window Resize Signal

SIGWINCH is sent automatically by the kernel to the foreground process group whenever the terminal window size changes. To react to resizes, install a signal handler using sigaction().

The typical pattern: the handler just sets a flag (or does nothing), and the main loop calls ioctl(TIOCGWINSZ) to get the new size and redraws the screen.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <unistd.h>

/* Volatile sig_atomic_t: safe to read/write from a signal handler */
static volatile sig_atomic_t winch_received = 0;

static void sigwinch_handler(int sig)
{
    (void)sig;
    winch_received = 1;   /* Just set a flag; do real work in main loop */
}

int main(void)
{
    struct winsize ws;
    struct sigaction sa;

    /* Install the SIGWINCH handler */
    sa.sa_handler = sigwinch_handler;
    sa.sa_flags   = 0;
    sigemptyset(&sa.sa_mask);

    if (sigaction(SIGWINCH, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    /* Print initial size */
    if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) {
        perror("ioctl");
        exit(EXIT_FAILURE);
    }
    printf("Initial size: %d rows x %d columns\n", ws.ws_row, ws.ws_col);
    printf("Resize the terminal window to see updates. Press Ctrl+C to quit.\n");

    /* Main loop: wait for SIGWINCH and handle it */
    for (;;) {
        pause();   /* Sleep until any signal arrives */

        if (winch_received) {
            winch_received = 0;

            /* Query the new window size */
            if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) {
                perror("ioctl TIOCGWINSZ");
                exit(EXIT_FAILURE);
            }

            printf("Window resized: %d rows x %d columns\n",
                   ws.ws_row, ws.ws_col);

            /* In a real app: redraw your TUI here */
        }
    }

    return 0;
}
gcc -o winsize_demo winsize_demo.c
./winsize_demo
# Now resize your terminal window and watch the output

Sample output when you resize the window three times:

Initial size: 24 rows x 80 columns
Resize the terminal window to see updates. Press Ctrl+C to quit.
Window resized: 35 rows x 80 columns
Window resized: 35 rows x 73 columns
Window resized: 22 rows x 73 columns

✏️ Setting Window Size: ioctl(TIOCSWINSZ)

Just as you can read the window size, you can also set it using TIOCSWINSZ (Set WINdow SiZe). You fill a winsize struct and pass it in.

struct winsize ws;

ws.ws_row    = 40;
ws.ws_col    = 100;
ws.ws_xpixel = 0;   /* Linux ignores pixel fields */
ws.ws_ypixel = 0;

if (ioctl(fd, TIOCSWINSZ, &ws) == -1) {
    perror("ioctl TIOCSWINSZ");
    exit(EXIT_FAILURE);
}

If the values in the new winsize differ from what the kernel currently has, two things happen automatically:

① Kernel Updates Its Records

The terminal driver’s internal winsize structure is updated with the new row and column values you provided.

② SIGWINCH is Sent

The kernel sends SIGWINCH to the foreground process group, notifying all processes that the window dimensions have changed.

Use case: Terminal emulators use TIOCSWINSZ to tell the kernel what the new window size is when you drag to resize the window. The kernel then forwards SIGWINCH to the running shell or application.

💻 Practical Example: Responsive TUI Layout

This example shows how a simple screen-drawing program adapts its output to always fit the current terminal window, responding dynamically to resize events.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <unistd.h>

static volatile sig_atomic_t need_redraw = 1; /* Draw on startup too */

static void sigwinch_handler(int sig)
{
    (void)sig;
    need_redraw = 1;
}

/* Draw a simple banner that fits the current terminal width */
static void draw_screen(int rows, int cols)
{
    /* Clear screen with ANSI escape */
    printf("\033[2J\033[H");

    /* Draw top border of dashes to fill the width */
    for (int i = 0; i < cols; i++) putchar('-');
    putchar('\n');

    /* Centered title */
    const char *title = "EmbeddedPathashala Terminal Demo";
    int tlen = strlen(title);
    int pad  = (cols - tlen) / 2;
    if (pad < 0) pad = 0;
    printf("%*s%s\n", pad, "", title);

    /* Show dimensions */
    printf("Window: %d rows x %d columns\n", rows, cols);

    /* Bottom border */
    for (int i = 0; i < cols; i++) putchar('-');
    printf("\nPress Ctrl+C to exit. Resize the window!\n");

    fflush(stdout);
}

int main(void)
{
    struct winsize ws;
    struct sigaction sa;

    sa.sa_handler = sigwinch_handler;
    sa.sa_flags   = 0;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGWINCH, &sa, NULL);

    for (;;) {
        if (need_redraw) {
            need_redraw = 0;

            if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) {
                perror("ioctl");
                exit(EXIT_FAILURE);
            }

            draw_screen(ws.ws_row, ws.ws_col);
        }
        pause(); /* Sleep until SIGWINCH or another signal */
    }

    return 0;
}
gcc -o tui_demo tui_demo.c
./tui_demo

When you resize your terminal, the border and layout automatically adjust to fit the new dimensions.

📌 Quick Utility: Print Terminal Size in One Shot

You do not always need a signal handler. Here is a minimal utility to just print the current terminal size and exit — useful for shell scripts or one-time queries.

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main(void)
{
    struct winsize ws;

    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) {
        perror("ioctl TIOCGWINSZ");
        exit(EXIT_FAILURE);
    }

    printf("Rows:    %hu\n", ws.ws_row);
    printf("Columns: %hu\n", ws.ws_col);

    return 0;
}

You can also get this from the shell directly using the stty size command, which internally uses the same TIOCGWINSZ ioctl:

$ stty size
24 80
# Output: rows columns

🎓 Interview Questions — Terminal Window Size

Q1. What is SIGWINCH and when is it sent?

A: SIGWINCH (Signal Window CHange) is sent by the kernel to the foreground process group whenever the terminal window is resized. By default it is ignored. Applications that draw text-based UIs (like vim, top, htop) install a handler for it so they can redraw their display to match the new dimensions.

Q2. What is TIOCGWINSZ and how do you use it?

A: It is an ioctl() request that reads the current terminal window dimensions into a struct winsize. You call it as ioctl(fd, TIOCGWINSZ, &ws) where fd is any file descriptor referring to a terminal. After the call, ws.ws_row holds the number of character rows and ws.ws_col holds the number of character columns.

Q3. Describe the fields of struct winsize. Which ones does Linux actually use?

A: The struct has four fields: ws_row (character rows), ws_col (character columns), ws_xpixel (pixel width), and ws_ypixel (pixel height). Linux only populates and uses ws_row and ws_col. The pixel fields are always zero on Linux.

Q4. What happens when you call ioctl(fd, TIOCSWINSZ, &ws) with new dimensions?

A: If the new values differ from the current size stored in the terminal driver, two things happen: (1) the kernel updates its internal winsize data structure with the new values, and (2) it sends SIGWINCH to the foreground process group to notify them of the change.

Q5. Why should a signal handler for SIGWINCH only set a flag instead of doing complex work?

A: Signal handlers run asynchronously and interrupt the normal flow of the program. Doing complex work (like calling printf or redrawing the screen) inside a signal handler is unsafe because many functions are not async-signal-safe. The safe pattern is to set a volatile sig_atomic_t flag in the handler and do the actual work (calling ioctl(TIOCGWINSZ) and redrawing) in the main loop after the handler returns.

Q6. Who sends SIGWINCH to the process when you resize a terminal window?

A: The terminal emulator (like xterm, gnome-terminal, or konsole) detects the window resize event from the windowing system. It calls ioctl(TIOCSWINSZ) to update the kernel’s record of the window size. As a side effect of that call, the kernel automatically sends SIGWINCH to the foreground process group of that terminal.

Q7. What is the difference between TIOCGWINSZ and TIOCSWINSZ?

A: TIOCGWINSZ (Get) reads the current window dimensions from the kernel into a struct winsize you provide. TIOCSWINSZ (Set) writes new dimensions from your struct winsize into the kernel’s terminal records and triggers SIGWINCH if the size actually changed.

Q8. Which file descriptors can you use as the fd argument in ioctl(TIOCGWINSZ)?

A: Any file descriptor that refers to a terminal — most commonly STDIN_FILENO (0), STDOUT_FILENO (1), or STDERR_FILENO (2) when the program is connected to a terminal. You can also use a descriptor from open("/dev/tty", O_RDWR) which always refers to the controlling terminal, even if stdin/stdout have been redirected.

Q9. How would you check the terminal size from a shell script?

A: Use the stty size command, which prints rows and columns separated by a space. Alternatively, the shell variables $LINES and $COLUMNS are updated automatically in bash when SIGWINCH is received. Both ultimately rely on the TIOCGWINSZ ioctl internally.

Chapter 62 Complete

You have covered Terminal Line Speed, Line Control, and Window Size — all key topics for Linux terminal programming.

EmbeddedPathashala Home

Leave a Reply

Your email address will not be published. Required fields are marked *