diff --git a/src/lib/internal.h b/src/lib/internal.h index ef31cb83d..82b7b666d 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -282,7 +282,7 @@ typedef struct tinfo { uint32_t bg_collides_default; bool sextants; // do we have (good, vetted) Unicode 13 sextant support? bool braille; // do we have Braille support? (linux console does not) - bool libsixel; // do we have Sixel support? + bool sixel; // do we have Sixel support? } tinfo; typedef struct ncinputlayer { @@ -373,8 +373,16 @@ void sigwinch_handler(int signo); void init_lang(notcurses* nc); // nc may be NULL, only used for logging int terminfostr(char** gseq, const char* name); + +// load |ti| from the terminfo database, which must already have been +// initialized. set |utf8| if we've verified UTF8 output encoding. int interrogate_terminfo(tinfo* ti, const char* termname, unsigned utf8); +// perform queries that require writing to the terminal, and reading a +// response, rather than simply reading the terminfo database. can result +// in a lengthy delay or even block if the terminal doesn't respond. +int query_term(tinfo* ti, int fd); + // if there were missing elements we wanted from terminfo, bitch about them here void warn_terminfo(const notcurses* nc, const tinfo* ti); @@ -507,26 +515,30 @@ rgb_greyscale(int r, int g, int b){ return fg * 255; } +// write(2) with retry on partial write or interrupted write +static inline ssize_t +writen(int fd, const void* buf, size_t len){ + ssize_t r; + size_t w = 0; + while(w < len){ + if((r = write(fd, (const char*)buf + w, len - w)) < 0){ + if(errno == EAGAIN || errno == EBUSY || errno == EINTR){ + continue; + } + return -1; + } + w += r; + } + return w; +} + static inline int tty_emit(const char* seq, int fd){ if(!seq){ return -1; } size_t slen = strlen(seq); - size_t written = 0; - do{ - ssize_t ret = write(fd, seq, slen); - if(ret > 0){ - written += ret; - } - if(ret < 0){ - if(errno != EAGAIN){ - break; - } - } - }while(written < slen); - if(written < slen){ -//fprintf(stderr, "Error emitting %zub escape (%s)\n", strlen(seq), strerror(errno)); + if(writen(fd, seq, slen) < 0){ return -1; } return 0; diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 4a0feaced..af32d6492 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1018,9 +1018,6 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){ if(ncinputlayer_init(&ret->input, stdin)){ goto err; } - if(make_nonblocking(ret->input.ttyinfd)){ - goto err; - } // Neither of these is supported on e.g. the "linux" virtual console. if(!(opts->flags & NCOPTION_NO_ALTERNATE_SCREEN)){ terminfostr(&ret->tcache.smcup, "smcup"); @@ -1034,6 +1031,12 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){ goto err; } if(ret->ttyfd >= 0){ + if(opts->flags & NCOPTION_VERIFY_SIXEL){ + if(query_term(&ret->tcache, ret->ttyfd)){ + free_plane(ret->stdplane); + goto err; + } + } if(ret->tcache.smkx && tty_emit(ret->tcache.smkx, ret->ttyfd)){ free_plane(ret->stdplane); goto err; @@ -1044,6 +1047,9 @@ notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){ } reset_term_attributes(ret); } + if(make_nonblocking(ret->input.ttyinfd)){ + goto err; + } if((ret->rstate.mstreamfp = open_memstream(&ret->rstate.mstream, &ret->rstate.mstrsize)) == NULL){ free_plane(ret->stdplane); goto err; @@ -2065,7 +2071,7 @@ bool notcurses_canchangecolor(const notcurses* nc){ } bool notcurses_cansixel(const notcurses* nc){ - return nc->tcache.libsixel; + return nc->tcache.sixel; } palette256* palette256_new(notcurses* nc){ diff --git a/src/lib/terminfo.c b/src/lib/terminfo.c index 9e7615422..f90a67c16 100644 --- a/src/lib/terminfo.c +++ b/src/lib/terminfo.c @@ -189,3 +189,59 @@ int interrogate_terminfo(tinfo* ti, const char* termname, unsigned utf8){ } return 0; } + +static int +query_sixel(tinfo* ti, int fd){ + if(writen(fd, "\033[c", 3) != 3){ + return -1; + } + char in; + enum { + WANT_CSI, + WANT_QMARK, + WANT_SEMI, + WANT_C, + DONE + } state = WANT_CSI; + while(read(fd, &in, 1) == 1){ + switch(state){ + case WANT_CSI: + if(in == NCKEY_ESC){ + state = WANT_QMARK; + } + break; + case WANT_QMARK: + if(in == '?'){ + state = WANT_SEMI; + } + break; + case WANT_SEMI: + if(in == ';'){ + state = WANT_C; + } + break; + case WANT_C: + if(in == 'c'){ + state = DONE; + }else if(in == '4'){ + ti->sixel = true; + } + break; + case DONE: + default: + break; + } + if(state == DONE){ + break; + } + } + return 0; +} + +// fd must be a real terminal, and must not be in nonblocking mode +int query_term(tinfo* ti, int fd){ + if(query_sixel(ti, fd)){ + return -1; + } + return 0; +} diff --git a/src/player/play.cpp b/src/player/play.cpp index d56489f4d..60148a66b 100644 --- a/src/player/play.cpp +++ b/src/player/play.cpp @@ -303,6 +303,7 @@ auto main(int argc, char** argv) -> int { float timescale, displaytime; ncscale_e scalemode; notcurses_options ncopts{}; + ncopts.flags = NCOPTION_VERIFY_SIXEL; ncblitter_e blitter = NCBLIT_DEFAULT; bool quiet = false; bool loop = false;