Logo  

CS351 - Computer Organization

C libraries

A programming library, is a external collection of code that can be used by other programs, but is not normally standard to the language. Libraries are usually an example of what is called an Application Programmer Interface (API) which is a set of data structures and functions that compose an interface to that libraries functionality, and an important aspect of programming is learning to work with various libraries and their API's so that you don't have to make everything yourself.

There are hundreds of libraries for C, including the standard C library (libc) which is included in your program by default, the C math library (libm) which you need if you want to do more than the most basic of floating point math, such as the sin(),cos(),pow() functions, etc. And there is the curses (or ncurses) library (libncurses) which we'll use to "draw" (with characters) on the terminal window.

To include a library in your program you need to usually include it's header file (#include <math.h> for libm, #include <curses.h> for libncurses, etc.) and then link the library to your program with -llib-name (minus the lib prefix, so -lm for the math library, -lncurses for the curses library) appended to your gcc command, so to make a program that needs both the math and ncurses library you'd type:

gcc -o prog prog.c -lm -lncurses

Introduction to Curses

Curses is a library that allows for optimal updating of character based terminal screens and allows for dynamic placing of text on the screen.

Your terminal screen is a 2 dimensional matrix of characters formatted into lines and columns. A typical 80x25 terminal window might be formatted like:

         ┌──────────────────────────── COLS ───────────────────────────┐
          0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2       7 7 7 7 7 7 7
          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0       3 4 5 6 7 8 9
   ┌     ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬     ┬─┬─┬─┬─┬─┬─┬─┐
   │   00│H│e│l│l│o│,│ │w│o│r│l│d│!│ │ │ │ │ │ │ │ │     │ │ │ │ │ │ │ │
   │     ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼     ┼─┼─┼─┼─┼─┼─┼─┤
   │   01│█│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │     │ │ │ │ │ │ │ │
   │     ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼ ... ┼─┼─┼─┼─┼─┼─┼─┤
   │   02│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │     │ │ │ │ │ │ │ │
   │     ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼     ┼─┼─┼─┼─┼─┼─┼─┤
   │   03│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │     │ │ │ │ │ │ │ │
 LINES   ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼     ┼─┼─┼─┼─┼─┼─┼─┤
   │                         .                       .           .
   │                         .                        .          .
   │                         .                         .         .
   │     ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼     ┼─┼─┼─┼─┼─┼─┼─┤
   │   23│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │     │ │ │ │ │ │ │ │
   │     ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼ ... ┼─┼─┼─┼─┼─┼─┼─┤
   │   24│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │     │ │ │ │ │ │ │ │
   └     └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴     ┴─┴─┴─┴─┴─┴─┴─┘


The origin of the screen is the upper left corner which in curses is at position 0,0. Unlike in geometry curses uses the line # followed by the column number, so (0,10) would be the 'l' character in the above example screen or row 0, line 10.

The cursor on your terminal represents where the next character will be printed. Curses provides means of moving the cursor to wherever you want prior to actually printing anything. In the above example it is currently sitting at line 1, column 0 and anything that is printed will start printing at that location.

Curses also maintains two global variables called:

LINES -- the total number of lines on the screen, and
COLS -- the total number of columns

which inform you about the size of your screen, which can be resized, even as the program runs (in which case LINES and COLS will change.)

Because curses controls everything (or at least attempts to,) that is seen on the terminal screen. To that end curses replaces all normal C library input and output functions with its own equivalents which you should always use when programming in curses. The equivalent functions are:

C function Curses equivalent:
Output:
putchar addch / mvaddch
printf printw / mvprintw
Input:
getchar getch / mvgetch
scanf scanw / mvscanw
fgets getnstr / mvgetnstr
  • The mv* functions move the cursor to a specific location before performing the rest of the function.

In curses there are two "screens" that curses maintains. The actual screen that you see and a virtual screen that all drawing operations are performed to. When a refresh() call is made the real screen is made to look like the virtual screen in as optimal a way as possible.

   ┌────────────────────┐
   │   Virtual Screen   │
┌──┴────────────────┐   │
│                   │   │
│    Real screen    │   │
│                   ├───┘
│                   │
└───────────────────┘

In graphics programming the virtual screen is a common programming practice, the computer draws the next scene on the virtual screen which then is made the real screen all at once. This is often called double buffering.

