In a comment, you mention that
Existing code uses readline and ansi escape strings for nice terminal display...
You really should put that information into the question, since it is quite important.
The fact that your code base uses readline
severely conditions your possibilities, since readline
does not actually work with ncurses
. In order to convert the program to use ncurses
, you would need to recreate those features of readline
which you rely upon. There may be additional libraries which can help with that, but I don't know of any.
On the other hand, ncurses is capable of dividing the screen into non-overlapping regions, and scrolling these regions independently. This is exactly what you need for an application which wants to keep status messages in a status line. Since version 5.7, released about a decade ago, ncurses has primitive threads support (on Linux, anyway), and it is possible to assign different "windows" (screen regions) to different threads. man curs_threads
provides some information.
Ncurses also provides easy-to-use interfaces which can replace the use of console control sequences.
So that's probably the long-term solution, but it's going to be a fair amount of work. In the meantime, it is just barely possible to do what you want by using features built in to the readline
library. Or at least I was able to write a proof-of-concept which successfully maintained a status line while accepting user input from readline. The import aspect of this solution is that readline
is (almost) always active; that is, that there is a thread which is in a hard loop calling readline and passing the buffer read to a processing thread. (With my POC implementation, if the thread calling readline also processes input and the input processing takes a significant amount of time, then the status line will not be updated while input processing takes place.)
The key is the rl_event_hook
function, which is called periodically by readline
(about 10 times per second) while it is waiting for input. My implementation of rl_event_hook
looks like this:
/* This function is never called directly. Enable it by setting:
* rl_event_hook = event_hook
* before the first call to readline.
*/
int event_hook(void) {
char* msg = NULL;
pthread_mutex_lock(&status_mutex_);
if (status_line_) {
msg = status_line_;
status_line_ = NULL;
}
pthread_mutex_unlock(&status_mutex_);
if (msg) {
free(saved_msg_);
saved_msg_ = msg; /* Save for redisplay */
/* Return value of `get_cursor` is a pointer to the `R` in the
* input buffer, or NULL to indicate that the status reply
* couldn't be parsed.
*/
char cursor_buf[2 + sizeof "x1b[999;999R"];
char* action = get_cursor(cursor_buf, sizeof cursor_buf - 1);
if (action) {
set_cursor(1, 1);
fputs(msg, stdout);
clear_eol();
*action = 'H';
fputs(cursor_buf, stdout);
}
}
return 0;
}
In order to get a status message to display, you need to lock the mutex and set status_line_
to a dynamically-allocated buffer containing the status line:
/* Set the status message, so that it will (soon) be shown */
void show_status(char* msg) {
pthread_mutex_lock(&status_mutex_);
free(status_line_);
status_line_ = msg;
pthread_mutex_unlock(&status_mutex_);
}
Since readline
does not preserve the status line when a newline character is read (and in certain other cases), and nothing will prevent the screen from scrolling when you send output to it, the above code keeps the current status line in saved_msg_
so that it can be redisplayed when necessary:
/* Show the status message again */
void reshow_status(void) {
pthread_mutex_lock(&status_line_mutex_);
msg_ = saved_msg_;
saved_msg_ = NULL;
pthread_mutex_unlock(&status_line_mutex_);
}
That's a pretty messy solution, and about the best that can be said for it is that it mostly works, in an imaginary context which might or might not have anything to do with your actual use case. (It's not perfect. There's at least one race condition, although it doesn't actually get triggered in my test code because the only calls to reshow_status
are performed in the thread which calls readline
, and so the function is only called if there is no possibility for the event hook to run.
It might also be possible for user input to be interlaced with the console's status return, but I think this will be very rare. My implementation of get_cursor
does attempt to deal with the possibility of user input characters arriving after the status request has been sent and before the status reply has been received:
fputs("\x1b[6n", stdout);
int ch;
while((ch = getchar()) != 0x1b) rl_stuff_char(ch);
I didn't test this thoroughly so about all I can say is that it seemed to work. (rl_stuff_char
inserts a character into a buffer to be used the next time that readline
's input loop runs.)