Merge branch 'dankamongmen/input-thread' of github.com:dankamongmen/notcurses into dankamongmen/input-thread

pull/2166/head
nick black 3 years ago
commit c286c01d16
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC

@ -1,6 +1,11 @@
This document attempts to list user-visible changes and any major internal
rearrangements of Notcurses.
* 2.4.1 (not yet released)
* `notcurses_check_pixel_support()` still returns 0 if there is no support
for bitmap graphics, but now returns an `ncpixelimple_e` to differentiate
the pixel backend otherwise. This result is strictly informative.
* 2.4.0 (2021-09-06)
* Mouse events in the Linux console are now reported from GPM when built
with `-DUSE_GPM=on`.

@ -334,10 +334,10 @@ bool notcurses_cansextants(const struct notcurses* nc);
// Can we draw Braille? The Linux console cannot.
bool notcurses_canbraille(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.
int notcurses_check_pixel_support(struct notcurses* nc);
// Returns a non-zero constant corresponding to some pixel-blitting
// mechanism if bitmap support (via any mechanism) has been detected,
// or else 0 (NCPIXEL_NONE).
ncpixelimpl_e notcurses_check_pixel_support(struct notcurses* nc);
```
## Direct mode

@ -36,7 +36,7 @@ notcurses_capabilities - runtime capability detection
**bool notcurses_canbraille(const struct notcurses* ***nc***);**
**int notcurses_check_pixel_support(struct notcurses* ***nc***);**
**ncpixelimpl_e notcurses_check_pixel_support(struct notcurses* ***nc***);**
# DESCRIPTION
@ -86,8 +86,9 @@ quadrants and halfblocks, respectively. **notcurses_canbraille** returns
**true** if Unicode Braille is expected to work on the terminal. None of
these functions return **true** unless UTF-8 encoding is in use.
**notcurses_check_pixel_support** returns 1 if bitmap support (via any
mechanism) has been detected, and otherwise 0.
**notcurses_check_pixel_support** returns a non-zero pixel implementation
if bitmap support (via any mechanism) has been detected, and otherwise 0
(**NCPIXEL_NONE**).
# NOTES
@ -95,8 +96,6 @@ Some terminals advertise support for TrueColor, but then downsample or
otherwise degrade the provided RGB. In this case **notcurses_cantruecolor**
will return **true**, but the full spectrum will not be available.
# RETURN VALUES
# SEE ALSO
**notcurses(3)**,

@ -540,7 +540,7 @@ ncchannels_set_bg_default(uint64_t* channels){
// An nccell corresponds to a single character cell on some plane, which can be
// occupied by a single grapheme cluster (some root spacing glyph, along with
// possible combining characters, which might span multiple columns). At any
// cell, we can have a theoretically arbitrarily long UTF-8 string, a foreground
// cell, we can have a theoretically arbitrarily long UTF-8 EGC, a foreground
// color, a background color, and an attribute set. Valid grapheme cluster
// contents include:
//
@ -1423,8 +1423,20 @@ API bool notcurses_cansextant(const struct notcurses* nc)
API bool notcurses_canbraille(const struct notcurses* nc)
__attribute__ ((nonnull (1))) __attribute__ ((pure));
// pixel blitting implementations. informative only; don't special-case
// based off any of this information!
typedef enum {
NCPIXEL_NONE = 0,
NCPIXEL_SIXEL, // sixel
NCPIXEL_LINUXFB, // linux framebuffer
NCPIXEL_ITERM2, // iTerm2
NCPIXEL_KITTY_STATIC, // kitty prior to C=1 and animation
NCPIXEL_KITTY_ANIMATED, // kitty with animation but not selfref
NCPIXEL_KITTY_SELFREF, // kitty with reflexive composition
} ncpixelimpl_e;
// Can we blit pixel-accurate bitmaps?
API int notcurses_check_pixel_support(const struct notcurses* nc)
API ncpixelimpl_e notcurses_check_pixel_support(const struct notcurses* nc)
__attribute__ ((nonnull (1))) __attribute__ ((pure));
// whenever a new field is added here, ensure we add the proper rule to

@ -663,6 +663,7 @@ int fpsgraph_init(struct notcurses* nc){
memset(&opts, 0, sizeof(opts));
opts.flags = NCPLOT_OPTION_LABELTICKSD |
NCPLOT_OPTION_EXPONENTIALD |
NCPLOT_OPTION_DETECTMAXONLY |
NCPLOT_OPTION_PRINTSAMPLE;
opts.gridtype = NCBLIT_BRAILLE;
opts.legendstyle = NCSTYLE_ITALIC | NCSTYLE_BOLD;

@ -341,33 +341,36 @@ tinfo_debug_bitmaps(struct ncplane* n, const tinfo* ti, const char* indent){
ti->bg_collides_default & 0xfffffful,
(ti->bg_collides_default & 0x01000000) ? "" : "not ");
finish_line(n);
if(!ti->pixel_draw && !ti->pixel_draw_late){
ncplane_printf(n, "%sno bitmap graphics detected", indent);
}else{ // we do have support; draw one
if(ti->color_registers){
ncpixelimpl_e blit = notcurses_check_pixel_support(ncplane_notcurses(n));
switch(blit){
case NCPIXEL_NONE:
ncplane_printf(n, "%sno bitmap graphics detected", indent);
break;
case NCPIXEL_SIXEL:
if(ti->sixel_maxy){
ncplane_printf(n, "%smax sixel size: %dx%d colorregs: %u",
indent, ti->sixel_maxy, ti->sixel_maxx, ti->color_registers);
}else{
ncplane_printf(n, "%ssixel colorregs: %u", indent, ti->color_registers);
}
}else if(ti->pixel_draw_late){
break;
case NCPIXEL_LINUXFB:
ncplane_printf(n, "%sframebuffer graphics supported", indent);
}else if(ti->sixel_maxy_pristine){
break;
case NCPIXEL_ITERM2:
ncplane_printf(n, "%siTerm2 graphics supported", indent);
break;
case NCPIXEL_KITTY_STATIC:
ncplane_printf(n, "%srgba pixel graphics support", indent);
}else{
ncplane_printf(n, "%srgba pixel animation support", indent);
}
break;
case NCPIXEL_KITTY_ANIMATED:
ncplane_printf(n, "%s1st gen rgba pixel animation support", indent);
break;
case NCPIXEL_KITTY_SELFREF:
ncplane_printf(n, "%s2nd gen rgba pixel animation support", indent);
break;
}
finish_line(n);
/*
ncplane_putstr(n, "\U0001F918");
ncplane_putstr(n, "\U0001F918\u200d\U0001F3FB");
ncplane_putstr(n, "\U0001F918\u200d\U0001F3FC");
ncplane_putstr(n, "\U0001F918\u200d\U0001F3FD");
ncplane_putstr(n, "\U0001F918\u200d\U0001F3FE");
ncplane_putstr(n, "\U0001F918\u200d\U0001F3FF");
*/
}
static void

@ -346,81 +346,11 @@ uint8_t* kitty_trans_auxvec(const tinfo* ti){
return a;
}
// we lay a cell-sixed animation block atop the graphic, giving it a
// cell id with which we can delete it in O(1) for a rebuild. this
// way, we needn't delete and redraw the entire sprixel.
int kitty_wipe_animation(sprixel* s, int ycell, int xcell){
if(init_sprixel_animation(s)){
return -1;
}
logdebug("wiping sprixel %u at %d/%d\n", s->id, ycell, xcell);
fbuf* f = &s->glyph;
if(fbuf_puts(f, "\x1b_Ga=f,x=") < 0){
return -1;
}
if(fbuf_putint(f, xcell * s->cellpxx) < 0){
return -1;
}
if(fbuf_puts(f, ",y=") < 0){
return -1;
}
if(fbuf_putint(f, ycell * s->cellpxy)){
return -1;
}
if(fbuf_puts(f, ",s=") < 0){
return -1;
}
if(fbuf_putint(f, s->cellpxx)){
return -1;
}
if(fbuf_puts(f, ",v=") < 0){
return -1;
}
if(fbuf_putint(f, s->cellpxy)){
return -1;
}
if(fbuf_puts(f, ",i=") < 0){
return -1;
}
if(fbuf_putint(f, s->id)){
return -1;
}
if(fbuf_puts(f, ",X=1,r=1,q=2;") < 0){
return -1;
}
// FIXME ought be smaller around the fringes!
int totalp = s->cellpxy * s->cellpxx;
// FIXME preserve so long as cellpixel geom stays constant?
for(int p = 0 ; p + 3 <= totalp ; p += 3){
if(fbuf_putn(f, "AAAAAAAAAAAAAAAA", strlen("AAAAAAAAAAAAAAAA")) < 0){
return -1;
}
}
if(totalp % 3 == 1){
if(fbuf_putn(f, "AAAAAA==", strlen("AAAAAA==")) < 0){
return -1;
}
}else if(totalp % 3 == 2){
if(fbuf_putn(f, "AAAAAAAAAAA=", strlen("AAAAAAAAAAA=")) < 0){
return -1;
}
}
// FIXME need chunking for cells of 768+ pixels
if(fbuf_putn(f, "\x1b\\", 2) < 0){
return -1;
}
s->invalidated = SPRIXEL_INVALIDATED;
return 1;
}
// FIXME merge back with kitty_wipe_animation
int kitty_wipe_selfref(sprixel* s, int ycell, int xcell){
if(init_sprixel_animation(s)){
return -1;
}
logdebug("Wiping sprixel %u at %d/%d\n", s->id, ycell, xcell);
fbuf* f = &s->glyph;
if(fbuf_printf(f, "\e_Ga=f,x=%d,y=%d,s=%d,v=%d,i=%d,X=1,r=2,c=1,q=2;",
// just dump the wipe into the fbuf -- don't manipulate any state. used both
// by the wipe proper, and when blitting a new frame with annihilations.
static int
kitty_blit_wipe_selfref(sprixel* s, fbuf* f, int ycell, int xcell){
if(fbuf_printf(f, "\x1b_Ga=f,x=%d,y=%d,s=%d,v=%d,i=%d,X=1,r=2,c=1,q=2;",
xcell * s->cellpxx, ycell * s->cellpxy,
s->cellpxx, s->cellpxy, s->id) < 0){
return -1;
@ -449,10 +379,42 @@ int kitty_wipe_selfref(sprixel* s, int ycell, int xcell){
#undef DUONULLALPHA
}
// FIXME need chunking for cells of 768+ pixels
if(fbuf_printf(f, "\e\\\e_Ga=a,i=%d,c=2,q=2;\e\\", s->id) < 0){
if(fbuf_printf(f, "\x1b\\\x1b_Ga=a,i=%d,c=2,q=2\x1b\\", s->id) < 0){
return -1;
}
return 0;
}
// we lay a cell-sixed animation block atop the graphic, giving it a
// cell id with which we can delete it in O(1) for a rebuild. this
// way, we needn't delete and redraw the entire sprixel.
int kitty_wipe_animation(sprixel* s, int ycell, int xcell){
if(init_sprixel_animation(s)){
return -1;
}
logdebug("wiping sprixel %u at %d/%d\n", s->id, ycell, xcell);
fbuf* f = &s->glyph;
if(kitty_blit_wipe_selfref(s, f, ycell, xcell) < 0){
return -1;
}
s->invalidated = SPRIXEL_INVALIDATED;
return 1;
}
int kitty_wipe_selfref(sprixel* s, int ycell, int xcell){
if(init_sprixel_animation(s)){
return -1;
}
const int tyx = xcell + ycell * s->dimx;
int state = s->n->tam[tyx].state;
void* auxvec = s->n->tam[tyx].auxvector;
logdebug("Wiping sprixel %u at %d/%d auxvec: %p state: %d\n", s->id, ycell, xcell, auxvec, state);
fbuf* f = &s->glyph;
if(kitty_blit_wipe_selfref(s, f, ycell, xcell)){
return -1;
}
s->invalidated = SPRIXEL_INVALIDATED;
memcpy(auxvec, &state, sizeof(state));
return 1;
}
@ -717,6 +679,31 @@ destroy_deflator(unsigned animated, z_stream* zctx, int pixy, int pixx){
}
}
// if we're KITTY_SELFREF, and we're blitting a secondary frame, we need
// carry through the TAM's annihilation entires...but we also need load the
// frame *without* annihilations, lest we be unable to build it. we thus go
// back through the TAM following a selfref blit, and any sprixcells which
// are annihilated will have their annhilation appended to the main blit.
// ought only be called for KITTY_SELFREF.
static int
finalize_multiframe_selfref(sprixel* s, fbuf* f){
int prewiped = 0;
for(int y = 0 ; y < s->dimy ; ++y){
for(int x = 0 ; x < s->dimx ; ++x){
int tyxidx = y * s->dimx + x;
int state = s->n->tam[tyxidx].state;
if(state >= SPRIXCELL_ANNIHILATED){
if(kitty_blit_wipe_selfref(s, f, y, x)){
return -1;
}
++prewiped;
}
}
}
loginfo("transitively wiped %d/%d\n", prewiped, s->dimy * s->dimx);
return 0;
}
// we can only write 4KiB at a time. we're writing base64-encoded RGBA. each
// pixel is 4B raw (32 bits). each chunk of three pixels is then 12 bytes, or
// 16 base64-encoded bytes. 4096 / 16 == 256 3-pixel groups, or 768 pixels.
@ -752,6 +739,9 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
int targetout = 0; // number of pixels expected out after this chunk
//fprintf(stderr, "total: %d chunks = %d, s=%d,v=%d\n", total, chunks, lenx, leny);
char out[17]; // three pixels base64 to no more than 17 bytes
// set high if we are (1) reloading a frame with (2) annihilated cells copied over
// from the TAM and (3) we are KITTY_SELFREF. calls finalize_multiframe_selfref().
bool selfref_annihilated = false;
while(chunks--){
// q=2 has been able to go on chunks other than the last chunk since
// 2021-03, but there's no harm in this small bit of backwards compat.
@ -832,16 +822,30 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
// transparent, but we need to update the auxiliary vector.
const int vyx = (y % cdimy) * cdimx + (x % cdimx);
tam[tyx].auxvector[vyx] = ncpixel_a(source[e]);
wipe[e] = 1;
}else if(level == KITTY_SELFREF){
selfref_annihilated = true;
}else{
wipe[e] = 1;
}
if(rgba_trans_p(source[e], transcolor)){
ncpixel_set_a(&source[e], 0); // in case it was transcolor
if(x % cdimx == 0 && y % cdimy == 0){
tam[tyx].state = SPRIXCELL_ANNIHILATED_TRANS;
if(level == KITTY_SELFREF){
*tam[tyx].auxvector = SPRIXCELL_TRANSPARENT;
}
}else if(level == KITTY_SELFREF && tam[tyx].state == SPRIXCELL_ANNIHILATED_TRANS){
*tam[tyx].auxvector = SPRIXCELL_MIXED_KITTY;
}
}else{
if(x % cdimx == 0 && y % cdimy == 0 && level == KITTY_SELFREF){
*tam[tyx].auxvector = SPRIXCELL_OPAQUE_KITTY;
}else if(level == KITTY_SELFREF && *tam[tyx].auxvector == SPRIXCELL_TRANSPARENT){
*tam[tyx].auxvector = SPRIXCELL_MIXED_KITTY;
}
tam[tyx].state = SPRIXCELL_ANNIHILATED;
}
wipe[e] = 1;
}else{
wipe[e] = 0;
if(rgba_trans_p(source[e], transcolor)){
@ -885,6 +889,11 @@ write_kitty_data(fbuf* f, int linesize, int leny, int lenx, int cols,
if(finalize_deflator(&zctx, f, leny, lenx)){
goto err;
}
if(selfref_annihilated){
if(finalize_multiframe_selfref(s, f)){
goto err;
}
}
}
scrub_tam_boundaries(tam, leny, lenx, cdimy, cdimx);
destroy_deflator(animated, &zctx, leny, lenx);

@ -984,11 +984,8 @@ recursive_lock_init(pthread_mutex_t *lock){
#endif
}
int notcurses_check_pixel_support(const notcurses* nc){
if(nc->tcache.pixel_draw || nc->tcache.pixel_draw_late){
return 1;
}
return 0;
ncpixelimpl_e notcurses_check_pixel_support(const notcurses* nc){
return nc->tcache.pixel_implementation;
}
// FIXME cut this up into a few distinct pieces, yearrrgh

@ -198,10 +198,12 @@ int redraw_pixelplot_##T(nc##X##plot* ncp){ \
} \
} \
if(ncp->plot.printsample){ \
int lastslot = ncp->plot.slotstart ? ncp->plot.slotstart - 1 : ncp->plot.slotcount - 1; \
ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
ncplane_set_channels(ncp->plot.ncp, ncp->plot.maxchannels); \
ncplane_printf_aligned(ncp->plot.ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[lastslot]); \
/* FIXME is this correct for double? */ \
/* we use idx, and thus get an immediate count, changing as we load it.
* if you want a stable summary, print the previous slot */ \
ncplane_printf_aligned(ncp->plot.ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[idx]); \
} \
ncplane_home(ncp->plot.ncp); \
struct ncvisual* ncv = ncvisual_from_rgba(pixels, dimy * states, dimx * scale * 4, dimx * scale); \
@ -391,10 +393,9 @@ int redraw_plot_##T(nc##X##plot* ncp){ \
} \
} \
if(ncp->plot.printsample){ \
int lastslot = ncp->plot.slotstart ? ncp->plot.slotstart - 1 : ncp->plot.slotcount - 1; \
ncplane_set_styles(ncp->plot.ncp, ncp->plot.legendstyle); \
ncplane_set_channels(ncp->plot.ncp, ncp->plot.maxchannels); \
ncplane_printf_aligned(ncp->plot.ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[lastslot]); \
ncplane_printf_aligned(ncp->plot.ncp, 0, NCALIGN_RIGHT, "%" PRIu64, (uint64_t)ncp->slots[idx]); \
} \
ncplane_home(ncp->plot.ncp); \
return 0; \
@ -416,11 +417,12 @@ create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T mi
return NULL; \
} \
if(opts->rangex < 0){ \
logerror("Supplied negative independent range %d\n", opts->rangex); \
logerror("error: supplied negative independent range %d\n", opts->rangex); \
ncplane_destroy(n); \
return NULL; \
} \
if(maxy < miny){ \
logerror("error: supplied maxy < miny\n"); \
ncplane_destroy(n); \
return NULL; \
} \
@ -487,7 +489,9 @@ create_##T(nc##X##plot* ncpp, ncplane* n, const ncplot_options* opts, const T mi
ncpp->plot.printsample = opts->flags & NCPLOT_OPTION_PRINTSAMPLE; \
if( (ncpp->plot.detectdomain = (miny == maxy)) ){ \
ncpp->maxy = trueminy; \
ncpp->miny = truemaxy; \
if(!ncpp->plot.detectonlymax){ \
ncpp->miny = truemaxy; \
} \
} \
ncpp->plot.slotstart = 0; \
ncpp->plot.slotx = 0; \

@ -23,11 +23,7 @@ void sprixel_debug(const sprixel* s, FILE* out){
for(int x = 0 ; x < s->dimx ; ++x){
if(s->n->tam[idx].state == SPRIXCELL_ANNIHILATED){
if(s->n->tam[idx].auxvector){
fprintf(out, "%03d] ", idx);
for(int p = 0 ; p < s->cellpxx * s->cellpxy ; ++p){
fprintf(out, "%02x ", s->n->tam[idx].auxvector[p]);
}
fprintf(out, "\n");
fprintf(out, "%03d] %p\n", idx, s->n->tam[idx].auxvector);
}else{
fprintf(out, "%03d] missing!\n", idx);
}

@ -67,6 +67,7 @@ setup_sixel_bitmaps(tinfo* ti, int fd, bool invert80){
ti->pixel_trans_auxvec = sixel_trans_auxvec;
ti->sprixel_scale_height = 6;
set_pixel_blitter(sixel_blit);
ti->pixel_implementation = NCPIXEL_SIXEL;
sprite_init(ti, fd);
}
@ -91,17 +92,20 @@ setup_kitty_bitmaps(tinfo* ti, int fd, kitty_graphics_e level){
ti->pixel_rebuild = kitty_rebuild;
ti->sixel_maxy_pristine = INT_MAX;
set_pixel_blitter(kitty_blit);
ti->pixel_implementation = NCPIXEL_KITTY_STATIC;
}else{
if(level == KITTY_ANIMATION){
ti->pixel_wipe = kitty_wipe_animation;
ti->pixel_rebuild = kitty_rebuild_animation;
ti->sixel_maxy_pristine = 0;
set_pixel_blitter(kitty_blit_animated);
ti->pixel_implementation = NCPIXEL_KITTY_ANIMATED;
}else{
ti->pixel_wipe = kitty_wipe_selfref;
ti->pixel_rebuild = kitty_rebuild_selfref;
ti->sixel_maxy_pristine = 0;
set_pixel_blitter(kitty_blit_selfref);
ti->pixel_implementation = NCPIXEL_KITTY_SELFREF;
}
}
sprite_init(ti, fd);
@ -124,6 +128,7 @@ setup_fbcon_bitmaps(tinfo* ti, int fd){
ti->pixel_wipe = fbcon_wipe;
ti->pixel_trans_auxvec = kitty_trans_auxvec;
set_pixel_blitter(fbcon_blit);
ti->pixel_implementation = NCPIXEL_LINUXFB;
sprite_init(ti, fd);
}
#endif
@ -521,9 +526,9 @@ apply_term_heuristics(tinfo* ti, const char* termname, queried_terminals_e qterm
if(add_smulx_escapes(ti, tablelen, tableused)){
return -1;
}
if(compare_versions(ti->termversion, "0.22.1") >= 0){
/*if(compare_versions(ti->termversion, "0.22.1") >= 0){
setup_kitty_bitmaps(ti, ti->ttyfd, KITTY_SELFREF);
}else if(compare_versions(ti->termversion, "0.20.0") >= 0){
}else*/ if(compare_versions(ti->termversion, "0.20.0") >= 0){
setup_kitty_bitmaps(ti, ti->ttyfd, KITTY_ANIMATION);
}else{
setup_kitty_bitmaps(ti, ti->ttyfd, KITTY_ALWAYS_SCROLLS);

@ -151,7 +151,9 @@ typedef struct tinfo {
// bg_collides_default is either 0x0000000 or (if in use) 0x1RRGGBB.
uint32_t bg_collides_default;
// bitmap support. if we support bitmaps, pixel_draw will be non-NULL
// bitmap support. if we support bitmaps, pixel_implementation will be a
// value other than NCPIXEL_NONE.
ncpixelimpl_e pixel_implementation;
// wipe out a cell's worth of pixels from within a sprixel. for sixel, this
// means leaving out the pixels (and likely resizes the string). for kitty,
// this means dialing down their alpha to 0 (in equivalent space).

Loading…
Cancel
Save