ncurses

The de facto standard for TUI development on UNIX-like systems. Low-level API for controlling terminal screen, characters, and user input. Foundation for many higher-level libraries.

TUITerminalLow-levelStandardUNIX

ncurses

ncurses (new curses) is the de facto standard library for terminal user interface development on UNIX-like systems. A mature project with development continuing since 1993, it provides low-level control over terminal screens, character rendering, and user input processing. Many high-level TUI libraries use ncurses as their foundation.

Features

Core Functionality

  • Screen Control: Complete control over terminal screen
  • Window Management: Multiple windows and subwindows
  • Input Processing: Keyboard and mouse input
  • Colors and Styles: Foreground/background colors and attributes

Terminal Abstraction

  • terminfo/termcap: Terminal capability abstraction
  • Portability: Support for various terminal types
  • Escape Sequences: Automatic handling
  • Screen Optimization: Efficient update algorithms

Extended Features

  • Panels: Window stacking management
  • Menus: Menu system construction
  • Forms: Form input processing
  • Mouse Support: Click and drag detection

Internationalization

  • Wide Characters: Unicode support (ncursesw)
  • Multibyte Characters: Japanese display support
  • UTF-8 Support: Complete UTF-8 compatibility
  • Locale Support: System locale utilization

Basic Usage

Installation

# Ubuntu/Debian
sudo apt-get install libncurses5-dev libncursesw5-dev

# macOS
brew install ncurses

# CentOS/RHEL
sudo yum install ncurses-devel

# Compilation
g++ -o myapp myapp.cpp -lncurses
# Wide character version
g++ -o myapp myapp.cpp -lncursesw

Hello World

#include <ncurses.h>

int main() {
    // Initialize ncurses
    initscr();
    
    // Display "Hello, World!"
    printw("Hello, World!");
    
    // Update the screen
    refresh();
    
    // Wait for key input
    getch();
    
    // Cleanup ncurses
    endwin();
    
    return 0;
}

Windows and Colors

#include <ncurses.h>

int main() {
    initscr();
    
    // Enable colors
    if (has_colors()) {
        start_color();
        
        // Define color pairs
        init_pair(1, COLOR_RED, COLOR_BLACK);
        init_pair(2, COLOR_GREEN, COLOR_BLACK);
        init_pair(3, COLOR_YELLOW, COLOR_BLUE);
    }
    
    // Get screen size
    int max_y, max_x;
    getmaxyx(stdscr, max_y, max_x);
    
    // Create a bordered window
    WINDOW* win = newwin(10, 30, (max_y - 10) / 2, (max_x - 30) / 2);
    box(win, 0, 0);
    
    // Display text in window
    wattron(win, COLOR_PAIR(1));
    mvwprintw(win, 1, 2, "ncurses Window Demo");
    wattroff(win, COLOR_PAIR(1));
    
    wattron(win, COLOR_PAIR(2));
    mvwprintw(win, 3, 2, "This is colored text");
    wattroff(win, COLOR_PAIR(2));
    
    // Use attributes
    wattron(win, A_BOLD | A_UNDERLINE);
    mvwprintw(win, 5, 2, "Bold and Underlined");
    wattroff(win, A_BOLD | A_UNDERLINE);
    
    // Update windows
    wrefresh(win);
    refresh();
    
    getch();
    
    // Cleanup
    delwin(win);
    endwin();
    
    return 0;
}

Input Processing and Menus

#include <ncurses.h>
#include <vector>
#include <string>

class SimpleMenu {
private:
    WINDOW* win;
    std::vector<std::string> items;
    int current_item;
    int start_y, start_x;
    
public:
    SimpleMenu(const std::vector<std::string>& menu_items) 
        : items(menu_items), current_item(0) {
        
        // Calculate menu window size
        int width = 0;
        for (const auto& item : items) {
            if (item.length() > width) {
                width = item.length();
            }
        }
        width += 4;  // Padding
        
        int height = items.size() + 2;
        
        // Center position
        int max_y, max_x;
        getmaxyx(stdscr, max_y, max_x);
        start_y = (max_y - height) / 2;
        start_x = (max_x - width) / 2;
        
        win = newwin(height, width, start_y, start_x);
        
        // Key input settings
        keypad(win, TRUE);
        noecho();
        curs_set(0);
    }
    
