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.
pull/1867/head
nick black 3 years ago
parent ed8c369d4e
commit dc02a8f04b
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC

@ -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){

@ -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:

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

Loading…
Cancel
Save