changeset 513:ef5a2aa8382b

Refactor input handling.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 05 Jun 2012 22:16:42 +0300
parents 93c8ba1ef55f
children fd4474955a79
files main.c ui.c ui.h util.c
diffstat 4 files changed, 458 insertions(+), 380 deletions(-) [+]
line wrap: on
line diff
--- a/main.c	Tue Jun 05 19:56:57 2012 +0300
+++ b/main.c	Tue Jun 05 22:16:42 2012 +0300
@@ -19,11 +19,9 @@
 #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_PROFILE_PREFIX "http://www.newbienudes.com/profile/%s/"
@@ -63,6 +61,9 @@
         *setBrowser = NULL;
 cfgitem_t *cfg = NULL;
 
+nn_editbuf_t *editHistBuf[SET_MAX_HISTORY+2];
+int      editHistPos = 0,
+         editHistMax = 0;
 
 /* Logging mode flags
  */
@@ -313,6 +314,19 @@
 }
 
 
+void debugMsg(const char *fmt, ...)
+{
+    if (optDebug)
+    {
+        va_list ap;
+
+        va_start(ap, fmt);
+        printMsgV(NULL, LOG_FILE|LOG_WINDOW, fmt, ap);
+        va_end(ap);
+    }
+}
+
+
 void errorFunc(struct _nn_conn_t *conn, const char *fmt, va_list ap)
 {
     (void) conn;
@@ -1292,24 +1306,161 @@
 }
 
 
