Mercurial > hg > nnchat
view nnchat.c @ 133:ffe8bbd429fa
Config file.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Sat, 30 Oct 2010 20:37:09 +0300 |
parents | 10daf4660cae |
children | f367fc14021a |
line wrap: on
line source
/* * NNChat - Custom chat client for NewbieNudes.com chatrooms * Written by Matti 'ccr' Hämäläinen * (C) Copyright 2008-2010 Tecnic Software productions (TNSP) */ #include "libnnchat.h" #include <stdlib.h> #include "th_args.h" #include "th_config.h" #include <string.h> #include <errno.h> #ifdef __WIN32 /* Undefine because both windows.h and curses.h #define it */ #undef MOUSE_MOVED #else #include <sys/wait.h> #endif #include <curses.h> #define SET_MAX_BACKBUF (1024) #define SET_MAX_HISTORY (16) #define SET_DELAY (15) #define SET_DELAY_USEC (SET_DELAY * 250) #define SET_KEEPALIVE (15*60) /* Ping/keepalive period in seconds */ char *ignoreList[] = { "purr_rr", "moisha", "leth", "shaz:)", "rayen", "iam2qute4you_92", NULL }; nn_userhash_t *nnUsers = NULL; /* Options */ int optPort = 8003; int optUserColor = 0x006080; char *optServer = "chat.newbienudes.com", *optUserName = NULL, *optUserName2 = NULL, *optPassword = NULL, *optLogFilename = NULL, *setTarget = NULL, *optSite = "NN"; char optNickSep = ':'; BOOL optDaemon = FALSE; FILE *optLogFile = NULL; WINDOW *mainWin = NULL, *statusWin = NULL, *editWin = NULL; BOOL setPrvMode = FALSE; BOOL ignoreMode = FALSE; char *setConfigFile = "config"; cfgitem_t *cfg = NULL; /* Arguments */ optarg_t optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 1, 'v', "verbose", "Be more verbose", OPT_NONE }, { 2, 'p', "port", "Connect to port", OPT_ARGREQ }, { 3, 's', "server", "Server to connect to", OPT_ARGREQ }, { 4, 'C', "color", "Initial color in RGB hex 000000", OPT_ARGREQ }, { 5, 'l', "logfile", "Log filename", OPT_ARGREQ }, { 6, 'D', "daemon", "A pseudo-daemon mode for logging", OPT_NONE }, { 7, 'S', "site", "Site (default: NN)", OPT_ARGREQ }, }; const int optListN = (sizeof(optList) / sizeof(optList[0])); void argShowHelp() { th_args_help(stdout, optList, optListN, th_prog_name, "[options] <username> <password>"); } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { switch (optN) { case 0: argShowHelp(); exit(0); break; case 1: th_verbosityLevel++; break; case 2: optPort = atoi(optArg); break; case 3: optServer = optArg; break; case 4: if ((optUserColor = th_get_hex_triplet(optArg)) < 0) { THERR("Invalid color argument '%s', should be a RGB hex triplet '000000'.\n", optArg); return FALSE; } THMSG(1, "Using color #%06x\n", optUserColor); break; case 5: optLogFilename = optArg; break; case 7: optSite = optArg; break; case 6: optDaemon = TRUE; THMSG(1, "Running in pseudo-daemon mode.\n"); break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } BOOL argHandleFile(char *currArg) { if (!optUserName) optUserName = currArg; else if (!optPassword) optPassword = currArg; else { THERR("Username '%s' already specified on commandline!\n", optUserName); return FALSE; } return TRUE; } void updateStatus(BOOL insertMode) { char tmpStr[128] = ""; time_t timeStamp; struct tm *tmpTime;; if (statusWin == NULL) return; timeStamp = time(NULL); if ((tmpTime = localtime(&timeStamp)) != NULL) { strftime(tmpStr, sizeof(tmpStr), "%H:%M:%S", tmpTime); } wbkgdset(statusWin, COLOR_PAIR(10)); werase(statusWin); wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); mvwaddstr(statusWin, 0, 1, tmpStr); wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); waddstr(statusWin, " | "); wattrset(statusWin, A_BOLD | COLOR_PAIR(16)); waddstr(statusWin, optUserName); wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); waddstr(statusWin, " | "); wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); waddstr(statusWin, insertMode ? "INS" : "DEL"); wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); waddstr(statusWin, " | Prv: "); wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); waddstr(statusWin, setTarget != NULL ? setTarget : "-"); wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); waddstr(statusWin, " | P/C: "); wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); snprintf(tmpStr, sizeof(tmpStr), "%d / #%06x", optPort, optUserColor); waddstr(statusWin, tmpStr); wrefresh(statusWin); } void printEditBuf(char *str, nn_editbuf_t *buf) { if (statusWin == NULL || buf == NULL) return; buf->data[buf->len] = 0; werase(editWin); wattrset(editWin, A_BOLD); mvwaddstr(editWin, 0, 0, str); waddstr(editWin, "> "); wattrset(editWin, A_NORMAL); if (buf->pos < buf->len) { waddnstr(editWin, 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 { waddnstr(editWin, buf->data, buf->len); wattrset(editWin, A_REVERSE); waddch(editWin, ' '); wattrset(editWin, A_NORMAL); } wrefresh(editWin); } 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, ((unsigned char) *s) | col); s++; } else { while (*s >= '0' && *s <= '9') { 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 { if (*s != '\r') waddch(win, ((unsigned char) *s) | col); s++; } } return 0; } int printFile(FILE *outFile, const char *fmt) { const char *s = fmt; while (*s) { if (*s == '½') { s++; if (*s == '½') { fputc((unsigned char) *s, outFile); s++; } else { while (*s && isdigit((int) *s)) s++; if (*s != '½') return -1; s++; } } else { fputc((unsigned char) *s, outFile); s++; } } return 0; } void printMsgV(BOOL addStamp, BOOL logOnly, const char *fmt, va_list ap) { char tmpStr[128] = "", buf[8192]; time_t timeStamp; struct tm *tmpTime;; timeStamp = time(NULL); if ((tmpTime = localtime(&timeStamp)) != NULL) { strftime(tmpStr, sizeof(tmpStr), "½17½[½11½%H:%M:%S½17½]½0½ ", tmpTime); } vsnprintf(buf, sizeof(buf), fmt, ap); if (optLogFile) { if (addStamp) printFile(optLogFile, tmpStr); printFile(optLogFile, buf); fflush(optLogFile); } if (!optDaemon && !logOnly) { if (addStamp) printWin(mainWin, tmpStr); printWin(mainWin, buf); wrefresh(mainWin); } } void printMsg(const char *fmt, ...) { va_list ap; va_start(ap, fmt); printMsgV(TRUE, FALSE, fmt, ap); va_end(ap); } void printMsgC(const char *fmt, ...) { va_list ap; va_start(ap, fmt); printMsgV(FALSE, FALSE, fmt, ap); va_end(ap); } void printMsgQ(BOOL logOnly, const char *fmt, ...) { va_list ap; va_start(ap, fmt); printMsgV(TRUE, logOnly, fmt, ap); va_end(ap); } void errorMsg(char *fmt, ...) { va_list ap1, ap2; va_start(ap1, fmt); va_copy(ap2, ap1); printMsgV(TRUE, FALSE, fmt, ap1); va_end(ap1); THERR_V(fmt, ap2); va_end(ap2); } int handleUser(int sock, const char *str) { const char *msg = "</USER><MESSAGE>", *p = str; char *q, *s, *t, *h, *p2; (void) sock; s = strstr(str, msg); if (!s) return 1; *s = 0; s += strlen(msg); q = strstr(s, "</MESSAGE>"); if (!q) return 3; *q = 0; s = nn_decode_str1(s); if (!s) return -1; p2 = nn_decode_str1(p); if (!p2) { th_free(s); return -2; } if (*s == '/') { /* Ignore room join/leave messages */ if (strstr(s, "left the room") || strstr(s, "joined the room from")) goto error; t = nn_strip_tags(s + 1); if (!strncmp(t, "BPRV", 4)) { h = nn_decode_str2(t + 1); printMsg("½11½%s½0½\n", h); } else { h = nn_decode_str2(t); printMsg("½9½* %s½0½\n", h); } th_free(h); th_free(t); } else { BOOL isMine = strcmp(p2, optUserName) == 0; BOOL logOnly = FALSE; t = nn_strip_tags(s); h = nn_decode_str2(t); if (ignoreMode) { char **user = ignoreList; while (*user != NULL) { if (strcasecmp(p2, *user) == 0) { logOnly = TRUE; break; } user++; } } printMsgQ(logOnly, "½5½<½%d½%s½5½>½0½ %s\n", isMine ? 14 : 15, p2, h); th_free(h); th_free(t); } error: th_free(s); th_free(p2); return 0; } int handleLogin(int sock, const char *str) { char tmpStr[256] = ""; time_t timeStamp; struct tm *tmpTime;; timeStamp = time(NULL); if ((tmpTime = localtime(&timeStamp)) != NULL) { strftime(tmpStr, sizeof(tmpStr), "%c", tmpTime); } if (!strncmp(str, "FAILURE", 7)) { printMsg("½1½Login failure½0½ - ½3½%s½0½\n", tmpStr); return -2; } else if (!strncmp(str, "SUCCESS", 7)) { printMsg("½2½Login success½0½ - ½3½%s½0½\n", tmpStr); nn_send_msg(sock, optUserName2, "%%2FRequestUserList"); return 0; } else return 1; } int handleAddUser(int sock, const char *str) { char *p, *s = strstr(str, "</ADD_USER>"); (void) sock; if (!s) return 1; *s = 0; p = nn_dbldecode_str(str); if (!p) return -1; nn_userhash_insert(nnUsers, p); printMsg("! ½3½%s½0½ ½2½ADDED.½0½\n", p); th_free(p); return 0; } int handleDeleteUser(int sock, const char *str) { char *p, *s = strstr(str, "</DELETE_USER>"); (void) sock; if (!s) return 1; *s = 0; p = nn_dbldecode_str(str); if (!p) return -1; nn_userhash_delete(nnUsers, p); printMsg("! ½3½%s½0½ ½1½DELETED.½0½\n", p); th_free(p); return 0; } int handleFoo(int sock, const char *str) { (void) sock; (void) str; return 0; } int handleBoot(int sock, const char *str) { (void) sock; (void) str; errorMsg("Booted by server.\n"); return -1; } typedef struct { char *cmd; ssize_t len; int (*handler)(int, const char *); } protocmd_t; static protocmd_t protoCmds[] = { { "<USER>", -1, handleUser }, { "<LOGIN_", -1, handleLogin }, { "<DELETE_USER>", -1, handleDeleteUser }, { "<ADD_USER>", -1, handleAddUser }, { "<NUMCLIENTS>", -1, handleFoo }, { "<BOOT />", -1, handleBoot }, }; static const int nprotoCmds = sizeof(protoCmds) / sizeof(protoCmds[0]); int handleProtocol(const int sock, const char *buf, const ssize_t bufLen) { static BOOL protoCmdsInit = FALSE; int i; if (!protoCmdsInit) { for (i = 0; i < nprotoCmds; i++) protoCmds[i].len = strlen(protoCmds[i].cmd); protoCmdsInit = TRUE; } for (i = 0; i < nprotoCmds; i++) { ssize_t cmdLen = protoCmds[i].len; if (cmdLen < bufLen && !strncmp(buf, protoCmds[i].cmd, cmdLen)) return protoCmds[i].handler(sock, buf + cmdLen); } return 1; } char * trimLeft(char *buf) { while (*buf != 0 && isspace(*buf)) buf++; return buf; } int handleUserInput(const int sock, char *buf, size_t bufLen) { char *tmpStr, tmpBuf[4096]; BOOL result; /* Trim right */ bufLen--; buf[bufLen--] = 0; while (bufLen > 0 && (buf[bufLen] == '\n' || buf[bufLen] == '\r' || th_isspace(buf[bufLen]))) buf[bufLen--] = 0; /* Check for special user commands */ if (*buf == 0) { return 1; } else if (!strncasecmp(buf, "/color ", 7)) { /* Change color */ int tmpInt; if ((tmpInt = th_get_hex_triplet(trimLeft(buf + 7))) < 0) { printMsg("Invalid color value '%s'\n", buf+7); return 1; } optUserColor = tmpInt; printMsg("Setting color to #%06x\n", optUserColor); nn_send_msg(sock, optUserName2, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor); return 0; } else if (!strncasecmp(buf, "/save", 5)) { /* Save configuration */ FILE *f = fopen(setConfigFile, "w"); if (f == NULL) { printMsg("Could not save configuration to file '%s'!\n", setConfigFile); return 0; } printMsg("Configuration saved in file '%s', res=%d\n", setConfigFile, th_cfg_write(f, setConfigFile, cfg)); fclose(f); return 0; } else if (!strncasecmp(buf, "/w ", 3)) { /* Open given username's profile via firefox in a new tab */ char *name = trimLeft(buf + 3), *browser = getenv("BROWSER"); if (browser == NULL) browser = "firefox"; printMsg("Opening profile for: '%s'\n", name); tmpStr = nn_encode_str1(name); snprintf(tmpBuf, sizeof(tmpBuf), "openurl(http://www.newbienudes.com/profile/%s/,new-tab)", tmpStr); th_free(tmpStr); #ifdef __WIN32 #else { int status; pid_t pid; if ((pid = fork()) < 0) { printMsg("Could not create sub-process!\n"); } else if (pid == 0) { execlp(browser, browser, "-remote", tmpBuf, NULL); _exit(errno); } wait(&status); } #endif return 0; } else if (!strncasecmp(buf, "/to ", 4)) { /* Set private messaging target */ th_free(setTarget); setTarget = th_strdup(trimLeft(buf + 4)); printMsg("Set prv target to '%s'\n", setTarget); return 0; } else if (!strncasecmp(buf, "/who", 4)) { /* Alias /who to /listallusers */ snprintf(tmpBuf, sizeof(tmpBuf), "/listallusers"); buf = tmpBuf; } else if (setPrvMode) { /* Private chat mode, send as PRV */ if (setTarget != NULL) { snprintf(tmpBuf, sizeof(tmpBuf), "/prv -to %s -msg %s", setTarget, buf); buf = tmpBuf; } else { printMsg("No target set, exiting prv mode.\n"); setPrvMode = FALSE; return 1; } } /* Send double-encoded */ tmpStr = nn_dblencode_str(buf); if (tmpStr == 0) return -2; result = nn_send_msg(sock, optUserName2, "%s", tmpStr); th_free(tmpStr); return result ? 0 : -1; } BOOL initializeWindows(void) { int w, h; getmaxyx(stdscr, h, w); if (mainWin) delwin(mainWin); if (statusWin) delwin(statusWin); if (editWin) delwin(editWin); mainWin = newwin(h - 4, w, 0, 0); statusWin = newwin(1, w, h - 4, 0); editWin = newwin(3, w, h - 3, 0); if (mainWin == NULL || statusWin == NULL || editWin == NULL) { THERR("Could not create ncurses windows!\n"); return FALSE; } scrollok(mainWin, 1); return TRUE; } /* Different tab completion scenarios: Line start "foo<tab>" -> * store "foo" as pattern * find match: "Foobar" * replace "foo" with match "Foobar: <tab>" -> * ": " -> cycle to next match * "Foorulez" * replace "Foobar: " with "Foorulez: " Same when completing in middle of line, but " " instead of ": ". */ BOOL performTabCompletion(nn_editbuf_t *buf) { static char *previous = NULL; static char *pattern = NULL; int mode = 0; BOOL again = FALSE; char *str = buf->data; ssize_t endPos = 0, startPos = buf->pos; if (startPos > 1 && str[startPos - 1] == ' ') { startPos -= 2; if (str[startPos] == optNickSep) startPos--; if (startPos <= 0 || str[startPos] == ' ') return FALSE; endPos = startPos; while (startPos > 0 && str[startPos - 1] != ' ') startPos--; mode = 1; } else if (str[startPos - 1] != ' ' && (startPos == buf->len || str[startPos] == ' ')) { char *npattern; endPos = startPos; while (startPos > 0 && str[startPos - 1] != ' ') startPos--; while (endPos < buf->len && str[endPos] != ' ') endPos++; while (endPos > 0 && str[endPos] == ' ') endPos--; if (str[endPos] == optNickSep) return FALSE; /* Get pattern, check if it matches previous pattern and set 'again' flag */ npattern = nn_editbuf_get_string(buf, startPos, endPos); if (pattern && npattern && strncasecmp(npattern, pattern, strlen(pattern)) == 0) again = TRUE; th_free(pattern); pattern = npattern; if (!again) { th_free(previous); previous = NULL; } mode = 2; } // printMsg("%d, %d <-> %d : '%s' (%d, %d)\n", startPos, endPos, buf->len, pattern, mode, again); if (pattern) { nn_user_t *user = nn_user_match(nnUsers, pattern, previous, again); if (user) { int i; char *c = user->name; // printMsg("match '%s' / prev '%s'\n", user->name, previous); for (i = startPos; i <= endPos; i++) nn_editbuf_delete(buf, startPos); for (i = startPos; *c; i++, c++) nn_editbuf_insert(buf, i, *c); if (previous == NULL || again) { if (startPos == 0) { nn_editbuf_insert(buf, i++, optNickSep); startPos++; } nn_editbuf_insert(buf, i++, ' '); } nn_editbuf_setpos(buf, startPos + 2 + strlen(user->name)); th_free(previous); previous = th_strdup(user->name); return TRUE; } } return FALSE; } int main(int argc, char *argv[]) { int tmpSocket = -1, curVis = ERR, updateCount = 0; struct hostent *tmpHost; BOOL argsOK, isError = FALSE, exitProg = FALSE, colorSet = FALSE, cursesInit = FALSE, networkInit = FALSE, insertMode = TRUE; time_t prevTime; struct timeval socktv; fd_set sockfds; char *tmpStr; nn_editbuf_t *editBuf = nn_editbuf_new(SET_BUFSIZE); nn_editbuf_t *histBuf[SET_MAX_HISTORY+2]; int histPos = 0, histMax = 0; memset(histBuf, 0, sizeof(histBuf)); /* Initialize */ th_init("NNChat", "Newbie Nudes chat client", "0.8.0", "Written and designed by Anonymous Finnish Guy (C) 2008-2010", "This software is freeware, use and distribute as you wish."); th_verbosityLevel = 0; /* Read config */ { cfgitem_t *tmpcfg = NULL; FILE *cfgfile; th_cfg_add_comment(&tmpcfg, "General settings"); th_cfg_add_string(&tmpcfg, "username", &optUserName, NULL); th_cfg_add_string(&tmpcfg, "password", &optPassword, NULL); th_cfg_add_hexvalue(&tmpcfg, "color", &optUserColor, optUserColor); th_cfg_add_section(&cfg, "general", tmpcfg); tmpcfg = NULL; th_cfg_add_comment(&tmpcfg, "Chat server hostname or IP address"); th_cfg_add_string(&tmpcfg, "host", &optServer, optServer); th_cfg_add_comment(&tmpcfg, "Default port to connect to (8002 = public room, 8003 = passion pit, 8005 = members only)"); th_cfg_add_int(&tmpcfg, "port", &optPort, optPort); th_cfg_add_section(&cfg, "server", tmpcfg); th_cfg_add_comment(&cfg, "People to be ignored in ignore mode"); th_cfg_add_string(&cfg, "ignores", ignoreList, NULL); if ((cfgfile = fopen(setConfigFile, "r")) != NULL) th_cfg_read(cfgfile, setConfigFile, cfg); } /* Parse arguments */ argsOK = th_args_process(argc, argv, optList, optListN, argHandleOpt, argHandleFile, FALSE); /* Check the mode and arguments */ if (optUserName == NULL || optPassword == NULL) { THERR("User/pass not specified, get some --help\n"); return -1; } if (!argsOK) return -2; /* Allocate userhash */ if ((nnUsers = nn_userhash_new()) == NULL) { THERR("Could not allocate userhash. Fatal error.\n"); return -105; } /* Open logfile */ if (optLogFilename) { THMSG(1, "Opening logfile '%s'\n", optLogFilename); if ((optLogFile = fopen(optLogFilename, "a")) == NULL) { THERR("Could not open logfile for appending!\n"); return -9; } } if (!nn_network_init()) { THERR("Could not initialize network subsystem.\n"); goto err_exit; } else networkInit = TRUE; /* Okay ... */ THMSG(1, "Trying to resolve host '%s' ...\n", optServer); tmpHost = gethostbyname(optServer); if (tmpHost == NULL) { THERR("Could not resolve hostname: %s.\n", hstrerror(h_errno)); goto err_exit; } THMSG(2, "True hostname: %s\n", tmpHost->h_name); #if 1 /* To emulate the official client, we first make a request for * policy file, even though we don't use it for anything... */ if ((tmpSocket = nn_open_connection((struct in_addr *) tmpHost->h_addr, 843)) < 0) { THERR("Policy file request connection setup failed!\n"); goto err_exit; } tmpStr = "<policy-file-request/>"; if (nn_send_to_socket(tmpSocket, tmpStr, strlen(tmpStr) + 1) == FALSE) { THERR("Failed to send policy file request.\n"); goto err_exit; } else { ssize_t gotBuf; char tmpBuf[SET_BUFSIZE]; gotBuf = recv(tmpSocket, tmpBuf, sizeof(tmpBuf), 0); tmpBuf[gotBuf-1] = 0; THMSG(2, "Probe got: %s\n", tmpBuf); nn_close_connection(tmpSocket); } #endif /* Okay, now do the proper connection ... */ if ((tmpSocket = nn_open_connection((struct in_addr *) tmpHost->h_addr, optPort)) < 0) { THERR("Main connection setup failed!\n"); goto err_exit; } THMSG(1, "Connected, logging in as '%s', site '%s'.\n", optUserName, optSite); optUserName2 = nn_dblencode_str(optUserName); tmpStr = nn_dblencode_str(optSite); nn_send_msg(tmpSocket, optUserName2, "%%2Flogin%%20%%2Dsite%%20%s%%20%%2Dpassword%%20%s", tmpStr, optPassword); th_free(tmpStr); /* Initialize NCurses */ if (!optDaemon) { if (LINES < 0 || LINES > 1000) LINES = 24; if (COLS < 0 || COLS > 1000) COLS = 80; initscr(); raw(); keypad(stdscr, TRUE); noecho(); meta(stdscr, TRUE); 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); } cursesInit = TRUE; if (!initializeWindows()) goto err_exit; nn_editbuf_clear(editBuf); printEditBuf("", editBuf); updateStatus(insertMode); } /* Enter mainloop */ prevTime = time(NULL); FD_ZERO(&sockfds); FD_SET(tmpSocket, &sockfds); while (!isError && !exitProg) { int result; fd_set tmpfds; /* Check for incoming data from the server */ socktv.tv_sec = 0; socktv.tv_usec = SET_DELAY_USEC; tmpfds = sockfds; if ((result = select(tmpSocket+1, &tmpfds, NULL, NULL, &socktv)) == -1) { int res = nn_get_socket_errno(); if (res != EINTR) { errorMsg("Error occured in select(sockfds): %d, %s\n", res, nn_get_socket_errstr(res)); isError = TRUE; } } else if (FD_ISSET(tmpSocket, &tmpfds)) { ssize_t gotBuf; char tmpBuf[8192]; char *bufPtr = tmpBuf; gotBuf = recv(tmpSocket, tmpBuf, sizeof(tmpBuf), 0); if (gotBuf < 0) { int res = nn_get_socket_errno(); errorMsg("Error in recv: %d, %s\n", res, nn_get_socket_errstr(res)); isError = TRUE; } else if (gotBuf == 0) { errorMsg("Server closed connection.\n"); isError = TRUE; } else { /* Handle protocol data */ tmpBuf[gotBuf] = 0; do { size_t bufLen = strlen(bufPtr) + 1; result = handleProtocol(tmpSocket, bufPtr, bufLen); if (result > 0) { /* Couldn't handle the message for some reason */ printMsg("Could not handle: %s\n", tmpBuf); } else if (result < 0) { /* Fatal error, quit */ errorMsg("Fatal error with message: %s\n", tmpBuf); isError = TRUE; } gotBuf -= bufLen; bufPtr += bufLen; } while (gotBuf > 0 && !isError); updateStatus(insertMode); } } /* Handle user input */ if (!optDaemon) { int c, cnt = 0; BOOL update = FALSE; /* Handle several buffered keypresses at once */ do { c = wgetch(stdscr); if (c == 0x1b) { c = wgetch(stdscr); if (c == 'O') { c = wgetch(stdscr); switch (c) { case 'd': c = 0x204; break; case 'c': c = 0x206; break; default: printMsg("Unhandled ESC-O key sequence 0x%02x\n", c); break; } } else { printMsg("Unhandled ESC key sequence 0x%02x\n", c); continue; } } switch (c) { #ifdef KEY_RESIZE case KEY_RESIZE: #ifdef PDCURSES resize_term(0, 0); erase(); timeout(SET_DELAY); #endif if (!initializeWindows()) { errorMsg("Error resizing curses windows\n"); isError = TRUE; } update = TRUE; break; #endif case KEY_ENTER: case '\n': case '\r': /* Call the user input handler */ if (editBuf->len > 0) { if (histMax > 0) { nn_editbuf_free(histBuf[SET_MAX_HISTORY+1]); histBuf[SET_MAX_HISTORY+1] = NULL; memmove(&histBuf[2], &histBuf[1], histMax * sizeof(histBuf[0])); } histPos = 0; histBuf[1] = nn_editbuf_copy(editBuf); if (histMax < SET_MAX_HISTORY) histMax++; nn_editbuf_insert(editBuf, editBuf->len, 0); result = handleUserInput(tmpSocket, editBuf->data, editBuf->len); nn_editbuf_clear(editBuf); if (result < 0) { errorMsg("Fatal error handling user input: %s\n", editBuf->data); isError = TRUE; } update = TRUE; } break; case KEY_UP: /* Backwards in input history */ if (histPos == 0) { nn_editbuf_free(histBuf[0]); histBuf[0] = nn_editbuf_copy(editBuf); } if (histPos < histMax) { histPos++; nn_editbuf_free(editBuf); editBuf = nn_editbuf_copy(histBuf[histPos]); update = TRUE; } break; case KEY_DOWN: /* Forwards in input history */ if (histPos > 0) { histPos--; nn_editbuf_free(editBuf); editBuf = nn_editbuf_copy(histBuf[histPos]); update = TRUE; } break; case 0x204: /* ctrl+left arrow = Skip words left */ while (editBuf->pos > 0 && isspace((int) editBuf->data[editBuf->pos - 1])) editBuf->pos--; while (editBuf->pos > 0 && !isspace((int) editBuf->data[editBuf->pos - 1])) editBuf->pos--; update = TRUE; break; case 0x206: /* ctrl+right arrow = Skip words right */ while (editBuf->pos < editBuf->len && isspace((int) editBuf->data[editBuf->pos])) editBuf->pos++; while (editBuf->pos < editBuf->len && !isspace((int) editBuf->data[editBuf->pos])) editBuf->pos++; if (editBuf->pos > editBuf->len) editBuf->pos = editBuf->len; update = TRUE; break; case KEY_HOME: nn_editbuf_setpos(editBuf, 0); update = TRUE; break; case KEY_END: nn_editbuf_setpos(editBuf, editBuf->len); update = TRUE; break; case KEY_LEFT: nn_editbuf_setpos(editBuf, editBuf->pos - 1); update = TRUE; break; case KEY_RIGHT: nn_editbuf_setpos(editBuf, editBuf->pos + 1); update = TRUE; break; case KEY_BACKSPACE: case 0x08: nn_editbuf_delete(editBuf, editBuf->pos - 1); nn_editbuf_setpos(editBuf, editBuf->pos - 1); update = TRUE; break; case KEY_DC: /* Delete character */ nn_editbuf_delete(editBuf, editBuf->pos); update = TRUE; break; case 0x109: /* F1 = Toggle insert / overwrite mode */ insertMode = !insertMode; update = TRUE; break; case 0x10a: /* F2 = Clear editbuffer */ nn_editbuf_clear(editBuf); update = TRUE; break; case 0x10c: /* F3 = Ignore mode */ ignoreMode = !ignoreMode; printMsg("Ignore mode = %s\n", ignoreMode ? "ON" : "OFF"); break; case 0x111: /* F9 = Quit */ printMsg("Quitting per user request.\n"); exitProg = TRUE; break; case 0x110: /* F8 = switch between PRV */ if (setPrvMode) setPrvMode = FALSE; else { if (setTarget != NULL) setPrvMode = TRUE; } update = TRUE; break; case 0x09: /* Tab = complete username */ performTabCompletion(editBuf); update = TRUE; break; case 0x0c: /* Ctrl + L */ redrawwin(mainWin); redrawwin(statusWin); redrawwin(editWin); break; case KEY_NPAGE: case KEY_PPAGE: break; case ERR: /* Ignore */ break; default: if (isprint(c) || c == 0xe4 || c == 0xf6 || c == 0xc4 || c == 0xd6) { if (insertMode) nn_editbuf_insert(editBuf, editBuf->pos, c); else nn_editbuf_write(editBuf, editBuf->pos, c); nn_editbuf_setpos(editBuf, editBuf->pos + 1); update = TRUE; } else { printMsg("Unhandled key: 0x%02x\n", c); } break; } } while (c != ERR && !exitProg && ++cnt < 10); if (update) { /* Update edit line */ printEditBuf(setPrvMode ? setTarget : "", editBuf); updateStatus(insertMode); } } /* !optDaemon */ if (++updateCount > 10) { time_t tmpTime = time(NULL); if (tmpTime - prevTime > SET_KEEPALIVE) { nn_send_msg(tmpSocket, optUserName2, "/listallusers"); prevTime = tmpTime; } if (!colorSet) { colorSet = TRUE; printMsg("%s v%s - %s\n", th_prog_name, th_prog_version, th_prog_fullname); printMsg("%s\n", th_prog_author); printMsg("%s\n", th_prog_license); nn_send_msg(tmpSocket, optUserName2, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor); } updateStatus(insertMode); updateCount = 0; } } /* Shutdown */ err_exit: nn_userhash_free(nnUsers); nn_editbuf_free(editBuf); for (histPos = 0; histPos <= SET_MAX_HISTORY; histPos++) nn_editbuf_free(histBuf[histPos]); if (cursesInit) { if (curVis != ERR) curs_set(curVis); endwin(); THMSG(1, "NCurses deinitialized.\n"); } if (isError) { THMSG(1, "Error exit.\n"); } th_free(optUserName2); nn_close_connection(tmpSocket); if (networkInit) nn_network_close(); THMSG(1, "Connection terminated.\n"); if (optLogFile) { THMSG(1, "Closing logfile.\n"); fclose(optLogFile); } return 0; }