Steps to using curses:

  • Use the following include in your source:
    #include <curses.h>

  • To compile your C program with curses, you must link it with the ncurses library:
    gcc -o program program.c -lncurses

  • To initialize the curses library in your program you should use the following functions:

    initscr();

    • Determines the terminal type being used and initializes curses.

    cbreak();

    • (optional) Disables input/output line buffering and some special character handling. This should be enabled when you want to read a keypress immediately instead of waiting until the user has entered an entire line of input.

    nodelay(stdscr, TRUE);

    • (optional) Makes it so that functions such as getch() will return immediately if there is no input from the user to read. Should be combined with cbreak() above.

    noecho();

    • (optional) Turns off the echoing of keys typed (i.e. keys you type won't display on the screen.)

    keypad(stdscr, TRUE);

    • (optional) Enables the keypad (when the numlock is off), arrow and function keys.
  • When the program is done, the following should be called:

    echo();

    • Restore key echo (if noecho() was used)

    nocbreak();

    • Turn line buffering back on (if cbreak() was used)

    endwin();

    • Shutdown curses.

When curses has been initialized, you may then begin "drawing" on the screen. Again, the global integers LINES and COLS are set to the height and width (in characters) of the terminal screen, but not before initscr() is called.

curses_hello.c

#include <curses.h>
#include <string.h>

// Compile with:
// gcc -o curses_hello curses_hello.c -lncurses

int main(void)
{
  // Define our message and it's "width" in characters:
  char *message = "Hello, world!";
  int mesglen = strlen(message);

  // Initialize curses:
  initscr();
  // Clear the virtual screen:
  clear();

  // Print the message in the middle of the virtual screen:
  // The message is centered on the line by subtracting half the width of the
  // message from the middle column of the screen:
  mvprintw(LINES/2, (COLS/2)-(mesglen/2), "%s", message);
  // Updates the screen to look like the virtual screen:
  refresh();

  // Wait for a key-press (get a character from the user.)
  getch();
  // Shutdown curses:
  endwin();
  return 0;
}

Practice:

  • Enter the programs found here, compile them and run them, particularly the etch-a-sketch.c found below.

  • Change some things and see what happens, for example:

    • make the curses_hello.c print the message in the lower left corner.
    • Change where the prompts appear in the form.c program.

Basic curses functions:

Output:

int refresh(void);

  • Makes the real screen look like the virtual screen.

int clear(void);

  • Clears virtual screen.

int move(int y, int x);

  • Move the cursor to postion y,x (line y, column x)

int addch(const chtype ch);
int mvaddch(int y, int x, const chtype ch);

  • Adds a character to the screen, either at the current cusor position or at position y,x

int printw(const char *fmt, ...);
int mvprintw(int y, int x, const char *fmt, ...);

  • Print a formatted string (just like printf()) either at the current cursor position or at position y,x

Input:

Notes:

  • Unless echo is enabled, user input is not shown on the screen.
  • Most input functions cause an automatic refresh() to occur, however that shouldn't always be relied upon (especially if noecho() is enabled.)
  • When keypad mode is on, arrow and function keys return the special KEY_* values, such as:

Arrow keys: KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT
read: man getch for a complete list.

int getch(void);

  • Gets a character from the user (like getchar()). When echo is turned off the user will not see what is typed.

int getnstr(char *str, int n);
int mvgetnstr(int y, int x, char *str, int n);

  • Get a string from the user, n is the maximum number of characters to read.

Example:
char str[31];
mvgetstr(str,30); // Reads a string into s up to 30 characters in length

int scanw(const char *fmt, ...);
int mvscanw(int y, int x, const char *fmt, ...);

  • Just like scanf().

Example:
int n;
mvscanf(10,5,"%d", &n);

  • Move to line 10, column 5 and read a number into n.

form.c

#include <curses.h>
#include <string.h>

// Compile with:
// gcc -o form form.c -lncurses

// Prints the string in 'mesg' in the center of the given 'line':
void center(int line, char mesg[]) {
  mvprintw(line, COLS/2-strlen(mesg)/2, mesg);
}

int main(void)
{
  int y, x;

  initscr();
  clear();

  // Reads the X and Y coordinates from the user:
  mvprintw(10,10, "Input X: ");
  scanw("%d", &x);
  mvprintw(11,10, "Input Y: ");
  scanw("%d", &y);

  clear();
  // Note that the y coordinate (the line) is always specified before the x
  // coordinate (the column) in curses functions that take coordinates:
  mvprintw(y, x, " <- %d,%d is there", x, y);

  center(LINES-1, "Press any key to exit");
  refresh();

  // Wait for the user to type a character:
  getch();

  endwin();
  return 0;
}

Etch-a-sketch.c

#include <stdio.h>
#include <curses.h>

// Compile with:
// gcc -o etch-a-sketch etch-a-sketch.c -lncurses

int main(void)
{
  initscr();
  // Disables echo of keys typed:
  noecho();
  // Turns off line buffering (so we can get key-presses immediately):
  cbreak();
  // Enable arrow keys:
  keypad(stdscr, TRUE);

  // Define our software cursor position:
  int x = 1, y = 1;
  // Clears the screen:
  clear();

  // Draws a nice box around the entire screen:
  border(0,0,0,0,0,0,0,0);

  // moves the cursor to (1,1):
  move(y,x);
  refresh();

  // Loops forever (a non-zero number is always "true"):
  while (1) {
    // Read a character from curses:
    int ch = getch();
    // Exit the loop on a 'q' or 'Q':
    if (ch == 'q' || ch == 'Q') break;
    // Clear the screen when a 'c' or 'C' is typed:
    if (ch == 'c' || ch == 'C') {
      // Clearing the screen, moves the cursor back to 0,0:
      clear();
      // Restore the cursor to where it should be:
      move(y,x);
      refresh();
      continue;
    }
    // Check for movement keys and update y,x, making sure the coordinates stay
    // inside of the screen
    if (ch == KEY_UP || ch == 'w') {
      if (y > 0) y--;
    }
    if (ch == KEY_DOWN || ch == 's') {
      if (y < LINES-1) y++;
    }
    if (ch == KEY_LEFT || ch == 'a') {
      if (x > 0) x--;
    }
    if (ch == KEY_RIGHT || ch == 'd') {
      if (x < COLS-1) x++;
    }
    // Adds "reversed" space character at the cursor position, we'll talk about
    // "attributes" like A_REVERSE next time.
    addch(' '|A_REVERSE);
    // addch moves the cursor forward, so put the cursor back:
    move(y,x);
    refresh();    
  }

  // Shutdown:
  echo();
  nocbreak();
  endwin();
  return 0;
}