+BOOL processUserInput(int c, nn_editbuf_t *editBuf, nn_editstate_t *editState)
+{
+    // Chat window switching via Meta/Esc-[1..9]
+    if (c >= 0x5001 && c <= 0x5009)
+    {
+        nn_window_t *win = nnwin_get(c - 0x5000);
+        if (win != NULL)
+        {
+            currWin = win;
+            editState->update = TRUE;
+        }
+    }
+    else
+    switch (c)
+    {
+    case KEY_ENTER:
+        // Call the user input handler
+        if (editBuf->len > 0)
+        {
+            int result;
+
+            if (editHistMax > 0)
+            {
+                nn_editbuf_free(editHistBuf[SET_MAX_HISTORY+1]);
+                editHistBuf[SET_MAX_HISTORY+1] = NULL;
+                memmove(&editHistBuf[2], &editHistBuf[1],
+                    editHistMax * sizeof(editHistBuf[0]));
+            }
+
+            editHistPos = 0;
+            editHistBuf[1] = nn_editbuf_copy(editBuf);
+            if (editHistMax < SET_MAX_HISTORY)
+                editHistMax++;
+
+            result = nn_handle_input(editState->conn, editBuf->data, editBuf->len);
+
+            nn_editbuf_clear(editBuf);
+
+            if (result < 0)
+            {
+                errorMsg("Fatal error handling user input: %s\n", editBuf->data);
+                editState->isError = TRUE;
+            }
+            else
+            {
+                // Update time value of last sent message for unidle timeouts
+                editState->prevKeepAlive = time(NULL);
+            }
+        }
+        break;
+
+    case KEY_NPAGE:
+    case KEY_PPAGE:
+        // Page Up / Page Down
+        if (currWin != NULL)
+        {
+            int oldPos = currWin->pos, page = (scrHeight - 4) / 3;
+
+            currWin->pos += (c == KEY_NPAGE) ? - page : page;
+
+            if (currWin->pos >= currWin->data->n - page)
+                currWin->pos = currWin->data->n - page;
+            if (currWin->pos < 0)
+                currWin->pos = 0;
+
+            if (oldPos != currWin->pos)
+                editState->update = TRUE;
+        }
+        break;
+
+    case KEY_UP: // Backwards in input history
+        if (editHistPos == 0)
+        {
+            nn_editbuf_free(editHistBuf[0]);
+            editHistBuf[0] = nn_editbuf_copy(editBuf);
+        }
+        if (editHistPos < editHistMax)
+        {
+            editHistPos++;
+            nn_editbuf_free(editBuf);
+            editBuf = nn_editbuf_copy(editHistBuf[editHistPos]);
+        }
+        break;
+
+    case KEY_DOWN: // Forwards in input history
+        if (editHistPos > 0)
+        {
+            editHistPos--;
+            nn_editbuf_free(editBuf);
+            editBuf = nn_editbuf_copy(editHistBuf[editHistPos]);
+        }
+        break;
+
+    case KEY_F(5): // F5 = Ignore mode
+        setIgnoreMode = setIgnoreMode;
+        printMsgQ(currWin, "Ignore mode = %s\n", setIgnoreMode ? "ON" : "OFF");
+        break;
+
+    case 0x03: // ^C = quit
+    case KEY_F(9): // F9 = Quit
+        printMsg(currWin, "Quitting per user request (%d/0x%x).\n", c, c);
+        editState->exitProg = TRUE;
+        break;
+
+    case 0x09: // Tab = complete username or command
+        nn_tabcomplete_buffer(editBuf);
+        break;
+    
+    default:
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+BOOL processUserPrompt(int c, nn_editbuf_t *editBuf, nn_editstate_t *editState)
+{
+    (void) editBuf;
+    
+    switch (c)
+    {
+    case KEY_ENTER:
+        editState->done = TRUE;
+        break;
+    
+    default:
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+void updateUserPrompt(nn_editbuf_t *editBuf, nn_editstate_t *editState)
+{
+    nnwin_update(editState->update, editState->mask, editBuf, optUserName, optUserColor);
+}
+
+
 int main(int argc, char *argv[])
 {
+    char *tmpStr;
+    int index, updateCount = 0;
+    BOOL argsOK, colorSet = FALSE;
     nn_conn_t *conn = NULL;
-    int updateCount = 0;
-    BOOL argsOK, isError = FALSE,
-        exitProg = FALSE,
-        colorSet = FALSE,
-        insertMode = TRUE;
-    time_t prevKeepAlive;
-    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, index;
-
+    nn_editstate_t editState;
     cfgitem_t *tmpcfg;
     char *setHomeDir = NULL;
 
-    memset(histBuf, 0, sizeof(histBuf));
+    memset(editHistBuf, 0, sizeof(editHistBuf));
+    memset(&editState, 0, sizeof(editState));
+    editState.insertMode = TRUE;
+    editState.debugMsg = debugMsg;
 
     // Initialize
     th_init("NNChat", "Newbie Nudes chat client", NN_VERSION,
@@ -1417,6 +1568,7 @@
 
     if (optUserNameCmd != NULL)
     {
+        THMSG(1, "Username set on commandline.\n");
         optUserName = optUserNameCmd;
         optPassword = optPasswordCmd;
     }
@@ -1453,14 +1605,26 @@
 
     if (cursesInit)
     {
-        nnwin_update(TRUE, NULL, optUserName, optUserColor);
-    }
+        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);
+
+        nnwin_update(TRUE, FALSE, NULL, optUserName, optUserColor);
 
-    // Check if we have username and password
-    if (cursesInit && (optUserName == NULL || optPassword == NULL))
-    {
-        optUserName = nnwin_prompt_requester("You can avoid this prompt by issuing '/save' after logging in.\nNN username: ", FALSE);
-        optPassword = nnwin_prompt_requester("NN password: ", TRUE);
+        // Check if we have username and password
+        if (optUserName == NULL || optPassword == NULL)
+        {
+            printMsg(NULL, "Please enter your NN login credentials.\n");
+            printMsg(NULL, "You can avoid doing this every time by issuing '/save' after logging in.\n");
+
+            printMsg(NULL, "Enter your NN username ...\n");
+            optUserName = nnwin_prompt_requester(FALSE, &editState, processUserPrompt, updateUserPrompt);
+
+            editState.mask = TRUE;
+            printMsg(NULL, "Enter your NN password ...\n");
+            optPassword = nnwin_prompt_requester(TRUE, &editState, processUserPrompt, updateUserPrompt);
+            editState.mask = FALSE;
+        }
     }
 
     if (optUserName == NULL || optPassword == NULL)
@@ -1476,6 +1640,8 @@
         errorMsg("Could not create connection structure.\n");
         goto err_exit;
     }
+    
+    editState.conn = conn;
 
     // Are we using a proxy?
     if (optProxyType != NN_PROXY_NONE && optProxyServer != NULL)
@@ -1541,25 +1707,12 @@
     nn_usercmd_init();
     
     // Initialize random numbers
-    prevKeepAlive = time(NULL);
-    srandom((int) prevKeepAlive);
-
-    if (cursesInit)
-    {
-        // Initialize rest of interactive UI code
-        nn_editbuf_clear(editBuf);
-
-        // First update of screen
-        nnwin_update(TRUE, editBuf, optUserName, optUserColor);
-
-        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);
-    }
+    editState.prevKeepAlive = time(NULL);
+    srandom((int) editState.prevKeepAlive);
 
     // Enter mainloop
     nn_conn_reset(conn);
-    while (!isError && !exitProg)
+    while (!editState.isError && !editState.exitProg)
     {
         int retries = 3, cres;
 
@@ -1569,7 +1722,7 @@
         {
             while (conn->ptr < conn->in_ptr &&
                    *(conn->in_ptr - 1) == 0 &&
-                   retries > 0 && !isError)
+                   retries > 0 && !editState.isError)
             {
 //                nn_conn_dump_buffer(stderr, conn);
                 int result = nn_parse_protocol(conn);
@@ -1589,326 +1742,29 @@
                     nn_conn_buf_skip(conn, strlen(conn->ptr) + 1);
                 }
                 else
-                    isError = TRUE;
+                    editState.isError = TRUE;
             }
         }
         else
         if (cres < 0 || !nn_conn_check(conn))
-            isError = TRUE;
+            editState.isError = TRUE;
 
         // Handle user input
         if (cursesInit)
         {
-            int c, cnt = 0;
-            BOOL update = 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]
-                        nn_window_t *win = nnwin_get(c - 0x30);
-                        if (win != NULL)
-                        {
-                            currWin = win;
-                            update = 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]
-                    nn_window_t *win = nnwin_get(c - 0x198);
-                    if (win != NULL)
-                    {
-                        currWin = win;
-                        update = TRUE;
-                    }
-                    c = ERR;
-                }
-#endif
-
-                switch (c)
-                {
-#ifdef KEY_RESIZE
-                case KEY_RESIZE:
-                    resize_term(0, 0);
-                    erase();
-                    timeout(SET_DELAY);
-                    nnwin_reset();
-                    update = 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++;
-
-                        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
-                            prevKeepAlive = time(NULL);
-                        }
-                    }
-                    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]);
-                    }
-                    break;
-
-                case KEY_DOWN: // Forwards in input history
-                    if (histPos > 0)
-                    {
-                        histPos--;
-                        nn_editbuf_free(editBuf);
-                        editBuf = nn_editbuf_copy(histBuf[histPos]);
-                    }
-                    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--;
-                    editBuf->dirty = 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++;
-                    editBuf->dirty = TRUE;
-                    break;
-
-                case KEY_HOME:
-                    nn_editbuf_setpos(editBuf, 0);
-                    break;
-                case KEY_END:
-                    nn_editbuf_setpos(editBuf, editBuf->len);
-                    break;
-                case KEY_LEFT:
-                    nn_editbuf_setpos(editBuf, editBuf->pos - 1);
-                    break;
-                case KEY_RIGHT:
-                    nn_editbuf_setpos(editBuf, editBuf->pos + 1);
-                    break;
-
-                case KEY_BACKSPACE:
-                case 0x08:
-                case 0x7f:
-                    nn_editbuf_delete(editBuf, editBuf->pos - 1);
-                    nn_editbuf_setpos(editBuf, editBuf->pos - 1);
-                    break;
-
-                case KEY_DC: // Delete character
-                    nn_editbuf_delete(editBuf, editBuf->pos);
-                    break;
-
-
-                case KEY_IC: // Ins = Toggle insert / overwrite mode
-                    insertMode = !insertMode;
-                    break;
-
-                case KEY_F(2): // F2 = Clear editbuffer
-                    nn_editbuf_clear(editBuf);
-                    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 or command
-                    nn_tabcomplete_buffer(editBuf);
-                    break;
-
-                case 0x0c: // Ctrl + L
-                    update = TRUE;
-                    break;
-
-                case KEY_NPAGE:
-                case KEY_PPAGE:
-                    // Page Up / Page Down
-                    if (currWin != NULL)
-                    {
-                        int oldPos = currWin->pos, page = scrHeight - 4;
-
-                        currWin->pos += (c == KEY_NPAGE) ? - page : page;
-
-                        if (currWin->pos >= currWin->data->n - page)
-                            currWin->pos = currWin->data->n - page;
-                        if (currWin->pos < 0)
-                            currWin->pos = 0;
-
-                        if (oldPos != currWin->pos)
-                            update = 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);
-                    }
-                    else
-                    {
-                        if (optDebug)
-                            printMsg(currWin, "Unhandled key: 0x%02x\n", c);
-                    }
-                    break;
-                }
-            }
-            while (c != ERR && !exitProg && ++cnt < 10);
-
-            nnwin_update(update, editBuf, optUserName, optUserColor);
-        } // cursesInit
+            nnwin_input_process(editBuf, &editState, processUserInput);
+            nnwin_update(editState.update, editState.mask, editBuf, optUserName, optUserColor);
+        }
 
         if (++updateCount > 10)
         {
             time_t tmpTime = time(NULL);
-            if (tmpTime - prevKeepAlive > SET_KEEPALIVE)
+            if (tmpTime - editState.prevKeepAlive > SET_KEEPALIVE)
             {
                 size_t n = ((size_t) random()) % th_llist_length(setIdleMessages);
                 qlist_t *node = th_llist_get_nth(setIdleMessages, n);
                 nn_conn_send_msg(conn, optUserNameEnc, node->data);
-                prevKeepAlive = tmpTime;
+                editState.prevKeepAlive = tmpTime;
             }
 
             if (!colorSet)
@@ -1916,8 +1772,12 @@
                 colorSet = TRUE;
                 nn_conn_send_msg_v(conn, optUserNameEnc, "%%2FSetFontColor%%20%%2Dcolor%%20%06X", optUserColor);
             }
