ncurses ライブラリを使う

ncurses ライブラリとは?

  • ncurses は curses から派生した亜種?で、元は curses ライブラリと同等と思われる
  • そもそも curses ライブラリは、シリアル接続されたターミナル(VT100など)と通信を行う事を前提にしている
  • テキストベースのアプリケーションを構築する場合、文字の操作は、固定されたアドレスに行う操作でなければ柔軟性が低い
  • curses ライブラリは、そのような操作を最適化して、シリアルターミナルに差分を送る事により行う
  • アプリケーションからは、単なる配列に対するアクセスでしかなく、ターミナル独特の性質を隠蔽出来る
  • これによりダイナミックな文字の書き換えを必要とするアプリケーションを実装しやすくする
  • 代表的なアプリとして、vi や emacs などがある
  • このライブラリは、40 年前には既に存在していて、UNIX マシンに、シリアル接続したターミナルで使えていた
  • その当時は1台の UNIX マシンに4台くらいのターミナルを接続してマルチユーザーで利用するのが当たり前だった
  • ターミナルは VT100 互換の制御コマンド(エスケープシーケンス)を実行出来る事が前提となる
  • 当時、このライブラリの存在を知って、テトリスを作って遊んでいた
  • curses ライブラリは、ターミナルの能力を識別する為に、UNIX システムのターミナル情報を参照する

今、何故、curses ライブラリが有用なのか?

  • 組み込みマイコンの場合、シリアル通信で、PC 上のターミナルエミュレータと通信する場合が多い
  • 組み込みマイコン側に curses ライブラリを移植する事で、アプリケーションの柔軟性が向上する
  • ただ、curses ライブラリはかなり大きく、高機能で、全ての API を移植するのは現実的では無い
  • 又、ターミナルの機能を参照する仕組みが、UNIX システムとかなり親密で、そこを移植するのもハードルが高い
  • curses を使って何かアプリを作る場合、ほとんどの場合一部の機能しか使わない事が多い
  • テキストベースのアプリケーションを組み込みマイコン側で動かすのは、色々とメリットがあり、欲しい機能ではある

まずは、本家でアプリを実装して、必要な API を最適化する

  • とりあえず、clang64 環境で、ncurses ライブラリを使ってみる
  • そこで、curses で使う API を最適化して、その API だけ、組み込みマイコン側に実装すれば良いように思う
  • 画面の差分をシリアルで転送する場合、VT100 の一部のエスケープシーケンスだけしか利用しない

clang64 に ncurses ライブラリをインストール

 % pacman -Ss ncurses
clang64/mingw-w64-clang-x86_64-ncurses 6.5.20250927-2 [インストール済み]
    System V Release 4.0 curses emulation library (mingw-w64)
 % ls -l /clang64/lib/libncur*
-rw-r--r-- 1 hira hira 692K 10月  5 00:44 /clang64/lib/libncurses.a
-rw-r--r-- 1 hira hira 127K 10月  5 00:44 /clang64/lib/libncurses++w.a
-rw-r--r-- 1 hira hira  84K 10月  5 00:44 /clang64/lib/libncurses++w.dll.a
-rw-r--r-- 1 hira hira 127K 10月  5 00:44 /clang64/lib/libncurses++w_g.a
-rw-r--r-- 1 hira hira 692K 10月  5 00:44 /clang64/lib/libncursesw.a
-rw-r--r-- 1 hira hira 157K 10月  5 00:44 /clang64/lib/libncursesw.dll.a
-rw-r--r-- 1 hira hira 914K 10月  5 00:44 /clang64/lib/libncursesw_g.a
  • ncurses ライブラリは「libncursesw.a」をリンクします。
  • 末尾に「w」が付加しています、これは、ワイド文字列をサポートしています
  • さらに「++w」は、curses ライブラリ API を C++ から便利に使う為にラッパークラスのようです
  • 今回は、組み込みマイコン側でも簡単に実装出来るようにするので、あえて C++ バージョンを使いません
LOCAL_INC_PATH := /clang64/include
NCURSES_PATH := $(LOCAL_INC_PATH)/ncurses
  • ncurses のヘッダーは「ncurses」ディレクトリに集約されています
  • コンパイル時、上のように、システムインクルードパスを追加します
#include <ncurses.h>
#include <locale.h>
  • ncurses でワイド文字列を扱う場合、ロケールの設定が必要なので、ヘッダーをインクルードします

   setlocale(LC_ALL, "ja_JP.UTF-8");
  • 通常、UTF-8 を使うと思うので、上記のようにロケールを設定しておきます

簡単なサンプル

int main(int argc, char** argv)
{
    setlocale(LC_ALL, "ja_JP.UTF-8");

    initscr();

    int ym;
    int xm;
    getmaxyx(stdscr, ym, xm);
    keypad(stdscr, TRUE);
    noecho();

    const char* str = "漢字";
    int x = (xm - sizeof(str)) / 2;
    int y = ym / 2;

    nodelay(stdscr, TRUE);

    int xx = -1;
    int yy = -1;
    while(true) {

        if(xx != x || yy != y) {
            clear();
            mvprintw(y, x, "%s", str);
            refresh();
            xx = x;
            yy = y;
        }

        auto ch = getch();
        if(ch < 0) continue;

        if(ch == 0x1b) {
            break;
        }

        switch(ch) {
        case KEY_UP:
            y--;
            break;
        case KEY_DOWN:
            ++y;
            break;
        case KEY_LEFT:
            x--;
            break;
        case KEY_RIGHT:
            ++x;
            break;
        default:
            break;
        }
    }

    endwin();
}
  • このサンプルでは、矢印キーで、中央に表示された文字列を動かします
  • ESC キーを押すとアプリを抜けます
  • curses での、ダイナミックな表示では、画面を全部消去して、全てのオブジェクトを描画します
  • そうする事で、色々なオブジェクトをダイナミックに表示する事が簡単に行えます
  • curses では、描画を最適化して差分をターミナルに送るので、シリアル通信の転送量を最適化します
    nodelay(stdscr, TRUE);
  • この設定により、「getch()」関数はブロッキングをしなくなり、キー入力が無い場合に (-1) を返します
  • 描画ループを一定間隔で回るようにすれば、アニメーションを簡単に行う事が出来ます

まとめ

curses を使い、最低限の機能を実装して、何となく使い方が理解出来たと思う。

これで、PC 上で curses API を使ったアプリを実装して、動作を検証できる

次は、組み込みでも動作する簡単なスクリーンエディターを実装して、組み込み用 curses ライブラリを構築してみようと思う

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください