|
CS256 - Principles of Structured Design
Fall 2021
| 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 -l lib-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();
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);
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, ...);
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;
}
|