Thread: Get stdin contents without newline, then replace

  1. #1
    Registered User
    Join Date
    Nov 2008
    Location
    Phoenix
    Posts
    70

    Get stdin contents without newline, then replace

    I'm learning internet sockets right now and to that end I've made a simple client/server chat program centered around select(). I've got it to where multiple messages can be sent and received on either side and the "prompt" will move down 1 line each time accordingly.

    My only sticking point is when someone is in the middle of typing a message and a new message is received. The message they are currently typing is going to be deleted, so they'll have to start over again. What I want to do is grab the current contents of the stdin buffer (meaning, there's no \n), save it, print the received message and move the prompt downward as usual, and then put that saved message back into the buffer, meaning not only is it back on the screen now, it's erasable too as if nothing ever happened.

    I know that this will definitely be some very very non-standard C, and that's fine. To that end, I've read that curses, GNU readline, and termios are possibilities for this. I've tried those, but am having trouble making it work. Maybe it's me, maybe it's the examples I'm seeing; any help with a more simple and direct example?

    This will be a moot point when I put a GUI on it soon (probably wx, but maybe Qt) since it won't even be an issue, but I'm determined to make this work. Both systems (the "client" and the "server") are Linux, one being Ubuntu and one being Debian.
    Last edited by Sorin; 02-19-2015 at 09:11 AM.

  2. #2
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Don't try to grab the user input line-by-line, but by character-by-character. Yes, this means you'll have to handle stuff like backspace, delete, and character insertion yourself. It's not difficult, however:
    Code:
    #define   NCURSES_WIDECHAR 1
    #include <locale.h>
    #include <string.h>
    #include <wchar.h>
    #include <time.h>
    #include <ncursesw/curses.h>
    
    size_t unicode_to_utf8(char *const utf8, const size_t max,
                           const int *const unicode, const size_t unicode_len)
    {
        const int           *src = unicode;
        const int *const     end = unicode + unicode_len;
        unsigned char *const dst = (unsigned char *const)utf8;
        size_t               len = 0;
    
        while (src < end)
            if (*src > 0 && *src < 0x80) {
                if (len < max)     dst[len] = *src;
                len += 1;
                src++;
            } else
            if (*src >= 0x80 && *src < 0x800) {
                if (len + 0 < max) dst[len + 0] = 0xC0 | ((*src >> 6) & 0x1F);
                if (len + 1 < max) dst[len + 1] = 0x80 | ((*src)      & 0x3F);
                len += 2;
                src++;
            } else
            if (*src >= 0x800 && *src < 0x10000) {
                if (len + 0 < max) dst[len + 0] = 0xE0 | ((*src >> 12) & 0x0F);
                if (len + 1 < max) dst[len + 1] = 0x80 | ((*src >>  6) & 0x3F);
                if (len + 2 < max) dst[len + 2] = 0x80 | ((*src)       & 0x3F);
                len += 3;
                src++;
            } else
            if (*src >= 0x10000 && *src < 0x200000) {
                if (len + 0 < max) dst[len + 0] = 0xF0 | ((*src >> 18) & 0x07);
                if (len + 1 < max) dst[len + 1] = 0x80 | ((*src >> 12) & 0x3F);
                if (len + 2 < max) dst[len + 2] = 0x80 | ((*src >>  6) & 0x3F);
                if (len + 3 < max) dst[len + 3] = 0x80 | ((*src)       & 0x3F);
                len += 4;
                src++;
            } else {
                /* Skip invalid code points. */
                src++;
            }
    
        /* Append EOS. */
        if (len < max)
            dst[len] = '\0';
        else
        if (max > 0)
            dst[max - 1] = '\0';
    
        return len;
    }
    
    int main()
    {
        WINDOW *chat_window;
        WINDOW *input_window;
        time_t  now, then;
        wint_t  c;
        int     t, in_key, in_char;
        int     line_buf[1024], line_len, line_pos;
        char    line_str[4 * sizeof line_buf / sizeof line_buf[0]];
        size_t  line_strlen;
    
        /* Enable wide character support. */
        setlocale(LC_ALL, "");
    
        /* Start curses mode. */
        initscr();
    
        /* No line buffering. */
        raw();
    
        /* No echoing keypresses to the screen. */
        noecho();
    
        /* Reserve five lines for the input window. */
        chat_window = newwin(LINES - 5, COLS, 0, 0);
        input_window = newwin(5, COLS, LINES - 5, 0);
    
        /* The main loop will quit with F4. */
        wclear(chat_window);
        scrollok(chat_window, 1);
        wprintw(chat_window, "Press F4 or CTRL+Q or CTRL+W to quit.");
        wrefresh(chat_window);
    
        /* Show the input prompt. */
        wclear(input_window);
        wprintw(input_window, "> ");
    
        /* wget_wch() returns ERR if there is no key pressed. */
        nodelay(input_window, TRUE);
    
        /* We want F1 et cetera. */
        keypad(input_window, TRUE);
    
        /* No input line yet. */
        line_len = 0;
        line_pos = 0;
        now = time(NULL);
    
        while (1) {
            int changes = 0;
            int cx, cy;
    
            /* Check for pending chat messages. */
            then = now;
            now = time(NULL);
            if (then != now) {
                wprintw(chat_window, "\n\rTick tock.");
                changes = 1;
            }
    
            /* Check for user input. */
            t = wget_wch(input_window, &c);
            if (t == KEY_CODE_YES) {
                in_key = c;
                in_char = 0;
            } else
            if (t == OK) {
                in_key = 0;
                in_char = c;
            } else {
                in_key = 0;
                in_char = 0;
            }
    
            /* F4, Ctrl-Q, and Ctrl-C quit. */
            if (in_key == KEY_F(4) || in_char == 3 || in_char == 17)
                break;
    
            /* Terminal window resized? */
            if (in_key == KEY_RESIZE) {
                wresize(chat_window, LINES - 5, COLS);
                wresize(input_window, 5, COLS);
                mvwin(input_window, LINES - 5, 0);
                changes = 1;
            }
    
            /* Cursor movement left and right. */
            if (in_key == KEY_LEFT && line_pos > 0) {
                line_pos--;
                changes = 1;
            } else
            if (in_key == KEY_RIGHT && line_pos < line_len) {
                line_pos++;
                changes = 1;
            }
    
            /* Up/Down == Home/End */
            if (in_key == KEY_UP || in_key == KEY_HOME) {
                line_pos = 0;
                changes = 1;
            } else
            if (in_key == KEY_DOWN || in_key == KEY_END) {
                line_pos = line_len;
                changes = 1;
            }
    
            /* Backspace deletes the previous character. */
            if (in_key == KEY_BACKSPACE || in_char == '\b') {
                if (line_pos > 0) {
                    if (line_len > line_pos)
                        memmove(line_buf + line_pos - 1, line_buf + line_pos, (line_len - line_pos) * sizeof line_buf[0]);
                    line_pos--;
                    line_len--;
                }
                changes = 1;
            }
    
            /* Delete deletes the next character. */
            if (in_key == KEY_DC || in_char == 127) {
                if (line_pos < line_len) {
                    if (line_pos + 1 < line_len)
                        memmove(line_buf + line_pos, line_buf + line_pos + 1, (line_len - line_pos - 1) * sizeof line_buf[0]);
                    line_len--;
                }
                changes = 1;
            }
    
            /* Enter submits the string. */
            if (in_key == KEY_ENTER || in_char == '\n') {
                line_strlen = unicode_to_utf8(line_str, sizeof line_str, line_buf, line_len);
                if (line_strlen > 0) {
                    /*
                     * TODO: Send line_str (contains line_strlen bytes).
                    */
                    wprintw(chat_window, "\n\rMe: %s", line_str);
                }
                line_len = 0;
                line_pos = 0;
                changes = 1;
            }
    
            /* New character. */
            if (in_char >= 32 && line_len < (int)(sizeof line_buf / sizeof line_buf[0])) {
                if (line_pos < line_len)
                    memmove(line_buf + line_pos + 1, line_buf + line_pos, (line_len - line_pos) * sizeof line_buf[0]);
                line_buf[line_pos] = in_char;
                line_pos++;
                line_len++;
                changes = 1;
            }
    
            /* If any changes, refresh output. */
            if (changes) {
                wrefresh(chat_window);
    
                wclear(input_window);
                wprintw(input_window, "> ");
    
                if (line_pos > 0) {
                    line_strlen = unicode_to_utf8(line_str, sizeof line_str, line_buf, line_pos);
                    wprintw(input_window, "%s", line_str);
                }
                if (line_len > line_pos) {
                    getyx(input_window, cy, cx); /* Macro, not a function. Modifies cx and cy! */
                    line_strlen = unicode_to_utf8(line_str, sizeof line_str, line_buf + line_pos, line_len - line_pos);
                    wprintw(input_window, "%s", line_str);
                    wmove(input_window, cy, cx);
                }
    
                wrefresh(input_window);
            }
        }
    
        /* End curses mode. */
        endwin();
    
        return 0;
    }
    If you save the above as chat-example.c, you can compile and run it using
    Code:
    gcc -Wall -O2 chat-example.c -lncursesw -o chat-example
    ./chat-example
    Make sure you have libncurses5w-dev or libncurses-devel packages installed.

    The above example uses wide character input, and assumes curses provides the keys as unicode code points. An int array is used to store the "string" currently being modified. When needed for output or printing, the unicode_to_utf8() function is used to convert the code points to an UTF-8 string. (Note that the resulting string can be at most four bytes per code point, plus one byte for the end-of-string mark, long.)

    The current input line is printed in two parts: first, up to current cursor position (line_pos), followed by the rest of the line (from line_pos to line_len), with the cursor repositioned into the middle if the second part was printed. That way the cursor should appear in the correct location.

    I don't have Windows, so I cannot check what modifications (aside from the include line) are needed for PDCurses. It might just work as-is, or it might only work for ASCII characters.

    If I were you, I'd consider writing an AJAX interface to your chat server, either as a CGI program, or by extending your chat server to a full HTTP/1.0 service. That way you could use your chat daemon via any browser, as long as the server runs on a machine that you can connect to.

  3. #3
    Registered User
    Join Date
    Nov 2008
    Location
    Phoenix
    Posts
    70
    Very solid and thorough answer Nominal, thanks. Worked great.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. newline
    By st00ch in forum C Programming
    Replies: 6
    Last Post: 03-19-2011, 04:10 AM
  2. fgets with newline in the middle of the stdin
    By sebby64 in forum C Programming
    Replies: 2
    Last Post: 11-27-2010, 01:27 PM
  3. Newbie Question - fflush(stdin) & fpurge(stdin) on Mac and PC
    By tvsinesperanto in forum C Programming
    Replies: 34
    Last Post: 03-11-2006, 12:13 PM
  4. ascii 10 in the stdin after fflush(stdin)??
    By Kev2 in forum A Brief History of Cprogramming.com
    Replies: 3
    Last Post: 06-03-2002, 03:53 PM
  5. Destroy a window and replace the contents
    By Robert602 in forum Windows Programming
    Replies: 1
    Last Post: 03-12-2002, 12:58 PM