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も混ぜておいた。