Table of Contents

ncurses

https://invisible-island.net/ncurses/announce.html

Ncurses ftp://ftp.invisible-island.net/ncurses/ncurses.tar.gz

Installation

Debian package

In Debian, the easiest way to install is with:

sudo apt install libncurses6

Build from source

To build from source, get the latest version from the download page.

# Download ncurses
wget ftp://ftp.invisible-island.net/ncurses/ncurses.tar.gz
# Extract
tar xzf ncurses.tar.gz
# Enter the directory
cd ncurses-6.3
 
# Typical build steps
./configure --help | less
# Set to install in home rather than system
./configure --prefix=$HOME/.local --with-shared
make
make install

If you install to your home rather than the system, when compiling, linking, and running, you may need to specify where the header files and libraries are. With gcc, that's the -I and -L options. When running, set the environment variable LD_LIBRARY_PATH to include path to libraries.

Documentation

Man pages

There are a few man pages that are particularly helpful. The man pages with API documentation will be in ~/.local/share/man/man{1,3,5,7} if you used the home prefix.

If you aren't sure which man pages mean what, run man man which tells you which man page number corresponds to which section. (1 is executable, 3 is library calls, 5 is file formats, 7 is misc).

# Read the developer documentation (page 3)
# This page tells you the #include statement to use,
# how to link against it, and instructions on how to
# initialize and use the library itself.
# It also lists out all the man pages for each function
man 3 ncurses
 
man 3 initscr
man 3 getch
man 3 getmouse
 
man 1 ncurses6-config

Online Docs

Other resources:

ncurses6-config tool

Ncurses comes with a helper function useful for these tasks, ncurses6-config which will be in ~/.local/bin/ if you used the home prefix.

# For compiling and specifying include dirs
ncurses6-config --cflags
# -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=600 -I/home/dano/.local/include/ncurses -I/home/dano/.local/include
 
# For linking and specifying lib dirs
ncurses6-config --libs
# -L/home/dano/.local/lib -lncurses
 
# For LD_LIBRARY_PATH when executing and linking dynamically
ncurses6-config  --libdir
#/home/dano/.local/lib

Examples

Hello world

hello_curses.cpp
// g++ hello_curses.cpp -lncurses
// add -static for static binary
#include <curses.h>
 
int main(int argc, char* argv[]) {
  initscr(); // Take over terminal and start managing it with curses
  endwin(); // Cleanup or terminal will be left in a weird state
  return 0;
}
example.cpp
// A more robust example, taken from
// https://invisible-island.net/ncurses/ncurses-intro.html#using
 
#include <stdlib.h>
#include <curses.h>
#include <signal.h>
 
static void finish(int sig);
 
int
main(int argc, char *argv[])
{
    int num = 0;
 
    /* initialize your non-curses data structures here */
 
    (void) signal(SIGINT, finish);      /* arrange interrupts to terminate */
 
    (void) initscr();      /* initialize the curses library */
    keypad(stdscr, TRUE);  /* enable keyboard mapping */
    (void) nonl();         /* tell curses not to do NL->CR/NL on output */
    (void) cbreak();       /* take input chars one at a time, no wait for \n */
    (void) echo();         /* echo input - in color */
 
    if (has_colors())
    {
        start_color();
 
        /*
         * Simple color assignment, often all we need.  Color pair 0 cannot
         * be redefined.  This example uses the same value for the color
         * pair as for the foreground color, though of course that is not
         * necessary:
         */
        init_pair(1, COLOR_RED,     COLOR_BLACK);
        init_pair(2, COLOR_GREEN,   COLOR_BLACK);
        init_pair(3, COLOR_YELLOW,  COLOR_BLACK);
        init_pair(4, COLOR_BLUE,    COLOR_BLACK);
        init_pair(5, COLOR_CYAN,    COLOR_BLACK);
        init_pair(6, COLOR_MAGENTA, COLOR_BLACK);
        init_pair(7, COLOR_WHITE,   COLOR_BLACK);
    }
 
    for (;;)
    {
        int c = getch();     /* refresh, accept single keystroke of input */
        attrset(COLOR_PAIR(num % 8));
        num++;
 
        /* process the command keystroke */
    }
 
    finish(0);               /* we are done */
}
 
static void finish(int sig)
{
    endwin();
 
    /* do your non-curses wrapup here */
 
    exit(0);
}

Shelling out

shell_out.cpp
// From https://invisible-island.net/ncurses/ncurses-intro.html#leaving
addstr("Shelling out...");
def_prog_mode();           /* save current tty modes */
endwin();                  /* restore original tty modes */
system("sh");              /* run shell */
addstr("returned.\n");     /* prepare return message */
refresh();                 /* restore save modes, repaint screen */

Test mouse input

mouse_test.cpp
#include <ncurses.h>
#include <iostream>
#include <bitset>
 
void redrawBorder() {
	clear();
	box(stdscr, 0, 0); // Redraw main border
 
}
 
