以非阻塞方式从 ncurses 中的 getstr 获取完整字符串
Get full strings from `getstr` in ncurses in a non-blocking way
我想做的是进行聊天 window,其中文本会不断更新,然后是输入 window,用户可以在其中输入一些消息以添加到聊天中。
我想知道是否可以与护士一起使用 getstr()
,这样 getstr()
只会 return 在我按下回车键后给我一个字符串。
这将允许我持续监控 TCP 连接以获取新消息,然后在用户输入 his/her 完整消息后向聊天添加消息。
我看到帖子建议 timeout()
的用户,但是这会导致 getstr()
到 return 当用户在一段时间内处于非活动状态时,无论用户输入什么。这并不是我真正想要的,因为在这种情况下 getstr()
仍然在用户输入时阻塞,如果用户在消息中途停下来思考他们正在写什么,那么文本是 return在用户确认这是 he/she 想要通过按回车键发送的消息之前由 getstr()
编辑。
我特别好奇让 getstr()
工作,因为这样我就不必手动处理 delete/backspace/cursor 移动操作。鉴于我的项目范围,投资有点太大了。
碰巧,我也在开发具有类似需求的东西(不过不是聊天应用程序)。
重申一下我之前说过的话:如果你不需要渲染任何东西,你可以使用第二个线程。但是,如果您 需要在等待输入时进行渲染,则需要自己进行输入处理。
为什么?因为 ncurses 只是通过 stdin
/ stdout
与终端对话。这意味着你只有一个光标来处理输入和输出,所以如果你移动光标来打印一些输出,正在进行的输入将被弄乱。
但是自己解释和呈现输入并不难。这是我的第一遍解决方案的简化版本:
// Compile with -lncurses
#include <ncurses.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
struct input_line {
char *ln;
int length;
int capacity;
int cursor;
int last_rendered;
};
void make_buffer(struct input_line *buf) {
buf->ln = NULL;
buf->length = 0;
buf->capacity = 0;
buf->cursor = 0;
buf->last_rendered = 0;
}
void destroy_buffer(struct input_line *buf) {
free(buf->ln);
make_buffer(buf);
}
void render_line(struct input_line *buf) {
int i = 0;
for(; i < buf->length; i ++) {
chtype c = buf->ln[i];
if(i == buf->cursor) {
c |= A_REVERSE;
}
addch(c);
}
if(buf->cursor == buf->length) {
addch(' ' | A_REVERSE);
i ++;
}
int rendered = i;
// Erase previously rendered characters
for(; i < buf->last_rendered; i ++) {
addch(' ');
}
buf->last_rendered = rendered;
}
int retrieve_content(struct input_line *buf, char *target, int max_len) {
int len = buf->length < (max_len - 1) ? buf->length : (max_len - 1);
memcpy(target, buf->ln, len);
target[len] = '[=10=]';
buf->cursor = 0;
buf->length = 0;
return len + 1;
}
void add_char(struct input_line *buf, char ch) {
// Ensure enough space for new character
if(buf->length == buf->capacity) {
int ncap = buf->capacity + 128;
char *nln = (char*) realloc(buf->ln, ncap);
if(!nln) {
// Out of memory!
return;
}
buf->ln = nln;
buf->capacity = ncap;
}
// Add new character
memmove(
&buf->ln[buf->cursor+1],
&buf->ln[buf->cursor],
buf->length - buf->cursor
);
buf->ln[buf->cursor] = ch;
++ buf->cursor;
++ buf->length;
}
int handle_input(struct input_line *buf, char *target, int max_len, int key) {
if(!(key & KEY_CODE_YES) && isprint(key)) {
add_char(buf, key);
return 0;
}
switch(key) {
case ERR: /* no key pressed */ break;
case KEY_LEFT: if(buf->cursor > 0) { buf->cursor --; } break;
case KEY_RIGHT: if(buf->cursor < buf->length) { buf->cursor ++; } break;
case KEY_HOME: buf->cursor = 0; break;
case KEY_END: buf->cursor = buf->length; break;
case '\t':
add_char(buf, '\t');
break;
case KEY_BACKSPACE:
case 127:
case 8:
if(buf->cursor <= 0) {
break;
}
buf->cursor --;
// Fall-through
case KEY_DC:
if(buf->cursor < buf->length) {
memmove(
&buf->ln[buf->cursor],
&buf->ln[buf->cursor+1],
buf->length - buf->cursor - 1
);
buf->length --;
}
break;
case KEY_ENTER:
case '\r':
case '\n':
return retrieve_content(buf, target, max_len);
}
return 0;
}
int get_line_non_blocking(struct input_line *buf, char *target, int max_len) {
while(1) {
int key = getch();
if(key == ERR) {
// No more input
return 0;
}
int n = handle_input(buf, target, max_len, key);
if(n) {
return n;
}
}
}
int main(void) {
initscr();
cbreak(); // Immediate key input
nonl(); // Get return key
timeout(0); // Non-blocking input
keypad(stdscr, 1); // Fix keypad
noecho(); // No automatic printing
curs_set(0); // Hide real cursor
intrflush(stdscr, 0); // Avoid potential graphical issues
leaveok(stdscr, 1); // Don't care where cursor is left
struct input_line lnbuffer;
make_buffer(&lnbuffer);
int lines_read = 0;
while(1) {
char ln[1024];
int len = get_line_non_blocking(&lnbuffer, ln, sizeof(ln));
if(len > 0) {
if(strcmp(ln, "exit") == 0) {
break;
}
mvaddstr(7 + lines_read, 5, ln);
lines_read ++;
}
move(5, 5);
render_line(&lnbuffer);
// Show that we are active
mvaddch(2, 2, '0' + (rand() % 10));
// (probably a good idea to sleep here)
}
destroy_buffer(&lnbuffer);
delwin(stdscr);
endwin();
refresh();
return 0;
}
有很多控制字符尚未在那里实现(最著名的是 INSERT),但是添加您认为对特定应用程序重要的任何内容应该非常简单。另请注意,如果您需要 unicode(推荐),则需要使用 ncursesw 及其替代函数。
我想做的是进行聊天 window,其中文本会不断更新,然后是输入 window,用户可以在其中输入一些消息以添加到聊天中。
我想知道是否可以与护士一起使用 getstr()
,这样 getstr()
只会 return 在我按下回车键后给我一个字符串。
这将允许我持续监控 TCP 连接以获取新消息,然后在用户输入 his/her 完整消息后向聊天添加消息。
我看到帖子建议 timeout()
的用户,但是这会导致 getstr()
到 return 当用户在一段时间内处于非活动状态时,无论用户输入什么。这并不是我真正想要的,因为在这种情况下 getstr()
仍然在用户输入时阻塞,如果用户在消息中途停下来思考他们正在写什么,那么文本是 return在用户确认这是 he/she 想要通过按回车键发送的消息之前由 getstr()
编辑。
我特别好奇让 getstr()
工作,因为这样我就不必手动处理 delete/backspace/cursor 移动操作。鉴于我的项目范围,投资有点太大了。
碰巧,我也在开发具有类似需求的东西(不过不是聊天应用程序)。
重申一下我之前说过的话:如果你不需要渲染任何东西,你可以使用第二个线程。但是,如果您 需要在等待输入时进行渲染,则需要自己进行输入处理。
为什么?因为 ncurses 只是通过 stdin
/ stdout
与终端对话。这意味着你只有一个光标来处理输入和输出,所以如果你移动光标来打印一些输出,正在进行的输入将被弄乱。
但是自己解释和呈现输入并不难。这是我的第一遍解决方案的简化版本:
// Compile with -lncurses
#include <ncurses.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
struct input_line {
char *ln;
int length;
int capacity;
int cursor;
int last_rendered;
};
void make_buffer(struct input_line *buf) {
buf->ln = NULL;
buf->length = 0;
buf->capacity = 0;
buf->cursor = 0;
buf->last_rendered = 0;
}
void destroy_buffer(struct input_line *buf) {
free(buf->ln);
make_buffer(buf);
}
void render_line(struct input_line *buf) {
int i = 0;
for(; i < buf->length; i ++) {
chtype c = buf->ln[i];
if(i == buf->cursor) {
c |= A_REVERSE;
}
addch(c);
}
if(buf->cursor == buf->length) {
addch(' ' | A_REVERSE);
i ++;
}
int rendered = i;
// Erase previously rendered characters
for(; i < buf->last_rendered; i ++) {
addch(' ');
}
buf->last_rendered = rendered;
}
int retrieve_content(struct input_line *buf, char *target, int max_len) {
int len = buf->length < (max_len - 1) ? buf->length : (max_len - 1);
memcpy(target, buf->ln, len);
target[len] = '[=10=]';
buf->cursor = 0;
buf->length = 0;
return len + 1;
}
void add_char(struct input_line *buf, char ch) {
// Ensure enough space for new character
if(buf->length == buf->capacity) {
int ncap = buf->capacity + 128;
char *nln = (char*) realloc(buf->ln, ncap);
if(!nln) {
// Out of memory!
return;
}
buf->ln = nln;
buf->capacity = ncap;
}
// Add new character
memmove(
&buf->ln[buf->cursor+1],
&buf->ln[buf->cursor],
buf->length - buf->cursor
);
buf->ln[buf->cursor] = ch;
++ buf->cursor;
++ buf->length;
}
int handle_input(struct input_line *buf, char *target, int max_len, int key) {
if(!(key & KEY_CODE_YES) && isprint(key)) {
add_char(buf, key);
return 0;
}
switch(key) {
case ERR: /* no key pressed */ break;
case KEY_LEFT: if(buf->cursor > 0) { buf->cursor --; } break;
case KEY_RIGHT: if(buf->cursor < buf->length) { buf->cursor ++; } break;
case KEY_HOME: buf->cursor = 0; break;
case KEY_END: buf->cursor = buf->length; break;
case '\t':
add_char(buf, '\t');
break;
case KEY_BACKSPACE:
case 127:
case 8:
if(buf->cursor <= 0) {
break;
}
buf->cursor --;
// Fall-through
case KEY_DC:
if(buf->cursor < buf->length) {
memmove(
&buf->ln[buf->cursor],
&buf->ln[buf->cursor+1],
buf->length - buf->cursor - 1
);
buf->length --;
}
break;
case KEY_ENTER:
case '\r':
case '\n':
return retrieve_content(buf, target, max_len);
}
return 0;
}
int get_line_non_blocking(struct input_line *buf, char *target, int max_len) {
while(1) {
int key = getch();
if(key == ERR) {
// No more input
return 0;
}
int n = handle_input(buf, target, max_len, key);
if(n) {
return n;
}
}
}
int main(void) {
initscr();
cbreak(); // Immediate key input
nonl(); // Get return key
timeout(0); // Non-blocking input
keypad(stdscr, 1); // Fix keypad
noecho(); // No automatic printing
curs_set(0); // Hide real cursor
intrflush(stdscr, 0); // Avoid potential graphical issues
leaveok(stdscr, 1); // Don't care where cursor is left
struct input_line lnbuffer;
make_buffer(&lnbuffer);
int lines_read = 0;
while(1) {
char ln[1024];
int len = get_line_non_blocking(&lnbuffer, ln, sizeof(ln));
if(len > 0) {
if(strcmp(ln, "exit") == 0) {
break;
}
mvaddstr(7 + lines_read, 5, ln);
lines_read ++;
}
move(5, 5);
render_line(&lnbuffer);
// Show that we are active
mvaddch(2, 2, '0' + (rand() % 10));
// (probably a good idea to sleep here)
}
destroy_buffer(&lnbuffer);
delwin(stdscr);
endwin();
refresh();
return 0;
}
有很多控制字符尚未在那里实现(最著名的是 INSERT),但是添加您认为对特定应用程序重要的任何内容应该非常简单。另请注意,如果您需要 unicode(推荐),则需要使用 ncursesw 及其替代函数。