I am writing a simple instant messaging client in c. It's currently working well, however if a user is typing and receives a message while typing, the message displays AFTER the text, then the user continues on the line below. It would look like this:
USER: I am trying to ty...FRIEND: Hello
pe a message. <--- (the end of the users message)
My idea is:
Somehow force the current data from stdin and load it into a buffer, then use a \r before printing FRIEND: to erase what is on the line, then print from the buffer. Does anyone have any concrete examples of how to accomplish this task?
The final result should be
FRIEND: Hello
USER: I am trying to type a message
The user started typing the message, received a message, the stdin line was shifted downwards, then the user completed their message.
Note: I am running GNOME Terminal 3.6.2 on the newest version of Linux Mint
The usual way to do this is using ncurses (any flavor of curses would work), accepting the input in one window and writing the result to another. Here is a short example:
#include <curses.h>
int
main(void)
{
bool done = FALSE;
WINDOW *input, *output;
char buffer[1024];
initscr();
cbreak();
echo();
input = newwin(1, COLS, LINES - 1, 0);
output = newwin(LINES - 1, COLS, 0, 0);
wmove(output, LINES - 2, 0); /* start at the bottom */
scrollok(output, TRUE);
while (!done) {
mvwprintw(input, 0, 0, "> ");
if (wgetnstr(input, buffer, COLS - 4) != OK) {
break;
}
werase(input);
waddch(output, '\n'); /* result from wgetnstr has no newline */
waddstr(output, buffer);
wrefresh(output);
done = (*buffer == 4); /* quit on control-D */
}
endwin();
return 0;
}
If you want to learn about VT100 control codes (as distinct from ECMA-48), vt100.net has manuals for some terminals.
Regarding the link VT100 control codes: that is a source of misinformation, as noted in the ncurses FAQ How do I get color with VT100?
Related
I am working a school project (building a very basic shell).
The idea is to be able to do line edition like in bash. For this, I change the terminal mode to non canonical and I stop echo.
I made a very simple code to expose my issue (please note, I do check for functions returns etc... I just made it as short as possible for this post)
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
int ret;
char buff;
char *term_name;
char *termcap;
struct termios termios_new;
struct termios termios_backup;
/*
** Init termcap library
*/
term_name = getenv("TERM");
tgetent(NULL, term_name);
/*
** Get the terminal mode to non canonical and shut down echo
*/
bzero(&termios_new, sizeof(struct termios));
tcgetattr(STDIN_FILENO, &termios_backup);
termios_new = termios_backup;
termios_new.c_lflag &= ~(ICANON);
termios_new.c_lflag &= ~(ECHO);
termios_new.c_cc[VMIN] = 1;
termios_new.c_cc[VTIME] = 0;
/*
** Set the change
*/
tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_new);
/*
** Get the termcap for clearing screen on the particular terminal
*/
termcap = tgetstr("cl", NULL);
/*
** Loop read to get user entries and clear screen for 'c', output char for 'b', break for 'q'
*/
while((ret = read(STDIN_FILENO, &buff, 1)) > 0)
{
if (buff == 'c')
tputs(termcap, 1, putchar);
else if (buff == 'b')
putchar(buff);
else if (buff == 'q')
break ;
buff = 0;
}
/*
** Put back the terminal mode as found before
*/
tcsetattr(STDIN_FILENO, TCSAFLUSH, &termios_backup);
return (0);
}
So basically it's a loop on read to catch user entries. It clears the screen for 'c', output char for 'b', break and restore the original terminal mode for 'q'.
Issue is :
Whenever I type anything, it seems to be buffered because nothing happens until I break the loop with 'q'.
At this moment, the output show on screen, if I typed 5 times b, I'll get the 5 b's and if I types 'c', the screen will be cleared.
BUT, only after typing 'q'.
The behaviour is the same while restoring or not the original terminal mode. (the last line before return)
What I suspect :
After making the code very short and checking all returns, I tend to think there could only be an issue with the way I change the terminal mode ?
I tries with the flags TCSAFLUSH and TCSADRAIN for the tcsetattr function with same result.
Thanks ! :)
✅ SOLVED :
Ok so for anyone who encounter this situation, it's quite interesting because it made me learn many stuffs (well it's a school project so...).
Using putchar in the tputs parameters was the issue, because putchar is buffered, juste like printf.
I ended up trying two things that led me to the realisation: fflush() after the tputs function call would work so obviously the issue was buffer related.
Then tried to output to STDERR_FILENO and it kinda solved it too, and in fact the stderr is the only one _IONBF, so not buffered.
So I ended up creating a putchar function with only write in it, and that was it.
Here are more info about the three buffer modes:
http://software-dl.ti.com/ccs/esd/documents/sdto_cgt_tips_for_using_printf.html
I've started to learn about ANSI escape sequences online through the magic of Google. It is neat being able to position the cursor \e[row;colH on the screen and set the colors of the outputs (ie: \e[31m).
Next I would like try and see how the mouse able to be captured in a virtual terminal. I realize this code is not portable, and I know I can use ncurses or some other curses library, but the goal here is to learn how it works, not write production code with it.
I have tried \e[?1003h and it starts to fill the screen with mouse events. (Pretty cool!) However, how do I capture these in a C or C++ program?
I saw an example of what I would like to do in PHP: https://stackoverflow.com/a/58390575/1770034
However, when I try to port the code over to something in C it just locks up in the while loop. (Tested with GDB to find that out.)
#include <stdio.h> //bring in printf and fread
int main()
{
system("stty -echo"); //make the terminal not output mouse events
system("stty -icanon"); //put stdin in raw mode
printf("\e[?1003h\e[?1015h\e[?1006h"); //start getting mouse events
char* buffer[255];
while(fread(buffer, 16, 1, stdin)) // <-- suppose to read in the mouse events
{
printf("here"); //Did you actually work?!
}
printf("\e[?1000l"); //Turn off mouse events
system("stty echo"); //Turn echoing of the display back on
return 0; //end the program in a successful state
}
I have also tried scanf and it just locks up until I hit enter, and I'm not convinced it is seeing the mouse events.
Edit
I now have some working code that spits out the mouse events.
Here is the updated code from applying the accepted answer to this question:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
system("stty -echo"); //don't show mouse events on screen
system("stty -icanon");
fprintf(stderr, "\e[?1003h\e[?1015h\e[?1006h"); //use stderr since not buffered turn on mouse event capture
char buffer[16] = " ";
char previousBuffer[16] = " ";
//Make standard in not be blocking
int flags = fcntl(stdin->_fileno, F_GETFL, 0);
fcntl(stdin->_fileno, F_SETFL, flags | O_NONBLOCK);
for (int hunSeconds = 0; hunSeconds < 500; hunSeconds++) //Run for 50 seconds
{
read(fileno(stdin), buffer, 16); //read mouse input
if (strcmp(buffer, previousBuffer) != 0) //only show event if it is different
{
fprintf(stderr, "%s", buffer);
strncpy(previousBuffer, buffer, 16);
}
usleep(100); // sleep for .1 seconds
}
printf("\e[?1000l"); //turn off mouse events
system("stty echo"); //turn on screen echo again
return 0;
}
Two problems:
printf is (using stdout) buffered, so there's no guarantee that the escape sequences got to the terminal before attempting to read.
stdin isn't necessarily the terminal (though it might be). Again, fread is buffered (and you may not get the result as promptly as you wish).
Since stderr is not buffered, it would help to send the escape sequences with that stream. Rather than using fread, it can help to use read, e.g.,
read(fileno(stdin), buffer, 16)
What I intend to do is to get the character entered and used it as a pattern. I've tried using getchar() but it won't work. I've hear of using scanf but it skips and stops whenever I press "shift" for the special characters on my keyboard.
int i, j, n;
char c;
c = getchar();
printf("Enter value of n: ");
scanf("%d", &n);
printf("Enter a Character: ");
getchar();
for(i=1; i<=n; i++)
{
for(j=1; j<=i; j++)
{
printf("%c", c);
}
printf("\n");
}
You need to assign the value returned by getchar to the variable c, and you had a redundent call to getchar that's why it skips reading the desired input:
int i, j, n;
char c;
printf("Enter value of n: ");
scanf("%d", &n);
printf("Enter a Character: ");
scanf(" %c", &c);
for(i=1; i<=n; i++)
{
for(j=1; j<=i; j++)
{
printf("%c", c);
}
printf("\n");
}
You can use %c with scanf:
scanf("%d %c", &n, %c);
This eliminates the need for the two getchar calls.
The space is required; it tells scanf to skip whitespace.
The problem you have is that your assumptions on getchar(3) are incorrect. You think getchar() is going to return the next key pressed in the input stream, but you are incorrectly assuming that it will be done without buffering or system processing (the terminal driver gives the program complete lines, or even worse, if you are reading from a file, complete buffer blocks, that have to be buffered so you miss no characters from the input stream)
You are assuming incorrectly that the end of line you need to press for the input to be feeded to the program does not count in the input stream.
What actually happens is:
you feed a complete line (because the kernel driver works that way) so you press your character, and then you see nothing, not after you have pressed the return key.
once you press it, you have more than one character (depending on how many you pressed before hitting the return key) that will stay in the buffer, until they are so consumed by the program. Normally this happens when you have executed more getchar() or scanf() statements.
The idea of this buffering mechanism is to allow a programmer to process character by charcacter large amounts of text, without the overhead of making a system call per character reading (this is a costly operation) so think of getchar() not as a sample function to get new users introduced to the world of programming, but as a hint to experienced programmers to use efficiently without having to think on buffering large amounts of text.
With stdio package, every character counts, so you have to think slowly and minuciously when you feed input to getchar(3).
The next question is: Right, then how can I solve and stop my program until I press some key? The first answer, with the set of tools you have exposed here is, be careful on what you input, instead of asking for any key, ask the user to press the return key, and then, do something like:
printf("Hit <ENTER> to continue"); fflush(stdout); /* so we get the line out, bypassing the buffering mechanism */
int c;
while ((c = getchar()) != EOF && c != '\n') {
/* just ignore the character we have received */
}
/* c == '\n' || c == EOF, so we can continue */
or, if you prefer, you can write a function just to do this (as there can be so many criteria to implement it, nobody included such a function in the standard C library, my apologies for that. ;) )
void wait_for_enter()
{
/* I use stderr, for two reasons:
* o stderr is normally unbuffered, so there's no need to fflush()
* o stdout can be redirected, so the prompt will not be visible in
* case you want to save the output of your program.
*/
fprintf(stderr, "Hit <ENTER> to continue");
int c;
while ((c = getchar()) != EOF && c != '\n') {
/* just ignore the character we have received
* until we get the end of file (ctrl-d at the terminal)
* or a new line */
}
/* c == '\n' || c == EOF, so we can continue */
/* it's assumed that the user pressed the enter key, so the echoed
* enter already did a newline, no need to do it here */
} /* wait_for_enter */
In order to wait for any character and in raw mode, you need first to ensure your input comes from a terminal (you cannot do the following on a normal file), then you have to switch the terminal driver to raw mode, so each character is given immediately to the program and no line editing processing is done, and then set the stdin descriptor to no buffering at all. Only then, you can receive individual characters with getchar(3), one by one, as they are keyed in. I think this is far out of the scope of this question, as the code to do that is far more complex than the above.
EDIT
Following is a complete sample of a program that uses raw input to process characters as they are keyed in.
/* pru.c -- program to show raw input from the terminal.
* Author: Luis Colorado <luiscoloradourcola#gmail.com>
* Date: Fri Sep 20 08:46:06 EEST 2019
* Copyright: (C) 2019 Luis Colorado. All rights reserved.
* License: BSD.
*/
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h> /* see termios(3) for a description on terminal conf */
#define F(_fmt) __FILE__":%d:%s: " _fmt, __LINE__, __func__
/* this function switches the terminal into raw mode and returns a malloc(3)ed
* terminal configuration, so it can be later restored. BEWARE that the returned
* configuration info must be deallocated by free(3) once it's not needed anymore.
* In case of failure of any system call, the function returns NULL, and errno is
* set to the failing cause. */
struct termios *set_raw(int fd)
{
struct termios *ret = malloc(sizeof *ret), cfg;
if (!ret) return NULL;
int res = tcgetattr(fd, &cfg);
if (res < 0) goto error;
*ret = cfg; /* save it for return */
cfmakeraw(&cfg);
/* set it after all buffered characters in the driver have drained out */
res = tcsetattr(fd, TCSADRAIN, &cfg);
if (res < 0) goto error;
return ret;
error:
free(ret);
return NULL;
} /* set_raw */
/* restores the configuration back to the associated file descriptor */
int restore_cfg(int fd, struct termios *cf)
{
/* set it after all buffered characters in the driver have drained out */
return tcsetattr(fd, TCSADRAIN, cf);
} /* restore_cfg */
int main()
{
struct termios *cfg = set_raw(fileno(stdin));
if (!cfg) {
fprintf(stderr, F("stdin: %s\n"),
strerror(errno));
}
setbuf(stdin, NULL); /* stdin unbuffered */
setbuf(stdout, NULL); /* stdout unbuffered */
/* BEWARE that raw mode doesn't process any characters, so no Ctrl-C(interrupt), Ctrl-D(EOF), etc.
* will be available, only if you read from a file, you'll get EOF, but you'll not be able to produce
* that on the terminal, you'll need to swith to another console and kill the process. */
int c;
while ((c = getchar()) != EOF && c != '\033') { /* ESCAPE key('\033') is a safeguard to end input */
/* print the input char as an hex number */
printf("[%02x]", c);
}
if (cfg) { /* if we were able to set the terminal to raw mode */
/* restore config */
restore_cfg(fileno(stdin), cfg);
/* and free it */
free(cfg);
}
exit(EXIT_SUCCESS);
} /* main */
The full source code can be also downloaded from here.
You can use this program to see how input keys get mapped into characters, as you'll note that when you press the enter key, the raw input is [0d] (ascii char 13, CARRY RETURN) while in normal line mode you get '\n' which is [0a] or ASCII LINE FEED, instead (you can check this if you redirect input from the pru.c text file). Also you'll see that you are unable to specify EOF from the terminal driver with Ctrl-D and that Ctrl-C does not come to help. Well, I have included a safeguard, by ending the program in case you press the ESC key, which generates an ASCII ESCAPE character (\033). This is also commented in the source code.
All of this processing is done by the kernel driver, so all unix implementations get the same line end characters or interpret the control characters the same way.
I have got a text based game in c that uses scanf.
There are a few times when the player is supposed to type in things, however, while he isn't, the cursor stays in the game, letting the user type in anything he wants, which ruins future scanfs and the story.
Is there a way to disallow input unless there is a scanf waiting for a response?
I think it would be helpful to step back and think about all the moving parts that exist in the execution environment of your program.
When executed, your program becomes a distinct process running in the multitasking environment of the OS. The terminal is a separate process with an associated GUI window, and which may be running locally or remotely (e.g. someone could theoretically run your game from a remote location by connecting over a network via ssh). The user interacts with the terminal program through their keyboard and screen.
Now, it is actually the terminal process (working closely with the OS kernel) that is responsible for most of the nuances of user input. It is the terminal that prints just-typed characters to its GUI window as soon as it receives them, and it is the terminal that maintains an input buffer of characters that have been typed but that have not yet been read by a foreground process.
Conveniently, terminals allow their behavior to be controlled by a set of configuration settings, and these settings can be changed programmatically during the run-time of the connected program. The C-level API that we can use to read and write these settings is called termios.
There's a great article on terminals I highly recommend: The TTY demystified. For the purposes of this question, the section Configuring the TTY device is most useful. It doesn't demonstrate the termios library directly, but shows how to use the stty utility which uses the termios library internally.
(Note that, although the links I've been giving so far are focused on Linux, they are applicable to all Unix-like systems, which includes Mac OS X.)
Unfortunately there's no way to completely "disallow" input with a single switch, but we can achieve the same effect by toggling a couple of terminal settings and manually discarding buffered input at the right times.
The two terminal settings we need to concern ourselves with are ECHO and ICANON. Both settings are normally on by default.
By turning off ECHO, we can prevent the terminal from printing just-typed characters to the terminal window when it receives them. Hence, while the program is running, any characters the user types will seem to be ignored completely, although they will still be buffered internally by the terminal.
By turning off ICANON, we ensure that the terminal will not wait for an enter keypress to submit a complete line of input before returning input to the program, e.g. when the program makes a read() call. Rather, it will return whatever characters it currently has buffered in its internal input buffer, thereby making it possible for us to discard them immediately and carry on with execution.
The full process will look like this:
1: Disable input, meaning turn off ECHO and ICANON.
2: Run some gameplay with output, not requiring any user input.
3: Enable input, meaning discard any buffered terminal input and then turn on ECHO and ICANON.
4: Read user input.
5: Repeat from step 1. Subsequent gameplay can now make use of the latest user input.
There is a complication in step 3 related to discarding buffered input. We can implement this discarding operation by simply reading input from stdin via read() with a fixed-length buffer until there's no more input to be read. But if there's no input ready to be read at all for the discarding operation, then the first call would block until the user types something. We need to prevent this blocking.
I believe there are two ways this could be done. There's such a thing called a non-blocking read, which can be set up with termios or fcntl() (or by opening a second file descriptor to the same endpoint with the O_NONBLOCK flag, I think) which would cause read() to return immediately with errno set to EAGAIN if it would block. The second way is to poll the file descriptor with poll() or select() to determine if there's data ready to be read; if not, we can avoid the read() call completely.
Here's a working solution that uses select() to avoid blocking:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
struct termios g_terminalSettings; // global to track and change terminal settings
void disableInput(void);
void enableInput(void);
void discardInputBuffer(void);
void discardInputLine(void);
void setTermiosBit(int fd, tcflag_t bit, int onElseOff );
void turnEchoOff(void);
void turnEchoOn(void);
void turnCanonOff(void);
void turnCanonOn(void);
int main(void) {
// prevent input immediately
disableInput();
printf("welcome to the game\n");
// infinite game loop
int line = 1;
int quit = 0;
while (1) {
// print dialogue
for (int i = 0; i < 3; ++i) {
printf("line of dialogue %d\n",line++);
sleep(1);
} // end for
// input loop
enableInput();
int input;
while (1) {
printf("choose a number in 1:3 (-1 to quit)\n");
int ret = scanf("%d",&input);
discardInputLine(); // clear any trailing garbage (can do this immediately for all cases)
if (ret == EOF) {
if (ferror(stdin)) { fprintf(stderr, "[error] scanf() failed: %s", strerror(errno) ); exit(1); }
printf("end of input\n");
quit = 1;
break;
} else if (ret == 0) { // invalid syntax
printf("invalid input\n");
} else if (input == -1) { // quit code
quit = 1;
break;
} else if (!(input >= 1 && input <= 3)) { // invalid value
printf("number is out-of-range\n");
} else { // valid
printf("you entered %d\n",input);
break;
} // end if
} // end while
if (quit) break;
disableInput();
} // end while
printf("goodbye\n");
return 0;
} // end main()
void disableInput(void) {
turnEchoOff(); // so the terminal won't display all the crap the user decides to type during gameplay
turnCanonOff(); // so the terminal will return crap characters immediately, so we can clear them later without waiting for a LF
} // end disableInput()
void enableInput(void) {
discardInputBuffer(); // clear all crap characters before enabling input
turnCanonOn(); // so the user can type and edit a full line of input before submitting it
turnEchoOn(); // so the user can see what he's doing as he's typing
} // end enableInput()
void turnEchoOff(void) { setTermiosBit(0,ECHO,0); }
void turnEchoOn(void) { setTermiosBit(0,ECHO,1); }
void turnCanonOff(void) { setTermiosBit(0,ICANON,0); }
void turnCanonOn(void) { setTermiosBit(0,ICANON,1); }
void setTermiosBit(int fd, tcflag_t bit, int onElseOff ) {
static int first = 1;
if (first) {
first = 0;
tcgetattr(fd,&g_terminalSettings);
} // end if
if (onElseOff)
g_terminalSettings.c_lflag |= bit;
else
g_terminalSettings.c_lflag &= ~bit;
tcsetattr(fd,TCSANOW,&g_terminalSettings);
} // end setTermiosBit()
void discardInputBuffer(void) {
struct timeval tv;
fd_set rfds;
while (1) {
// poll stdin to see if there's anything on it
FD_ZERO(&rfds);
FD_SET(0,&rfds);
tv.tv_sec = 0;
tv.tv_usec = 0;
if (select(1,&rfds,0,0,&tv) == -1) { fprintf(stderr, "[error] select() failed: %s", strerror(errno) ); exit(1); }
if (!FD_ISSET(0,&rfds)) break; // can break if the input buffer is clean
// select() doesn't tell us how many characters are ready to be read; just grab a big chunk of whatever is there
char buf[500];
ssize_t numRead = read(0,buf,500);
if (numRead == -1) { fprintf(stderr, "[error] read() failed: %s", strerror(errno) ); exit(1); }
printf("[debug] cleared %d chars\n",numRead);
} // end while
} // end discardInputBuffer()
void discardInputLine(void) {
// assumes the input line has already been submitted and is sitting in the input buffer
int c;
while ((c = getchar()) != EOF && c != '\n');
} // end discardInputLine()
I should clarify that the discardInputLine() feature I included is completely separate from the discarding of the input buffer, which is implemented in discardInputBuffer() and called by enableInput(). Discarding of the input buffer is an essential step in the solution of temporarily disallowing user input, while discarding the remainder of the input line that is left unread by scanf() is not exactly essential. But I think it does make sense to prevent residual line input from being scanned on subsequent iterations of the input loop. It's also necessary to prevent infinite loops if the user entered invalid input, so for that reason we can probably call it essential.
Here's a demo of me playing around with the input:
welcome to the game
line of dialogue 1
line of dialogue 2
line of dialogue 3
[debug] cleared 12 chars
choose a number in 1:3 (-1 to quit)
0
number is out-of-range
choose a number in 1:3 (-1 to quit)
4
number is out-of-range
choose a number in 1:3 (-1 to quit)
asdf
invalid input
choose a number in 1:3 (-1 to quit)
asdf 1 2 3
invalid input
choose a number in 1:3 (-1 to quit)
0 1
number is out-of-range
choose a number in 1:3 (-1 to quit)
1 4
you entered 1
line of dialogue 4
line of dialogue 5
line of dialogue 6
choose a number in 1:3 (-1 to quit)
2
you entered 2
line of dialogue 7
line of dialogue 8
line of dialogue 9
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 256 chars
[debug] cleared 238 chars
choose a number in 1:3 (-1 to quit)
-1
goodbye
During the first triplet of dialogue I typed 12 random characters which were discarded afterward. Then I demonstrated various types of invalid input and how the program responds to them. During the second triplet of dialogue I didn't type anything, so no characters were discarded. During the final triplet of dialogue I quickly pasted a large block of text into my terminal several times (using a mouse right-click, which is a quick and easy shortcut for pasting into my particular terminal), and you can see it discarded all of it properly, taking several iterations of the select()/read() loop to complete.
On Linux and HP-UX machines, use
to disable display of inputs from keyboard on terminal
stty -echo
to enable display of inputs from keyboard on terminal
stty echo
I use GNU Readline in the "select" fashion, by registering a callback function like so:
rl_callback_handler_install("", on_readline_input);
And then hooking up rl_callback_read_char as the callback for my select() loop for STDIN_FILENO. That's all pretty standard stuff, and works fine.
Now, my program asynchronously prints messages to the screen, sometimes interleaved with input from the user. A "clean" session would look like this:
user input
SERVER OUTPUT
SERVER OUTPUT
user input
SERVER OUTPUT
But what if the user is midway through a line when the server response arrives? Then it gets ugly:
user input
SERVER OUTPUT
user inSERVER OUTPUT
put
SERVER OUTPUT
I fixed this simply by printing a newline before the server output if the user had typed anything (this is easy to tell by checking rl_line_buffer), and then doing rl_forced_update_display() after printing the server output. Now it looks like this:
user input
SERVER OUTPUT
user in
SERVER OUTPUT
user input
SERVER OUTPUT
This is better, but still not perfect. The problem comes when the user typed an entire line but didn't yet press Enter--then it looks like this:
user input
SERVER OUTPUT
user input
SERVER OUTPUT
user input
SERVER OUTPUT
This is bad because it appears to the user that they typed three commands (three responses for three inputs is just as possible as three responses for two inputs, which is what actually happened).
A nasty hack (which works) is to do this:
user input
SERVER OUTPUT
user input - INCOMPLETE
SERVER OUTPUT
user input
SERVER OUTPUT
I figured I could improve this by printing backspace ('\b') characters instead of " - INCOMPLETE", but that doesn't seem to do anything at all on my terminal (gnome-terminal on Ubuntu Hardy). printf("ABC\b"); just prints ABC, for whatever reason.
So how can I erase the incomplete input line? Either by printing backspaces somehow (I can figure out how many to print--it's strlen(rl_line_buffer)), or by using some Readline facility I don't yet know about?
After quite a lot of hacking I was able to get this mechanism. I hope other people will find it useful. It does not even use select(), but I hope you will get the point.
#include <readline/readline.h>
#include <readline/history.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
const char const* prompt = "PROMPT> ";
void printlog(int c) {
char* saved_line;
int saved_point;
saved_point = rl_point;
saved_line = rl_copy_text(0, rl_end);
rl_set_prompt("");
rl_replace_line("", 0);
rl_redisplay();
printf("Message: %d\n", c);
rl_set_prompt(prompt);
rl_replace_line(saved_line, 0);
rl_point = saved_point;
rl_redisplay();
free(saved_line);
}
void handle_line(char* ch) {
printf("%s\n", ch);
add_history(ch);
}
int main() {
int c = 1;
printf("Start.\n");
rl_callback_handler_install(prompt, handle_line);
while (1) {
if (((++c) % 5) == 0) {
printlog(c);
}
usleep(10);
rl_callback_read_char();
}
rl_callback_handler_remove();
}
With spaces? Try to print "\b \b" for each character you want to "delete" rather than a single '\b'.
Edit
How it works
Suppose you have written "Hello, world!" to the display device and you want to replace "world!" with "Jim."
Hello, world!
^ /* active position */ /* now write "\b \b" */
/* '\b' moves the active position back;
// ' ' writes a space (erases the '!')
// and another '\b' to go back again */
Hello, world
^ /* active position */ /* now write "\b \b" again */
Hello, worl
^ /* active position */ /* now write "\b \b" 4 times ... */
Hello,
^ /* active position */ /* now write "Jim." */
Hello, Jim.
^ /* active position */
Portability
I'm not sure, but the Standard specifically describes the behaviour of '\b' and '\r' as has been described in answers to your question.
Section 5.2.2 Character display semantics
> 1 The active position is that location on a display device where the next character output by
> the fputc function would appear. The intent of writing a printing character (as defined
> by the isprint function) to a display device is to display a graphic representation of
> that character at the active position and then advance the active position to the next
> position on the current line. The direction of writing is locale-specific. If the active
> position is at the final position of a line (if there is one), the behavior of the display devic e
> is unspecified.
>
> 2 Alphabetic escape sequences representing nongraphic characters in the execution
> character set are intended to produce actions on display devices as follows:
> \a (alert) Produces an audible or visible alert without changing the active position.
> \b (backspace) Moves the active position to the previous position on the current line. If
> the active position is at the initial position of a line, the behavior of the display
> device is unspecified.
> \f ( form feed) Moves the active position to the initial position at the start of the next
> logical page.
> \n (new line) Moves the active position to the initial position of the next line.
> \r (carriage return) Moves the active position to the initial position of the current line.
> \t (horizontal tab) Moves the active position to the next horizontal tabulation position
> on the current line. If the active position is at or past the last defined horizontal
> tabulation position, the behavior of the display device is unspecified.
> \v (vertical tab) Moves the active position to the initial position of the next vertical
> tabulation position. If the active position is at or past the last defined vertical
> tabulation position, the behavior of the display device is unspecified.
>
> 3 Each of these escape sequences shall produce a unique implementation-defined value
> which can be stored in a single char object. The external representations in a text file
> need not be identical to the internal representations, and are outside the scope of this
> International Standard.
One thing you can do is to use \r to jump to the beginning of the line for the server output. Then you can use field width specifiers to right pad the output to the rest of the line. This will, in effect, overwrite whatever the user had already entered.
fprintf (stdout, "\r%-20s\n", "SERVER OUTPUT");
You may want to fflush(stdout) to ensure that the buffers are in a consistent state before you do that.
I tried to separate server output and user input with ncurses windows. Server output is simulated with a thread. The program run until You enter a line beginning with 'q'.
#include <unistd.h>
#include <curses.h>
#include <pthread.h>
WINDOW *top, *bottom;
int win_update( WINDOW *win, void *data ){
wprintw(win,"%s", (char*)data ); wrefresh(win);
return 0;
}
void *top_thread( void *data ){
char buff[1024];
int i=0;
while(1){
snprintf(buff, 1024, "SERVER OUTPUT: %i\n", i++ );
use_window( top, win_update, (void*)buff );
sleep(1);
}
return NULL;
}
int main(){
initscr();
int maxy, maxx;
getmaxyx( stdscr, maxy, maxx );
top = newwin(maxy-1,maxx,0,0);
wsetscrreg(top,0,maxy-1); idlok(top,1); scrollok(top,1);
pthread_t top_tid;
pthread_create(&top_tid, NULL, top_thread, NULL);
bottom = newwin(1,maxx,maxy-1,0);
char buff[1024], input[maxx];
do{
werase(bottom); wmove(bottom,0,0);
wprintw(bottom,"input> " ); wrefresh(bottom);
wgetnstr(bottom,input,sizeof(input));
snprintf(buff, 1024, "user input: '%s'\n", input );
use_window( top, win_update, (void*)buff );
}while( input[0] != 'q' );
endwin();
}
Do any of these functions help?
rl_reset_line_state()
rl_clear_message()
rl_delete_text()
rl_kill_text()
Also, can you mediate the server output - have the server output controlled so that it only appears when and where you want it to, rather than just sprawling over what the user is typing? For example, if your application is running in curses mode, could you have a split window with a line or two at the bottom in one sub-window reserved for user input and the rest of the output (server output and accepted user input) in a second sub-window above it?
This also seems to work:
rl_clear_visible_line();
printf(...);
rl_reset_line_state();
rl_redisplay();