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.
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
Feature | ncurses | FTXUI | Notcurses |
---|---|---|---|
Level | Low | High | Medium |
Paradigm | Imperative | Functional | Imperative |
Features | Basic | Rich | Advanced |
Learning Cost | High | Medium | Medium |
Maturity | Very High | Medium | Low |
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.