From 669bc5fa330a1ce3ddc5f7d44baadc62717fb1ca Mon Sep 17 00:00:00 2001 From: nick black Date: Mon, 12 Apr 2021 04:02:23 -0400 Subject: [PATCH] [mlterm] sixel cursor hack At least mlterm unhides the cursor after emitting a Sixel, even if it was hidden beforehand. Track this behavior using 'sprixel_cursor_hack' in the tinfo cache. Set this based on an "mlterm" TERM heuristic match. When it is set, supply the 'civis' capability as cursor_hack in blitterargs, and emit it at the end of the sixel in sixel_blit() #1524. --- src/lib/internal.h | 44 +++++++++++++++++++++++++++++--------------- src/lib/sixel.c | 26 +++++++++++++++++--------- src/lib/terminfo.c | 2 ++ src/lib/visual.c | 6 ++++++ 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/lib/internal.h b/src/lib/internal.h index 300dc04b5..9959488b4 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -299,7 +299,8 @@ typedef struct nctabbed { nctabbed_options opts; // copied in nctabbed_create() } nctabbed; -// terminfo cache +// terminfo cache. FIXME shrink this and kill a pointer deref by writing them +// all into one buffer, and storing 1-biased indices with 0 for NULL. typedef struct tinfo { unsigned colors;// number of colors terminfo reported usable for this screen char* sgr; // set many graphics properties at once @@ -340,12 +341,6 @@ typedef struct tinfo { char* getm; // get mouse events char* smcup; // enter alternate mode char* rmcup; // restore primary mode - bool RGBflag; // "RGB" flag for 24bpc truecolor - bool CCCflag; // "CCC" flag for palette set capability - bool BCEflag; // "BCE" flag for erases with background color - bool AMflag; // "AM" flag for automatic movement to next line - bool utf8; // are we using utf-8 encoding, as hoped? - // we use the cell's size in pixels for pixel blitting. this information can // be acquired on all terminals with pixel support. int cellpixy; // cell pixel height, might be 0 @@ -356,10 +351,14 @@ 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; + + // sprixel support. there are several different sprixel protocols, of + // which we support sixel and kitty. the kitty protocol is used based + // on TERM heuristics. otherwise, we attempt to detect sixel support, and + // query the details of the implementation. pthread_mutex_t pixel_query; // only query for pixel support once int color_registers; // sixel color registers (post pixel_query_done) int sixel_maxx, sixel_maxy; // sixel size maxima (post pixel_query_done) - bool bitmap_supported; // do we support bitmaps (post pixel_query_done)? int sprixelnonce; // next sprixel id int (*pixel_destroy)(const struct notcurses* nc, const struct ncpile* p, FILE* out, sprixel* s); // wipe out a cell's worth of pixels from within a sprixel. for sixel, this @@ -368,16 +367,27 @@ typedef struct tinfo { int (*pixel_cell_wipe)(const struct notcurses* nc, sprixel* s, int y, int x); int (*pixel_init)(int fd); int (*pixel_draw)(const struct notcurses* n, const struct ncpile* p, sprixel* s, FILE* out); - bool pixel_query_done; // have we yet performed pixel query? - bool quadrants; // do we have (good, vetted) Unicode 1 quadrant support? - bool sextants; // do we have (good, vetted) Unicode 13 sextant support? - bool braille; // do we have Braille support? (linux console does not) + bool bitmap_supported; // do we support bitmaps (post pixel_query_done)? + bool sprixel_cursor_hack; // do sprixels reset the cursor? (mlterm) + bool pixel_query_done; // have we yet performed pixel query? // alacritty went rather off the reservation for their sixel support. they // reply to DSA with CSI?6c, meaning VT102, but no VT102 had Sixel support, - // and indeed they don't respond to XTSMGRAPHICS (which we need to query - // after validating basic Sixel). so if the TERM variable contains - // "alacritty", *and* we get VT102, we go ahead and query XTSMGRAPHICS. + // so if the TERM variable contains "alacritty", *and* we get VT102, we go + // ahead and query XTSMGRAPHICS. bool alacritty_sixel_hack; + + bool RGBflag; // "RGB" flag for 24bpc truecolor + bool CCCflag; // "CCC" flag for palette set capability + bool BCEflag; // "BCE" flag for erases with background color + bool AMflag; // "AM" flag for automatic movement to next line + + // assigned based off nl_langinfo() in notcurses_core_init() + bool utf8; // are we using utf-8 encoding, as hoped? + + // these are assigned wholly through TERM-based heuristics + bool quadrants; // do we have (good, vetted) Unicode 1 quadrant support? + 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 { @@ -516,6 +526,10 @@ typedef struct { int celldimy; // vertical pixels per cell int colorregs; // number of color registers sprixel* spx; // sprixel object + // in at least mlterm, emitting a sixel makes the cursor visible. + // if the cursor is hidden, and sprixel_cursor_hack is set, this + // is set to the civis capability. + const char* cursor_hack; } pixel; // for pixels } u; } blitterargs; diff --git a/src/lib/sixel.c b/src/lib/sixel.c index 4a384d3d1..651a6913c 100644 --- a/src/lib/sixel.c +++ b/src/lib/sixel.c @@ -137,9 +137,13 @@ update_deets(uint32_t rgb, cdetails* deets){ // count, and a sum of all three channels. in addition, we track whether we've // seen at least two colors in the chunk. static inline int -extract_color_table(const uint32_t* data, int linesize, int begy, int begx, int cols, - int leny, int lenx, int cdimy, int cdimx, sixeltable* stab, - sprixcell_e* tacache, uint32_t transcolor){ +extract_color_table(const uint32_t* data, int linesize, int cols, + int leny, int lenx, sixeltable* stab, + sprixcell_e* tacache, const blitterargs* bargs){ + const int begx = bargs->begx; + const int begy = bargs->begy; + const int cdimy = bargs->u.pixel.celldimy; + const int cdimx = bargs->u.pixel.celldimx; unsigned char mask = 0xc0; int pos = 0; // pixel position for(int visy = begy ; visy < (begy + leny) ; visy += 6){ // pixel row @@ -147,7 +151,7 @@ extract_color_table(const uint32_t* data, int linesize, int begy, int begx, int for(int sy = visy ; sy < (begy + leny) && sy < visy + 6 ; ++sy){ // offset within sprixel const uint32_t* rgb = (data + (linesize / 4 * sy) + visx); int txyidx = (sy / cdimy) * cols + (visx / cdimx); - if(rgba_trans_p(*rgb, transcolor)){ + if(rgba_trans_p(*rgb, bargs->transcolor)){ if(tacache[txyidx] == SPRIXCELL_NORMAL){ tacache[txyidx] = SPRIXCELL_CONTAINS_TRANS; } @@ -316,7 +320,8 @@ write_rle(int* printed, int color, FILE* fp, int seenrle, unsigned char crle){ // Emit the sprixel in its entirety, plus enable and disable pixel mode. // Closes |fp| on all paths. static int -write_sixel_data(FILE* fp, int leny, int lenx, const sixeltable* stab, int* parse_start){ +write_sixel_data(FILE* fp, int leny, int lenx, const sixeltable* stab, int* parse_start, + const char* cursor_hack){ // Set P2=1, turning empty pixels transparent *parse_start = fprintf(fp, "\eP0;1;0q"); // Set Raster Attributes - pan/pad=1 (pixel aspect ratio), Ph=lenx, Pv=leny @@ -377,6 +382,9 @@ write_sixel_data(FILE* fp, int leny, int lenx, const sixeltable* stab, int* pars // \x9c: 8-bit "string terminator" (end sixel) doesn't work on at // least xterm; we instead use '\e\\' fprintf(fp, "\e\\"); + if(cursor_hack){ + fprintf(fp, "%s", cursor_hack); + } if(fclose(fp) == EOF){ return -1; } @@ -398,7 +406,8 @@ sixel_blit_inner(int leny, int lenx, const sixeltable* stab, int rows, int cols, } int parse_start = 0; // calls fclose() on success - if(write_sixel_data(fp, leny, lenx, stab, &parse_start)){ + if(write_sixel_data(fp, leny, lenx, stab, &parse_start, + bargs->u.pixel.cursor_hack)){ free(buf); return -1; } @@ -459,9 +468,8 @@ int sixel_blit(ncplane* n, int linesize, const void* data, } memset(tacache, 0, sizeof(*tacache) * rows * cols); } - if(extract_color_table(data, linesize, bargs->begy, bargs->begx, cols, leny, lenx, - bargs->u.pixel.celldimy, bargs->u.pixel.celldimx, - &stable, tacache, bargs->transcolor)){ + if(extract_color_table(data, linesize, cols, leny, lenx, + &stable, tacache, bargs)){ if(!reuse){ free(tacache); } diff --git a/src/lib/terminfo.c b/src/lib/terminfo.c index 5a26ea592..2471fe39d 100644 --- a/src/lib/terminfo.c +++ b/src/lib/terminfo.c @@ -80,6 +80,7 @@ apply_term_heuristics(tinfo* ti, const char* termname){ // st had neithersextants nor quadrants last i checked (0.8.4) }else if(strstr(termname, "mlterm")){ ti->quadrants = true; // good quadrants, no sextants as of 3.9.0 + ti->sprixel_cursor_hack = true; }else if(strstr(termname, "xterm")){ // xterm has nothing beyond halfblocks. this is going to catch all kinds // of people using xterm when they shouldn't be, or even real database @@ -88,6 +89,7 @@ apply_term_heuristics(tinfo* ti, const char* termname){ }else if(strcmp(termname, "linux") == 0){ ti->braille = false; // no braille, no sextants in linux console // FIXME if the NCOPTION_NO_FONT_CHANGES, this isn't true + // FIXME we probably want to do this based off ioctl()s in linux.c ti->quadrants = true; // we program quadrants on the console } // run a wcwidth(⣿) to guarantee libc Unicode 3 support, independent of term diff --git a/src/lib/visual.c b/src/lib/visual.c index 1012fb00d..072bff4db 100644 --- a/src/lib/visual.c +++ b/src/lib/visual.c @@ -686,6 +686,12 @@ ncplane* ncvisual_render_pixels(notcurses* nc, ncvisual* ncv, const struct blits ncv->spx = sprixel_recycle(n, ncv); } bargs.u.pixel.spx = ncv->spx; + // FIXME only set this if cursor is indeed hidden + if(nc->tcache.sprixel_cursor_hack){ + bargs.u.pixel.cursor_hack = nc->tcache.civis; + }else{ + bargs.u.pixel.cursor_hack = NULL; + } if(ncvisual_blit(ncv, disprows, dispcols, n, bset, disprows, dispcols, &bargs)){ goto err; }