diff --git a/src/lib/direct.c b/src/lib/direct.c index f8e1e4672..bb911e87b 100644 --- a/src/lib/direct.c +++ b/src/lib/direct.c @@ -1250,12 +1250,7 @@ bool ncdirect_canutf8(const ncdirect* n){ } int ncdirect_flush(const ncdirect* nc){ - while(fflush(nc->ttyfp) == EOF){ - if(errno != EAGAIN){ - return -1; - } - } - return 0; + return ncflush(nc->ttyfp); } int ncdirect_check_pixel_support(const ncdirect* n){ diff --git a/src/lib/internal.h b/src/lib/internal.h index a03a8a964..7dfd25f8a 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -1204,14 +1204,24 @@ tty_emit(const char* seq, int fd){ return 0; } -// reliably flush a FILE* +int set_fd_nonblocking(int fd, unsigned state, unsigned* oldstate); + +// reliably flush a FILE*...except you can't, so far as i can tell. at least +// on glibc, a single fflush() error latches the FILE* error, but ceases to +// perform any work (even following a clearerr()), despite returning 0 from +// that point on. thus, after a fflush() error, even on EAGAIN and friends, +// you can't use the stream any further. doesn't this make fflush() pretty +// much useless? it sure would seem to, which is why we use a memstream for +// all our important I/O, which we then blit with blocking_write(). if you +// care about your data, you'll do the same. static inline int ncflush(FILE* out){ - while(fflush(out) == EOF){ - if(errno != EAGAIN && errno != EINTR && errno != EBUSY){ - logerror("Unrecoverable error flushing io (%s)\n", strerror(errno)); - return -1; - } + if(ferror(out)){ + logerror("Not attempting a flush following error\n"); + } + if(fflush(out) == EOF){ + logerror("Unrecoverable error flushing io (%s)\n", strerror(errno)); + return -1; } return 0; } @@ -1304,6 +1314,24 @@ coerce_styles(FILE* out, const tinfo* ti, uint16_t* curstyle, return ret; } +#define SET_BTN_EVENT_MOUSE "1002" +#define SET_FOCUS_EVENT_MOUSE "1004" +#define SET_SGR_MODE_MOUSE "1006" + +static inline int +mouse_enable(FILE* out){ + return term_emit("\x1b[?" SET_BTN_EVENT_MOUSE ";" + /*SET_FOCUS_EVENT_MOUSE ";" */SET_SGR_MODE_MOUSE "h", + out, false); +} + +static inline int +mouse_disable(FILE* out){ + return term_emit("\x1b[?" SET_BTN_EVENT_MOUSE ";" + /*SET_FOCUS_EVENT_MOUSE ";" */SET_SGR_MODE_MOUSE "l", + out, false); +} + // how many edges need touch a corner for it to be printed? static inline unsigned box_corner_needs(unsigned ctlword){ @@ -1591,8 +1619,6 @@ cellcmp_and_dupfar(egcpool* dampool, nccell* damcell, return 1; } -int set_fd_nonblocking(int fd, unsigned state, unsigned* oldstate); - int get_tty_fd(FILE* ttyfp); // Given the four channels arguments, verify that: diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 353fc2a76..2d7d08900 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -53,7 +53,12 @@ int reset_term_attributes(const tinfo* ti, FILE* fp){ static int notcurses_stop_minimal(void* vnc){ notcurses* nc = vnc; + // collect output into the memstream buffer, and then dump it directly using + // blocking_write(), to avoid problems with unreliable fflush(). + FILE* out = nc->rstate.mstreamfp; + fseeko(out, 0, SEEK_SET); int ret = 0; + ret |= drop_signals(nc); // be sure to write the restoration sequences *prior* to running rmcup, as // they apply to the screen (alternate or otherwise) we're actually using. const char* esc; @@ -61,35 +66,32 @@ notcurses_stop_minimal(void* vnc){ // byte. if we leave an active escape open, it can lock up the terminal. // we only want to do it when in the middle of a rasterization, though. FIXME if(nc->tcache.pixel_shutdown){ - ret |= nc->tcache.pixel_shutdown(nc->ttyfp); + ret |= nc->tcache.pixel_shutdown(out); } - ret |= notcurses_mouse_disable(nc); - ret |= reset_term_attributes(&nc->tcache, nc->ttyfp); + ret |= mouse_disable(out); + ret |= reset_term_attributes(&nc->tcache, out); if(nc->ttyfd >= 0){ if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){ - if(sprite_clear_all(&nc->tcache, nc->ttyfp)){ + if(sprite_clear_all(&nc->tcache, out)){ ret = -1; } - if(term_emit(esc, nc->ttyfp, false)){ + if(term_emit(esc, out, false)){ ret = -1; } } ret |= tcsetattr(nc->ttyfd, TCSANOW, &nc->tcache.tpreserved); } - if((esc = get_escape(&nc->tcache, ESCAPE_RMKX)) && term_emit(esc, nc->ttyfp, false)){ + if((esc = get_escape(&nc->tcache, ESCAPE_RMKX)) && term_emit(esc, out, false)){ ret = -1; } const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM); - if(cnorm && term_emit(cnorm, nc->ttyfp, false)){ + if(cnorm && term_emit(cnorm, out, false)){ ret = -1; } - if(ncflush(nc->ttyfp)){ - ret = -1; + if(fflush(out)){ + return -1; } - // see #1872; without keeping this at the end, we run into mysterious, - // poorly-understood issues during shutdown =[. cowardly. FIXME - ret |= drop_signals(nc); - return ret; + return blocking_write(fileno(nc->ttyfp), nc->rstate.mstream, nc->rstate.mstrsize); } // make a heap-allocated wchar_t expansion of the multibyte string at s @@ -2159,14 +2161,8 @@ ncplane* ncplane_above(ncplane* n){ return n->above; } -#define SET_BTN_EVENT_MOUSE "1002" -#define SET_FOCUS_EVENT_MOUSE "1004" -#define SET_SGR_MODE_MOUSE "1006" int notcurses_mouse_enable(notcurses* n){ if(n->ttyfd >= 0){ - return term_emit(ESC "[?" SET_BTN_EVENT_MOUSE ";" - /*SET_FOCUS_EVENT_MOUSE ";" */SET_SGR_MODE_MOUSE "h", - n->ttyfp, false); } return 0; } @@ -2175,9 +2171,6 @@ int notcurses_mouse_enable(notcurses* n){ // the sequences 1000 etc? int notcurses_mouse_disable(notcurses* n){ if(n->ttyfd >= 0){ - return term_emit(ESC "[?" SET_BTN_EVENT_MOUSE ";" - /*SET_FOCUS_EVENT_MOUSE ";" */SET_SGR_MODE_MOUSE "l", - n->ttyfp, false); } return 0; }