terminfoメモ(libev化)

とりあえず、libevでメインループを置き換えてみる。node.jsで使っているのは、memcacheのlibeventじゃなくてlibevの方だった。

// g++ -o cursor -g cursor.cpp -g -lcurses -lreadline -lev

#include <stdlib.h>
#include <term.h>
#include <curses.h>
#include <errno.h>
#include <readline/readline.h>

#include <ev.h>
#include <fcntl.h>

class Termios
{
    struct termios save_term;
    struct termios temp_term;

    void update()
    {
        errno = 0;
        if(tcsetattr(fileno(stdin), TCSANOW, &temp_term) == -1){
            perror("tcsetattr failure");
            exit(EXIT_FAILURE);
        }
    }

    public:
    Termios()
    {
        // save term
        errno = 0;
        if(tcgetattr(fileno(stdin), &save_term) == -1){
            perror("tcgetattr failure");
            exit(EXIT_FAILURE);
        }
        temp_term=save_term;
    }

    ~Termios()
    {
        restore();
    }

    void no_canonical()
    {
        temp_term.c_iflag &= IGNCR;
        temp_term.c_oflag &= ONLRET;
        temp_term.c_lflag &= (~ISIG & ~ICANON & ~ECHO);
        temp_term.c_cc[VMIN] = 1;
        temp_term.c_cc[VTIME] = 5;
        update();
    }

    void restore()
    {
        tcsetattr(fileno(stdin), TCSANOW, &save_term);
        temp_term=save_term;
    }

    void echo(bool enable)
    {
        if(enable){
            temp_term.c_lflag |= ECHO;
        }
        else{
            temp_term.c_lflag &= ~ECHO;
        }
        update();
    }
};


class Terminfo
{
    public:
        static void initialize()
        {
            if(setupterm(NULL, fileno(stdout), (int *)0) == ERR){
                fprintf(stderr,"setupterm failure\n");
                exit(EXIT_FAILURE);
            }
            cmd("clear");
        }

        static void cmd(const char *cmd)
        {
            errno=0;
            char *parmcmd=tparm(tigetstr(cmd));
            if(parmcmd==NULL){
                perror("tparm NULL");
                exit(EXIT_FAILURE);
            }
            tputs(parmcmd, 1, putchar);
        }
};


Termios termios;


// all watcher callbacks have a similar signature
// this callback is called when data is readable on stdin
static void stdin_cb (EV_P_ ev_io *w, int revents)
{
    if(revents & EV_READ){
        int c;
        while((c=getchar())!=EOF){
            switch(c)
            {
                default:
                    break;
                case 'q':
                    ev_unloop(EV_A_ EVUNLOOP_ALL);
                    break;
                case 'h':
                    Terminfo::cmd("cub1");
                    break;
                case 'j':
                    Terminfo::cmd("cud1");
                    break;
                case 'k':
                    Terminfo::cmd("cuu1");
                    break;
                case 'l':
                    Terminfo::cmd("cuf1");
                    break;
                case ' ':
                    termios.echo(true);
                    readline("input:");
                    termios.echo(false);
                    break;
            }
        }
    }
}


int main(int argc, char **argv)
{
    termios.no_canonical();
    Terminfo::initialize();
    fcntl(fileno(stdin), F_SETFL, O_NONBLOCK);

    // setup libev
    struct ev_loop *loop = EV_DEFAULT;
    ev_io stdin_watcher;
    ev_io_init (&stdin_watcher, stdin_cb, fileno(stdin), EV_READ);
    ev_io_start (loop, &stdin_watcher);

    // start libev
    ev_loop(loop, 0);

    return 0;
}

node.js向けのterminfoをwrapしたc++モジュールを作った

今日の目標地点まで実装完了。
下記のようなjavascriptで、カーソルをhjklで上下左右に動かすのに成功した。

var TERMUTIL=require('./build/default/termutil');

var term=new TERMUTIL.Term();


