Mercurial > hg > nnchat
diff main.c @ 413:14b685cdbd2c
Rename files.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Thu, 24 May 2012 06:41:07 +0300 |
parents | nnchat.c@3e64acb433e8 |
children | 8263cb88556a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.c Thu May 24 06:41:07 2012 +0300 @@ -0,0 +1,2158 @@ +/* + * NNChat - Custom chat client for NewbieNudes.com chatrooms + * Written by Matti 'ccr' Hämäläinen + * (C) Copyright 2008-2011 Tecnic Software productions (TNSP) + */ +#include "util.h" +#include "network.h" +#include "th_args.h" +#include "th_config.h" +#ifdef __WIN32 +/* Undefine because both windows.h and curses.h #define it */ +#undef MOUSE_MOVED +#include <shlwapi.h> +#else +#include <sys/wait.h> +#endif +#ifdef HAVE_NCURSES_H +#include <ncurses.h> +#else +#include <curses.h> +#endif + +#ifdef __WIN32 +#define SET_CONFIG_FILE "nnchat.txt" +#define SET_DIR_SEPARATOR "\\" +#define SET_DELAY (0) +#else +#define SET_CONFIG_FILE ".nnchat" +#define SET_DIR_SEPARATOR "/" +#define SET_DELAY (5) +#endif + +#define SET_NICK_SEPARATOR ':' + +#define SET_MAX_HISTORY (16) /* Command history length */ +#define SET_KEEPALIVE (15*60) /* Ping/keepalive period in seconds */ +#define SET_MAX_WINDOWS (32) + + +/* Options + */ +int optPort = 8005, + optProxyPort = 1080, + optProxyType = NN_PROXY_NONE; +int optUserColor = 0x000000; +char *optServer = "chat.newbienudes.com", + *optProxyServer = NULL, + *optUserName = NULL, + *optUserNameCmd = NULL, + *optUserNameEnc = NULL, + *optPassword = NULL, + *optPasswordCmd = NULL, + *optLogFilename = NULL, + *optSite = "NN", + *optNickSepStr = NULL; +char optNickSep; +BOOL optDaemon = FALSE; +FILE *optLogFile = NULL; +BOOL setIgnoreMode = FALSE; +BOOL optDebug = FALSE; +BOOL optLogEnable = FALSE; + +nn_window_t *chatWindows[SET_MAX_WINDOWS], + *currWin = NULL; +WINDOW *mainWin = NULL, + *statusWin = NULL, + *editWin = NULL; + +qlist_t *setIgnoreList = NULL, + *setIdleMessages = NULL; +nn_userhash_t *nnUsers = NULL; +char *setConfigFile = NULL, + *setBrowser = NULL; +cfgitem_t *cfg = NULL; + + +/* Logging mode flags + */ +enum +{ + LOG_FILE = 1, + LOG_WINDOW = 2, + LOG_STAMP = 4 +}; + + +/* 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, 'f', "force-site", "Force site (default: NN)", OPT_ARGREQ }, + { 8, 'd', "debug", "Enable various debug features", OPT_NONE }, + + {10, '4', "socks4", "SOCKS4 proxy server", OPT_ARGREQ }, + {11, 'A', "socks4a", "SOCKS4A proxy server", OPT_ARGREQ }, + {12, 'P', "proxy-port", "Proxy port (default: 1080)", OPT_ARGREQ }, +}; + +const int optListN = (sizeof(optList) / sizeof(optList[0])); + + +void argShowHelp(void) +{ + th_print_banner(stdout, th_prog_name, + "[options] <username> <password>"); + + th_args_help(stdout, optList, optListN); +} + + +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; + optLogEnable = TRUE; + break; + + case 7: + optSite = optArg; + break; + + case 6: + optDaemon = TRUE; + THMSG(1, "Running in pseudo-daemon mode.\n"); + break; + + case 8: + optDebug = TRUE; + THMSG(1, "Debug mode enabled.\n"); + break; + + + case 10: + optProxyServer = optArg; + optProxyType = NN_PROXY_SOCKS4; + break; + + case 11: + optProxyServer = optArg; + optProxyType = NN_PROXY_SOCKS4A; + break; + + case 12: + optPort = atoi(optArg); + break; + + + default: + THERR("Unknown option '%s'.\n", currArg); + return FALSE; + } + + return TRUE; +} + + +BOOL argHandleFile(char *currArg) +{ + if (!optUserNameCmd) + optUserNameCmd = currArg; + else if (!optPasswordCmd) + optPasswordCmd = currArg; + else + { + THERR("Username '%s' already specified on commandline!\n", optUserNameCmd); + return FALSE; + } + + return TRUE; +} + +BOOL str_get_timestamp(char *str, size_t len, const char *fmt) +{ + time_t stamp = time(NULL); + struct tm *stamp_tm; + if ((stamp_tm = localtime(&stamp)) != NULL) + { + strftime(str, len, fmt, stamp_tm); + return TRUE; + } + else + { + str[0] = 0; + return FALSE; + } +} + + +char * str_trim_left(char *buf) +{ + while (*buf != 0 && th_isspace(*buf)) buf++; + return buf; +} + +int compareUsername(const void *s1, const void *s2) +{ + return th_strcasecmp((char *) s1, (char *) s2); +} + +nn_window_t *findWindow(const char *id) +{ + int i; + + for (i = 0; i < SET_MAX_WINDOWS; i++) + if (chatWindows[i] != NULL && + chatWindows[i]->id != NULL && + th_strcasecmp(id, chatWindows[i]->id) == 0) + return chatWindows[i]; + + return NULL; +} + + +BOOL openWindow(const char *name, BOOL curwin) +{ + int i; + nn_window_t *res; + if (name == NULL) + return FALSE; + + if ((res = nn_window_new(name)) == NULL) + return FALSE; + + for (i = 1; i < SET_MAX_WINDOWS; i++) + if (chatWindows[i] == NULL) + { + res->num = i; + chatWindows[i] = res; + if (curwin) + currWin = res; + return TRUE; + } + + return FALSE; +} + + +void closeWindow(nn_window_t *win) +{ + int i; + if (win == NULL) return; + + for (i = 1; i < SET_MAX_WINDOWS; i++) + if (chatWindows[i] == win) + { + chatWindows[i] = NULL; + nn_window_free(win); + return; + } +} + + +void updateStatus(void) +{ + char tmpStr[128]; + int i; + + if (statusWin == NULL) return; + + str_get_timestamp(tmpStr, sizeof(tmpStr), "%H:%M:%S"); + + 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)); + + wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); + waddstr(statusWin, " | "); + wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); + snprintf(tmpStr, sizeof(tmpStr), "#%06x", optUserColor); + waddstr(statusWin, tmpStr); + + wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); + waddstr(statusWin, " | WIN: "); + snprintf(tmpStr, sizeof(tmpStr), "%d: %s / %d", + currWin->num + 1, + currWin->id != NULL ? currWin->id : "MAIN", + currWin->pos); + waddstr(statusWin, tmpStr); + + wattrset(statusWin, A_BOLD | COLOR_PAIR(13)); + waddstr(statusWin, " | "); + wattrset(statusWin, A_BOLD | COLOR_PAIR(11)); + + for (i = 0; i < SET_MAX_WINDOWS; i++) + if (chatWindows[i] != NULL && chatWindows[i]->dirty) + { + snprintf(tmpStr, sizeof(tmpStr), "%d ", i + 1); + waddstr(statusWin, tmpStr); + } + + wrefresh(statusWin); +} + + +void printEditBuf(nn_editbuf_t *buf) +{ + char *tmp; + if (editWin == NULL || buf == NULL) return; + + buf->data[buf->len] = 0; + tmp = nn_username_decode(th_strdup(buf->data)); + + werase(editWin); + + wattrset(editWin, A_NORMAL); + + if (buf->pos < buf->len) + { + waddnstr(editWin, tmp, buf->pos); + wattrset(editWin, A_REVERSE); + waddch(editWin, tmp[buf->pos]); + wattrset(editWin, A_NORMAL); + waddnstr(editWin, tmp + buf->pos + 1, buf->len - buf->pos - 1); + } + else + { + waddnstr(editWin, tmp, buf->len); + wattrset(editWin, A_REVERSE); + waddch(editWin, ' '); + wattrset(editWin, A_NORMAL); + } + wrefresh(editWin); + th_free(tmp); +} + + +int printWin(WINDOW *win, const char *fmt) +{ + const char *s = fmt; + int col = 0; + + while (*s) + { + if (*s == '½') + { + s++; + if (*s == '½') + { + waddch(win, ((unsigned char) *s) | col); + s++; + } + else + { + memcpy(&col, s, sizeof(int)); + s += sizeof(int); + } + } + else + { + waddch(win, ((unsigned char) *s) | col); + s++; + } + } + return 0; +} + + +#define QPUTCH(ch) th_vputch(&(win->buf), &(win->bufsize), &(win->len), ch) + +int nn_window_print(nn_window_t *win, const char *fmt) +{ + const char *s = fmt; + int col = 0; + while (*s) + { + if (*s == '½') + { + s++; + if (*s == '½') + { + QPUTCH(*s); + QPUTCH(*s); + win->chlen++; + } + else + { + int val = 0; + while (*s >= '0' && *s <= '9') + { + val *= 10; + val += (*s - '0'); + s++; + } + if (*s != '½') return -1; + + if (val < 9) + col = A_DIM | COLOR_PAIR(val); + else if (val < 30) + col = A_BOLD | COLOR_PAIR(val - 9); + + QPUTCH('½'); + + if (!th_growbuf(&(win->buf), &(win->bufsize), &(win->len), sizeof(int))) + return -2; + + memcpy(win->buf + win->len, &col, sizeof(int)); + win->len += sizeof(int); + } + } + else if (*s == '\n') + { + QPUTCH('\n'); + QPUTCH(0); + th_ringbuf_add(win->data, win->buf); + win->buf = NULL; + win->chlen = 0; + win->dirty = TRUE; + } + else if (*s != '\r') + { + QPUTCH((unsigned char) *s == 255 ? ' ' : *s); + win->chlen++; + } + + s++; + } + + return 0; +} + + +BOOL updateMainWin(BOOL force) +{ + int h, offs; + qringbuf_t *buf; + + /* Check pointers */ + if (mainWin == NULL || currWin == NULL) + return FALSE; + + /* Check if update is forced or if the window is dirty */ + if (!force && !currWin->dirty) + return FALSE; + + /* Compute how many lines from backbuffer fit on the screen */ + buf = currWin->data; + h = getmaxy(mainWin); + + /* Clear and redraw window */ + werase(mainWin); + scrollok(mainWin, 1); + for (offs = buf->size - h - currWin->pos; offs >= 0 && offs < buf->size - currWin->pos && offs < buf->size; offs++) + { + if (buf->data[offs] != NULL) + printWin(mainWin, buf->data[offs]); + } + + currWin->dirty = FALSE; + wrefresh(mainWin); + return TRUE; +} + + +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 + { + if ((unsigned char) *s == 255) + fputc(' ', outFile); + else + fputc((unsigned char) *s, outFile); + s++; + } + } + + return 0; +} + +void printMsgV(nn_window_t *win, int flags, const char *fmt, va_list ap) +{ + char tmpStr[128], *buf; + + str_get_timestamp(tmpStr, sizeof(tmpStr), "½17½[½11½%H:%M:%S½17½]½0½ "); + + buf = th_strdup_vprintf(fmt, ap); + + if (optLogFile && (flags & LOG_FILE)) + { + if (flags & LOG_STAMP) printFile(optLogFile, tmpStr); + printFile(optLogFile, buf); + fflush(optLogFile); + } + + if (!optDaemon && (flags & LOG_WINDOW)) + { + nn_window_t *tmp = win != NULL ? win : chatWindows[0]; + if (flags & LOG_STAMP) nn_window_print(tmp, tmpStr); + nn_window_print(tmp, buf); + } + + th_free(buf); +} + +void printMsg(nn_window_t *win, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + printMsgV(win, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap); + va_end(ap); +} + +void printMsgF(nn_window_t *win, int flags, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + printMsgV(win, flags | LOG_STAMP, fmt, ap); + va_end(ap); +} + +void printMsgQ(nn_window_t *win, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + printMsgV(win, LOG_STAMP | LOG_WINDOW, fmt, ap); + va_end(ap); +} + + +char *errorMessages = NULL; + +void errorMsgV(const char *fmt, va_list ap) +{ + char *tmp = th_strdup_vprintf(fmt, ap); + + printMsg(NULL, "%s", tmp); + + if (errorMessages != NULL) + { + char *tmp2 = th_strdup_printf("%s%s", errorMessages, tmp); + th_free(errorMessages); + th_free(tmp); + errorMessages = tmp2; + } + else + errorMessages = tmp; +} + +void errorMsg(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + errorMsgV(fmt, ap); + va_end(ap); +} + +void errorFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap) +{ + (void) conn; + errorMsgV(fmt, ap); +} + +void messageFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap) +{ + (void) conn; + printMsgV(NULL, LOG_STAMP | LOG_WINDOW | LOG_FILE, fmt, ap); +} + + +BOOL checkIgnoreList(const char *name) +{ + qlist_t *node = setIgnoreList; + while (node != NULL) + { + if (th_strcasecmp(name, (char *) node->data) == 0) + return TRUE; + node = node->next; + } + return FALSE; +} + + +int nnproto_handle_user(nn_conn_t *conn) +{ + static const char *msg = "</USER><MESSAGE>"; + char *p = conn->ptr; + BOOL isMine, isIgnored = FALSE; + char *s, *t, *userName; + + /* Find start of the message */ + s = strstr(p, msg); + if (!s) return 1; + *s = 0; + s += strlen(msg); + + /* Find end of the message */ + t = strstr(s, "</MESSAGE>"); + if (!t) return 3; + *t = 0; + + /* Decode message string */ + s = nn_decode_str1(s); + if (!s) return -1; + + /* Decode username */ + userName = nn_decode_str1(p); + if (!userName) + { + th_free(s); + return -2; + } + + /* Check if the username is on our ignore list and + * that it is not our OWN username! + */ + isMine = strcmp(userName, optUserName) == 0; + isIgnored = setIgnoreMode && !isMine && checkIgnoreList(userName); + + /* Is it a special control message? */ + if (*s == '/') + { + /* Ignore room join/leave messages */ + if (!optDebug && (strstr(s, "left the room") || strstr(s, "joined the room from"))) + goto done; + + t = nn_strip_tags(s + 1); + if (!strncmp(t, "BPRV ", 5)) + { + char *name, *tmp, *msg, *h; + nn_window_t *win; + h = nn_decode_str2(t + 1); + + if (!strncmp(t, "BPRV from ", 10)) + { + name = nn_decode_str2(t + 10); + isMine = FALSE; + } + else + { + name = nn_decode_str2(t + 8); + isMine = TRUE; + } + + for (tmp = name; *tmp && *tmp != ':'; tmp++); + if (tmp[0] != 0 && tmp[1] == ' ') + msg = tmp + 2; + else + msg = ""; + *tmp = 0; + + isIgnored = setIgnoreMode && checkIgnoreList(name); + win = findWindow(name); + + if (win != NULL) + { + printMsgF(win, isIgnored ? 0 : LOG_WINDOW, + "½5½<½%d½%s½5½>½0½ %s\n", + isMine ? 14 : 15, isMine ? optUserName : name, msg); + + printMsgF(NULL, LOG_FILE, "½11½%s½0½\n", h); + } + else + { + printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE), + "½11½%s½0½\n", h); + } + th_free(name); + th_free(h); + } + else + { + /* It's an action (/me) */ + char *h = nn_decode_str2(t); + printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE), + "½9½* %s½0½\n", h); + th_free(h); + } + th_free(t); + } + else + { + /* It's a normal message */ + char *h; + t = nn_strip_tags(s); + h = nn_decode_str2(t); + printMsgF(NULL, isIgnored ? LOG_FILE : (LOG_WINDOW | LOG_FILE), + "½5½<½%d½%s½5½>½0½ %s\n", isMine ? 14 : 15, userName, h); + th_free(h); + th_free(t); + } + +done: + th_free(s); + th_free(userName); + return 0; +} + + +int nnproto_handle_login(nn_conn_t *conn) +{ + char tmpStr[256]; + str_get_timestamp(tmpStr, sizeof(tmpStr), "%c"); + + if (!nn_conn_buf_strcmp(conn, "FAILURE>")) + { + printMsg(NULL, "½1½Login failure½0½ - ½3½%s½0½\n", tmpStr); + return -2; + } + else if (!nn_conn_buf_strcmp(conn, "SUCCESS>")) + { + printMsg(NULL, "½2½Login success½0½ - ½3½%s½0½\n", tmpStr); + nn_conn_send_msg(conn, optUserNameEnc, "%%2FRequestUserList"); + return 0; + } + else + return 1; +} + + +int nnproto_handle_add_user(nn_conn_t *conn) +{ + char *p, *s, *str = conn->ptr; + nn_window_t *win; + + s = nn_conn_buf_strstr(conn, "</ADD_USER>"); + if (!s) return 1; + *s = 0; + + p = nn_dbldecode_str(str); + if (!p) return -1; + + win = findWindow(p); + nn_userhash_insert(nnUsers, nn_username_encode(p)); + + printMsg(NULL, "! ½3½%s½0½ ½2½ADDED.½0½\n", p); + if (win != NULL) + printMsg(win, "! ½3½%s½0½ ½2½joined the chat.½0½\n", p); + + th_free(p); + return 0; +} + + +int nnproto_handle_delete_user(nn_conn_t *conn) +{ + char *p, *s, *str = conn->ptr; + nn_window_t *win; + + s = nn_conn_buf_strstr(conn, "</DELETE_USER>"); + if (!s) return 1; + *s = 0; + + p = nn_dbldecode_str(str); + if (!p) return -1; + + win = findWindow(p); + nn_userhash_delete(nnUsers, nn_username_encode(p)); + + printMsg(NULL, "! ½3½%s½0½ ½1½DELETED.½0½\n", p); + if (win != NULL) + printMsg(win, "! ½3½%s½0½ ½1½left the chat.½0½\n", p); + + th_free(p); + return 0; +} + + +int nnproto_handle_num_clients(nn_conn_t *conn) +{ + nn_conn_buf_strstr(conn, "</NUMCLIENTS>"); + return 0; +} + + +int nnproto_handle_boot(nn_conn_t *conn) +{ + (void) conn; + errorMsg("Booted by server.\n"); + return -1; +} + + +typedef struct +{ + char *cmd; + ssize_t len; + int (*handler)(nn_conn_t *); +} nn_protocolcmd_t; + + +static nn_protocolcmd_t protoCmds[] = +{ + { "<USER>", -1, nnproto_handle_user }, + { "<LOGIN_", -1, nnproto_handle_login }, + { "<DELETE_USER>", -1, nnproto_handle_delete_user }, + { "<ADD_USER>", -1, nnproto_handle_add_user }, + { "<NUMCLIENTS>", -1, nnproto_handle_num_clients }, + { "<BOOT />", -1, nnproto_handle_boot }, +}; + +static const int nprotoCmds = sizeof(protoCmds) / sizeof(protoCmds[0]); + + +int nn_parse_protocol(nn_conn_t *conn) +{ + 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++) + { + if (!nn_conn_buf_strncmp(conn, protoCmds[i].cmd, protoCmds[i].len)) + return protoCmds[i].handler(conn); + } + + if (optDebug) + { + printMsg(NULL, "Unknown protocmd: \"%s\"\n", conn->ptr); + return 0; + } + else + return 1; +} + + +int nn_handle_input(nn_conn_t *conn, char *buf, size_t bufLen) +{ + char *tmpStr, tmpBuf[4096]; + BOOL result; + + /* Trim right */ + bufLen--; + buf[bufLen--] = 0; + while (bufLen > 0 && th_isspace(buf[bufLen])) + buf[bufLen--] = 0; + + /* Decode completed usernames */ + nn_username_decode(buf); + + /* Check for special user commands */ + if (*buf == 0) + { + return 1; + } + else if (!th_strncasecmp(buf, "/color ", 7)) + { + /* Change color */ + int tmpInt; + if ((tmpInt = th_get_hex_triplet(str_trim_left(buf + 7))) < 0) + { + printMsgQ(currWin, "Invalid color value '%s'\n", buf+7); + return 1; + } + optUserColor = tmpInt; + printMsgQ(currWin, "Setting color to #%06x\n", optUserColor); + nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor); + return 0; + } + else if (!th_strncasecmp(buf, "/ignore", 7)) + { + char *name = str_trim_left(buf + 7); + if (strlen(name) > 0) + { + /* Add or remove someone to/from ignore */ + qlist_t *user = th_llist_find_func(setIgnoreList, name, compareUsername); + if (user != NULL) + { + printMsgQ(currWin, "Removed user '%s' from ignore.\n", name); + th_llist_delete_node(&setIgnoreList, user); + } + else + { + printMsgQ(currWin, "Now ignoring '%s'.\n", name); + th_llist_append(&setIgnoreList, th_strdup(name)); + } + } + else + { + /* Just list whomever is in ignore now */ + qlist_t *user = setIgnoreList; + ssize_t nuser = th_llist_length(setIgnoreList); + char *result = th_strdup_printf("Users ignored (%d): ", nuser); + while (user != NULL) + { + if (user->data != NULL) + { + th_pstr_printf(&result, "%s'%s'", result, (char *) user->data); + if (--nuser > 0) + th_pstr_printf(&result, "%s, ", result); + } + user = user->next; + } + printMsgQ(currWin, "%s\n", result); + th_free(result); + } + return 0; + } + else if (!th_strncasecmp(buf, "/query", 6)) + { + char *name = str_trim_left(buf + 6); + if (strlen(name) > 0) + { + nn_user_t *user = nn_user_find(nnUsers, nn_username_encode(name)); + if (user != NULL) + { + name = nn_username_decode(th_strdup(user->name)); + printMsgQ(currWin, "Opening PRV query for '%s'.\n", name); + if (openWindow(name, TRUE)) + printMsgQ(currWin, "In PRV query with '%s'.\n", name); + th_free(name); + } + } + else + { + printMsgQ(currWin, "Usage: /query username\n"); + printMsgQ(currWin, "To close a PRV query, use /close [username]\n"); + printMsgQ(currWin, "/close without username will close the current PRV window.\n"); + } + return 0; + } + else if (!th_strncasecmp(buf, "/win", 4)) + { + /* Change color */ + char *tmp = str_trim_left(buf + 4); + if (strlen(tmp) > 0) + { + int val = atoi(tmp); + if (val >= 1 && val < SET_MAX_WINDOWS) + { + if (chatWindows[val - 1] != NULL) + currWin = chatWindows[val - 1]; + } + else + { + printMsgQ(currWin, "Invalid window number '%s'\n", tmp); + return 1; + } + } + else + { + printMsgQ(currWin, "Window : #%d\n", currWin->num); + printMsgQ(currWin, "ID : %s\n", currWin->id); + } + return 0; + } + else if (!th_strncasecmp(buf, "/close", 6)) + { + char *name = str_trim_left(buf + 6); + if (strlen(name) > 0) + { + nn_window_t *win = findWindow(name); + if (win != NULL) + { + closeWindow(win); + printMsgQ(currWin, "Closed PRV query to '%s'.\n", name); + } + else + { + printMsgQ(currWin, "No PRV query by name '%s'.\n", name); + } + } + else + { + if (currWin != chatWindows[0]) + { + closeWindow(currWin); + currWin = chatWindows[0]; + } + } + return 0; + } + else if (!th_strncasecmp(buf, "/save", 5)) + { + /* Save configuration */ + FILE *cfgfile = fopen(setConfigFile, "w"); + if (cfgfile == NULL) + { + printMsgQ(currWin, "Could not create configuration to file '%s': %s\n", + setConfigFile, strerror(errno)); + return 0; + } + printMsgQ(currWin, "Configuration saved in file '%s', res=%d\n", + setConfigFile, + th_cfg_write(cfgfile, setConfigFile, cfg)); + + fclose(cfgfile); + return 0; + } + else if (!th_strncasecmp(buf, "/w ", 3)) + { + /* Open given username's profile via firefox in a new tab */ + char *name = str_trim_left(buf + 3); + + printMsg(currWin, "Opening profile for: '%s'\n", name); + + tmpStr = nn_encode_str1(name); +#ifdef __WIN32 + { + HINSTANCE status; + snprintf(tmpBuf, sizeof(tmpBuf), "http://www.newbienudes.com/profile/%s/", tmpStr); + th_free(tmpStr); + status = ShellExecute(NULL, "open", tmpBuf, NULL, NULL, SW_SHOWNA); + if (status <= (HINSTANCE) 32) + printMsgQ(currWin, "Could not launch default web browser: %d\n", status); + } +#else + { + int status; + int fds[2]; + pid_t pid; + snprintf(tmpBuf, sizeof(tmpBuf), "openurl(http://www.newbienudes.com/profile/%s/,new-tab)", tmpStr); + th_free(tmpStr); + + if (pipe(fds) == -1) + { + int ret = errno; + printMsgQ(currWin, "Could not open process communication pipe! (%d, %s)\n", ret, strerror(ret)); + return 0; + } + + if ((pid = fork()) < 0) + { + printMsgQ(currWin, "Could not create sub-process!\n"); + } + else if (pid == 0) + { + dup2(fds[1], STDOUT_FILENO); + dup2(fds[0], STDERR_FILENO); + execlp(setBrowser, setBrowser, "-remote", tmpBuf, (void *)NULL); + _exit(errno); + } + + wait(&status); + } +#endif + return 0; + } + else if (!th_strncasecmp(buf, "/who", 4)) + { + /* Alias /who to /listallusers */ + snprintf(tmpBuf, sizeof(tmpBuf), "/listallusers"); + buf = tmpBuf; + } + + if (currWin != chatWindows[0]) + { + if (currWin->id != NULL) + { + snprintf(tmpBuf, sizeof(tmpBuf), "/prv -to %s -msg %s", currWin->id, buf); + buf = tmpBuf; + } + else + { + printMsgQ(NULL, "No target set, exiting prv mode.\n"); + return 1; + } + } + + /* Send double-encoded */ + tmpStr = nn_dblencode_str(nn_username_decode(buf)); + if (tmpStr == 0) return -2; + result = nn_conn_send_msg(conn, optUserNameEnc, "%s", tmpStr); + th_free(tmpStr); + + return result ? 0 : -1; +} + + +void closeWindows(void) +{ + if (mainWin) delwin(mainWin); + if (statusWin) delwin(statusWin); + if (editWin) delwin(editWin); +} + + +BOOL initializeWindows(void) +{ + int w, h; + + getmaxyx(stdscr, h, w); + + closeWindows(); + + mainWin = subwin(stdscr, h - 4, w, 0, 0); + statusWin = subwin(stdscr, 1, w, h - 4, 0); + editWin = subwin(stdscr, 3, w, h - 3, 0); + + if (mainWin == NULL || statusWin == NULL || editWin == NULL) + return FALSE; + + return TRUE; +} + + +void updateWindows(void) +{ + if (mainWin) redrawwin(mainWin); + if (statusWin) redrawwin(statusWin); + if (editWin) redrawwin(editWin); +} + + +BOOL performTabCompletion(nn_editbuf_t *buf) +{ + static char *previous = NULL, *pattern = NULL; + BOOL again = FALSE, hasSeparator = FALSE, newPattern = FALSE, hasSpace = FALSE; + char *str = buf->data; + int mode = 0; + ssize_t endPos, startPos = buf->pos; + + /* previous word */ + if (startPos >= 2 && str[startPos - 1] == ' ' && str[startPos - 2] != ' ') + { + startPos -= 2; + endPos = startPos; + while (startPos > 0 && str[startPos - 1] != ' ') startPos--; + mode = 1; + } + else + /* middle of a word, new pattern */ + if (startPos < buf->len && str[startPos] != ' ') + { + endPos = startPos; + while (startPos > 0 && str[startPos - 1] != ' ') startPos--; + while (endPos < buf->len - 1 && str[endPos + 1] != ' ') endPos++; + newPattern = TRUE; + mode = 2; + } + else + /* previous word, new pattern */ + if (startPos >= 1 && str[startPos - 1] != ' ') + { + startPos -= 1; + endPos = startPos; + while (startPos > 0 && str[startPos - 1] != ' ') startPos--; + newPattern = TRUE; + mode = 3; + } + else + { + if (optDebug) + printMsg(currWin, "no mode\n"); + return FALSE; + } + + if (str[endPos] == optNickSep) + { + endPos--; + if (startPos > 0) + { + if (optDebug) + printMsg(currWin, "str[endPos] == optNickSep && startPos > 0 (%d)\n", startPos); + return FALSE; + } + hasSeparator = TRUE; + } + + if (buf->pos > 0 && str[buf->pos - 1] == ' ') + hasSpace = TRUE; + if (buf->pos <= buf->len && str[buf->pos] == ' ') + hasSpace = TRUE; + + if (newPattern) + { + /* Get pattern, check if it matches previous pattern and set 'again' flag */ + char *npattern = nn_editbuf_get_string(buf, startPos, endPos); + if (pattern && npattern && th_strcasecmp(npattern, pattern) == 0) + again = TRUE; + + th_free(pattern); + pattern = npattern; + + if (!again) + { + th_free(previous); + previous = NULL; + } + } + + if (optDebug) + { + printMsg(currWin, "sPos=%d, ePos=%d <-> bPos=%d, bufLen=%d : pat='%s' (again=%s, hassep=%s, hasspc=%s, newpat=%s, mode=%d)\n", + startPos, endPos, buf->pos, buf->len, pattern, + again ? "yes" : "no", + hasSeparator ? "yes" : "no", + hasSpace ? "yes" : "no", + newPattern ? "yes" : "no", mode); + } + + if (pattern) + { + nn_user_t *user = nn_user_match(nnUsers, pattern, previous, again); + + if (user) + { + int i; + char *c = user->name; + if (optDebug) + printMsg(currWin, "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 (!hasSeparator && startPos == 0) + { + nn_editbuf_insert(buf, i++, optNickSep); + startPos++; + } + if (hasSeparator) + startPos++; + if (!hasSpace) + nn_editbuf_insert(buf, i++, ' '); + + nn_editbuf_setpos(buf, startPos + 1 + strlen(user->name)); + + th_free(previous); + previous = th_strdup(user->name); + + return TRUE; + } + } + + return FALSE; +} + + +#define VPUTCH(CH) th_vputch(&bufData, &bufSize, &bufLen, CH) +#define VPUTS(STR) th_vputs(&bufData, &bufSize, &bufLen, STR) + +char *logParseFilename(const char *fmt, int id) +{ + size_t bufSize = strlen(fmt) + TH_BUFGROW, bufLen = 0; + char *bufData = th_malloc(bufSize); + char tmpBuf[32]; + const char *s = fmt; + + while (*s) + { + if (*s == '%') + { + s++; + switch (*s) + { + case 'i': + snprintf(tmpBuf, sizeof(tmpBuf), "%05d", id); + VPUTS(tmpBuf); + break; + + case 'd': + snprintf(tmpBuf, sizeof(tmpBuf), "%d", id); + VPUTS(tmpBuf); + break; + + case '%': + VPUTCH('%'); + break; + } + s++; + } + else + { + VPUTCH(*s); + s++; + } + } + + VPUTCH(0); + return bufData; +} + + +BOOL logFileOpen(void) +{ + char *filename; + + if (optLogFilename == NULL || !optLogEnable) + return FALSE; + + filename = logParseFilename(optLogFilename, optPort); + + if ((optLogFile = fopen(filename, "a")) == NULL) + { + errorMsg("Could not open logfile '%s' for appending!\n", filename); + th_free(filename); + return FALSE; + } + + th_free(filename); + + return TRUE; +} + + +void logFileClose(void) +{ + if (optLogFile) + { + fclose(optLogFile); + optLogFile = NULL; + } +} + + +char *promptRequester(WINDOW *win, const char *info, BOOL allowEmpty) +{ + char tmpBuf[512], *ptr; + ssize_t pos; + int curVis = curs_set(1); + + echo(); + waddstr(win, info); + wgetnstr(win, tmpBuf, sizeof(tmpBuf) - 1); + noecho(); + if (curVis != ERR) + curs_set(curVis); + + for (pos = strlen(tmpBuf) - 1; pos > 0 && th_isspace(tmpBuf[pos]); pos--) + tmpBuf[pos] = 0; + + ptr = str_trim_left(tmpBuf); + + if (allowEmpty || strlen(ptr) > 0) + return th_strdup(ptr); + else + return NULL; +} + + +void printHelp(void) +{ + printMsgQ(currWin, "\n" + "NNChat Help\n" + "===========\n" + "\n" + "F1 This help.\n" + "F2 \n" + ); +} + + +int main(int argc, char *argv[]) +{ + nn_conn_t *conn = NULL; + int curVis = ERR, updateCount = 0; + BOOL argsOK, isError = FALSE, + exitProg = FALSE, + colorSet = FALSE, + cursesInit = FALSE, + networkInit = FALSE, + insertMode = TRUE, + firstUpdate = TRUE; + time_t prevTime; + char *tmpStr; + nn_editbuf_t *editBuf = nn_editbuf_new(NN_TMPBUF_SIZE); + nn_editbuf_t *histBuf[SET_MAX_HISTORY+2]; + int histPos = 0, histMax = 0; + + cfgitem_t *tmpcfg; + char *homeDir = NULL; + + memset(histBuf, 0, sizeof(histBuf)); + + /* Initialize */ + th_init("NNChat", "Newbie Nudes chat client", NN_VERSION, + "Written and designed by Anonymous Finnish Guy (C) 2008-2012", + "This software is freeware, use and distribute as you wish."); + th_verbosityLevel = 0; + + /* Read configuration file */ + tmpcfg = NULL; + 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_comment(&tmpcfg, "Default color as a hex-triplet"); + th_cfg_add_hexvalue(&tmpcfg, "color", &optUserColor, optUserColor); + + th_cfg_add_comment(&tmpcfg, "Default setting of ignore mode"); + th_cfg_add_bool(&tmpcfg, "ignore", &setIgnoreMode, setIgnoreMode); + th_cfg_add_comment(&tmpcfg, "People to be ignored when ignore mode is enabled"); + th_cfg_add_string_list(&tmpcfg, "ignore_list", &setIgnoreList); + + th_cfg_add_comment(&tmpcfg, "Random messages for idle timeout protection. If none are set, plain '.' is used."); + th_cfg_add_string_list(&tmpcfg, "idle_messages", &setIdleMessages); + + th_cfg_add_comment(&tmpcfg, "Character used as nickname auto-completion separator (default is ':')"); + th_cfg_add_string(&tmpcfg, "nick_separator", &optNickSepStr, NULL); + + 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 (8005 = main room, 8003 = passion pit)"); + th_cfg_add_int(&tmpcfg, "port", &optPort, optPort); + th_cfg_add_section(&cfg, "server", tmpcfg); + + tmpcfg = NULL; + th_cfg_add_comment(&tmpcfg, "Proxy server type (0 = none, 1 = SOCKS 4, 2 = SOCKS 4a)"); + th_cfg_add_int(&tmpcfg, "type", &optProxyType, optProxyType); + th_cfg_add_comment(&tmpcfg, "Proxy server host name"); + th_cfg_add_string(&tmpcfg, "host", &optProxyServer, optProxyServer); + th_cfg_add_comment(&tmpcfg, "Proxy port, 1080 is the standard SOCKS port"); + th_cfg_add_int(&tmpcfg, "port", &optProxyPort, optProxyPort); + th_cfg_add_section(&cfg, "proxy", tmpcfg); + + tmpcfg = NULL; + th_cfg_add_comment(&tmpcfg, "Enable logging"); + th_cfg_add_bool(&tmpcfg, "enable", &optLogEnable, optLogEnable); + th_cfg_add_comment(&tmpcfg, "Log filename format"); + th_cfg_add_string(&tmpcfg, "filename", &optLogFilename, optLogFilename); + th_cfg_add_section(&cfg, "logging", tmpcfg); + +#ifdef __WIN32 + { + char tmpPath[MAX_PATH]; + if (SHGetFolderPath(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, tmpPath) == S_OK) + homeDir = th_strdup(tmpPath); + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); + } +#else + homeDir = th_strdup(getenv("HOME")); +#endif + + if (homeDir != NULL) + { + FILE *cfgfile; + setConfigFile = th_strdup_printf("%s" SET_DIR_SEPARATOR "%s", homeDir, SET_CONFIG_FILE); + + THMSG(0, "Reading configuration from '%s'.\n", setConfigFile); + + if ((cfgfile = fopen(setConfigFile, "r")) != NULL) + { + th_cfg_read(cfgfile, setConfigFile, cfg); + fclose(cfgfile); + } + } + + if (optNickSepStr) + optNickSep = optNickSepStr[0]; + else + optNickSep = SET_NICK_SEPARATOR; + + + setBrowser = getenv("BROWSER"); + if (setBrowser == NULL) + setBrowser = "firefox"; + + /* Parse command line arguments */ + argsOK = th_args_process(argc, argv, optList, optListN, + argHandleOpt, argHandleFile, FALSE); + + if (optUserNameCmd != NULL) + { + optUserName = optUserNameCmd; + optPassword = optPasswordCmd; + } + + if (!argsOK) + return -2; + + /* Allocate userhash */ + if ((nnUsers = nn_userhash_new()) == NULL) + { + THERR("Could not allocate userhash. Fatal error.\n"); + return -105; + } + + /* If no idle messages are set, add default */ + if (setIdleMessages == NULL) + { + th_llist_append(&setIdleMessages, th_strdup(".")); + } + + /* Open logfile */ + logFileOpen(); + + /* Initialize network */ + if (!nn_network_init()) + { + THERR("Could not initialize network subsystem.\n"); + goto err_exit; + } + else + networkInit = TRUE; + + /* 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; + +#ifdef PDCURSES + PDC_set_title("NNChat v" NN_VERSION); +#endif + + memset(chatWindows, 0, sizeof(chatWindows)); + chatWindows[0] = nn_window_new(NULL); + currWin = chatWindows[0]; + updateStatus(); + } + + /* Check if we have username and password */ + if (cursesInit && (optUserName == NULL || optPassword == NULL)) + { + printWin(editWin, "You can avoid this prompt by issuing '/save' after logging in.\n"); + optUserName = promptRequester(editWin, "NN username: ", FALSE); + optPassword = promptRequester(editWin, "NN password: ", TRUE); + } + + if (optUserName == NULL || optPassword == NULL) + { + errorMsg("Username and/or password not specified.\n"); + goto err_exit; + } + + /* Create a connection */ + conn = nn_conn_new(errorFunc, messageFunc); + if (conn == NULL) + { + errorMsg("Could not create connection structure.\n"); + goto err_exit; + } + + /* Are we using a proxy? */ + if (optProxyType != NN_PROXY_NONE && optProxyServer != NULL) + { + if (nn_conn_set_proxy(conn, optProxyType, optProxyPort, optProxyServer) != 0) + { + errorMsg("Error setting proxy information.\n"); + goto err_exit; + } + } + + /* Okay ... */ + printMsg(currWin, "Trying to resolve host '%s' ...\n", optServer); + conn->host = th_strdup(optServer); + conn->hst = nn_resolve_host(conn, optServer); + if (conn->hst == NULL) + { + errorMsg("Could not resolve hostname: %s.\n", strerror(h_errno)); + goto err_exit; + } + +#ifdef FINAL_BUILD + /* To emulate the official client, we first make a request for + * policy file, even though we don't use it for anything... + */ + if (nn_conn_open(conn, 843, NULL) != 0) + { + errorMsg("Policy file request connection setup failed!\n"); + goto err_exit; + } + + tmpStr = "<policy-file-request/>"; + if (nn_conn_send_buf(conn, tmpStr, strlen(tmpStr) + 1) == FALSE) + { + errorMsg("Failed to send policy file request.\n"); + goto err_exit; + } + else + { + int cres = nn_conn_pull(conn); + if (cres == 0) + { + printMsg(currWin, "Probe got: %s\n", conn->buf); + } + else + { + printMsg(currWin, "Could not get policy probe.\n"); + } + } + nn_conn_close(conn); +#endif + + /* Okay, now do the proper connection ... */ + if (nn_conn_open(conn, optPort, NULL) != 0) + { + errorMsg("Main connection setup failed!\n"); + goto err_exit; + } + + /* Send login command */ + optUserNameEnc = nn_dblencode_str(optUserName); + tmpStr = nn_dblencode_str(optSite); + nn_conn_send_msg(conn, optUserNameEnc, "%%2Flogin%%20%%2Dsite%%20%s%%20%%2Dpassword%%20%s", tmpStr, optPassword); + th_free(tmpStr); + + /* Initialize random numbers */ + prevTime = time(NULL); + srandom((int) prevTime); + + if (cursesInit) + { + /* Initialize rest of interactive UI code */ + nn_editbuf_clear(editBuf); + + /* First update of screen */ + printEditBuf(editBuf); + updateStatus(); + + printMsg(NULL, "%s v%s - %s\n", th_prog_name, th_prog_version, th_prog_fullname); + printMsg(NULL, "%s\n", th_prog_author); + printMsg(NULL, "%s\n", th_prog_license); + } + + /* Enter mainloop */ + nn_conn_reset(conn); + while (!isError && !exitProg) + { + nn_conn_reset(conn); + do { + int cres = nn_conn_pull(conn); + if (cres == 0 && *(conn->in_ptr - 1) == 0) + { + int result = nn_parse_protocol(conn); + if (result > 0) + { + /* Couldn't handle the message for some reason */ + printMsg(currWin, "Could not handle: %s\n", conn->ptr); + } + else if (result < 0) + { + /* Fatal error, quit */ + errorMsg("Fatal error with message: %s\n", conn->ptr); + isError = TRUE; + } + } + else if (cres < 0) + isError = TRUE; + else + break; + } + while (conn->total_bytes > 0 && !isError); + + if (!nn_conn_check(conn)) + isError = TRUE; + + /* Handle user input */ + if (cursesInit) + { + int c, cnt = 0; + BOOL update = FALSE, updateMain = FALSE; + + /* Handle several buffered keypresses at once */ + do + { + c = wgetch(stdscr); + + /* Handle various problematic cases where terminal + * keycodes do not get properly translated by curses + */ + if (c == 0x1b) + { + /* ^[O */ + c = wgetch(stdscr); + if (c == 'O') + { + c = wgetch(stdscr); + switch (c) + { + case 'd': + c = 0x204; + break; + case 'c': + c = 0x206; + break; + default: + if (optDebug) + printMsg(currWin, "Unhandled ESC-O key sequence 0x%02x\n", c); + break; + } + } + /* ^[[ */ + else if (c == '[') + { + c = wgetch(stdscr); + switch (c) + { + case 0x31: + c = wgetch(stdscr); + if (c >= 0x31 && c <= 0x39) + c = KEY_F(c - 0x30); + else + c = ERR; + break; + + case 0x32: + c = KEY_IC; + break; + case 0x33: + c = KEY_DC; + break; + + case 0x35: + c = KEY_PPAGE; + break; + case 0x36: + c = KEY_NPAGE; + break; + + case 0x37: + c = KEY_HOME; + break; + case 0x38: + c = KEY_END; + break; + + default: + if (optDebug) + printMsg(currWin, "Unhandled ESC-[*~ key sequence 0x%02x\n", c); + c = ERR; + break; + } + /* Get the trailing ~ */ + if (c != ERR) + wgetch(stdscr); + } + if (c >= 0x31 && c <= 0x39) + { + /* Chat window switching via Meta/Esc-[1..9] */ + int win = c - 0x31; + if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL) + { + currWin = chatWindows[win]; + update = updateMain = TRUE; + } + c = ERR; + } + else + { + if (optDebug) + printMsg(currWin, "Unhandled ESC key sequence 0x%02x\n", c); + } + } +#if defined(__WIN32) && defined(PDCURSES) + else if (c >= 0x198 && c <= 0x1a0) + { + /* Chat window switching via Meta/Esc-[1..9] */ + int win = c - 0x198; + if (win < SET_MAX_WINDOWS && chatWindows[win] != NULL) + { + currWin = chatWindows[win]; + update = updateMain = TRUE; + } + c = ERR; + } +#endif + + switch (c) + { +#ifdef KEY_RESIZE + case KEY_RESIZE: + resize_term(0, 0); + erase(); + timeout(SET_DELAY); + + if (!initializeWindows()) + { + errorMsg("Error resizing curses chatWindows\n"); + isError = TRUE; + } + update = updateMain = TRUE; + break; +#endif + + case KEY_ENTER: + case '\n': + case '\r': + /* Call the user input handler */ + if (editBuf->len > 0) + { + int result; + + 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 = nn_handle_input(conn, editBuf->data, editBuf->len); + + nn_editbuf_clear(editBuf); + + if (result < 0) + { + errorMsg("Fatal error handling user input: %s\n", editBuf->data); + isError = TRUE; + } + else + { + /* Update time value of last sent message for unidle timeouts */ + prevTime = time(NULL); + } + + updateMain = 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 */ + case 0x20b: + 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 */ + case 0x210: + 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++; + 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: + case 0x7f: + 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 KEY_IC: /* Ins = Toggle insert / overwrite mode */ + insertMode = !insertMode; + update = TRUE; + break; + + case KEY_F(1): /* F1 = Print help */ + printHelp(); + updateMain = TRUE; + break; + + case KEY_F(2): /* F2 = Clear editbuffer */ + nn_editbuf_clear(editBuf); + update = TRUE; + break; + + case KEY_F(5): /* F5 = Ignore mode */ + setIgnoreMode = !setIgnoreMode; + printMsgQ(currWin, "Ignore mode = %s\n", setIgnoreMode ? "ON" : "OFF"); + break; + +#if 0 + case KEY_F(8): /* F8 = Debug */ + optDebug = !optDebug; + update = TRUE; + break; +#endif + + case 0x03: /* ^C = quit */ + case KEY_F(9): /* F9 = Quit */ + printMsg(currWin, "Quitting per user request (%d/0x%x).\n", c, c); + exitProg = TRUE; + break; + + case 0x09: /* Tab = complete username */ + performTabCompletion(editBuf); + update = TRUE; + break; + + case 0x0c: /* Ctrl + L */ + updateWindows(); + update = updateMain = TRUE; + break; + + case KEY_NPAGE: + case KEY_PPAGE: + /* Page Up / Page Down */ + if (currWin != NULL) + { + int oldPos = currWin->pos; + + currWin->pos += (c == KEY_NPAGE) ? -10 : +10; + + if (currWin->pos < 0) + currWin->pos = 0; + else if (currWin->pos >= currWin->data->n - 10) + currWin->pos = currWin->data->n - 10; + + if (oldPos != currWin->pos) + updateMain = TRUE; + } + 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 + { + if (optDebug) + printMsg(currWin, "Unhandled key: 0x%02x\n", c); + } + break; + } + } + while (c != ERR && !exitProg && ++cnt < 10); + + updateMainWin(updateMain); + + if (update || firstUpdate) + { + /* Update edit line */ + updateStatus(); + printEditBuf(editBuf); + firstUpdate = FALSE; /* a nasty hack ... */ + } + + } /* cursesInit */ + + if (++updateCount > 10) + { + time_t tmpTime = time(NULL); + if (tmpTime - prevTime > SET_KEEPALIVE) + { + int n = random() % th_llist_length(setIdleMessages); + qlist_t *node = th_llist_get_nth(setIdleMessages, n); + nn_conn_send_msg(conn, optUserNameEnc, node->data); + prevTime = tmpTime; + } + + if (!colorSet) + { + colorSet = TRUE; + nn_conn_send_msg(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor); + } + + updateStatus(); + printEditBuf(editBuf); + updateCount = 0; + } + + } + + /* Shutdown */ +err_exit: + th_cfg_free(cfg); + th_free(homeDir); + th_llist_free_func(setIdleMessages, th_free); + nn_userhash_free(nnUsers); + nn_editbuf_free(editBuf); + + { + int i; + for (i = 0; i <= SET_MAX_HISTORY; i++) + nn_editbuf_free(histBuf[i]); + + for (i = 0; i < SET_MAX_WINDOWS; i++) + nn_window_free(chatWindows[i]); + } + +#ifdef __WIN32 + if (errorMessages) + { + char *tmp; + wclear(editWin); + tmp = promptRequester(editWin, "Press enter to quit.\n", FALSE); + th_free(tmp); + } +#endif + + if (cursesInit) + { + if (curVis != ERR) + curs_set(curVis); + closeWindows(); + endwin(); + THMSG(1, "NCurses deinitialized.\n"); + } + +#ifndef __WIN32 + if (errorMessages) + THERR("%s", errorMessages); +#endif + + th_free(optUserNameEnc); + + nn_conn_close(conn); + + if (networkInit) + nn_network_close(); + + THMSG(1, "Connection terminated.\n"); + + logFileClose(); + + return 0; +}