From fb8b453d61c01c7c689952de107282299ec7c539 Mon Sep 17 00:00:00 2001 From: nick black Date: Sun, 11 Jul 2021 11:46:16 -0400 Subject: [PATCH] get look_for_clrs() down on paper #1692 --- src/lib/input.c | 76 ++++++++++++++++++++++++++++++++++------------ src/lib/input.h | 5 +++ src/lib/termdesc.c | 13 ++++++++ src/lib/termdesc.h | 2 ++ 4 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/lib/input.c b/src/lib/input.c index 383e627f1..64700e216 100644 --- a/src/lib/input.c +++ b/src/lib/input.c @@ -470,7 +470,7 @@ handle_queued_input(ncinputlayer* nc, ncinput* ni, // is attempt to fill up the input ringbuffer, exiting either when that // condition is met, or when we get an EAGAIN. it does no processing. static int -fill_buffer(ncinputlayer* nc, const sigset_t* sigmask){ +fill_buffer(ncinputlayer* nc){ ssize_t r = 0; size_t rlen; //fprintf(stderr, "OCCUPY: %u@%u read: %d %zd\n", nc->inputbuf_occupied, nc->inputbuf_write_at, nc->inputbuf[nc->inputbuf_write_at], r); @@ -480,23 +480,13 @@ fill_buffer(ncinputlayer* nc, const sigset_t* sigmask){ if(rlen >= sizeof(nc->inputbuf) / sizeof(*nc->inputbuf) - nc->inputbuf_write_at){ rlen = sizeof(nc->inputbuf) / sizeof(*nc->inputbuf) - nc->inputbuf_write_at; } - while((r = read(nc->infd, nc->inputbuf + nc->inputbuf_write_at, rlen)) > 0){ + if((r = read(nc->infd, nc->inputbuf + nc->inputbuf_write_at, rlen)) > 0){ nc->inputbuf_write_at += r; if(nc->inputbuf_write_at == sizeof(nc->inputbuf) / sizeof(*nc->inputbuf)){ nc->inputbuf_write_at = 0; } nc->inputbuf_occupied += r; - // specify a 0 timeout, meaning we only check to see if there's more - // input available immediately. basically, if we only read through the - // end of the ringbuffer, this gets us the first part filled as well. - // this is less about performance, and more about avoiding partial - // reads of multibyte characters and control sequences. - const struct timespec ts = {}; - if(block_on_input(nc->infd, &ts, sigmask) < 1){ - break; - } - } - if(r < 0){ + }else if(r < 0){ if(errno != EAGAIN && errno != EBUSY && errno != EWOULDBLOCK){ return -1; } @@ -508,9 +498,8 @@ fill_buffer(ncinputlayer* nc, const sigset_t* sigmask){ // user-mode call to actual input i/o, which will get the next character from // the input buffer. static char32_t -handle_input(ncinputlayer* nc, ncinput* ni, int leftmargin, int topmargin, - const sigset_t* sigmask){ - fill_buffer(nc, sigmask); +handle_input(ncinputlayer* nc, ncinput* ni, int leftmargin, int topmargin){ + fill_buffer(nc); // highest priority is resize notifications, since they don't queue if(resize_seen){ resize_seen = 0; @@ -520,12 +509,11 @@ handle_input(ncinputlayer* nc, ncinput* ni, int leftmargin, int topmargin, } static char32_t -handle_ncinput(ncinputlayer* nc, ncinput* ni, int leftmargin, int topmargin, - const sigset_t* sigmask){ +handle_ncinput(ncinputlayer* nc, ncinput* ni, int leftmargin, int topmargin){ if(ni){ memset(ni, 0, sizeof(*ni)); } - char32_t r = handle_input(nc, ni, leftmargin, topmargin, sigmask); + char32_t r = handle_input(nc, ni, leftmargin, topmargin); // ctrl (*without* alt) + letter maps to [1..26], and is independent of shift // FIXME need to distinguish between: // - Enter and ^J @@ -563,7 +551,7 @@ ncinputlayer_prestamp(ncinputlayer* nc, const struct timespec *ts, errno = 0; if(block_on_input(nc->infd, ts, sigmask) > 0){ //fprintf(stderr, "%d events from input!\n", events); - return handle_ncinput(nc, ni, leftmargin, topmargin, sigmask); + return handle_ncinput(nc, ni, leftmargin, topmargin); } //fprintf(stderr, "ERROR: %d events from input!\n", events); return -1; @@ -1414,6 +1402,8 @@ int ncinputlayer_init(tinfo* tcache, FILE* infp, queried_terminals_e* detected, nilayer->inputbuf_write_at = 0; nilayer->input_events = 0; nilayer->creport_queue = NULL; + nilayer->user_wants_data = false; + nilayer->inner_wants_data = false; pthread_cond_init(&nilayer->creport_cond, NULL); int csifd = nilayer->ttyfd >= 0 ? nilayer->ttyfd : nilayer->infd; if(isatty(csifd)){ @@ -1444,3 +1434,49 @@ int ncinputlayer_init(tinfo* tcache, FILE* infp, queried_terminals_e* detected, } return 0; } + +// assuming the user context is not active, go through current data looking +// for a cursor location report. if we find none, block on input, and read if +// appropriate. we can be interrupted by a new user context. we enter holding +// the input lock, and leave holding the input lock, giving it up only while +// blocking for readable action. +void ncinput_extract_clrs(ncinputlayer* ni){ + do{ + // FIXME optimize this via remembered offset + invalidation + if(ni->inputbuf_occupied){ + // FIXME look through outstanding data + if(ni->creport_queue){ + return; + } + } + size_t rlen = input_queue_space(ni); + if(rlen){ + if(rlen >= sizeof(ni->inputbuf) / sizeof(*ni->inputbuf) - ni->inputbuf_write_at){ + rlen = sizeof(ni->inputbuf) / sizeof(*ni->inputbuf) - ni->inputbuf_write_at; + } + logdebug("Reading %zu from %d\n", rlen, ni->infd); + ssize_t r; + if((r = read(ni->infd, ni->inputbuf + ni->inputbuf_write_at, rlen)) > 0){ + logdebug("Read %zu from %d\n", r, ni->infd); + ni->inputbuf_write_at += r; + if(ni->inputbuf_write_at == sizeof(ni->inputbuf) / sizeof(*ni->inputbuf)){ + ni->inputbuf_write_at = 0; + } + ni->inputbuf_occupied += r; + continue; + } + ni->inner_wants_data = true; + pthread_mutex_unlock(&ni->lock); + // specify a NULL timeout, meaning we block as long as we need, until + // there's input available, or we are interrupted by a signal. + logdebug("Blocking on input"); + if(block_on_input(ni->infd, NULL, NULL) < 1){ + pthread_mutex_lock(&ni->lock); // interrupted? + break; + } + ni->inner_wants_data = false; + logdebug("Reacquiring input lock"); + pthread_mutex_lock(&ni->lock); + } + }while(!ni->user_wants_data); +} diff --git a/src/lib/input.h b/src/lib/input.h index af4b27f8d..7220a7c81 100644 --- a/src/lib/input.h +++ b/src/lib/input.h @@ -44,6 +44,11 @@ void ncinputlayer_stop(struct ncinputlayer* nilayer); // FIXME absorb into ncinputlayer_init() int cbreak_mode(int ttyfd, const struct termios* tpreserved); +// assuming the user context is not active, go through current data looking +// for a cursor location report. if we find none, block on input, and read if +// appropriate. we can be interrupted by a new user context. +void ncinput_extract_clrs(struct ncinputlayer* nilayer); + #ifdef __cplusplus } #endif diff --git a/src/lib/termdesc.c b/src/lib/termdesc.c index 1aed607ef..90787d940 100644 --- a/src/lib/termdesc.c +++ b/src/lib/termdesc.c @@ -784,9 +784,12 @@ int locate_cursor(tinfo* ti, int fd, int* cursor_y, int* cursor_x){ } bool emitted_u7 = false; // only want to send one max cursorreport* clr; + loginfo("Acquiring input lock\n"); pthread_mutex_lock(&ti->input.lock); while((clr = ti->input.creport_queue) == NULL){ + logdebug("No report yet\n"); if(!emitted_u7){ + logdebug("Emitting u7\n"); // FIXME i'd rather not do this while holding the lock =[ if(tty_emit(u7, fd)){ pthread_mutex_unlock(&ti->input.lock); @@ -794,9 +797,18 @@ int locate_cursor(tinfo* ti, int fd, int* cursor_y, int* cursor_x){ } emitted_u7 = true; } + // this can block. we must enter holding the input lock, and it will + // return to us holding the input lock. + ncinput_extract_clrs(&ti->input); + if((clr = ti->input.creport_queue) == NULL){ + 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); *cursor_y = clr->y; *cursor_x = clr->x; if(ti->inverted_cursor){ @@ -804,5 +816,6 @@ int locate_cursor(tinfo* ti, int fd, int* cursor_y, int* cursor_x){ *cursor_y = *cursor_x; *cursor_x = tmp; } + free(clr); return 0; } diff --git a/src/lib/termdesc.h b/src/lib/termdesc.h index 75527958d..2416a6b38 100644 --- a/src/lib/termdesc.h +++ b/src/lib/termdesc.h @@ -107,6 +107,8 @@ typedef struct ncinputlayer { 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; // terminal interface description. most of these are acquired from terminfo(5)