以非阻塞的方式从ncurses中的`getstr`获取完整的字符串

时间:2016-10-29 12:47:27

标签: c nonblocking ncurses

我要做的是建立一个不断更新文本的聊天窗口,然后是一个输入窗口,用户可以输入一些消息添加到聊天中。

我想知道是否可以getstr()与护士一起使用getstr()只有在我按下回车键后才会向我返回一个字符串。

这将允许我持续监控新消息的TCP连接,然后在用户输入完整消息后向聊天添加消息。

我看过帖子提示timeout()的用户,但这会导致getstr()返回用户在一段时间内处于非活动状态时输入的内容。这不是我真正想要的,因为在这种情况下getstr()在用户输入时仍会阻塞,并且如果用户停下来想一想他们正在写入消息的一半,那么该文本将由getstr()在用户确认这是他/她想要通过按Enter键发送的消息之前。{/ p>

我对让getstr()工作感到特别好奇,因为我不必手动处理删除/退格/光标移动操作。鉴于我的项目范围,投资有点过大。

1 个答案:

答案 0 :(得分:1)

碰巧的是,我也在处理类似要求的事情(不过聊天应用程序)。

重申我之前说的话:如果你不需要渲染任何东西,你可以使用第二个线程。如果你需要在等待输入时进行渲染,你需要自己进行输入处理。

为什么呢?因为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] = '\0';
    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及其替代功能。