PDCurses

cursing_with_curses

Curses is a venerable API for interacting with computer terminals on a per-cell basis. What follows are some quick and unfinished notes about using the PDCurses implementation with C terminal programs in Windows 10 while I dabble with the library.

Many Curses functions return ERR (-1) upon failure. My notes do not cover error handling for all of these cases, because the tutorials I find on the net don’t seem to either. Nonetheless you may want to catch these error codes if you are writing something more serious.

 

Curses resources

Pradeep Padala’s venerable NCurses Tutorial

Invisible Island NCurses FAQ

Curses Questions and Answers on Stackoverflow

(Note: ncurses is for POSIX systems. PDCurses is a different implementation with support for Windows.)

 

Adding PDCurses to a Code::Blocks project

I seem to forget how to do this every time I reinstall Windows. There is video walkthrough by Cymonsgames here.

 

Startup and Shutdown

#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "curses.h" // Get Psyched!

int main() {
  initscr();        // Start Curses.
  
  start_color();    // Initialize color.

  raw();            // Accept raw keyboard input, so you don't have to
                    // strike Enter after every keypress.

  noecho();         // Don't echo user input back to the screen.

  keypad( stdscr, TRUE ); // Accept arrow keys, function keys, etc as input.

  endwin();         // When you are done, free Curses resources and return
                    // to normal command prompt behavior.

  return 0;
}

 

 

Printing text and drawing individual characters

Curses has a suite of printing functions, from printf()-style variable length formatted strings, to plotting individual characters. I use mvprintw() and mvaddch() most often, along with attron() and attroff() to control color and brightness.

 mvprintw( 10, 10, "Hello, World" ); // Print text (Y, X, printf-style
                                     // formatted string).

 attron( A_REVERSE );                // Reverse the FG / BG colors going
                                     // forward.

 mvaddch( 10, 22, '!' );             // Draw a single character.

 attroff( A_REVERSE );               // Disable reversed attribute going
                                     // forward.

 refresh();                          // Force screen update.

 getch();                            // Wait for user input.

 

Input

With raw(), getch() will wait for the user to press a key, and return a key code. You can use getch() alone without an assignment to pause the program until any key is pressed. Many keys can be checked by their char literal value or escape sequence (like “\t” for the tab key), and PDCurses defines many macros to represent non-typewriter keys, such as KEY_LEFT, KEY_UP, etc, for the arrow keys.

int input = 0;

while( input != toupper('q') ) {
    input = getch();

    clear();             // wipe screen of previous contents
    mvprintw( "You pressed key %c, code %d.", input, input );
    refresh();
}

PDCurses doesn’t seem to have macros for the number pad when Numlock is on, so you will have to define those yourself.

Some keys generate multi-byte scancodes, particularly the Escape key. You may need to call getch() again to confirm. (Esc is scancode 27 followed by ERR (-1)).

 

Cursor control

The blinking cursor moves around as Curses draws and prints to the screen. If you want, you can manually position the cursor at the end of your update routine, or disable it.

curs_set(0); // 0: Don't show
             // 1: Blinking underline
             // 2: Blinking square.

move(y, x);  // Specifically move the cursor to a
             // coordinate.

Color Pairs

Though Curses does define macros to represent eight colors (0 through 7 — COLOR_BLACK, COLOR_WHITE etc), you can’t pass these to the Curses drawing functions directly. Instead, you initialize a series of color pairs, and pass the index of that pair as an attribute. If you want to address all 8 foreground and 8 background colors independently, you can initialize 64 pairs with each combination.

(Note: Color Pair 0 is already initialized, and reserved for monochrome mode.)

int cursesIndexColorPairs(void) {
    int n_colors = 8;
    int color_index_start = 1;
    int i;

    for( i = 0; i < n_colors * n_colors; i++ ) {
        if( init_pair( color_index_start + i, i % n_colors, i / n_colors ) == ERR) {
            return ERR;
        }
    }
    return 0;
}

 

Bright and Dark colors

windows_cli_colors_default

The Windows command prompt supports legacy IBM PC Text Mode colors. There are 16 colors total, but they are accessed as 8 base colors, plus a brightness flag. The foreground and background of a given character get their own color and brightness parameters. In DOS, if the background color has a brightness flag, then the foreground color will blink on and off and the background intensity will be unchanged. A non-fullscreen Windows command prompt will just draw the background color as bright.

PDCurses will draw a character with a bright foreground if A_BOLD is passed as an attribute, and it will draw with a bright background if A_BLINK is passed, and both if they are passed sequentially or binary OR’d together. I can’t imagine A_BLINK serving as a bright background would be portable on other systems, but it’s the only attribute I could find that did so on Windows 10.

 

