Ncurses

From Hamsterworks Wiki!

Jump to: navigation, search

If you want your 'C' programs to be a little prettier, maybe using a dash of colour and making use of function keys the easiest way is with the ncurses library. This article is a quick look at the very basics of ncurses - just enough to get you started.

When learning C you quickly become familiar with using the console and write programs interacts with the user like this;

printf("Please enter your name? :");
scanf("%s",name);

Although functional it looks pretty ordinary. With a little more work your programs can look much nicer and have much greater functionality

Contents

Compiling a C program that use ncurses

To use ncurses in your programs you need to include the "ncurses.h" header file within your C source, and use the "-lncurses" linker option in your compile command to inform the compiler that you will be using the library. Oh, and you must also have then "ncurses-devel" package or equivalent installed.

If you were to put the code from this article into a file called "console.c" you could compile it with the following command:

gcc -o console -Wall console.c -lncurses

Including the headers in your program

The only header file is required to use ncurses is "ncurses.h" so it must be included at the start of your program along with your other 'C' header files:

#include <ncurses.h>

Initialising the screen

The code in "setup_screen()" is pretty straight forward. It tries to initialise the terminal, exiting if it gets an error . It then does a lot of 'stuff' to make the keyboard work as expected. Most of these keyboard related functions will little sense unless you are familiar with the intricacies of old-school serial terminals. In the days of mainframes it was common for remote terminals to display characters locally as you typed them (called "local echo"). Using local echo removed any delays between typing and displaying characters and as a bonus avoided interrupting the main computer to process every keystroke.

Here is the complete setup function:

static void setup_screen()
{
  /* This sets up the screen */
  if(initscr()==NULL) {
    printf("Screen setup failed\n");
    exit(2);
  }

  /* This sets up the keyboard to send character by
     character, without echoing to the screen */
  cbreak();
  noecho();
  keypad(stdscr, TRUE);
  nonl();
  intrflush(stdscr, FALSE);

  /* If this is a colour display then select the colours */
  if(has_colors()) {
    start_color();
    init_pair(1,COLOR_GREEN,COLOR_BLACK);
    init_pair(2,COLOR_WHITE,COLOR_RED);
  } 
}

The ncurses interface for using colours is quite archaic, harking back to time when memory was expensive and terminals could only display eight or sixteen different colours. It uses the concept of "colour pairs", each of which are assigned a foreground and background colour. This is actually quite useful, allowing you to give users to have different themes by only changing the assignment of colour pairs, perhaps based on a users's profile.

Display text

The displaying text is pretty simple - just put the cursor where you want it using the "move()" function, and then use printw() to output your text, very much like using the printf() function. Everything written to the screen uses the current display 'attributes', such as colour (if available), underlining or enhanced text.

static void display_screen(void)
{
  int maxx, maxy;

  /* Work out how big the screen is */
  getmaxyx(stdscr, maxy, maxx);

  /* This is what we want to display */
  move(0,0); printw("This is how you own the whole screen");

  /* Display this in green */
  if(has_colors()) attron(COLOR_PAIR(1));
  move(2,2); printw("output at line two, column two");

  /* Display something centered on the bottom line */
  move(maxy-1,maxx/2-11);
  if(has_colors())
    attron(COLOR_PAIR(2));
  else
    attron(A_STANDOUT);
  printw("Press 'q' or F1 to quit");

  /* Update the screen */
  refresh();
}

Once your program has drawn the screen you should call refresh(). This tells ncurses to work out the best way to draw the changes on the screen and then sends them to the terminal. ncurses tries to help you out with this - whenever you you read from the keyboard it will call refresh() for you. This is especially important when displaying progress of some process - as you won't be reading from the keyboard your screen will not be updated unless you call refresh().

Although most terminal programs default to an 80x24 character window, you should not assume that the screen is always that size. Using the getmaxyx() function your program can discover the size of the terminal and use that to control the placement of text on the screen - in this case ensuring that the text is placed on the centre of the bottom line.

Processing input

Great! we have something on the screen, so we need to know how to interact with the user. One of the big features of using ncurses is that you have a nice generic way to access the none-standard keyboard keys, allowing you to make use of your keyboard's function keys within your program. Here's how:

void wait_for_keypress()
{
  int c;

  /* Wait for 'q' or F1 to be pressed */
  c = getch();
  while(c != 'q' && c != KEY_F(1))
    c = getch();
}

(The full list of symbolic names for keys can be found in the man page for 'getch()').

If you don't want to wait forever for a keypress, you can make use of the timeout() function (detailed in "man curs_inopts"). It allows you to set how long to wait for a keystroke, allowing you to "do other stuff". For example, you could update an onscreen clock, or check for new data to display.

Finishing

It is important to call the functions to tidy up the display. If you fail to do this your terminal may be left in unusable state, where you can't see what you are typing. Here's how to do do this:

static void finish(void)
{
  /* Tidy up the screen and set everything back to normal */
  clear();
  refresh();
  endwin();
}

Should this happen to you typing "stty sane" and pushing enter may save you, but resetting your terminal to a sensible configuration.

Putting it all together

Now all that remains is to put all four of these functions together as your program's main() function:

int main(int argc, char *argv[])
{
 setup_screen();
 display_screen();
 wait_for_keypress();
 finish();
 return 0;
}

And there you have it, a small program that can display text anywhere on the screen, can use colour where available, and has the ability to make full use of the keyboard. Character mode programs may not be as flashy as a full GUI based program but they are quick and simple to write and require little system resources - perfect for use on systems such as the Raspberry Pi.

Full Source code

/******************************************************
* console.c - a quick look at the basics of the ncurses
*             screen handling library
*
* Compile with
*    gcc -o console -Wall console.c -lncurses
*
* You will need to have ncurses-devel installed for it
* to compile correctly
******************************************************/
#include <stdlib.h>
#include <unistd.h>
#include <ncurses.h>

static void setup_screen()
{
  /* This sets up the screen */
  if(initscr()==NULL) {
    printf("Screen setup failed\n");
    exit(2);
  }

  /* If this is a colour display then select the colours */
  if(has_colors()) {
    start_color();
    init_pair(1,COLOR_GREEN,COLOR_BLACK);
    init_pair(2,COLOR_WHITE,COLOR_RED);
  }

  /* This sets up the keyboard to send character by
     character, without echoing to the screen */
  cbreak();
  noecho();
  keypad(stdscr, TRUE);
  nonl();
  intrflush(stdscr, FALSE);
}

static void display_screen(void)
{
  int maxx, maxy;

  /* Work out how big the screen is */
  getmaxyx(stdscr, maxy, maxx);

  /* This is what we want to display */
  move(0,0); printw("This is how you own the whole screen");

  /* Display this in green */
  if(has_colors()) attron(COLOR_PAIR(1));
  move(2,2); printw("output at line two, column two");

  /* Display something centered on the bottom line */
  move(maxy-1,maxx/2-11);
  if(has_colors())
    attron(COLOR_PAIR(2));
  else
    attron(A_STANDOUT);
  printw("Press 'q' or F1 to quit");

  /* Update the screen */
  refresh();
}

void wait_for_keypress()
{
  int c;

  /* Wait for 'q' or F1 to be pressed */
  c = getch();
  while(c != 'q' && c != KEY_F(1))
    c = getch();
}

static void finish(void)
{
  /* Tidy up the screen and set everything back to normal */
  clear();
  refresh();
  endwin();
}

int main(int argc, char *argv[])
{
  setup_screen();
  display_screen();
  wait_for_keypress();
  finish();
  return 0;
}

Personal tools