# HG changeset patch # User Matti Hamalainen # Date 1217667429 -10800 # Node ID 512775f6b081014c5c79dfbf1fd8d663e6280a20 # Parent da721f94c60fe48cef8f530b36636e56163775fb A refactored ncurses-based UI. diff -r da721f94c60f -r 512775f6b081 Makefile.gen --- a/Makefile.gen Sat Aug 02 04:04:58 2008 +0300 +++ b/Makefile.gen Sat Aug 02 11:57:09 2008 +0300 @@ -18,7 +18,7 @@ $(COMP) -c -o $@ $< $(NNCHAT_BIN): nnchat.c th_util.o th_string.o th_args.o - $(COMP) -o $@ $+ $(LDFLAGS) + $(COMP) -o $@ $+ $(LDFLAGS) -lcurses # # Special targets diff -r da721f94c60f -r 512775f6b081 nnchat.c --- a/nnchat.c Sat Aug 02 04:04:58 2008 +0300 +++ b/nnchat.c Sat Aug 02 11:57:09 2008 +0300 @@ -1,12 +1,8 @@ -#ifdef __WIN32 -#include -#else #include #include #include #include #include -#endif #include #include @@ -16,31 +12,13 @@ #include #include #include +#include +#define SET_BUFSIZE (4096) #define SET_ALLOC_SIZE (128) -#define SET_SELECT_USEC (100000) - -#define ANSI_BLACK "\x1b[0;30m" -#define ANSI_RED "\x1b[0;31m" -#define ANSI_GREEN "\x1b[0;32m" -#define ANSI_YELLOW "\x1b[0;33m" -#define ANSI_BLUE "\x1b[0;34m" -#define ANSI_MAGENTA "\x1b[0;35m" -#define ANSI_CYAN "\x1b[0;36m" -#define ANSI_WHITE "\x1b[0;37m" - -#define ANSI_L_BLACK "\x1b[0;1;30m" -#define ANSI_L_RED "\x1b[0;1;31m" -#define ANSI_L_GREEN "\x1b[0;1;32m" -#define ANSI_L_YELLOW "\x1b[0;1;33m" -#define ANSI_L_BLUE "\x1b[0;1;34m" -#define ANSI_L_MAGENTA "\x1b[0;1;35m" -#define ANSI_L_CYAN "\x1b[0;1;36m" -#define ANSI_L_WHITE "\x1b[0;1;37m" - -#define ANSI_END "\x1b[0m" - +#define SET_DELAY (15) +#define SET_DELAY_USEC (SET_DELAY * 1000) /* Options */ @@ -54,7 +32,11 @@ *setTarget = NULL; BOOL optDaemon = FALSE; FILE *optLogFile = NULL; +WINDOW *mainWin = NULL, + *statusWin = NULL, + *editWin = NULL; +BOOL setInsertMode = TRUE; /* Arguments */ @@ -78,18 +60,6 @@ } -#ifdef __WIN32 -/* Just a bogus stub - */ -const char *hstrerror(int err) -{ - (void) err; - - return "???"; -} -#endif - - int getColor(char *str) { char *p = str; @@ -171,6 +141,122 @@ } +typedef struct { + ssize_t pos, len; + char data[SET_BUFSIZE]; +} editbuf_t; + + +int writeBuf(editbuf_t *buf, ssize_t pos, int ch) +{ + /* Check arguments */ + if (buf->len+1 >= SET_BUFSIZE) return -3; + + if (pos < 0) + return -1; + else if (pos >= buf->len) { + buf->data[buf->len++] = ch; + } else { + buf->data[pos] = ch; + } + return 0; +} + +int insertBuf(editbuf_t *buf, ssize_t pos, int ch) +{ + /* Check arguments */ + if (buf->len+1 >= SET_BUFSIZE) return -3; + + if (pos < 0) + return -1; + else if (pos >= buf->len) { + buf->data[buf->len] = ch; + } else { + memmove(&(buf->data[pos+1]), &(buf->data[pos]), buf->len - pos + 1); + buf->data[pos] = ch; + } + buf->len++; + return 0; +} + +int deleteBuf(editbuf_t *buf, ssize_t pos) +{ + /* Check arguments */ + if (pos < 0) + return -1; + else if (pos < buf->len) { + memmove(&(buf->data[pos]), &(buf->data[pos+1]), buf->len - pos); + buf->len--; + return 0; + } else + return -2; +} + +void clearBuf(editbuf_t *buf) +{ + buf->len = 0; + buf->pos = 0; +} + +void setBufPos(editbuf_t *buf, ssize_t pos) +{ + /* Check arguments */ + if (pos < 0) + buf->pos = 0; + else if (pos >= buf->len) + buf->pos = buf->len; + else + buf->pos = pos; +} + +void updateStatus(void) +{ + char tmpStr[128] = ""; + time_t timeStamp; + struct tm *tmpTime;; + + timeStamp = time(NULL); + if ((tmpTime = localtime(&timeStamp)) != NULL) { + strftime(tmpStr, sizeof(tmpStr), "%H:%M:%S", tmpTime); + } + + wbkgdset(statusWin, 0x0d00); + werase(statusWin); + + wattrset(statusWin, A_BOLD); + mvwaddstr(statusWin, 0, 1, tmpStr); + waddstr(statusWin, " | "); + wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); + waddstr(statusWin, setInsertMode ? "INS" : "DEL"); + + wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); + waddstr(statusWin, " | Private target: "); + + wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); + waddstr(statusWin, setTarget != NULL ? setTarget : "--"); + + wrefresh(statusWin); +} + +void printEditBuf(editbuf_t *buf) +{ + buf->data[buf->len] = 0; + werase(editWin); + if (buf->pos < buf->len) { + mvwaddnstr(editWin, 0, 0, buf->data, buf->pos); + wattrset(editWin, A_REVERSE); + waddch(editWin, buf->data[buf->pos]); + wattrset(editWin, A_NORMAL); + waddnstr(editWin, buf->data + buf->pos + 1, buf->len - buf->pos - 1); + } else { + mvwaddnstr(editWin, 0, 0, buf->data, buf->len); + wattrset(editWin, A_REVERSE); + waddch(editWin, ' '); + wattrset(editWin, A_NORMAL); + } + wrefresh(editWin); +} + int openConnection(struct in_addr *addr, int port) { struct sockaddr_in tmpAddr; @@ -202,11 +288,7 @@ void closeConnection(int sock) { if (sock >= 0) { -#ifdef __WIN32 - closesocket(sock); -#else close(sock); -#endif } } @@ -226,33 +308,90 @@ return TRUE; } +int printWin(WINDOW *win, const char *fmt) +{ + const char *s = fmt; + int col = 0; + + while (*s) { + if (*s == '½') { + int val = 0; + s++; + if (*s == '½') { + waddch(win, *s | col); + s++; + } else { + while (*s && isdigit(*s)) { + val *= 10; + val += (*s - '0'); + s++; + } + if (*s != '½') return -1; + s++; + + if (val < 9) { + col = A_DIM | COLOR_PAIR(val); + } else if (val < 30) { + col = A_BOLD | COLOR_PAIR(val - 9); + } + } + } else { + waddch(win, *s | col); + s++; + } + } + return 0; +} -void printMsg(char *fmt, char *fmt2, ...) +int printFile(FILE *outFile, const char *fmt) { - char tmpStr[64] = ""; + const char *s = fmt; + + while (*s) { + if (*s == '½') { + s++; + if (*s == '½') { + s++; + } else { + while (*s && isdigit(*s)) s++; + if (*s != '½') return -1; + s++; + } + } else { + fputc(*s, outFile); + s++; + } + } + + return 0; +} + +void printMsg(char *fmt, ...) +{ + char tmpStr[128] = "", buf[8192]; va_list ap; time_t timeStamp; struct tm *tmpTime;; timeStamp = time(NULL); if ((tmpTime = localtime(&timeStamp)) != NULL) { - strftime(tmpStr, sizeof(tmpStr), "%H:%M:%S", tmpTime); + strftime(tmpStr, sizeof(tmpStr), "½17½[½11½%H:%M:%S½17½]½0½ ", tmpTime); } + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (optLogFile) { - fprintf(optLogFile, "[%s] ", tmpStr); - va_start(ap, fmt2); - vfprintf(optLogFile, fmt, ap); - va_end(ap); + printFile(optLogFile, tmpStr); + printFile(optLogFile, buf); fflush(optLogFile); } if (!optDaemon) { - fprintf(stdout, ANSI_L_BLACK "[" ANSI_L_GREEN "%s" ANSI_L_BLACK "]" ANSI_END " ", tmpStr); - va_start(ap, fmt2); - vfprintf(stdout, fmt2, ap); - va_end(ap); - fflush(stdout); + printWin(mainWin, tmpStr); + printWin(mainWin, buf); + wrefresh(mainWin); } } @@ -362,6 +501,13 @@ s++; break; + case '½': + /* Escape these .. */ + PUSHCHAR('½'); + PUSHCHAR('½'); + s++; + break; + case '\r': PUSHCHAR(' '); s++; @@ -550,18 +696,18 @@ if (!strncmp(s, "/BPRV", 5)) { t = stripTags(s + 2); h = decodeStr2(t); - printMsg("%s\n", ANSI_YELLOW "%s" ANSI_END "\n", h); + printMsg("%s\n", h); } else { t = stripTags(s + 1); h = decodeStr2(t); - printMsg("* %s\n", ANSI_L_YELLOW "* %s" ANSI_END "\n", h); + printMsg("* %s\n", h); } th_free(h); th_free(t); } else { t = stripTags(s); h = decodeStr2(t); - printMsg("<%s> %s\n", ANSI_MAGENTA "<" ANSI_L_CYAN "%s" ANSI_MAGENTA ">" ANSI_END " %s\n", p, h); + printMsg("½5½<½15½%s½5½>½0½ %s\n", p, h); th_free(h); th_free(t); } @@ -584,10 +730,10 @@ } if (!strncmp(str, "FAILURE", 7)) { - printMsg("Login failure - %s\n", "Login failure - %s\n", tmpStr); + printMsg("½1½Login failure½0½ - ½3½%s½0½\n", tmpStr); return -2; } else if (!strncmp(str, "SUCCESS", 7)) { - printMsg("Login success - %s\n", "Login success - %s\n", tmpStr); + printMsg("½2½Login success½0½ - ½3½%s½0½\n", tmpStr); sendUserMsg(sock, optUserName2, "%%2FRequestUserList"); return 0; } else @@ -607,7 +753,7 @@ p = decodeStr1(str); if (!p) return -1; - printMsg("! %s ADDED.\n", "! " ANSI_GREEN "%s" ANSI_END " ADDED.\n", p); + printMsg("! ½3½%s½0½ ½2½ADDED.½0½\n", p); th_free(p); return 0; } @@ -625,7 +771,7 @@ p = decodeStr1(str); if (!p) return -1; - printMsg("! %s DELETED.\n", "! " ANSI_RED "%s" ANSI_END " DELETED.\n", p); + printMsg("! ½3½%s½0½ ½1½DELETED.½0½\n", p); th_free(p); return 0; } @@ -681,33 +827,17 @@ while (bufLen > 0 && (buf[bufLen] == '\n' || buf[bufLen] == '\r' || th_isspace(buf[bufLen]))) buf[bufLen--] = 0; - //fprintf(stderr, "'%s'\n", buf); fflush(stderr); - /* Check command */ if (*buf == 0) { return 1; } else if (!strncmp(buf, "/color ", 7)) { if ((optUserColor = getColor(buf+7)) < 0) { - printMsg("Invalid color value '%s'\n", "Invalid color value '%s'\n", buf+7); + printMsg("Invalid color value '%s'\n", buf+7); return 1; } - printMsg("Setting color to #%06x\n", "Setting color to #%06x\n", optUserColor); + printMsg("Setting color to #%06x\n", optUserColor); sendUserMsg(sock, optUserName2, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor); return 0; - } else if (!strncmp(buf, "/fake ", 6)) { - printMsg("Sending /%s\n", "Sending /%s\n", buf+6); - tmpStr = encodeStr2(tmpBuf); - if (!tmpStr) return -2; - tmpStr2 = encodeStr1(tmpStr); - if (!tmpStr2) { - th_free(tmpStr); - return -3; - } - sendUserMsg(sock ,optUserName2, "%%2F%s", tmpStr2); - - th_free(tmpStr); - th_free(tmpStr2); - return 0; } else if (!strncmp(buf, "/flood ", 7)) { int i; @@ -732,27 +862,23 @@ th_free(tmpStr2); return 0; } else if (!strncmp(buf, "/msg ", 5)) { - if (setTarget) { + if (setTarget != NULL) { snprintf(tmpBuf, sizeof(tmpBuf), "/prv -to %s -msg %s", setTarget, buf+5); buf = tmpBuf; } else { - printMsg("No target set!\n", ANSI_L_RED "No target set!" ANSI_END "\n"); + printMsg("No target set!\n"); return 1; } } else if (!strncmp(buf, "/to ", 4)) { buf += 4; th_free(setTarget); setTarget = th_strdup(buf); - printMsg("Set prv target to '%s'\n", - "Set prv target to '" ANSI_L_GREEN "%s" ANSI_END "'\n", setTarget); + printMsg("Set prv target to '%s'\n", setTarget); return 0; } { /* Send double-encoded */ - //printf("ENC>%s\n", buf); - //fflush(stdout); - tmpStr = encodeStr2(buf); if (!tmpStr) return -2; tmpStr2 = encodeStr1(tmpStr); @@ -774,13 +900,16 @@ int main(int argc, char *argv[]) { - int tmpSocket; + int tmpSocket, curVis, updateCount = 0; struct hostent *tmpHost; - BOOL argsOK, exitProg = FALSE, colorSet = FALSE; + BOOL argsOK, isError = FALSE, + exitProg = FALSE, + colorSet = FALSE, + cursesInit = FALSE; struct timeval tv; fd_set sockfds; - fd_set inputfds; char *tmpStr; + editbuf_t *editBuf = calloc(1, sizeof(editbuf_t)); /* Initialize */ th_init("NNChat", "Newbie Nudes chat client", "0.4", @@ -811,16 +940,6 @@ } } -#ifdef __WIN32 - { - WSADATA wsaData; - if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { - THERR("WinSock API v2.0 not supported.\n"); - return -20; - } - } -#endif - /* Okay ... */ THMSG(1, "Trying to resolve host '%s' ...\n", optServer); tmpHost = gethostbyname(optServer); @@ -855,42 +974,87 @@ THERR("Main connection setup failed!\n"); goto err_exit; } - - + THMSG(1, "Connected, logging in as '%s'.\n", optUserName); optUserName2 = encodeStr1(optUserName); sendUserMsg(tmpSocket, optUserName2, "%%2Flogin%%20%%2Dsite%%20NN%%20%%2Dpassword%%20%s", optPassword); - FD_ZERO(&inputfds); - FD_SET(0, &inputfds); + /* Initialize curses */ + + if (!optDaemon) { + initscr(); + raw(); + keypad(stdscr, TRUE); + noecho(); + timeout(SET_DELAY); + curVis = curs_set(0); + 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_BLACK); + init_pair(4, COLOR_BLUE, COLOR_BLACK); + init_pair(5, COLOR_MAGENTA, COLOR_BLACK); + init_pair(6, COLOR_CYAN, COLOR_BLACK); + init_pair(7, COLOR_WHITE, COLOR_BLACK); + init_pair(8, COLOR_BLACK, COLOR_BLACK); + + init_pair(10, COLOR_BLACK, COLOR_RED); + init_pair(11, COLOR_WHITE, COLOR_RED); + init_pair(12, COLOR_GREEN, COLOR_RED); + init_pair(13, COLOR_YELLOW, COLOR_RED); + init_pair(14, COLOR_BLUE, COLOR_RED); + init_pair(15, COLOR_MAGENTA, COLOR_RED); + init_pair(16, COLOR_CYAN, COLOR_RED); + + } + + mainWin = newwin(LINES - 4, COLS, 0, 0); + statusWin = newwin(1, COLS, LINES - 4, 0); + editWin = newwin(2, COLS, LINES - 3, 0); + + if (mainWin == NULL || statusWin == NULL || editWin == NULL) { + THERR("Could not create ncurses windows!\n"); + goto err_exit; + } + scrollok(mainWin, 1); + + clearBuf(editBuf); + printEditBuf(editBuf); + updateStatus(); + + cursesInit = TRUE; + } + + FD_ZERO(&sockfds); FD_SET(tmpSocket, &sockfds); - while (!exitProg) { - ssize_t gotBuf; + while (!isError && !exitProg) { int result; - char tmpBuf[4096]; fd_set tmpfds; /* Check for incoming data from the server */ tv.tv_sec = 0; - tv.tv_usec = SET_SELECT_USEC; + tv.tv_usec = SET_DELAY_USEC; tmpfds = sockfds; if ((result = select(tmpSocket+1, &tmpfds, NULL, NULL, &tv)) == -1) { printMsg("Error occured in select(sockfds): %s\n", - "Error occured in select(sockfds): %s\n", strerror(errno)); - exitProg = TRUE; + isError = TRUE; } else if (FD_ISSET(tmpSocket, &tmpfds)) { + ssize_t gotBuf; + char tmpBuf[4096]; gotBuf = recv(tmpSocket, tmpBuf, sizeof(tmpBuf), 0); if (gotBuf < 0) { printMsg("Error in recv: %s\n", strerror(errno)); - exitProg = TRUE; + isError = TRUE; } else if (gotBuf == 0) { - printMsg("Server closed connection.\n", "Server closed connection.\n"); - exitProg = TRUE; + printMsg("Server closed connection.\n"); + isError = TRUE; } else { /* Handle protocol data */ tmpBuf[gotBuf] = 0; @@ -898,61 +1062,157 @@ if (result > 0) { /* Couldn't handle the message for some reason */ - printMsg("Could not handle: %s\n", "Could not handle: %s\n", tmpBuf); + printMsg("Could not handle: %s\n", tmpBuf); } else if (result < 0) { /* Fatal error, quit */ printMsg("Fatal error with message: %s\n", tmpBuf); - exitProg = TRUE; + isError = TRUE; } + updateStatus(); } } - /* Check for user input */ + /* Handle user input */ if (!optDaemon) { - tv.tv_sec = 0; - tv.tv_usec = SET_SELECT_USEC; - tmpfds = inputfds; - if ((result = select(1, &tmpfds, NULL, NULL, &tv)) == -1) { - printMsg("Error occured in select(inputfds): %s\n", strerror(errno)); - exitProg = TRUE; - } else if (FD_ISSET(0, &tmpfds)) { - gotBuf = read(0, tmpBuf, sizeof(tmpBuf)); + int c, cnt = 0; + BOOL update = FALSE; + + /* Handle several buffered keypresses at once */ + do { + c = getch(); + switch (c) { + case KEY_ENTER: + case '\n': + case '\r': + /* Call the user input handler */ + if (editBuf->len > 0) { + insertBuf(editBuf, editBuf->pos, 0); + result = handleUserInput(tmpSocket, editBuf->data, editBuf->len); + clearBuf(editBuf); + + if (result < 0) { + printMsg("Fatal error handling user input: %s\n", editBuf->data); + isError = TRUE; + } + + update = TRUE; + } + break; + + case 0x109: /* F1 */ + if (setInsertMode) + setInsertMode = FALSE; + else + setInsertMode = TRUE; + update = TRUE; + break; + + case 0x204: /* ctrl+left */ + editBuf->pos--; + while (editBuf->pos > 0 && !isspace(editBuf->data[editBuf->pos])) + editBuf->pos--; + if (editBuf->pos < 0) + editBuf->pos = 0; + update = TRUE; + break; + + case 0x206: /* ctrl+right */ + editBuf->pos++; + while (editBuf->pos < editBuf->len && !isspace(editBuf->data[editBuf->pos])) + editBuf->pos++; + if (editBuf->pos > editBuf->len) + editBuf->pos = editBuf->len; + update = TRUE; + break; + + case 0x111: /* F9 */ + printMsg("Quitting per user request."); + exitProg = TRUE; + break; - if (gotBuf < 0) { - printMsg("Error in reading stdio.\n", "Error in reading stdio.\n"); - exitProg = TRUE; - } else { - /* Call the user input handler */ - result = handleUserInput(tmpSocket, tmpBuf, gotBuf); - if (result < 0) { - printMsg("Fatal error handling user input: %s\n", - tmpBuf); - exitProg = TRUE; + case 0x10a: /* F2 */ + clearBuf(editBuf); + update = TRUE; + break; + + case KEY_HOME: setBufPos(editBuf, 0); update = TRUE; break; + case KEY_END: setBufPos(editBuf, editBuf->len); update = TRUE; break; + case KEY_LEFT: setBufPos(editBuf, editBuf->pos - 1); update = TRUE; break; + case KEY_RIGHT: setBufPos(editBuf, editBuf->pos + 1); update = TRUE; break; + + case KEY_BACKSPACE: + deleteBuf(editBuf, editBuf->pos - 1); + setBufPos(editBuf, editBuf->pos - 1); + update = TRUE; + break; + + case 0x14a: + /* Delete */ + deleteBuf(editBuf, editBuf->pos); + update = TRUE; + break; + + case 0x0c: + /* ctrl+l */ + redrawwin(mainWin); + redrawwin(statusWin); + redrawwin(editWin); + break; + + case ERR: + /* Ignore */ + break; + + default: + if (isprint(c)) { + if (setInsertMode) + insertBuf(editBuf, editBuf->pos, c); + else + writeBuf(editBuf, editBuf->pos, c); + setBufPos(editBuf, editBuf->pos + 1); + update = TRUE; + } else { + printMsg("Unhandled key: %02x\n", c); } + break; } + } while (c != ERR && !exitProg && ++cnt < 10); + + if (update) { + /* Update edit line */ + printEditBuf(editBuf); + updateStatus(); + } + } /* !optDaemon */ + + if (++updateCount > 10) { + updateStatus(); + updateCount = 0; } - } /* !optDaemon */ if (!colorSet) { colorSet = TRUE; sendUserMsg(tmpSocket, optUserName2, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor); } - - fflush(stdout); - fflush(stderr); } - /* Shotdiwn */ + /* Shutdown */ err_exit: - THMSG(1, "Error exit.\n"); + if (cursesInit) { + if (curVis != ERR) + curs_set(curVis); + endwin(); + THMSG(1, "NCurses deinitialized.\n"); + } + + if (isError) { + THMSG(1, "Error exit.\n"); + } + th_free(optUserName2); closeConnection(tmpSocket); -#ifdef __WIN32 - WSACleanup(); -#endif - THMSG(1, "Connection terminated.\n"); if (optLogFile) { @@ -960,6 +1220,5 @@ fclose(optLogFile); } - return 0; }