+            
+            if (cursesInit)
+            {
+                nnwin_update(FALSE, editState.mask, editBuf, optUserName, optUserColor);
+            }
 
-            nnwin_update(FALSE, editBuf, optUserName, optUserColor);
             updateCount = 0;
         }
 
@@ -1932,7 +1792,7 @@
     nn_editbuf_free(editBuf);
 
     for (index = 0; index <= SET_MAX_HISTORY; index++)
-        nn_editbuf_free(histBuf[index]);
+        nn_editbuf_free(editHistBuf[index]);
 
 #ifdef __WIN32
     if (errorMessages)
--- a/ui.c	Tue Jun 05 19:56:57 2012 +0300
+++ b/ui.c	Tue Jun 05 22:16:42 2012 +0300
@@ -6,7 +6,6 @@
 #include "util.h"
 #include "ui.h"
 
-#define STATUS_YPOS 0
 
 nn_window_t *chatWindows[SET_MAX_WINDOWS],
             *currWin = NULL;
@@ -292,27 +291,6 @@
 }
 
 
-char *nnwin_prompt_requester(const char *info, BOOL allowEmpty)
-{
-    char buf[512], *ptr;
-
-    echo();
-    wattrset(stdscr, A_NORMAL);
-    wbkgdset(stdscr, COLOR_PAIR(0));
-    waddstr(stdscr, info);
-    wgetnstr(stdscr, buf, sizeof(buf) - 1);
-    noecho();
-
-    str_trim_right(buf);
-    ptr = str_trim_left(buf);
-
-    if (allowEmpty || ptr[0])
-        return th_strdup(ptr);
-    else
-        return NULL;
-}
-
-
 static void nnwin_print_str(WINDOW *win, const char *fmt, BOOL clip)
 {
     const char *s = fmt;
@@ -345,7 +323,7 @@
 }
 
 
