How to Get the Dimensions of a Linux Terminal Window in C

Here I will be addressing a topic of interest to those of you who want to write full-screen applications for the Linux terminal. Yes, you could use a library like curses for this, but I’m going to go a bit deeper than that and show you how it all works under the hood. Or at least one aspect of it.

To get the terminal window dimensions, we will be using two constructs in the POSIX API: the ioctl() function and the winsize struct. Both of these are defined in sys/ioctl.h.

The definition of the winsize struct looks like this:


struct winsize
{
  unsigned short ws_row, ws_col;
  unsigned short ws_xpixel, ws_ypixel;
};

The fields ws_row and ws_col give the current height and width of the terminal window in characters. The fields ws_xpixel and ws_ypixel give the terminal width and height in pixels. The latter two are useful for displaying graphics directly in the console as you would with the Linux Framebuffer API. Since we are not displaying in-terminal graphics at this time we don’t need them, and we will be focusing on the first two instead. But the tools and procedures I will be outlining here work equally well for both.

Let’s move on to ioctl() now. ioctl() is basically the dumping ground for the POSIX API. It’s a single C function encapsulating a myriad of commands that couldn’t be conveniently fit into their own functions. Its prototype looks like this:


int ioctlint fd, int cmd, ... );

The first argument designates what file descriptor to use. Usually this will be set to 0 for standard input. The second argument designates what command to execute on that file descriptor. The … is for an optional argument.

There are two ioctl commands that concern window size. They are TIOCSWINSZ and TIOCGWINSZ. Notice that the fifth letter in each of those is different: the first has an S because it sets the window dimensions to the values given by the third argument; the second has a G because it gets the window dimensions and stores them in the third argument. The third argument in this case is a pointer to a winsize struct.

To write any full-screen program in the Linux terminal, you need to first get the dimensions and then draw the elements in the terminal accordingly. Let’s say you want to fill the terminal with plus signs. The code would look something like this:


struct winsize sz;
ioctl0TIOCGWINSZ, &sz );
int limit = sz.ws_row * sz.ws_col;
forint i = 0; i < limit; i++ ){
        putchar'+' );
}

That’s just a silly example, but hopefully it will help elucidate how these two programming constructs can be used to determine the dimensions of a Linux terminal window and then draw a full-screen interface accordingly.

It gets more complicated than that though, because not only do you have to get the window size when the program starts; you also have to detect when the window is resized and redraw the interface according to the new dimensions every time that happens. This can be a very complicated process, but I’ll show you the basics.

To detect when the terminal window is resized, we will use process signals. Process signals are integer values that are sent to processes either by other processes or by the Linux kernel in response to certain events. The receiving process can either ignore the signal, specify an action to perform (called a signal handler), or go with the default action (which usually means the program is terminated).

Linux provides a special signal called SIGWINCH which is triggered every time the user resizes the terminal window. We want to set up a handler for this signal so that it calls a function that redraws the screen.

To handle a signal, we use the signal() function, defined in signal.h. This function is fairly simple: It takes two arguments – first the number of the symbol (typically we would use a macro for this) and then a specifier for the action to take – either SIG_DFL to take the default action, SIG_IGN to ignore the signal, or a pointer to a signal handling function.

We will call our handler redraw(). Here is the code we would add to handle a window resizing:


signalSIGWINCH, redraw );

Actually, we can simplify our code by putting the entire screen drawing procedure into the redraw() function, and just calling it at the beginning. This way the same procedure is called at the beginning and whenever the window is resized. So in our silly example with the plusses, our code would look something like this:


#include <stdio.h>
#include <ioctl.h>
#include <signal.h>

struct winsize sz;

void redraw( int signum ){
        printf"\e[2J" );
        ioctl0TIOCGWINSZ, &sz );
        int limit = sz.ws_row * sz.ws_col;
        forint i = 0; i < limit; i++ ){
                putchar'+' );
        }
}

int mainint argc, char **argv ){
        signalSIGWINCH, redraw );
        redraw();
        /* rest of the program */
        return 0;
}

One thing in that C program that may still be unfamiliar to some is the printf( "\e[2J" ); part. This is an ANSI escape sequence that clears the screen before redrawing it. Don’t want the terminal to be filled with accumulating gobbledigook from previous draws.

Well, that’s about it for today. If you liked this tutorial be sure to follow me, because I will be posting a lot more like it in the future. Yeah, and share it with your friends on social media if they’re into Linux and/or Linux programming. This blog is brand new and I hope to be posting far more helpful content as it grows and reaches more people. Until then, farewell.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s