diff --git a/src/lib/input.c b/src/lib/input.c index 257dd9349..dbf994522 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -722,6 +722,7 @@ void ncinputlayer_stop(ncinputlayer* nilayer){ if(nilayer->ttyfd >= 0){ close(nilayer->ttyfd); } + // do *not* close infd; it's just a fileno extracted from stdin input_free_esctrie(&nilayer->inputescapes); } @@ -1372,18 +1373,20 @@ control_read(int ttyfd, query_state* qstate){ return -1; } errno = 0; - while((s = read(ttyfd, buf, BUFSIZ)) != -1){ - for(ssize_t idx = 0; idx < s ; ++idx){ - int r = pump_control_read(qstate, buf[idx]); - if(r == 1){ // success! - free(buf); -//fprintf(stderr, "at end, derived terminal %d\n", inits.qterm); - return 0; - }else if(r < 0){ - goto err; + do{ + while((s = read(ttyfd, buf, BUFSIZ)) != -1){ + for(ssize_t idx = 0; idx < s ; ++idx){ + int r = pump_control_read(qstate, buf[idx]); + if(r == 1){ // success! + free(buf); + //fprintf(stderr, "at end, derived terminal %d\n", inits.qterm); + return 0; + }else if(r < 0){ + goto err; + } } } - } + }while(errno == EINTR || errno == EAGAIN); err: fprintf(stderr, "Reading control replies failed on %d (%s)\n", ttyfd, strerror(errno)); free(buf); diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 797c58759..ab9dfb349 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1178,7 +1178,7 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){ } if(ret->rstate.logendy >= 0){ // if either is set, both are if(!ret->suppress_banner && ret->ttyfd >= 0){ - if(locate_cursor(&ret->tcache, ret->ttyfd, &ret->rstate.logendy, &ret->rstate.logendx)){ + if(locate_cursor(&ret->tcache, &ret->rstate.logendy, &ret->rstate.logendx)){ free_plane(ret->stdplane); goto err; } diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c index a6ccc7113..61519a87b 100644 --- a/src/lib/termdesc.c +++ b/src/lib/termdesc.c @@ -792,17 +792,54 @@ char* termdesc_longterm(const tinfo* ti){ return ret; } -// send a u7 request, and wait until we have a cursor report -int locate_cursor(tinfo* ti, int fd, int* cursor_y, int* cursor_x){ - if(fd < 0){ - logwarn("Can't request on fd %d\n", fd); +// when we have input->ttyfd, everything's simple -- we're reading from a +// different source than the user is, so we can just write the query, and block +// on the response, easy peasy. +// FIXME still, we ought reuse buffer, and pass on any excess reads... +static int +locate_cursor_simple(int fd, const char* u7, int* cursor_y, int* cursor_x){ + char* buf = malloc(BUFSIZ); + if(buf == NULL){ + return -1; + } + if(tty_emit(u7, fd)){ + free(buf); return -1; } + ssize_t r; + // FIXME rigourize for multiple reads + if((r = read(fd, buf, BUFSIZ - 1)) > 0){ + buf[r] = '\0'; + if(sscanf(buf, "\e[%d;%dR", cursor_y, cursor_x) != 2){ + loginfo("Not a cursor location report: %s\n", buf); + free(buf); + return -1; + } + --*cursor_y; + --*cursor_x; + } + free(buf); + loginfo("Located cursor with %d: %d/%d\n", fd, *cursor_y, *cursor_x); + return 0; +} + +// send a u7 request, and wait until we have a cursor report. if input's ttyfd +// is valid, we can just camp there. otherwise, we need dance with potential +// user input looking at infd. +int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x){ const char* u7 = get_escape(ti, ESCAPE_DSRCPR); if(u7 == NULL){ logwarn("No support in terminfo\n"); return -1; } + if(ti->input.ttyfd >= 0){ + return locate_cursor_simple(ti->input.ttyfd, u7, cursor_y, cursor_x); + } + int fd = ti->input.infd; + if(fd < 0){ + logwarn("No valid path for cursor report\n"); + return -1; + } bool emitted_u7 = false; // only want to send one max cursorreport* clr; loginfo("Acquiring input lock\n"); @@ -822,14 +859,13 @@ int locate_cursor(tinfo* ti, int fd, int* cursor_y, int* cursor_x){ // return to us holding the input lock. ncinput_extract_clrs(&ti->input); if( (clr = ti->input.creport_queue) ){ - logdebug("Hustled up a CL report\n"); break; } pthread_cond_wait(&ti->input.creport_cond, &ti->input.lock); } ti->input.creport_queue = clr->next; pthread_mutex_unlock(&ti->input.lock); - loginfo("Got a report %d/%d\n", clr->y, clr->x); + loginfo("Got a report from %d %d/%d\n", fd, clr->y, clr->x); *cursor_y = clr->y; *cursor_x = clr->x; if(ti->inverted_cursor){ diff --git a/src/lib/termdesc.h b/src/lib/termdesc.h index 0490ac45d..9797c35be 100644 --- a/src/lib/termdesc.h +++ b/src/lib/termdesc.h @@ -89,9 +89,12 @@ typedef struct ncinputlayer { // only allow one reader at a time, whether it's the user trying to do so, // or our desire for a cursor report competing with the user. pthread_mutex_t lock; + // must be held to operate on the cursor report queue shared between pure + // input and the control layer. + pthread_cond_t creport_cond; // ttyfd is only valid if we are connected to a tty, *and* stdin is not - // connected to that tty. in that case, we read control sequences only - // from ttyfd. + // connected to that tty (this usually means stdin was redirected). in that + // case, we read control sequences only from ttyfd. int ttyfd; // file descriptor for connected tty int infd; // file descriptor for processing input, from stdin unsigned char inputbuf[BUFSIZ]; @@ -108,7 +111,6 @@ typedef struct ncinputlayer { uint64_t input_events; struct esctrie* inputescapes; // trie of input escapes -> ncspecial_keys cursorreport* creport_queue; // queue of cursor reports - pthread_cond_t creport_cond; bool user_wants_data; // a user context is active bool inner_wants_data; // if we're blocking on input } ncinputlayer; @@ -219,7 +221,7 @@ void free_terminfo_cache(tinfo* ti); // return a heap-allocated copy of termname + termversion char* termdesc_longterm(const tinfo* ti); -int locate_cursor(tinfo* ti, int fd, int* cursor_y, int* cursor_x); +int locate_cursor(tinfo* ti, int* cursor_y, int* cursor_x); #ifdef __cplusplus }