/* ARISA - Curses functions for arisa-sh * Copyright (C) 2003, 2004 Carl Ritson * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #define HISTORY_SIZE 20 /* Enums and Typedefs **/ enum { C_DEFAULT = 1, C_RED, C_BLUE, C_GREEN, C_YELLOW, C_MAGENTA, C_PURPLE, C_CYAN, C_WHITE, C_BLACK }; enum { RET_NOP = 0, RET_RESIZE = -1, RET_PAGEUP = -11, RET_PAGEDOWN = -12 }; typedef struct inputbuf_t { WINDOW *win; char *buffer; size_t bufsize; int width; int used; int shift; int cursor; char **history; int no_history; int history_next; int history_current; } inputbuf_t; /** Global Variables **/ static WINDOW *termwin, *lines, *mid, *bot; static inputbuf_t *inputbuf; static volatile int need_resize = 0; static int x_size = 0, y_size = 0; static int no_lines = 500, line_shift = 0; /** Functions **/ /* Input Buffer */ static void inputbuf_clear(inputbuf_t *ib); // pre-declaration static inputbuf_t *inputbuf_alloc(WINDOW *win, size_t size) { inputbuf_t *ib; int x,y; if(size < 1) return NULL; getmaxyx(win,y,x); ib = malloc(sizeof(inputbuf_t)); ib->win = win; ib->buffer = malloc(size); ib->bufsize = size; ib->width = x; ib->used = 0; ib->no_history = HISTORY_SIZE; ib->history = malloc(sizeof(char *) * ib->no_history); ib->history_next = 0; ib->history_current = 0; inputbuf_clear(ib); return ib; } static void inputbuf_free(inputbuf_t *ib) { int i; free(ib->buffer); for(i = 0; i < ib->no_history; ++i) { if(ib->history[i] != NULL) free(ib->history[i]); } free(ib->history); free(ib); } static int cursor_off_screen(inputbuf_t *ib) { if((ib->cursor - ib->shift) > (ib->width - 1)) return 1; else if((ib->cursor - ib->shift) < 0) return 1; else return 0; } static void inputbuf_redraw_cursor(inputbuf_t *ib) { wmove(ib->win,0,ib->cursor - ib->shift); wnoutrefresh(ib->win); } static void inputbuf_redraw(inputbuf_t *ib) { int tmp; if(cursor_off_screen(ib)) { tmp = ib->cursor / (ib->width / 2); if(ib->cursor < ib->shift) ib->shift = tmp * (ib->width / 2); else ib->shift = (tmp - 1) * (ib->width / 2); } werase(ib->win); waddnstr(ib->win, ib->buffer + ib->shift, ib->width); inputbuf_redraw_cursor(ib); } static void inputbuf_change_width(inputbuf_t *ib, int width) { if(width > 1) { ib->width = width; inputbuf_redraw(ib); } } static void inputbuf_clear(inputbuf_t *ib) { ib->buffer[0] = '\0'; ib->cursor = 0; ib->used = 0; ib->shift = 0; werase(ib->win); } static void inputbuf_loadstr(inputbuf_t *ib, const char *str) { strncpy(ib->buffer,str,ib->bufsize - 1); ib->buffer[ib->bufsize - 1] = '\0'; ib->used = strlen(ib->buffer); ib->cursor = ib->used; ib->shift = 0; inputbuf_redraw(ib); } static void inputbuf_removechar(inputbuf_t *ib) { memmove(ib->buffer + ib->cursor, ib->buffer + ib->cursor + 1, ib->used - ib->cursor); ib->buffer[--ib->used] = '\0'; wdelch(ib->win); if((ib->used - ib->shift) >= ib->width) { mvwaddch(ib->win,0,ib->width - 1, ib->buffer[ib->shift + ib->width - 1]); } } static void inputbuf_addchar(inputbuf_t *ib, const int c) { if(ib->cursor == ib->used) { ib->buffer[ib->cursor + 1] = '\0'; waddch(ib->win,c); } else { memmove(ib->buffer + ib->cursor, ib->buffer + ib->cursor - 1, ib->used - (ib->cursor - 1)); winsch(ib->win,c); } ib->buffer[ib->cursor] = (char) c; ib->cursor++; ib->used++; } static void history_add(inputbuf_t *ib, const char *str) { if(str == NULL) return; if(strlen(str) == 0) return; if(ib->history[ib->history_next] != NULL) free(ib->history[ib->history_next]); ib->history[ib->history_next] = strdup(str); ib->history_next++; if(ib->history_next >= ib->no_history) ib->history_next = 0; ib->history_current = ib->history_next; } static void history_load(inputbuf_t *ib, int shift) { int pos = ib->history_current + shift; pos = pos >= 0 ? pos % ib->no_history : ib->no_history - ((-pos) % ib->no_history); if(shift == 1 && pos == ib->history_next) inputbuf_loadstr(ib,""); else if(ib->history[pos] != NULL) { inputbuf_loadstr(ib,ib->history[pos]); ib->history_current = pos; } } static int inputbuf_readchars(inputbuf_t *ib, char *buffer, size_t bufsize) { int c, cread = -1, ret = RET_NOP; do { cread++; c = wgetch(ib->win); switch (c) { case KEY_RESIZE: ret = RET_RESIZE; case ERR: break; case KEY_LEFT: if(ib->cursor > 0) ib->cursor--; break; case KEY_RIGHT: if(ib->cursor < ib->used) ib->cursor++; break; case KEY_UP: history_load(ib,-1); break; case KEY_DOWN: history_load(ib,+1); break; case KEY_PPAGE: ret = RET_PAGEUP; break; case KEY_NPAGE: ret = RET_PAGEDOWN; break; case KEY_DC: if(ib->used > 0 && ib->cursor < ib->used) inputbuf_removechar(ib); break; case 0177: case KEY_BACKSPACE: if(ib->cursor > 0) { ib->cursor--; inputbuf_redraw_cursor(ib); inputbuf_removechar(ib); } break; case KEY_HOME: ib->cursor = 0; break; case KEY_END: ib->cursor = ib->used; break; case KEY_ENTER: case '\n': ib->buffer[ib->used] = '\0'; history_add(ib,ib->buffer); snprintf(buffer,bufsize,"%s",ib->buffer); ret = strlen(buffer); inputbuf_clear(ib); break; default: if(!iscntrl(c) && ib->used < ib->bufsize - 1) inputbuf_addchar(ib,c); break; } if(cursor_off_screen(ib)) inputbuf_redraw(ib); inputbuf_redraw_cursor(ib); } while(ret == RET_NOP && c != ERR); if(cread > 0) wnoutrefresh(ib->win); return ret; } /* The Rest */ static void cleanup(void) { endwin(); inputbuf_free(inputbuf); } static void refresh_lines(WINDOW *win) { int pminrow = no_lines - ((y_size - 2) + line_shift); if(pminrow < 0) pminrow = 0; prefresh(win,pminrow,0,0,0,y_size - 3,x_size); } static void put_line(WINDOW *win, const char *line) { attr_t attr = A_NORMAL; int i; waddch(win,'\n'); for(i = 0; line[i] != '\0'; ++i) { if(line[i] == '%') { i++; switch(line[i]) { case 'n': attr = A_NORMAL; wcolor_set(win,C_DEFAULT,NULL); break; case '_': case '9': attr = (attr & A_BOLD) ? attr & ~A_BOLD : attr | A_BOLD; break; case 'U': attr = (attr & A_UNDERLINE) ? attr & ~A_UNDERLINE : attr | A_UNDERLINE; break; case 'B': attr |= A_BOLD; case 'b': wcolor_set(win,C_BLUE,NULL); break; case 'R': attr |= A_BOLD; case 'r': wcolor_set(win,C_RED,NULL); break; case 'G': attr |= A_BOLD; case 'g': wcolor_set(win,C_GREEN,NULL); break; case 'Y': attr |= A_BOLD; case 'y': wcolor_set(win,C_YELLOW,NULL); break; case 'M': attr |= A_BOLD; case 'm': wcolor_set(win,C_MAGENTA,NULL); break; case 'P': attr |= A_BOLD; case 'p': wcolor_set(win,C_PURPLE,NULL); break; case 'C': attr |= A_BOLD; case 'c': wcolor_set(win,C_CYAN,NULL); break; case 'W': attr |= A_BOLD; case 'w': wcolor_set(win,C_WHITE,NULL); break; case 'K': attr |= A_BOLD; case 'k': wcolor_set(win,C_BLACK,NULL); break; default: waddch(win,line[i-1] | attr); if(line[i] != '\0' && line[i] != '%') waddch(win,line[i] | attr); break; } } else waddch(win,line[i] | attr); } wcolor_set(win,C_DEFAULT,NULL); refresh_lines(win); } static void curses_print(WINDOW *win, const char *line) { if(timestamp) { struct tm timeval; char tsbuf[32], buffer[600]; time_t now = time(NULL); localtime_r(&now,&timeval); if(timestamp == 2) strftime(tsbuf,sizeof(tsbuf),"%T",&timeval); else strftime(tsbuf,sizeof(tsbuf),"%R",&timeval); snprintf(buffer,sizeof(buffer),"%%K[%%n%s%%K]%%n %s", tsbuf,line); put_line(win,buffer); } else put_line(win,line); } static void draw_prompt(WINDOW *win, int x) { int i,len; werase(win); wprintw(win,"%s",prompt); len = strlen(prompt); for(i = 0; i < (x-len); ++i) wprintw(win,"-"); wnoutrefresh(win); } static int get_term_size(int *width, int *height) { #ifdef TIOCGWINSZ struct winsize ws; if (ioctl(0, TIOCGWINSZ, &ws) < 0) return -1; if (ws.ws_row == 0 && ws.ws_col == 0) return -1; *width = ws.ws_col; *height = ws.ws_row; if (*width < 1) *width = 1; if (*height < 1) *height = 1; return 0; #else return -1; #endif /* TIOCGWINSZ */ } static void resize(void) { char buffer[64]; int x,y; if(get_term_size(&x,&y) != 0) return; if(x != x_size || y != y_size) { x_size = x; y_size = y; resizeterm(y_size,x_size); wresize(termwin,y_size,x_size); wresize(lines,no_lines,x_size); wresize(mid,1,x_size); mvwin(mid,y_size-2,0); wresize(bot,1,x_size); mvwin(bot,y_size-1,0); snprintf(buffer,sizeof(buffer), "%%b* Terminal Size Changed To: %dx%d", x_size,y_size); put_line(lines,buffer); draw_prompt(mid,x_size); inputbuf_change_width(inputbuf,x); doupdate(); } need_resize = 0; } static void sigwinch(int sig) { need_resize = 1; } static void pageup(void) { line_shift += (y_size-2) / 2; if(line_shift > (no_lines - ((y_size-2) / 2))) line_shift = (no_lines - ((y_size-2) / 2)); refresh_lines(lines); inputbuf_redraw_cursor(inputbuf); } static void pagedown(void) { line_shift -= (y_size-2) / 2; if(line_shift < 0) line_shift = 0; refresh_lines(lines); inputbuf_redraw_cursor(inputbuf); } static int ncurses_main(void) { #define KBD 0 #define SOK 1 char buffer[512], recvbuf[512]; struct pollfd pfd[2]; int x,y,ret = 0; /* root window */ termwin = initscr(); /* init colours */ start_color(); #ifdef NCURSES_VERSION #define BG_COLOR (-1) use_default_colors(); init_pair(C_DEFAULT, -1, -1); #else #define BG_COLOR COLOR_BLACK init_pair(C_DEFAULT, COLOR_WHITE, COLOR_BLACK); #endif init_pair(C_RED, COLOR_RED, BG_COLOR); init_pair(C_BLUE, COLOR_BLUE, BG_COLOR); init_pair(C_GREEN, COLOR_GREEN, BG_COLOR); init_pair(C_YELLOW, COLOR_YELLOW, BG_COLOR); init_pair(C_MAGENTA, COLOR_MAGENTA, BG_COLOR); init_pair(C_CYAN, COLOR_CYAN, BG_COLOR); init_pair(C_WHITE, COLOR_WHITE, BG_COLOR); init_pair(C_BLACK, COLOR_BLACK, BG_COLOR); /* single characters with no echo */ noecho(); cbreak(); #ifdef SIGWINCH signal(SIGWINCH,sigwinch); #endif atexit(cleanup); /* allocate windows */ getmaxyx(termwin,y,x); x_size = x; y_size = y; lines = newpad(no_lines,x); mid = newwin(1,x,y-2,0); bot = newwin(1,x,y-1,0); /* prompt window */ draw_prompt(mid,x_size); /* input window */ keypad(bot,TRUE); nodelay(bot,TRUE); inputbuf = inputbuf_alloc(bot,510); /* output window */ scrollok(lines,TRUE); wmove(lines,no_lines-1,0); refresh_lines(lines); inputbuf_redraw_cursor(inputbuf); doupdate(); pfd[KBD].fd = fileno(stdin); pfd[KBD].events = POLLIN; pfd[SOK].fd = sok; pfd[SOK].events = POLLIN; /* tell arisa to send colour codes */ send(sok,"colour int\r\n",12,0); for(;;) { pfd[KBD].revents = 0; pfd[SOK].revents = 0; ret = poll(pfd,2,-1); x = errno; if(need_resize) resize(); if(ret == 0 || (ret == -1 && x == EINTR)) { continue; } else if(ret == -1) { // FIXME: message exit(1); } if(pfd[KBD].revents == POLLIN) { ret = inputbuf_readchars(inputbuf, recvbuf,sizeof(recvbuf)); if(ret > RET_NOP) { ret = snprintf(buffer,sizeof(buffer), "%s\r\n",recvbuf); ret = send(sok,buffer,ret,0); if(ret == -1) { snprintf(buffer,sizeof(buffer), "%%r* Send Error: %s", strerror(errno)); curses_print(lines,buffer); doupdate(); exit(1); } snprintf(buffer,sizeof(buffer),"%%b> %s", recvbuf); curses_print(lines,buffer); inputbuf_redraw_cursor(inputbuf); } else if(ret == RET_RESIZE) { resize(); } else if(ret == RET_PAGEDOWN) { pagedown(); } else if(ret == RET_PAGEUP) { pageup(); } } else if(pfd[KBD].revents != 0) exit(1); // terminal probably hung up if(pfd[SOK].revents == POLLIN) { do { ret = recv_line(recvbuf,sizeof(recvbuf)); if(ret >= 0) { if(recvbuf[0] == '[' && recvbuf[strlen(recvbuf)-1] == ']') { if(prompt != NULL) free(prompt); prompt = strdup(recvbuf); draw_prompt(mid,x_size); } else curses_print(lines,recvbuf); inputbuf_redraw_cursor(inputbuf); } } while(has_pending_data(sok) > 0 && ret >= 0); if(ret == -1) { snprintf(buffer,sizeof(buffer), "%%r* Socket Error: %s", strerror(errno)); curses_print(lines,buffer); doupdate(); exit(1); } } else if(pfd[SOK].revents != 0) { socklen_t len; int val; len = sizeof(val); ret = getsockopt(sok,SOL_SOCKET,SO_ERROR,&val,&len); snprintf(buffer,sizeof(buffer), "%%r* Socket Error: %s", strerror(ret != -1 ? val : errno)); curses_print(lines,buffer); doupdate(); exit(1); } doupdate(); } return 0; #undef KBD #undef SOK }