-void nnwin_update(BOOL force, nn_editbuf_t *ebuf, char *username, int usercolor)
+void nnwin_update(BOOL force, BOOL mask, nn_editbuf_t *ebuf, char *username, int usercolor)
 {
     int sx, sy;
     
@@ -454,20 +432,244 @@
     {
         int yoffs = ebuf->pos / scrWidth,
             xoffs = ebuf->pos % scrWidth;
-        char *tmp;
-
-        ebuf->dirty = FALSE;
-        ebuf->data[ebuf->len] = 0;
-        tmp = nn_username_decode(th_strdup(ebuf->data));
 
         wmove(stdscr, scrHeight - 3, 0);
         wattrset(stdscr, A_NORMAL);
         wbkgdset(stdscr, COLOR_PAIR(0));
-        waddnstr(stdscr, tmp, ebuf->len);
+
+        ebuf->dirty = FALSE;
+        if (mask)
+        {
+            size_t i;
+            for (i = 0; i < ebuf->len; i++)
+                waddch(stdscr, '*');
+        }
+        else
+        {
+            char *tmp;
+            ebuf->data[ebuf->len] = 0;
+            tmp = nn_username_decode(th_strdup(ebuf->data));
+            waddnstr(stdscr, tmp, ebuf->len);
+            th_free(tmp);
+        }
         wmove(stdscr, scrHeight - 3 + yoffs, xoffs);
 
-        th_free(tmp);
     }
 
     wrefresh(stdscr);
 }
