ncurses

UNIX系システムにおけるTUI開発の事実上の標準。低レベルAPIでターミナル画面、文字、ユーザー入力を制御。多くの高レベルライブラリの基盤。

TUITerminalLow-levelStandardUNIX

ncurses

ncurses(new curses)は、UNIX系システムにおけるターミナルユーザーインターフェース開発の事実上の標準ライブラリです。1993年から開発が続く成熟したプロジェクトで、ターミナル画面の制御、文字の描画、ユーザー入力の処理を低レベルで制御できます。多くの高レベルTUIライブラリがncursesを基盤として使用しています。

特徴

コア機能

  • 画面制御: ターミナル画面の完全な制御
  • ウィンドウ管理: 複数のウィンドウとサブウィンドウ
  • 入力処理: キーボードとマウスの入力
  • 色とスタイル: 前景・背景色、属性の制御

ターミナル抽象化

  • terminfo/termcap: ターミナル機能の抽象化
  • ポータビリティ: 様々なターミナルタイプに対応
  • エスケープシーケンス: 自動的な処理
  • 画面最適化: 効率的な更新アルゴリズム

拡張機能

  • パネル: ウィンドウの重ね合わせ管理
  • メニュー: メニューシステムの構築
  • フォーム: フォーム入力の処理
  • マウスサポート: クリックやドラッグの検出

国際化

  • ワイド文字: Unicode対応(ncursesw)
  • マルチバイト文字: 日本語などの表示
  • UTF-8サポート: 完全なUTF-8対応
  • ロケール対応: システムロケールの利用

基本的な使用方法

インストール

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

# macOS
brew install ncurses

# CentOS/RHEL
sudo yum install ncurses-devel

# コンパイル
g++ -o myapp myapp.cpp -lncurses
# ワイド文字対応版
g++ -o myapp myapp.cpp -lncursesw

Hello World

#include <ncurses.h>

int main() {
    // ncursesの初期化
    initscr();
    
    // "Hello, World!"を表示
    printw("Hello, World!");
    
    // 画面を更新
    refresh();
    
    // キー入力を待つ
    getch();
    
    // ncursesを終了
    endwin();
    
    return 0;
}

ウィンドウとカラー

#include <ncurses.h>

int main() {
    initscr();
    
    // カラーを有効化
    if (has_colors()) {
        start_color();
        
        // カラーペアを定義
        init_pair(1, COLOR_RED, COLOR_BLACK);
        init_pair(2, COLOR_GREEN, COLOR_BLACK);
        init_pair(3, COLOR_YELLOW, COLOR_BLUE);
    }
    
    // 画面サイズを取得
    int max_y, max_x;
    getmaxyx(stdscr, max_y, max_x);
    
    // ボーダー付きウィンドウを作成
    WINDOW* win = newwin(10, 30, (max_y - 10) / 2, (max_x - 30) / 2);
    box(win, 0, 0);
    
    // ウィンドウにテキストを表示
    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));
    
    // 属性を使用
    wattron(win, A_BOLD | A_UNDERLINE);
    mvwprintw(win, 5, 2, "Bold and Underlined");
    wattroff(win, A_BOLD | A_UNDERLINE);
    
    // ウィンドウを更新
    wrefresh(win);
    refresh();
    
    getch();
    
    // クリーンアップ
    delwin(win);
    endwin();
    
    return 0;
}

入力処理とメニュー

#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) {
        
        // メニューウィンドウのサイズを計算
        int width = 0;
        for (const auto& item : items) {
            if (item.length() > width) {
                width = item.length();
            }
        }
        width += 4;  // パディング
        
        int height = items.size() + 2;
        
        // 中央に配置
        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);
        
        // キー入力設定
        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();
    
    // メインメニュー
    std::vector<std::string> menu_items = {
        "新規作成",
        "開く",
        "保存",
        "設定",
        "終了"
    };
    
    SimpleMenu menu(menu_items);
    int selected = menu.run();
    
    endwin();
    
    if (selected >= 0) {
        printf("選択: %s\n", menu_items[selected].c_str());
    } else {
        printf("キャンセルされました\n");
    }
    
    return 0;
}

ワイド文字(日本語)対応

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

