6

Sorry for the long title. I am developing a network program in C which may display messages received from network on stdout and accept user input on stdin via the GNU readline library. The problem is, when the user is typing commands on the main thread via readline, a network message arrives and output to stdout, which will produce something like this:

Scenario:

Input: 1234567890
Network message: Hello
The network message arrives when the user just typed "7"

Actual output on terminal:

Input> 1234567Hello
890_

Is there a way to have the output like this?

Hello
Input> 1234567890_

p.s. _ is the cursor.

Thanks in advance!

howanghk
  • 3,070
  • 2
  • 21
  • 34
  • I think what you want to achieve is rather difficult, due to readline's design. There are tricks you could try with attaching cursor-movement escapes to your message and sending it all in a single `write`, but there still may be race conditions, and it's going to be hard to deal with input lines that have wrapped. – R.. GitHub STOP HELPING ICE Feb 21 '11 at 18:49

4 Answers4

7

OK I have found a solution to this after searching around and made the following replacement to printf(). By using rl_replace_line("", 0) it will clear the current line and place the cursor at the start of line, then I can print a line of message, and then restore the readline prompt, replace the original line back in place, and restore the cursor position.
However, this hack requires any call to this printf function to include a \n at the end, otherwise the line will be overwritten by readline.

#define printf(...) my_rl_printf(__VA_ARGS__)
void my_rl_printf(char *fmt, ...)
{
    int need_hack = (rl_readline_state & RL_STATE_READCMD) > 0;
    char *saved_line;
    int saved_point;
    if (need_hack)
    {
        saved_point = rl_point;
        saved_line = rl_copy_text(0, rl_end);
        rl_save_prompt();
        rl_replace_line("", 0);
        rl_redisplay();
    }

    va_list args;
    va_start(args, fmt);
    vprintf(fmt, args);
    va_end(args);

    if (need_hack)
    {
        rl_restore_prompt();
        rl_replace_line(saved_line, 0);
        rl_point = saved_point;
        rl_redisplay();
        free(saved_line);
    }
}
howanghk
  • 3,070
  • 2
  • 21
  • 34
1

Dump readline and read every char the user is inputting. If you get any network messages while the user is inputting clear the current line, output the network message, then reprint the line the user is currently inputting.

That might seem like a lot of work, but it's the only way that comes to my mind right now.

If you do dump readline then you can use curses which makes it easier...

RedX
  • 14,749
  • 1
  • 53
  • 76
0

You should put some kind of synchronization between those two threads. A mutex lock or just a flag from one thread to another indicating that it is working on the stdin or stdout.

Himanshu
  • 1,989
  • 16
  • 12
  • in this case I have to wait for the user have finish enter the line to display the incoming message. However, the message should display as soon as it arrives. – howanghk Feb 21 '11 at 17:43
  • Oh, then I guess RedX's method would be the answer. – Himanshu Feb 21 '11 at 19:56
0

This can be done using the curses(3) library.

Steve Emmerson
  • 7,702
  • 5
  • 33
  • 59
  • Unfortunately, from [this post](http://stackoverflow.com/questions/691652/using-gnu-readline-how-can-i-add-ncurses-in-the-same-program), curses and readline don't mix. – howanghk Feb 21 '11 at 18:41