    ~SimpleMenu() {
        delwin(win);
    }
    
    void draw() {
        werase(win);
        box(win, 0, 0);
        
        for (int i = 0; i < items.size(); i++) {
            if (i == current_item) {
                wattron(win, A_REVERSE);
            }
            mvwprintw(win, i + 1, 2, "%s", items[i].c_str());
            if (i == current_item) {
                wattroff(win, A_REVERSE);
            }
        }
        
        wrefresh(win);
    }
    
    int run() {
        int ch;
        
        while (true) {
            draw();
            ch = wgetch(win);
            
            switch (ch) {
                case KEY_UP:
                    current_item = (current_item - 1 + items.size()) % items.size();
                    break;
                    
                case KEY_DOWN:
                    current_item = (current_item + 1) % items.size();
                    break;
                    
                case 10:  // Enter key
                    return current_item;
                    
                case 27:  // ESC key
                    return -1;
            }
        }
    }
};

int main() {
    initscr();
    start_color();
    
    // Main menu
    std::vector<std::string> menu_items = {
        "New",
        "Open",
        "Save",
        "Settings",
        "Exit"
    };
    
    SimpleMenu menu(menu_items);
    int selected = menu.run();
    
    endwin();
    
    if (selected >= 0) {
        printf("Selected: %s\n", menu_items[selected].c_str());
    } else {
        printf("Cancelled\n");
    }
    
    return 0;
}

Wide Character (Unicode) Support

#include <ncursesw/ncurses.h>
#include <locale.h>
#include <string>

int main() {
    // Set locale
    setlocale(LC_ALL, "");
    
    initscr();
    start_color();
    
    // Display UTF-8 strings
    mvprintw(0, 0, "ncurses Wide Character Support Demo");
    mvprintw(2, 0, "Emoji support: 🎮 🖥️ ⌨️");
    mvprintw(3, 0, "Unicode: こんにちは世界 (Hello World)");
    
    // Set up color pairs
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_GREEN, COLOR_BLACK);
    init_pair(3, COLOR_YELLOW, COLOR_BLACK);
    
    // Apply colors to Unicode text
    attron(COLOR_PAIR(1));
    mvprintw(5, 0, "Red colored text: 红色文字");
    attroff(COLOR_PAIR(1));
    
    attron(COLOR_PAIR(2));
    mvprintw(6, 0, "Green colored text: 緑色のテキスト");
    attroff(COLOR_PAIR(2));
    
    attron(COLOR_PAIR(3) | A_BOLD);
    mvprintw(7, 0, "Bold yellow text: Жёлтый текст");
    attroff(COLOR_PAIR(3) | A_BOLD);
    
    // Box drawing characters
    mvprintw(9, 0, "Box drawing: ┌─┬─┐");
    mvprintw(10, 0, "            │ │ │");
    mvprintw(11, 0, "            ├─┼─┤");
    mvprintw(12, 0, "            │ │ │");
    mvprintw(13, 0, "            └─┴─┘");
    
    refresh();
    getch();
    endwin();
    
    return 0;
}

Mouse Support

#include <ncurses.h>
#include <string>

void draw_button(WINDOW* win, int y, int x, const char* label, bool highlighted) {
    if (highlighted) {
        wattron(win, A_REVERSE);
    }
    mvwprintw(win, y, x, "[ %s ]", label);
    if (highlighted) {
        wattroff(win, A_REVERSE);
    }
}