+
+
+void nnwin_input_process(nn_editbuf_t *editBuf, nn_editstate_t *editState,
+    BOOL (*callback)(int, nn_editbuf_t *, nn_editstate_t *))
+{
+    int c, cnt = 0;
+    
+    // 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 == 10 || c == 13)
+            c = KEY_ENTER;
+        else
+        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:
+                    editState->debugMsg("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:
+                    editState->debugMsg("Unhandled ESC-[*~ key sequence 0x%02x\n", c);
+                    c = ERR;
+                    break;
+                }
+                // Get the trailing ~
+                if (c != ERR)
+                    wgetch(stdscr);
+            }
+            else
+            if (c >= 0x31 && c <= 0x39)
+            {
+                c = c - 0x30 + 0x5000;
+            }
+            else
+            {
+                editState->debugMsg("Unhandled ESC key sequence 0x%02x\n", c);
+            }
+        }
+#if defined(__WIN32) && defined(PDCURSES)
+        else
+        if (c >= 0x198 && c <= 0x1a0)
+        {
+            c = c - 0x198 + 0x5000;
+        }
+#endif
+
+        switch (c)
+        {
+#ifdef KEY_RESIZE
+        case KEY_RESIZE:
+            resize_term(0, 0);
+            erase();
+            timeout(SET_DELAY);
+            nnwin_reset();
+            editState->update = TRUE;
+            break;
+#endif
+
+        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--;
+            editBuf->dirty = 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++;
+            editBuf->dirty = TRUE;
+            break;
+
+        case KEY_HOME:
+            nn_editbuf_setpos(editBuf, 0);
+            break;
+        case KEY_END:
+            nn_editbuf_setpos(editBuf, editBuf->len);
+            break;
+        case KEY_LEFT:
+            nn_editbuf_setpos(editBuf, editBuf->pos - 1);
+            break;
+        case KEY_RIGHT:
+            nn_editbuf_setpos(editBuf, editBuf->pos + 1);
+            break;
+
+        case KEY_BACKSPACE:
+        case 0x08:
+        case 0x7f:
+            nn_editbuf_delete(editBuf, editBuf->pos - 1);
+            nn_editbuf_setpos(editBuf, editBuf->pos - 1);
+            break;
+
+        case KEY_DC: // Delete character
+            nn_editbuf_delete(editBuf, editBuf->pos);
+            break;
+
+        case KEY_IC: // Ins = Toggle insert / overwrite mode
+            editState->insertMode = !editState->insertMode;
+            break;
+
+        case KEY_F(2): // F2 = Clear editbuffer
+            nn_editbuf_clear(editBuf);
+            break;
+
+        case 0x0c: // Ctrl + L
+            editState->update = TRUE;
+            break;
+
+        case ERR:
+            // Ignore
+            break;
+
+        default:
+            if (!callback(c, editBuf, editState))
+            {
+                if (isprint(c) || c == 0xe4 || c == 0xf6 || c == 0xc4 || c == 0xd6)
+                {
+                    if (editState->insertMode)
+                        nn_editbuf_insert(editBuf, editBuf->pos, c);
+                    else
+                        nn_editbuf_write(editBuf, editBuf->pos, c);
+                    nn_editbuf_setpos(editBuf, editBuf->pos + 1);
+                }
+                else
+                    editState->debugMsg("Unhandled key: 0x%02x\n", c);
+            }
+            break;
+        }
+    }
+    while (c != ERR && ++cnt < 10);
+}
+
+
+
+char *nnwin_prompt_requester(BOOL allowEmpty, nn_editstate_t *editState,
+    BOOL (*callback)(int, nn_editbuf_t *, nn_editstate_t *),
+    void (*update)(nn_editbuf_t *, nn_editstate_t *))
+{
+    nn_editbuf_t *editBuf = nn_editbuf_new(NN_TMPBUF_SIZE);
+    char *res;
+
+    editState->done = FALSE;
+    while (!editState->isError && !editState->exitProg && !editState->done)
+    {
+        nnwin_input_process(editBuf, editState, callback);
+        update(editBuf, editState);
+    }    
+    
+    if (allowEmpty || editBuf->len > 0)
+        res = nn_editbuf_get_string(editBuf, 0, editBuf->len);
+    else
+        res = NULL;
+
+    nn_editbuf_free(editBuf);
+    
+    return res;
+}
--- a/ui.h	Tue Jun 05 19:56:57 2012 +0300
+++ b/ui.h	Tue Jun 05 22:16:42 2012 +0300
@@ -7,9 +7,13 @@
 #define LIBNNUI_H
 
 #ifdef __WIN32