#define N_COLORS 8

int colorGetPair(int fg, int bg) {
  // return monochrome (pair 0) if requested colors are out of range
  if(fg < 0 || fg > N_COLORS || bg < 0 || bg > N_COLORS ) {
    return 0; 
  }
  else {
    return 1 + fg + (bg * N_COLORS);
  }
}

void colorSet( int fg, int bg, int fg_intensity, int bg_blink ) {
  if( fg_intensity ) {
    attron( A_BOLD );
  }
  else {
    attroff( A_BOLD );
  }

  if( bg_blink ) {
    attron( A_BLINK );
  }
  else {
    attroff( A_BLINK );
  }

  attron( COLOR_PAIR( colPair( fg, bg ) ) );
}

int main() {

  // ... snip
  colorSet( COLOR_MAGENTA, COLOR_CYAN, 1, 1 );
  mvprintw( 0, 0, "\"We need more searing Magenta and Cyan,\"\n said the CGA Project Lead." );
  getch();

  // ... snip

more_magenta_and_cyan

 

getch() timeout via halfdelay()

halfdelay() will add a timeout to getch(), measured in tenths of a second, if no user input is received. It can be used as a 10FPS sleep timer for animations, while still allowing user intervention. Use nocbreak() to disable the timeout.

 

Screen Dimensions

Upon calling initscr(), the display dimensions are stored in two variables: COLS (width) and LINES (height). These variables do not seem to update upon resizing the window, but if you change the size settings, exit and restart the program, the new dimensions will be detected. My understanding is that other Curses environments are able to handle runtime screen resizing.

 

Windows 10 command prompt blanking on resize

If you find the Windows 10 command prompt is blanking out the display upon resizing the window:

  • Right-click the window title bar and choose Properties
  • Goto the Layout tab
  • Uncheck Wrap text output on resize

cli_wrap

 

SDL

With some caveats, you can basically drop in the SDL version of pdcurses.dll, along with v1.2 of sdl.dll in place of the command line version. Screen dimensions are 80×25 cells by default — to adjust, it seems you need to change environment variables pdc_lines and pdc_cols. You can do this within your program prior to init_scr() with putenv(), or you can set the environment variables temporarily within a command prompt with the SET command.

putenv() :

putenv( "pdc_lines=20" );
putenv( "pdc_cols=20" );

// ...

initscr();

Windows SET:

C:\set pdc_lines=20

C:\set pdc_cols=20

C:\random_text.exe

pdcurses_20x20

In SDL mode, you can drop in a new monospace bitmapped font with new dimensions — upon calling initscr(), PDCurses will search for and use pdcfont.bmp in the working directory. The PDCurses page on SourceForge has a set of example fonts. The font layout is Code Page 437. (Update: I’m having issues changing the foreground color when using a custom font — maybe I’ve missed a setting somewhere.)

 

Todo List for this post

  • Extended Character Sets (AFAIK no-go in SDL mode, but Code Page 437’s symbols should be available.)
  • Portability between PDCurses and NCurses
  • Mouse support: Curses defines structures and functions for reading mouse events. If this actually works in PDCurses and Windows 10, I would assume the command prompt application’s own mouse interactions (pausing everything to allow highlighting and copying) would interfere. But maybe the SDL port would be better for that. Will try it sometime later.
  • Setup of NCurses on Ubuntu
  • refresh() : Pradeep’s NCurses tutorial says that drawing is queued and refresh() updates the screen, however it seems that draw updates show up just fine in my programs without it. Maybe this doesn’t apply to my specific OS, or to PDCurses, or I’m missing a setting somewhere.
  • clear() : clear() wipes the screen. I have found on Win10 that it sometimes causes flicker. TODO investigate.
  • Modifying terminal colors: I can’t get this to work.
  • Curses Windowing: I haven’t touched Curses’s own windowing system. It seems to be intended for non-overlapping screen sectors.
  • Project Pluto’s PDCurses fork is a fuller implementation. This looks awesome and I need to dig into it sometime.
  • Foreground color change not working for me when I use a custom font in SDL mode.

(e: 6/Nov/2016: Screen dimensions)
(e: 11/Nov/2016: Input, Screen dimensions. SDL stuff)
(e: 3/Feb/2017: Color attribute wrapper, wording)
(e: 11/Feb/2017: Wording; corrected pdcfont.bmp; added putenv(); added a note that foreground color change isn’t working for me when using custom fonts with the SDL DLL.)