var keymap={
    113: // q
        function(){ process.exit(); },
    0x68: // h
        function(){ term.tcmd('cub1'); },
    0x6a: // j
        function(){ term.tcmd('cud1'); },
    0x6b: // k
        function(){ term.tcmd('cuu1'); },
    0x6c: // l
        function(){ term.tcmd('cuf1'); },
};

//var stdin = process.openStdin();
//stdin.on('data', function(chunk){ この方式だとkeyrepeat(non canonical)がかかったあたりでsegv。謎
term.on('keyinput', function(code){
    if(code in keymap){
        keymap[code]();
    }
    else{
        process.stdout.write('['+code+']');
    }
});

C++のソースはあとで整理してから載せる、多分。
要点は、

  • C++のclassとか関数登録(v8)
  • C++関数側での引数受け取り(v8)
  • libevイベント登録とコールバックからのnode.jsのEvent発動(node.js)
  • EventEmitterの継承(node.js)

といったところか。
前もってcool.io(元rev。libevのrubyバインディング)を触っていたので、libevがさくっと理解できて順調なのであった。

terminfoメモ

node.jsでwrapする前にc++でterminfo, termios, readlineの使いかたを練習。

// g++ -o cursor cursor.cpp -lcurses -lreadline

#include <stdlib.h>
#include <term.h>
#include <curses.h>
#include <errno.h>
#include <readline/readline.h>


class Termios
{
    struct termios save_term;
    struct termios temp_term;

    void update()
    {
        errno = 0;
        if(tcsetattr(fileno(stdin), TCSANOW, &temp_term) == -1){
            perror("tcsetattr failure");
            exit(EXIT_FAILURE);
        }
    }

    public:
    Termios()
    {
        // save term
        errno = 0;
        if(tcgetattr(fileno(stdin), &save_term) == -1){
            perror("tcgetattr failure");
            exit(EXIT_FAILURE);
        }
        temp_term=save_term;
    }

    ~Termios()
    {
        restore();
    }

    void no_canonical()
    {
        temp_term.c_iflag &= IGNCR;
        temp_term.c_oflag &= ONLRET;
        temp_term.c_lflag &= (~ISIG & ~ICANON & ~ECHO);
        temp_term.c_cc[VMIN] = 1;
        temp_term.c_cc[VTIME] = 5;
        update();
    }

    void restore()
    {
        tcsetattr(fileno(stdin), TCSANOW, &save_term);
        temp_term=save_term;
    }

    void echo(bool enable)
    {
        if(enable){
            temp_term.c_lflag |= ECHO;
        }
        else{
            temp_term.c_lflag &= ~ECHO;
        }
        update();
    }
};


class Terminfo
{
    public:
        static void initialize()
        {
            if(setupterm(NULL, fileno(stdout), (int *)0) == ERR){
                fprintf(stderr,"setupterm failure\n");
                exit(EXIT_FAILURE);
            }
            cmd("clear");
        }

        static void cmd(const char *cmd)
        {
            errno=0;
            char *parmcmd=tparm(tigetstr(cmd));
            if(parmcmd==NULL){
                perror("tparm NULL");
                exit(EXIT_FAILURE);
            }
            tputs(parmcmd, 1, putchar);
        }
};


Termios termios;

int main(int argc, char **argv)
{
    termios.no_canonical();
    Terminfo::initialize();
    bool loop=true;
    while(loop){
        switch(getchar())
        {
            default:
                break;
            case 'q':
                loop=false;
                break;
            case 'h':
                Terminfo::cmd("cub1");
                break;
            case 'j':
                Terminfo::cmd("cud1");
                break;
            case 'k':
                Terminfo::cmd("cuu1");
                break;
            case 'l':
                Terminfo::cmd("cuf1");
                break;
            case ' ':
                termios.echo(true);
                readline("input:");
                termios.echo(false);
                break;
        }
    }

    return 0;
}

node.jsのncursesを見てみたが、terminfoの関数(tputs, tigetstr, tparmあたり)はエクスポートしていないみたいなので自作することにした。ついでにreadlineも混ぜておいた。