changeset 28:512775f6b081

A refactored ncurses-based UI.
author Matti Hamalainen <ccr@tnsp.org>
date Sat, 02 Aug 2008 11:57:09 +0300
parents da721f94c60f
children a27ef0e359b9
files Makefile.gen nnchat.c
diffstat 2 files changed, 404 insertions(+), 145 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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 <winsock2.h>
-#else
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <arpa/inet.h>
 #include <sys/time.h>
 #include <netdb.h>
-#endif
 
 #include <unistd.h>
 #include <stdlib.h>
@@ -16,31 +12,13 @@
 #include <string.h>
 #include <errno.h>
 #include <time.h>
+#include <ncurses.h>
 
 
+#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;
 }