require explicit check for pixel support

Add `notcurses_check_pixel_support()` and
`ncdirect_check_pixel_support()` per #1367. Removes
NCOPTION_VERIFY_SIXEL, again per #1367. Adds
`free_terminfo_cache()`, and calls it from both
`notcurses_stop_minimal()` and `ncdirect_stop()`.
Update all documentation. Closes #1371 and #1367.
pull/1377/head
nick black 3 years ago committed by Nick Black
parent 4533d42fa0
commit 6c7c9be6d2

@ -3,11 +3,15 @@ rearrangements of Notcurses.
* 2.2.3 (not yet released)
* Add `SIGILL` to the set of fatal signals we handle.
* Added `NCKEY_SIGNAL`. `NCKEY_RESIZE` is now an alias for `NCKEY_SIGNAL`.
* `SIGCONT` now synthesizes a `NCKEY_SIGNAL`.
* Add the `nctree` widget for line-oriented hierarchical data. See
the new `notcurses_tree(3)` man page for complete information.
* Implemented `NCBLIT_PIXEL` for terminals reporting Sixel support.
Attempt Sixel detection iff `NCOPTION_VERIFY_SIXEL` is provided to
`notcurses_init()`. Sixel detection can delay or even block initialization.
Added `notcurses_check_pixel_support()` and its companion
`ncdirect_check_pixel_support()`, which must be called (and must return
success) before `NCBLIT_PIXEL` will be available. `NCBLIT_PIXEL` degrades
to `NCBLIT_3x2` until support is verified.
* 2.2.2 (2021-02-18):
* `notcurses_stats()` no longer qualifies its `notcurses*` argument with

