The following programs demonstrate uses of low-level ETI (curses) functions. See the demonstration programs delivered on the ETI product diskettes for programs that use the high-level ETI functions.
This program illustrates how to use curses routines to write a screen editor. For simplicity, editor keeps the buffer in stdscr; obviously, a real screen editor would have a separate data structure for the buffer. This program has many other simplifications: no provision is made for files of any length other than the size of the screen, for lines longer than the width of the screen, or for control characters in the file.
Several points about this program are worth making. First, it uses the move(), mvaddstr(), flash(), wnoutrefresh() and clrtoeol() routines that are all discussed in this document.
Second, it also uses some curses routines that are not discussed in this document. For example, the function to write out a file uses the mvinch() routine, which returns a character in a window at a given position. The data structure used to write out a file does not keep track of the number of characters in a line or the number of lines in the file. so trailing blanks are eliminated when the file is written. The program also uses the insch(), delch(), insertln(), and deleteln() routines. These functions insert and delete a character or line. See curses(3X) for more information about these routines.
Third, the editor command interpreter accepts special keys, as well as ASCII characters. On one hand, new users find an editor that handles special keys easier to learn about. For example, it's easier for new users to use the arrow keys to move a cursor than it is to memorize that the letter h means left, j means down, k means up, and l means right. On the other hand, experienced users usually like having the ASCII characters to avoid moving their hands from the home row position to use special keys.
NOTE: Because not all terminals have arrow keys, your curses programs will work on more terminals if there is an ASCII character associated with each special key.
Fourth, the CTRL-L command illustrates a feature most programs using curses routines should have. Often some program beyond the control of the routines writes something to the screen (for instance, a broadcast message) or some line noise affects the screen so much that the routines cannot keep track of it. A user invoking editor can type CTRL-L, causing the screen to be cleared and redrawn with a call to wrefresh (curscr).
Finally, another important point is that the input command is terminated by CTRL-D, not the escape key. It is very tempting to use escape as a command, since escape is one of the few special keys available on every keyboard. (Return and break are the only others.) However, using escape as a separate key introduces an ambiguity. Most terminals use sequences of characters beginning with escape (i.e., escape sequences) to control the terminal and have special keys that send escape sequences to the computer. If a computer receives and escape from a terminal, it cannot tell whether the user depressed the escape key or whether a special key was pressed.
editor and other curses programs handle the ambiguity by setting a time. If another character is received during this time, and if that character might be the beginning of a special key, the program reads more input until either a full special key is read, the time out is reached, or a character is received that could not have been generated by a special key. While this strategy works most of the time, it is not foolproof. It is possible for the user to press escape, then to type another key quickly, which causes the curses program to think a special key has been pressed. Also, a pause occurs until the escape can be passed to the user program, resulting in a slower response to the escape key.
Many existing programs use escape as a fundamental command, which cannot be changed without infuriating a large class of users. These programs cannot make use of special keys without dealing with this ambiguity, and at best must resort to a time-out solution. The moral is clear: when designing your curses programs, avoid the escape key.
/* editor: A screen-oriented editor. The user * interface is similar to a subset of vi. The buffer * is kept in stdscr to simplify the program. */ #include <stdio.h> #include <curses.h> #define CTRL(c) ((c) & 037) main(argc, argv) int argc; char **argv; { extern void perror(), exti(); init i, n, l; int c; int line = 0; FILE *fd; if (argc !=2 { fprintf(stderr, "Usage: %s file\n", argv[0]); exit (1); } fd = fopen (argv[1], "r"); if (fd == NULL) { perror (argv[1]); exit (2); } initscr(); cbreak(); nonl(); noecho(); idlok(stdscr, TRUE); keypad(stdscr, TRUE); /*Read in the file*/ while ((c = getc (fd) ) !=EOF) { if (c == '\n) line++; if (line > LINES -2) break; addch(c); } fclose(fd); move(0,0); refresh(); edit(); /*Write out the file*/ fd = fopen (argv[1], "w"); for (l = 0; l < LINES - 1; l++) { n = len(l); for (i=0; i < n; i++) putc(mvinch(l, i) &A_CHARTEXT, fd); putc('\n\, fd); } fclose(fd); endwin(); exit(0); } len(lineno) int lineno; { int linelen = COLS - 1; while(linelen >= 0 && mvinch(lineo,linelen)== '') linelen--; return linelen +1; } /*Global value of current cursor position*/ int row, col; edit() { int c; for (;;) { move(row, col); refresh(); c = getch(); /*Editor commands*/ switch (c) { /*hjkl and arrow keys: move cursor *in direction indicated*/ case'h' case KEY _LEFT: if (col > 0) col--; else flash(); break; case 'j': case KEY_DOWN: if (row <LINES - 1) row++; else flash(); break; case 'k': case KEY_UP: if (row > 0) row--; else flash(); break; case 'l': case KEY_RIGHT: if (col < COLS - 1) col++; else flash(); break; /* i: enter input mode*/ case KEY_IC: case 'i': input(); break; /* x: delete current character8? case KEY_CD: case 'x': delch(); break; /* o: open up a new line and enter input mode*/ case KEY_IL: case '0': move(++row, col = 0); insertln(); input(); break; /*d: delete current line*/ case KEY_DL: case 'd': deleteln(); break; /*^L: redraw screen*/ case KEY_CLEAR: case CTRL('L'): wrefresh(curscr); break; /*w: write and quit*/ case 'w'" return; /* q: quit without writing*/ case 'q': endwin(); exit(2); default: flash(); break; } } } /* *Insert mode: accept characters and insert them. *End with ^D or EIC */ input() { int c; standout(); mvaddstr(LINES -1, COLS - 20, "INPUT MODE"); standend(); move(row, col); refresh(); for(;;) { c = getch (); if (c==CTRL('D') || c == KEY_EIC) break' insch (c); move (row, ++col); refresh(); } move(LINES =1, COLS -20); clrtoeol(); move(row, col); refresh(); }
This program illustrates a use of the routine attrset(). highlight reads a text file and uses embedded escape sequences to control attributes. \U turns on underlining, \B turns on bold, and \N restores the default output attributes.
Note the first call to scrollok(), a routine that we have not previously discussed (see curses(3X)). This routine allows the terminal to scroll if the file is longer than one screen. When an attempt is made to draw past the bottom of the screen, scrollok() automatically scrolls the terminal up a line and calls refresh().
/* *highlight: a program to turn \U, \B, and *\N sequences into highlighted *output, allowing words to be *displayed underlined or in bold. */ #include <stdio.h> #include <curses.h> main(argc, argv) int argc; char **argv; { FILE *fd; int c, c2; void exit(), perror(); if (argc ! = 2) { fprintf(stderr, "Usage: highlight file\n"); exit (1); } fd = fopen(argv[1], "r"); if (fd == NULL) { perror (argv[1]); exit (2); } initscr(); scrollok (stdscr, TRUE); nonl(); while ((c = getc(fd)) ! = EOF) { if (c == '\\') { c2 = getc (fd); switch (c2) { case 'B': attrset (A_BOLD); continue; case 'U': attrset (A_UNDERLINE); continue; case 'N': attrset(0); continue; } addch(c); addch(c2); } else addch(c); } fclose(fd); refresh(); edwin(); exit(0); }
This program takes the first LINES - 1 lines of characters from the standard input and displays the characters on a terminal screen in a random order. For this program to work properly, the input file should not contain tabs or non-printing characters.
/* * The scatter program. */ #include <curses.h> #include <sys/types.h> extern time_t time(); #define MAXLINES 120 #define MAXCOLS 160 char s[MAXLINES] [MAXCOLS]; /*Screen Array*/ int T[MAXLINES] [MAXCOLS]; /*Tag Array-Keeps track * *of the number of * * characters printed and * * their positions.*/ main() { register int row = 0, col = 0; register int c; int char_count = 0; time_t t; void exit(), srand(); initscr(); for (row = 0; row<MAXLINES; row++) for(col = 0; col <MAXCOLS; col++) s[row][col]=''; col = row = 0; /*Read screen in */ while ((c=getchar()) !=EOF && row <LINES){ if (c ! ='\n') { /*Place char in screen array*/ s[row][col++] = c; if (c !=' ') char_count++; } else { col = 0; row++; } } time(&t);/*Seed the random number generator*/ srand ((unsigned)t); while (char_count) { row = rand() % LINES; col =(rand() >> 2) % COLS; if (T[row][col] !=1 && s[row][col] !='') { move (row, col); addch(s[row][col]); T[row][col] = 1; char_count--; refresh(); } } endwin(); exit(0); }
This program pages through a file, showing one screen of its contents each time you depress the space bar. The program call cbreak() so that you can depress the space bar without having to hit return; it calls noecho() to prevent the space from echoing on the screen. The nonl() routine, which we have not previously discussed, is called to enable more cursor optimization. The idlok() routine, which we also have not discussed, is called to allow insert and delete line. (See curses(3X) for more information about these routines). Also notice that clrtoeol() and clrtobot() are called.
By creating an input file for show made up of screen-sized (about 24 lines) pages, each varying slightly from the previous page, nearly any exercise for a curses() program can be created. This type of input file is called a show script.
#include <curses.h> #include <signal.h> main(argc, argv) int argc; char *argv[]; { FILE *fd; char linebuf [BUFSIZ]; int line; void done(), perror(), exit(); if (argc !=2) { fprintf(stderr, "usage:%s file\n", argv[0]; exit(1); } if ((fd=fopen(argv[1],"r"))==NULL) { perror(argv[1]); exit(2); } signal(SIGINT, done); initscr(); noecho(); cbreak(); nonl(); idlok(stdscr, TRUE); while(1) { move(0,0); for (line=0;line<LINES;line++) { if(!fets(linebuf, sizeof linebuf, fd)) { clrtobot(); done(); } move(line, 0); printw("%s", linebuf); } refresh(); if (getch()=='q') done(); } } void done() { move(LINES -1, 0); clrtoeol(); refresh(); endwin(); exit(0); }
This program pages through a file, writing one page to the terminal from which the program is invoked and the next page to the terminal name on the command line. it then waits for a space to by typed on either terminal and writes the next page to the terminal at which the space is typed.
The two program is just a simple example of a two-terminal curses program. It does not handle notification; instead, it requires the name and type of the second terminal on the command line. As written, the command "sleep 100000"must be typed at the second terminal to put it to sleep while the program runs, and the user of the first terminal must have both read and write permission on the second terminal.
#include <curses.h> #include <signal.h> SCREEN *me, *you; SCREEN *set_term(); FILE *fd, *fdyou; char linebuf[512]; main (argc, argv) int argc; char **argv; { void done(), exit(); unsigned sleep(); char *getenv(); int c; if (argc !=4) { fprintf(stderr, "Usage: two othertty otherttytype inputfile\n"); exit (1); } fd = fopen(argv[3], "r"); fdyou = fopen(argv[1], "w+"); signal (SIGINT, done); /*die gracefully*/ me=newterm(getenv("TERM"), stdout, stdin); /*initialize my tty*/ you=newterm(argv[2], fdyou, fdyou); /*Initialize the other terminal*/ set_term(me); /*Set modes for my terminal*/ noecho();/*turn off tty echo*/ cbreak();/*enter cbreak mode*/ nonl(); /*Allow linefeed*/ nodelay(stsdscr, TRUE); /*No hand on input*/ set_term(you); /*Set modes for other terminal*/ noecho(); cbreak(); nonl(); nodelay(stdscr, TRUE); /*Dump first screen full on my terminal*/ dump_page(me); /*Dump second screen full on the other terminal*/ dump_page(you); for (;;) /* for each screen full */ { set_term(me); c=getch(); if (c=='q') /* wait for user to read it*/ done(); if (c==' ') dump_page(me); set_term(you); c=getch(); if (c=='q')/*wait for user to read it*/ done(); if (c==' ') dump_page(you); sleep(1); } } dump_page(term) SCREEN*term; { int line; set_term(term); move(0,0); for (line=0; line<LINES -1; line++) { if (fgets (fgets (linebuf, sizeof linebuf, fd)==NULL) { clrtobot(); done(); } mvaddstr(line, 0, linebuf); } standout(); mvprintw(LINES -1, 0,"-More-"); standend(); refresh(); /*sync screen*/ } /* Clean up and exit. */ void done() { /*Clean up first terminal*/ set_term(you); move(LINES -1,0);/*to lower left corner*/ clrtoeol(); /*clear bottom line*/ refresh(); /*flush out everything*/ endwin();/*curses cleanup*/ /*Clean up second terminal*/ set_term(me); move(LINE-1,0); /*to lower left corner*/ clrtoeol(); /*clear bottom line*/ refresh(); /*flush out everything*/ endwin();/*curses cleanup*/ exit(0); }
This example program demonstrated the use of multiple windows. The main display is kept in stdscr. When you start to put something other than what is in stdscr on the physical terminal screen temporarily, a new window is created covering part of the screen. A call to wrefresh() for that window causes it to be written over the stdscr image on the terminal screen. Calling refresh() on stdscr results in the original window being redrawn on the screen. Note the calls to the touchwin() routine (which we have not discussed -see curses(3X)) that occur before writing out a window over an existing window on the terminal screen. This routine prevents screen optimization in a curses program. If you have trouble refreshing a new window that overlaps an old window, it may be necessary to call touchwin() for the new window to get it completely written out.
#include <curses.h> WINDOW *cmdwin; main() { int i, c; char buf[120]; void exit(); initscr(); nonl(); noecho(); cbreak(); cmdwin=newwin(3, COLS,0,0);/*top 3 lines*/ for (i=0; i<LINES; i++) mvprintw(i,0,"this is line %d of stdscr", i); for (;;) { refresh(); c = getch(); switch (c) { case 'c':/*Enter command from keyboard*/ werase(cmdwin); wprintw(cmdwin,"Enter command:"); wmove(cmdwin, 2, 0); for (i=0; i < COLS; i++) waddch(cmdwin, '_'); wmove(cmdwin, 1, 0); touchwin(cmdwin); wrefresh(cmdwin); wgetstr(cmdwin, buf); touchwin(stdscr); /* *The command is now in buf. *It should be processed here. */ case 'q': endwin(); exit(0); } } }
This program creates two windows. All characters displayed in the first window will be in read, on a blue background. All characters displayed in the second window will be in yellow, on a magenta background.
#include <curses.h> #define PAIR1 1 #define PAIR2 2 main() { WINDOW *win1, *win2; initscr(); if ((start_color())==OK) { /*create windows*/ win1=newwin(5, 40, 0, 0); win2=newwin(5, 40, 15, 40); /*create two color pairs */ init_pair(PAIR1,COLOR_RED,COLOR_BLUE); init_pair(PAIR2,COLOR_YELLOW,COLOR_MAGENTA); /*turn on color attributes for each window*/ wattron(win1, COLOR_PAIR(PAIR1)); wattron(win2, COLOR_PAIR(PAIR2)); /*print some text in each window and exit*/ waddstr(win1, "This should be red on blue"); waddstr(win2,"This should be yellow on magenta"); wnoutrefresh(win1); wnoutrefresh(win2); doupdate(); /*wait for any key before terminating*/ wgetch(win2); } endwin(); }