以非阻塞方式从 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 及其替代函数。