int main() {
 
    initscr();
    mousemask(ALL_MOUSE_EVENTS, NULL);
 
    /**
     *  Colors
     */
    if (has_colors()) { start_color(); }
    //use_default_colors();
    init_pair(1, COLOR_CYAN, COLOR_BLACK);
    init_pair(2, COLOR_WHITE, COLOR_RED);
 
    cbreak();
    keypad(stdscr, TRUE);
    nonl();         /* tell curses not to do NL->CR/NL on output */
    noecho();
    curs_set(0);
 
    attrset(COLOR_PAIR(1));
	redrawBorder();
    //refresh();
 
 
 
    // short id;         /* ID to distinguish multiple devices */
    // int x, y, z;      /* event coordinates */
    // mmask_t bstate;   /* button state bits */
 
    while (true) {
	    int c = getch();
	    switch (c) {
			case 'q':
				endwin();
				exit(0);
			case KEY_MOUSE:
				MEVENT event;
				if (getmouse(&event) == OK) {
					clear();
					redrawBorder();
 
					attrset(COLOR_PAIR(2));
					mvaddstr(5, 5, std::to_string(event.x).c_str());
					mvaddstr(6, 5, std::to_string(event.y).c_str());
					mvaddstr(7, 5, "Mouse bitset: ");
					addstr(std::bitset<32>(event.bstate).to_string().c_str());
					if (event.bstate & BUTTON1_PRESSED) {
						addstr(" - Button 1 pressed");
					} else if (event.bstate & BUTTON1_DOUBLE_CLICKED) {
						addstr(" - Button 1 dclick");
					}
					attrset(COLOR_PAIR(1));
 
				}
				break;
 
				/**
				BUTTON1_PRESSED          mouse button 1 down
				BUTTON1_RELEASED         mouse button 1 up
				BUTTON1_CLICKED          mouse button 1 clicked
				BUTTON1_DOUBLE_CLICKED   mouse button 1 double clicked
				BUTTON1_TRIPLE_CLICKED   mouse button 1 triple clicked
				BUTTON2_PRESSED          mouse button 2 down
				BUTTON2_RELEASED         mouse button 2 up
				BUTTON2_CLICKED          mouse button 2 clicked
				BUTTON2_DOUBLE_CLICKED   mouse button 2 double clicked
				BUTTON2_TRIPLE_CLICKED   mouse button 2 triple clicked
				BUTTON3_PRESSED          mouse button 3 down
				BUTTON3_RELEASED         mouse button 3 up
				BUTTON3_CLICKED          mouse button 3 clicked
				BUTTON3_DOUBLE_CLICKED   mouse button 3 double clicked
				BUTTON3_TRIPLE_CLICKED   mouse button 3 triple clicked
				BUTTON4_PRESSED          mouse button 4 down
				BUTTON4_RELEASED         mouse button 4 up
				BUTTON4_CLICKED          mouse button 4 clicked
				BUTTON4_DOUBLE_CLICKED   mouse button 4 double clicked
				BUTTON4_TRIPLE_CLICKED   mouse button 4 triple clicked
				BUTTON_SHIFT             shift was down during button state change
				BUTTON_CTRL              control was down during button state change
				BUTTON_ALT               alt was down during button state change
				ALL_MOUSE_EVENTS         report all button state changes
				REPORT_MOUSE_POSITION    report mouse movement
				*/ 
 
			case KEY_RESIZE:
				redrawBorder();
				mvaddstr(10, 10, "Window resized");
				break;
 
			default:
				break;
		}
	 }
 
 
 
	/**
	 * Cleanup and shutdown
	 */
    endwin();
    return 0;
}

Modifier keys

// int c = getch();
// switch (c)
case ctrl('j'): // #define ctrl(x) ((x) & 0x1f)
	addstr("ctrl j detected");
	break;
default:
	if (c == KEY_ENTER) {
		addstr("Keypad enter pressed.");
	}
	if (c == 27) { // Alt key was down when key pressed
		// Get the next char that came with the alt
		c = getch();
		addstr("Alt/escaped key pressed: ");
		addch(c); // could be 'a' or 'A' if alt+shift
	}
 

Important variables and functions

initscr(); // Initialize ncurses
endwin(); // Cleanup for exit or shell out (refresh will bring state)
getmaxyx() // get terminal size
 
cbreak()
nonl()
noecho()
curs_set()
 
 
newwin()
 
// Add chars to window
mvaddch(y,x,ch)
addstr()
printw()
addch()
 
 
// Get input
getch()
wgetstr()
wscanw()
keypad(stdscr, TRUE)
 
if (has_colors()) { start_color(); }
use_default_colors()
 
 
refresh() // Refresh from stdscr
wrefresh() // Refresh using non-stdscr window
wnoutrefresh() // Disable auto update on window update (pair with doupdate)
doupdate() // Manually repaint all updates
touchwin()
 
box()
border()
 
// Colors
init_pair()
COLOR_PAIR
 
attron()
attroff()
attrset()
 
move(y, x)
 
 
 
mousemask(ALL_MOUSE_EVENTS, null) // Specify which mouse events to get
MEVENT // data type with bits set for specific mouse events like BUTTON1_CLICKED
 
// Special events from wgetch()
KEY_MOUSE // Mouse event (use getmouse())
KEY_RESIZE // Terminal was resized and adjusted
 
 
 
 
scrollok()
leaveok()
 
derwin()
subwin()
delwin()
 
 
define_key() // function-key control sequences
keyok() // toggle the listening for special control sequences