@ -89,13 +89,14 @@ typedef enum {
// prior to notcurses_init(), you should not set this bit. Even if you are
// invoking setlocale(), this behavior shouldn't be an issue unless you're
// doing something weird (setting a locale not based on LANG).
#define NCOPTION_INHIBIT_SETLOCALE 0x0001
#define NCOPTION_INHIBIT_SETLOCALE 0x0001
// Checking for Sixel support requires writing an escape, and then reading an
// inline reply from the terminal. Since this can interact poorly with actual
// user input, it's not done unless Sixel will actually be used. Set this flag
// to unconditionally test for Sixel support in notcurses_init().
#define NCOPTION_VERIFY_SIXEL 0x0002
// Checking for pixel support might require writing a control sequence, and
// then reading a reply directly from the terminal. If the terminal doesn't
// support this, the application will lock up. If you'll be using pixels, set
// this flag to perform the check in notcurses_init(). You must otherwise call
// notcurses_check_pixel() before NCBLIT_PIXEL will become available.
#define NCOPTION_VERIFY_PIXEL 0x0002ull
// We typically install a signal handler for SIGWINCH that generates a resize
// event in the notcurses_getc() queue. Set to inhibit this handler.
@ -306,6 +307,11 @@ bool notcurses_cansextants(const struct notcurses* nc);
// Can we draw Braille? The Linux console cannot.
bool notcurses_canbraille(const struct notcurses* nc);
// If NCOPTION_VERIFY_PIXEL was not supplied to notcurses_init(), this
// function must successfully return before NCBLIT_PIXEL is available. Returns
// -1 on error, 0 if pixel mode is not supported, or 1 if it is supported.
int notcurses_check_pixel(struct notcurses* nc);
```
## Direct mode

@ -72,6 +72,8 @@ notcurses_direct - minimal notcurses instances for styling text
**bool ncdirect_canutf8(const struct ncdirect* ***n***);**
**int ncdirect_check_pixel_support(struct ncdirect* ***n***);**
**int ncdirect_hline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);**
**int ncdirect_vline_interp(struct ncdirect* ***n***, const char* ***egc***, int ***len***, uint64_t ***h1***, uint64_t ***h2***);**
@ -144,6 +146,10 @@ information, consult **readline(3)**. If you want input echoed to the
terminal while using **ncdirect_readline**, **NCDIRECT_OPTION_INHIBIT_CBREAK**
must be supplied to **ncdirect_init**.
**ncdirect_check_pixel_support** must be called (and successfully return)
before **NCBLIT_PIXEL** can be used to render images; see
**notcurses_visual(3)** for more details.
# RETURN VALUES
**ncdirect_init** returns **NULL** on failure. Otherwise, the return value
@ -153,6 +159,9 @@ to **ncdirect_stop**.
**ncdirect_putstr** and **ncdirect_printf_aligned** return the number of bytes
written on success. On failure, they return some negative number.
**ncdirect_check_pixel_support** returns -1 on error, 0 if there is no pixel
support, and 1 if pixel support is successfully detected.
All other functions return 0 on success, and non-zero on error.
# SEE ALSO
@ -161,5 +170,6 @@ All other functions return 0 on success, and non-zero on error.
**readline(3)**
**notcurses(3)**,
**notcurses_plane(3)**,
**notcurses_visual(3)**,
**terminfo(5)**,
**termios(3)**

@ -12,7 +12,6 @@ notcurses_init - initialize a notcurses instance
```c
#define NCOPTION_INHIBIT_SETLOCALE 0x0001ull
#define NCOPTION_VERIFY_SIXEL 0x0002ull
#define NCOPTION_NO_WINCH_SIGHANDLER 0x0004ull
#define NCOPTION_NO_QUIT_SIGHANDLERS 0x0008ull
#define NCOPTION_SUPPRESS_BANNERS 0x0020ull
@ -111,12 +110,6 @@ zero. The following flags are defined:
the **LANG** environment variable. Your program should call **setlocale(3)**
itself, usually as one of the first lines.
* **NCOPTION_VERIFY_SIXEL**: Checking for Sixel support requires writing an
escape, and then reading an inline reply from the terminal. Since this can
interact poorly with actual user input, it's not done unless Sixel will
actually be used. Set this flag to unconditionally test for Sixel support
in **notcurses_init**.
* **NCOPTION_NO_WINCH_SIGHANDLER**: A signal handler will usually be installed
for **SIGWINCH**, resulting in **NCKEY_RESIZE** events being generated on
input. With this flag, the handler will not be installed.

@ -92,6 +92,8 @@ typedef int (*streamcb)(struct notcurses*, struct ncvisual*, void*);
**int ncplane_qrcode(struct ncplane* ***n***, int* ***ymax***, int* ***xmax***, const void* ***data***, size_t ***len***)**
**int notcurses_check_pixel_support(struct notcurses* ***nc***);**
# DESCRIPTION
An **ncvisual** is a virtual pixel framebuffer. They can be created from
@ -203,6 +205,16 @@ Finally, rendering operates slightly differently when two planes have both been
blitted, and one lies atop the other. See **notcurses_render(3)** for more
information.
# PIXEL BLITTING
Some terminals support pixel-based output, either via Sixel or some bespoke
mechanism. Checking for Sixel support requires interrogating the terminal and
reading a response. This takes time, and will never complete if the terminal
doesn't respond. Before **NCBLIT_PIXEL** can be used, it is thus necessary to
check for support with **notcurses_check_pixel_support**. If this function has
not successfully returned, attempts to use **NCBLIT_PIXEL** will fall back to
**NCBLIT_3x2** (or fail, if **NCVISUAL_OPTION_NODEGRADE** is used).
# RETURN VALUES
**ncvisual_from_file** returns an **ncvisual** object on success, or **NULL**

@ -217,6 +217,11 @@ namespace ncpp
return ncdirect_canutf8 (direct);
}
int check_pixel_support() noexcept
{
return ncdirect_check_pixel_support (direct);
}
private:
ncdirect *direct;
};

@ -216,6 +216,11 @@ API bool ncdirect_canopen_images(const struct ncdirect* n);
// Is our encoding UTF-8? Requires LANG being set to a UTF8 locale.
API bool ncdirect_canutf8(const struct ncdirect* n);
// This function must successfully return before NCBLIT_PIXEL is available.
// Returns -1 on error, 0 for no support, or 1 if pixel output is supported.
// Must not be called concurrently with either input or rasterization.
API int ncdirect_check_pixel_support(struct ncdirect* n);
// Draw horizontal/vertical lines using the specified channels, interpolating
// between them as we go. The EGC may not use more than one column. For a
// horizontal line, |len| cannot exceed the screen width minus the cursor's

