Non-Blocking Getch(), Ncurses

Non-blocking getch(), ncurses

The curses library is a package deal. You can't just pull out one routine and hope for the best without properly initializing the library. Here's a code that correctly blocks on getch():

#include <curses.h>

int main(void) {
initscr();
timeout(-1);
int c = getch();
endwin();
printf ("%d %c\n", c, c);
return 0;
}

Ncurses, non-blocking getch misses first character

What do you expect?

if((getch()) == ERR)
{
// no user input
}

discards the first character, if there is one.

Non blocking getch()

It's operating system specific but your library probably has a function called kbhit() or similar that will do this

Get full strings from `getstr` in ncurses in a non-blocking way

As it happens, I'm also working on something with a similar requirement (not a chat application though).

To reiterate what I said before: if you don't need to render anything, you could just use a second thread. Buf if you do need to render while waiting for input, you'll need to do the input handling yourself.

Why? because ncurses just talks to the terminal through stdin / stdout. That means you only get one cursor for handling both input and output, so if you move the cursor to print some output, the in-progress input will get messed up.

But it's not that hard to interpret & render the input yourself. Here's a reduced version of my first-pass solution:

// 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;
}

There are plenty of control characters which haven't been implemented there (most notably INSERT), but it should be quite straightforward to add anything you think is important to your particular application. Also note that if you want unicode (recommended) you'll need to use ncursesw and its alternative functions.

ncurses non-blocking read pushes cursor to bottom of window

Agreeing that it seems surprising, but SVr4 curses (which ncurses does match in this detail) always moves to the next row after completing the (attempt to) read characters.

You can see the corresponding code for (Open)Solaris at Illumos's Github in lines 191-207:

/*
* The following code is equivalent to waddch(win, '\n')
* except that it does not do a wclrtoeol.
*/
if (doecho) {
SP->fl_echoit = TRUE;
win->_curx = 0;
if (win->_cury + 1 > win->_bmarg)
(void) wscrl(win, 1);
else
win->_cury++;

win->_sync = savsync;
win->_immed = savimmed;
win->_leave = savleave;
(void) wrefresh(win);
}

that is, the "win->_cury++;" (or the scrolling operation).



Related Topics



Leave a reply



Submit