在评论中,您提到了这一点
现有代码使用 readline 和 ansi 转义字符串来实现漂亮的终端显示...
您确实应该将这些信息放入问题中,因为它非常重要。
您的代码库使用readline 的事实严重限制了您的可能性,因为readline 实际上不适用于ncurses。为了将程序转换为使用ncurses,您需要重新创建您所依赖的readline 的那些功能。可能有其他库可以帮助解决这个问题,但我不知道。
另一方面,ncurses 能够将屏幕划分为不重叠的区域,并独立滚动这些区域。这正是您想要将状态消息保留在状态行中的应用程序所需要的。从大约十年前发布的 5.7 版开始,ncurses 具有原始线程支持(无论如何在 Linux 上),并且可以将不同的“窗口”(屏幕区域)分配给不同的线程。 man curs_threads 提供了一些信息。
Ncurses 还提供易于使用的界面,可以替代控制台控制序列的使用。
所以这可能是长期的解决方案,但这将是相当多的工作。与此同时,几乎不可能通过使用readline 库中内置的功能来做你想做的事。或者至少我能够编写一个概念验证,它在接受来自 readline 的用户输入的同时成功地维护了一个状态行。此解决方案的导入方面是 readline (几乎)始终处于活动状态;也就是说,有一个线程在调用 readline 并将缓冲区读取传递给处理线程的硬循环中。 (在我的 POC 实现中,如果调用 readline 的线程也处理输入并且输入处理需要大量时间,那么在进行输入处理时状态行将不会更新。)
关键是rl_event_hook函数,它在等待输入时被readline周期性调用(大约每秒10次)。我对rl_event_hook 的实现如下所示:
/* 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;
}
为了显示状态消息,您需要锁定互斥体并将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_);
}
由于readline 在读取换行符时(以及在某些其他情况下)不保留状态行,并且当您向其发送输出时不会阻止屏幕滚动,因此上述代码保留当前状态行在saved_msg_ 中,以便在必要时重新显示:
/* 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_);
}
这是一个非常混乱的解决方案,可以说的最好的一点是,它主要是在一个与您的实际用例可能或可能没有任何关系的虚构上下文中工作的。 (这并不完美。至少有一个竞态条件,尽管它实际上并没有在我的测试代码中被触发,因为对reshow_status 的唯一调用是在调用readline 的线程中执行的,因此只调用该函数如果事件挂钩不可能运行。
用户输入也可能与控制台的状态返回交错,但我认为这种情况非常罕见。我对get_cursor 的实现确实尝试处理在发送状态请求之后和收到状态回复之前到达的用户输入字符的可能性:
fputs("\x1b[6n", stdout);
int ch;
while((ch = getchar()) != 0x1b) rl_stuff_char(ch);
我没有对此进行彻底的测试,所以我只能说它似乎有效。 (rl_stuff_char 将一个字符插入到缓冲区中,以便下次 readline 的输入循环运行时使用。)