@ -9,7 +9,7 @@ extern "C" {
// Special composed key definitions. These values are added to 0x100000.
#define NCKEY_INVALID suppuabize(0)
#define NCKEY_SIGNAL suppuabize(1) // generated internally in response to SIGWINCH / SIGCONT
#define NCKEY_SIGNAL suppuabize(1) // we received either SIGWINCH or SIGCONT
#define NCKEY_UP suppuabize(2)
#define NCKEY_RIGHT suppuabize(3)
#define NCKEY_DOWN suppuabize(4)

@ -826,11 +826,7 @@ typedef enum {
// doing something weird (setting a locale not based on LANG).
#define NCOPTION_INHIBIT_SETLOCALE 0x0001ull
// Checking for Sixel support requires writing an escape, and then reading an
// inline reply from the terminal. Since this can interact poorly with actual
// user input, it's not done unless Sixel will actually be used. Set this flag
// to unconditionally test for Sixel support in notcurses_init().
#define NCOPTION_VERIFY_SIXEL 0x0002ull
// NCOPTION_VERIFY_PIXEL was removed in 2.2.3. It ought be repurposed. FIXME.
// We typically install a signal handler for SIGWINCH that generates a resize
// event in the notcurses_getc() queue. Set to inhibit this handler.
@ -1237,6 +1233,11 @@ API bool notcurses_canbraille(const struct notcurses* nc);
// Can we blit to pixel graphics?
API bool notcurses_canpixel(const struct notcurses* nc);
// This function must successfully return before NCBLIT_PIXEL is available.
// Returns -1 on error, 0 for no support, or 1 if pixel output is supported.
// Must not be called concurrently with either input or rasterization.
API int notcurses_check_pixel_support(struct notcurses* nc);
typedef struct ncstats {
// purely increasing stats
uint64_t renders; // successful ncpile_render() runs

@ -637,6 +637,7 @@ ncdirect_stop_minimal(void* vnc){
ret |= close(nc->ctermfd);
}
ret |= ncdirect_flush(nc);
free_terminfo_cache(&nc->tcache);
return ret;
}
@ -1102,3 +1103,13 @@ int ncdirect_flush(const ncdirect* nc){
}
return 0;
}
int ncdirect_check_pixel_support(ncdirect* n){
if(query_term(&n->tcache, n->ctermfd)){
return -1;
}
if(n->tcache.pixelon){
return 1;
}
return 0;
}

@ -286,11 +286,12 @@ typedef struct tinfo {
// background_opaque is in use. detect this, and avoid the default if so.
// bg_collides_default is either 0x0000000 or 0x1RRGGBB.
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)
pthread_mutex_t pixel_query; // only query for pixel support once
char* pixelon; // enter pixel graphics mode
char* pixeloff; // leave pixel graphics mode
bool sixel; // do we have Sixel support?
bool pixel_query_done; // have we yet performed pixel query?
bool sextants; // do we have (good, vetted) Unicode 13 sextant support?
bool braille; // do we have Braille support? (linux console does not)
} tinfo;
typedef struct ncinputlayer {
@ -386,6 +387,8 @@ int terminfostr(char** gseq, const char* name);
// initialized. set |utf8| if we've verified UTF8 output encoding.
int interrogate_terminfo(tinfo* ti, const char* termname, unsigned utf8);
void free_terminfo_cache(tinfo* ti);
// 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.

@ -777,14 +777,13 @@ init_banner(const notcurses* nc){
term_fg_palindex(nc, stdout, nc->tcache.colors <= 256 ? 50 % nc->tcache.colors : 0x20e080);
printf("\n notcurses %s by nick black et al", notcurses_version());
term_fg_palindex(nc, stdout, nc->tcache.colors <= 256 ? 12 % nc->tcache.colors : 0x2080e0);
printf("\n %d rows %d cols (%sB) %zuB cells %d colors%s%s\n"
printf("\n %d rows %d cols (%sB) %zuB cells %d colors%s\n"
" compiled with gcc-%s, %s-endian\n"
" terminfo from %s\n",
nc->stdplane->leny, nc->stdplane->lenx,
bprefix(nc->stats.fbbytes, 1, prefixbuf, 0), sizeof(nccell),
nc->tcache.colors,
nc->tcache.RGBflag ? "+RGB" : "",
nc->tcache.sixel ? "+Sixel" : "",
__VERSION__,
#ifdef __BYTE_ORDER__
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
@ -908,6 +907,16 @@ recursive_lock_init(pthread_mutex_t *lock){
#endif
}
int notcurses_check_pixel_support(notcurses* nc){
if(query_term(&nc->tcache, nc->ttyfd)){
return -1;
}
if(nc->tcache.pixelon){
return 1;
}
return 0;
}
// FIXME cut this up into a few distinct pieces, yearrrgh
notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
notcurses_options defaultopts;
@ -1033,12 +1042,6 @@ 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;
@ -1215,6 +1218,7 @@ int notcurses_stop(notcurses* nc){
del_curterm(cur_term);
ret |= pthread_mutex_destroy(&nc->statlock);
ret |= pthread_mutex_destroy(&nc->pilelock);
free_terminfo_cache(&nc->tcache);
free(nc);
}
return ret;
@ -2073,7 +2077,7 @@ bool notcurses_canchangecolor(const notcurses* nc){
}
bool notcurses_canpixel(const notcurses* nc){
return nc->tcache.sixel;
return nc->tcache.pixelon;
}
palette256* palette256_new(notcurses* nc){

@ -71,6 +71,10 @@ apply_term_heuristics(tinfo* ti, const char* termname){
return 0;
}
void free_terminfo_cache(tinfo* ti){
pthread_mutex_destroy(&ti->pixel_query);
}
// termname is just the TERM environment variable. some details are not
// exposed via terminfo, and we must make heuristic decisions based on
// the detected terminal type, yuck :/.
@ -184,6 +188,8 @@ int interrogate_terminfo(tinfo* ti, const char* termname, unsigned utf8){
ti->fgop = "\x1b[39m";
ti->bgop = "\x1b[49m";
}
pthread_mutex_init(&ti->pixel_query, NULL);
ti->pixel_query_done = false;
if(apply_term_heuristics(ti, termname)){
return -1;
}
@ -229,8 +235,8 @@ query_sixel(tinfo* ti, int fd){
state = DONE;
}else if(in == '4'){
if(!ti->pixelon){
ti->pixelon = strdup("\ePq");
ti->pixeloff = strdup("\e\\");
ti->pixelon = "\ePq";
ti->pixeloff = "\e\\";
} // FIXME else warning?
}
break;
@ -245,10 +251,18 @@ query_sixel(tinfo* ti, int fd){
return 0;
}
// fd must be a real terminal, and must not be in nonblocking mode
// fd must be a real terminal, and must not be in nonblocking mode. uses the
// pthread_mutex_t of |ti| to only act once.
int query_term(tinfo* ti, int fd){
if(query_sixel(ti, fd)){
if(fd < 0){
return -1;
}
return 0;
int ret = 0;
pthread_mutex_lock(&ti->pixel_query);
if(!ti->pixel_query_done){
ret = query_sixel(ti, fd);
ti->pixel_query_done = true;
}
pthread_mutex_unlock(&ti->pixel_query);
return ret;
}

@ -143,6 +143,9 @@ auto perframe(struct ncvisual* ncv, struct ncvisual_options* vopts,
}else if(keyp >= '0' && keyp <= '8'){ // FIXME eliminate ctrl/alt
marsh->blitter = static_cast<ncblitter_e>(keyp - '0');
vopts->blitter = marsh->blitter;
if(vopts->blitter == NCBLIT_PIXEL){
notcurses_check_pixel_support(nc);
}
continue;
}else if(keyp == NCKey::Up){
// FIXME
@ -280,6 +283,9 @@ int direct_mode_player(int argc, char** argv, ncscale_e scalemode, ncblitter_e b
return -1;
}
bool failed = false;
if(blitter == NCBLIT_PIXEL){
dm.check_pixel_support();
}
{
for(auto i = 0 ; i < argc ; ++i){
try{
@ -303,7 +309,6 @@ 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_PIXEL;
bool quiet = false;
bool loop = false;
@ -348,6 +353,9 @@ auto main(int argc, char** argv) -> int {
vopts.n = *stdn;
vopts.scaling = scalemode;
vopts.blitter = blitter;
if(vopts.blitter == NCBLIT_PIXEL){
notcurses_check_pixel_support(nc);
}
do{
struct marshal marsh = {
.subtitle_plane = nullptr,

Loading…
Cancel
Save