int main() {
    // ロケールを設定
    setlocale(LC_ALL, "");
    
    initscr();
    start_color();
    
    // UTF-8文字列の表示
    mvprintw(0, 0, "ncursesワイド文字対応デモ");
    mvprintw(2, 0, "絵文字も表示可能: 🎮 🖥️ ⌨️");
    
    // カラーペアの設定
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_GREEN, COLOR_BLACK);
    init_pair(3, COLOR_YELLOW, COLOR_BLACK);
    
    // 日本語テキストにカラーを適用
    attron(COLOR_PAIR(1));
    mvprintw(4, 0, "赤色のテキスト");
    attroff(COLOR_PAIR(1));
    
    attron(COLOR_PAIR(2));
    mvprintw(5, 0, "緑色のテキスト");
    attroff(COLOR_PAIR(2));
    
    attron(COLOR_PAIR(3) | A_BOLD);
    mvprintw(6, 0, "黄色の太字テキスト");
    attroff(COLOR_PAIR(3) | A_BOLD);
    
    // ボックス描画文字
    mvprintw(8, 0, "ボックス描画: ┌─┬─┐");
    mvprintw(9, 0, "       │ │ │");
    mvprintw(10, 0, "       ├─┼─┤");
    mvprintw(11, 0, "       │ │ │");
    mvprintw(12, 0, "       └─┴─┘");
    
    refresh();
    getch();
    endwin();
    
    return 0;
}

マウスサポート

#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);
    
    // マウスイベントを有効化
    mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL);
    
    // マウスクリックを報告
    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_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) {
                // マウス座標をウィンドウ座標に変換
                int win_y = event.y - 5;
                int win_x = event.x - 10;
                
                // ボタン1の範囲チェック
                button1_highlighted = (win_y == 5 && win_x >= 5 && win_x <= 14);
                
                // ボタン2の範囲チェック
                button2_highlighted = (win_y == 5 && win_x >= 20 && win_x <= 29);
                
                // クリックイベント
                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;
        }
    }
    
    // マウスクリック報告を無効化
    printf("\033[?1003l\n");
    
    delwin(win);
    endwin();
    
    return 0;
}

高度な機能

パネルライブラリ

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

PANEL* panels[3];

void init_panels() {
    WINDOW* wins[3];
    
    // 3つのウィンドウを作成
    wins[0] = newwin(10, 30, 2, 5);
    wins[1] = newwin(10, 30, 5, 15);
    wins[2] = newwin(10, 30, 8, 25);
    
    // 各ウィンドウにコンテンツを追加
    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");
    
    // パネルを作成
    for (int i = 0; i < 3; i++) {
        panels[i] = new_panel(wins[i]);
    }
}

フォームライブラリ

#include <form.h>

// フォームフィールドの作成
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;

// フォームの作成
FORM* form = new_form(fields);

エコシステム

ncursesを使用した有名なアプリケーション

  • vim/neovim: テキストエディタ
  • htop: プロセスビューア
  • midnight commander: ファイルマネージャー
  • aptitude: パッケージマネージャー

派生ライブラリ

  • CDK: Curses Development Kit
  • Dialog: ダイアログボックスツール
  • ncurses++: C++ラッパー

利点

  • 標準的: UNIX系システムの事実上の標準
  • 成熟: 30年以上の開発歴
  • 安定性: 高い互換性と安定性
  • ドキュメント: 豊富な資料とサンプル
  • ポータビリティ: 幅広いプラットフォーム対応

制約事項

  • 低レベル: 基本的な機能のみ提供
  • C言語API: オブジェクト指向ではない
  • 複雑性: 大規模アプリケーションでは管理が困難
  • モダン機能: True Colorなど最新機能の限定的サポート

他のライブラリとの比較

項目ncursesFTXUINotcurses
レベル
パラダイム命令型関数型命令型
機能基本的豊富高度
学習コスト
成熟度非常に高

まとめ

ncursesは、UNIX系システムにおけるTUI開発の基盤となるライブラリです。低レベルAPIのため学習コストは高いですが、その分柔軟性と制御性に優れています。30年以上の歴史を持ち、安定性と互換性は抜群です。モダンなTUIフレームワークの多くがncursesを基盤として使用しており、TUI開発を深く理解したい開発者にとって必須の知識と言えるでしょう。