+#define SET_DELAY          (0)
 // Undefine because both windows.h and curses.h #define it
 #undef MOUSE_MOVED
+#else
+#define SET_DELAY          (5)
 #endif
+
 #ifdef HAVE_NCURSES_H
 #include <ncurses.h>
 #else
@@ -17,7 +21,7 @@
 #endif
 #include "th_types.h"
 #include "th_string.h"
-
+#include "network.h"
 
 #define SET_MAX_WINDOWS   (32)
 #define NN_BACKBUF_LEN    (512)       // Backbuffer size (in lines)
@@ -43,6 +47,15 @@
 } nn_window_t;
 
 
+typedef struct
+{
+    time_t prevKeepAlive;
+    BOOL insertMode, exitProg, isError, update, mask, done;
+    nn_conn_t *conn;
+    void (*debugMsg)(const char *fmt, ...);
+} nn_editstate_t;
+
+
 extern nn_window_t *currWin;
 extern BOOL cursesInit;
 extern int scrHeight, scrWidth;
@@ -51,7 +64,7 @@
 void           nnwin_shutdown();
 void           nnwin_reset(void);
 
-void           nnwin_update(BOOL force, nn_editbuf_t *ebuf, char *optUserName, int optUserColor);
+void           nnwin_update(BOOL force, BOOL mask, nn_editbuf_t *ebuf, char *optUserName, int optUserColor);
 
 nn_window_t *  nnwin_main_window();
 nn_window_t *  nnwin_get(const int index);
@@ -62,6 +75,12 @@
 void           nnwin_close(nn_window_t *win);
 
 int            nnwin_print(nn_window_t *win, const char *fmt);
-char *         nnwin_prompt_requester(const char *info, BOOL allowEmpty);
+
+void nnwin_input_process(nn_editbuf_t *editBuf, nn_editstate_t *editState,
+    BOOL (*callback)(int, nn_editbuf_t *, nn_editstate_t *));
+
+char *         nnwin_prompt_requester(BOOL allowEmpty, nn_editstate_t *,
+    BOOL (*callback)(int, nn_editbuf_t *, nn_editstate_t *),
+    void (*update)(nn_editbuf_t *, nn_editstate_t *));
 
 #endif
--- a/util.c	Tue Jun 05 19:56:57 2012 +0300
+++ b/util.c	Tue Jun 05 22:16:42 2012 +0300
@@ -426,10 +426,7 @@
     char *str;
     size_t siz;
 
-    if (buf == NULL)
-        return NULL;
-
-    if (end > buf->len || start >= buf->len)
+    if (buf == NULL || end > buf->len || start >= buf->len)
         return NULL;
 
     if (start <= end)