int main() {
    initscr();
    cbreak();
    noecho();
    keypad(stdscr, TRUE);
    
    // Enable mouse events
    mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
    
    // Report mouse clicks
    printf("\033[?1003h\n");
    
    WINDOW* win = newwin(10, 40, 5, 10);
    box(win, 0, 0);
    
    bool button1_highlighted = false;
    bool button2_highlighted = false;
    
    mvwprintw(win, 1, 2, "Mouse Demo - Click buttons or ESC");
    
    while (true) {
        // Draw buttons
        draw_button(win, 5, 5, "Button 1", button1_highlighted);
        draw_button(win, 5, 20, "Button 2", button2_highlighted);
        
        wrefresh(win);
        
        int ch = getch();
        
        if (ch == KEY_MOUSE) {
            MEVENT event;
            if (getmouse(&event) == OK) {
                // Convert mouse coords to window coords
                int win_y = event.y - 5;
                int win_x = event.x - 10;
                
                // Check button 1 bounds
                button1_highlighted = (win_y == 5 && win_x >= 5 && win_x <= 14);
                
                // Check button 2 bounds
                button2_highlighted = (win_y == 5 && win_x >= 20 && win_x <= 29);
                
                // Click events
                if (event.bstate & BUTTON1_CLICKED) {
                    if (button1_highlighted) {
                        mvwprintw(win, 7, 2, "Button 1 clicked!            ");
                    } else if (button2_highlighted) {
                        mvwprintw(win, 7, 2, "Button 2 clicked!            ");
                    }
                }
            }
        } else if (ch == 27) {  // ESC
            break;
        }
    }
    
    // Disable mouse click reporting
    printf("\033[?1003l\n");
    
    delwin(win);
    endwin();
    
    return 0;
}

Advanced Features

Panel Library

#include <ncurses.h>
#include <panel.h>

PANEL* panels[3];

void init_panels() {
    WINDOW* wins[3];
    
    // Create 3 windows
    wins[0] = newwin(10, 30, 2, 5);
    wins[1] = newwin(10, 30, 5, 15);
    wins[2] = newwin(10, 30, 8, 25);
    
    // Add content to each window
    box(wins[0], 0, 0);
    mvwprintw(wins[0], 1, 2, "Panel 1");
    
    box(wins[1], 0, 0);
    mvwprintw(wins[1], 1, 2, "Panel 2");
    
    box(wins[2], 0, 0);
    mvwprintw(wins[2], 1, 2, "Panel 3");
    
    // Create panels
    for (int i = 0; i < 3; i++) {
        panels[i] = new_panel(wins[i]);
    }
}

Form Library

#include <form.h>

// Create form fields
FIELD* fields[3];
fields[0] = new_field(1, 20, 0, 0, 0, 0);
fields[1] = new_field(1, 20, 2, 0, 0, 0);
fields[2] = NULL;

// Create form
FORM* form = new_form(fields);

Ecosystem

Famous Applications Using ncurses

  • vim/neovim: Text editors
  • htop: Process viewer
  • midnight commander: File manager
  • aptitude: Package manager

Derivative Libraries

  • CDK: Curses Development Kit
  • Dialog: Dialog box tool
  • ncurses++: C++ wrapper

Advantages

  • Standard: De facto standard on UNIX-like systems
  • Mature: Over 30 years of development
  • Stability: High compatibility and stability
  • Documentation: Rich resources and samples
  • Portability: Wide platform support

Limitations

  • Low-Level: Only provides basic functionality
  • C Language API: Not object-oriented
  • Complexity: Difficult to manage in large applications
  • Modern Features: Limited support for features like True Color

Comparison with Other Libraries

FeaturencursesFTXUINotcurses
LevelLowHighMedium
ParadigmImperativeFunctionalImperative
FeaturesBasicRichAdvanced
Learning CostHighMediumMedium
MaturityVery HighMediumLow

Summary

ncurses is the foundational library for TUI development on UNIX-like systems. While its low-level API has a steep learning curve, it offers excellent flexibility and control. With over 30 years of history, its stability and compatibility are outstanding. Many modern TUI frameworks use ncurses as their foundation, making it essential knowledge for developers who want to deeply understand TUI development.