notcurses_stop_minimal: use reliable I/O

fflush(), so far as i can tell, cannot be reliably used
in glibc. after it fails once, subsequent calls return 0
and do not set errno -- only the persistent ferror() serves
to indicate that flushing is not going on. instead, write
into the render state's memstream, and then use reliable
blocking_write() to dump it to stdout. closes #1872.
This commit is contained in:
nick black 2021-07-04 05:09:17 -04:00
parent ed8c369d4e
commit dc02a8f04b
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC
3 changed files with 50 additions and 36 deletions

View File

@ -1250,12 +1250,7 @@ bool ncdirect_canutf8(const ncdirect* n){
} }
int ncdirect_flush(const ncdirect* nc){ int ncdirect_flush(const ncdirect* nc){
while(fflush(nc->ttyfp) == EOF){ return ncflush(nc->ttyfp);
if(errno != EAGAIN){
return -1;
}
}
return 0;
} }
int ncdirect_check_pixel_support(const ncdirect* n){ int ncdirect_check_pixel_support(const ncdirect* n){

View File

@ -1204,14 +1204,24 @@ tty_emit(const char* seq, int fd){
return 0; 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 static inline int
ncflush(FILE* out){ ncflush(FILE* out){
while(fflush(out) == EOF){ if(ferror(out)){
if(errno != EAGAIN && errno != EINTR && errno != EBUSY){ logerror("Not attempting a flush following error\n");
logerror("Unrecoverable error flushing io (%s)\n", strerror(errno)); }
return -1; if(fflush(out) == EOF){
} logerror("Unrecoverable error flushing io (%s)\n", strerror(errno));
return -1;
} }
return 0; return 0;
} }
@ -1304,6 +1314,24 @@ coerce_styles(FILE* out, const tinfo* ti, uint16_t* curstyle,
return ret; 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? // how many edges need touch a corner for it to be printed?
static inline unsigned static inline unsigned
box_corner_needs(unsigned ctlword){ box_corner_needs(unsigned ctlword){
@ -1591,8 +1619,6 @@ cellcmp_and_dupfar(egcpool* dampool, nccell* damcell,
return 1; return 1;
} }
int set_fd_nonblocking(int fd, unsigned state, unsigned* oldstate);
int get_tty_fd(FILE* ttyfp); int get_tty_fd(FILE* ttyfp);
// Given the four channels arguments, verify that: // Given the four channels arguments, verify that:

View File

@ -53,7 +53,12 @@ int reset_term_attributes(const tinfo* ti, FILE* fp){
static int static int
notcurses_stop_minimal(void* vnc){ notcurses_stop_minimal(void* vnc){
notcurses* nc = 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; int ret = 0;
ret |= drop_signals(nc);
// be sure to write the restoration sequences *prior* to running rmcup, as // be sure to write the restoration sequences *prior* to running rmcup, as
// they apply to the screen (alternate or otherwise) we're actually using. // they apply to the screen (alternate or otherwise) we're actually using.
const char* esc; 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. // 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 // we only want to do it when in the middle of a rasterization, though. FIXME
if(nc->tcache.pixel_shutdown){ if(nc->tcache.pixel_shutdown){
ret |= nc->tcache.pixel_shutdown(nc->ttyfp); ret |= nc->tcache.pixel_shutdown(out);
} }
ret |= notcurses_mouse_disable(nc); ret |= mouse_disable(out);
ret |= reset_term_attributes(&nc->tcache, nc->ttyfp); ret |= reset_term_attributes(&nc->tcache, out);
if(nc->ttyfd >= 0){ if(nc->ttyfd >= 0){
if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){ 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; ret = -1;
} }
if(term_emit(esc, nc->ttyfp, false)){ if(term_emit(esc, out, false)){
ret = -1; ret = -1;
} }
} }
ret |= tcsetattr(nc->ttyfd, TCSANOW, &nc->tcache.tpreserved); 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; ret = -1;
} }
const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM); 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; ret = -1;
} }
if(ncflush(nc->ttyfp)){ if(fflush(out)){
ret = -1; return -1;
} }
// see #1872; without keeping this at the end, we run into mysterious, return blocking_write(fileno(nc->ttyfp), nc->rstate.mstream, nc->rstate.mstrsize);
// poorly-understood issues during shutdown =[. cowardly. FIXME
ret |= drop_signals(nc);
return ret;
} }
// make a heap-allocated wchar_t expansion of the multibyte string at s // 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; 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){ int notcurses_mouse_enable(notcurses* n){
if(n->ttyfd >= 0){ 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; return 0;
} }
@ -2175,9 +2171,6 @@ int notcurses_mouse_enable(notcurses* n){
// the sequences 1000 etc? // the sequences 1000 etc?
int notcurses_mouse_disable(notcurses* n){ int notcurses_mouse_disable(notcurses* n){
if(n->ttyfd >= 0){ 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; return 0;
} }