Notcurses 3.0.13
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
render.c
Go to the documentation of this file.
1#include <ctype.h>
2#include <limits.h>
3#include <unistd.h>
4#include "internal.h"
5#include "unixsig.h"
6
7sig_atomic_t sigcont_seen_for_render = 0;
8
9// update for a new visual area of |rows|x|cols|, neither of which may be zero.
10// copies that area of the lastframe (damage map) which is shared between the
11// two. new areas are initialized to empty, just like a new plane. lost areas
12// have their egcpool entries purged.
13static nccell*
14restripe_lastframe(notcurses* nc, unsigned rows, unsigned cols){
15 assert(rows);
16 assert(cols);
17 const size_t size = sizeof(*nc->lastframe) * (rows * cols);
18 nccell* tmp = malloc(size);
19 if(tmp == NULL){
20 return NULL;
21 }
22 size_t copycols = nc->lfdimx > cols ? cols : nc->lfdimx;
23 size_t maxlinecopy = sizeof(nccell) * copycols;
24 size_t minlineset = sizeof(nccell) * cols - maxlinecopy;
25 unsigned zorch = nc->lfdimx > cols ? nc->lfdimx - cols : 0;
26 for(unsigned y = 0 ; y < rows ; ++y){
27 if(y < nc->lfdimy){
28 if(maxlinecopy){
29 memcpy(&tmp[cols * y], &nc->lastframe[nc->lfdimx * y], maxlinecopy);
30 }
31 if(minlineset){
32 memset(&tmp[cols * y + copycols], 0, minlineset);
33 }
34 // excise any egcpool entries from the right of the new plane area
35 if(zorch){
36 for(unsigned x = copycols ; x < copycols + zorch ; ++x){
37 pool_release(&nc->pool, &nc->lastframe[fbcellidx(y, nc->lfdimx, x)]);
38 }
39 }
40 }else{
41 memset(&tmp[cols * y], 0, sizeof(nccell) * cols);
42 }
43 }
44 // excise any egcpool entries from below the new plane area
45 for(unsigned y = rows ; y < nc->lfdimy ; ++y){
46 for(unsigned x = 0 ; x < nc->lfdimx ; ++x){
47 pool_release(&nc->pool, &nc->lastframe[fbcellidx(y, nc->lfdimx, x)]);
48 }
49 }
50 free(nc->lastframe);
51 nc->lastframe = tmp;
52 nc->lfdimy = rows;
53 nc->lfdimx = cols;
54 return 0;
55}
56
57// Check whether the terminal geometry has changed, and if so, copy what can
58// be copied from the old lastframe. Assumes that the screen is always anchored
59// at the same origin. Initiates a resize cascade for the pile containing |pp|.
60// The current terminal geometry, changed or not, is written to |rows|/|cols|.
61static int
62notcurses_resize_internal(ncplane* pp, unsigned* restrict rows, unsigned* restrict cols){
64 unsigned r, c;
65 if(rows == NULL){
66 rows = &r;
67 }
68 if(cols == NULL){
69 cols = &c;
70 }
71 ncpile* pile = ncplane_pile(pp);
72 unsigned oldrows = pile->dimy;
73 unsigned oldcols = pile->dimx;
74 *rows = oldrows;
75 *cols = oldcols;
76 unsigned cgeo_changed;
77 unsigned pgeo_changed;
78 if(update_term_dimensions(rows, cols, &n->tcache, n->margin_b,
79 &cgeo_changed, &pgeo_changed)){
80 return -1;
81 }
82 n->stats.s.cell_geo_changes += cgeo_changed;
83 n->stats.s.pixel_geo_changes += pgeo_changed;
84 *rows -= n->margin_t + n->margin_b;
85 if(*rows <= 0){
86 *rows = 1;
87 }
88 *cols -= n->margin_l + n->margin_r;
89 if(*cols <= 0){
90 *cols = 1;
91 }
92 if(*rows != n->lfdimy || *cols != n->lfdimx){
93 if(restripe_lastframe(n, *rows, *cols)){
94 return -1;
95 }
96 }
97//fprintf(stderr, "r: %d or: %d c: %d oc: %d\n", *rows, oldrows, *cols, oldcols);
98 if(*rows == oldrows && *cols == oldcols){
99 return 0; // no change
100 }
101 pile->dimy = *rows;
102 pile->dimx = *cols;
103 int ret = 0;
104//notcurses_debug(n, stderr);
105 // if this pile contains the standard plane, it ought be resized to match
106 // the viewing area before invoking any other resize callbacks.
107 if(ncplane_pile(notcurses_stdplane(n)) == pile){
109 }
110 // now, begin a resize callback cascade on the root planes of the pile.
111 for(ncplane* rootn = pile->roots ; rootn ; rootn = rootn->bnext){
112 if(rootn->resizecb){
113 ret |= rootn->resizecb(rootn);
114 }
115 }
116 return ret;
117}
118
119// Check for a window resize on the standard pile.
120static int
121notcurses_resize(notcurses* n, unsigned* restrict rows, unsigned* restrict cols){
122 pthread_mutex_lock(&n->pilelock);
123 int ret = notcurses_resize_internal(notcurses_stdplane(n), rows, cols);
124 pthread_mutex_unlock(&n->pilelock);
125 return ret;
126}
127
129 pool_release(&n->pool, c);
130}
131
132// Duplicate one cell onto another when they share a plane. Convenience wrapper.
134 if(cell_duplicate_far(&n->pool, targ, n, c) < 0){
135 logerror("failed duplicating cell");
136 return -1;
137 }
138 return 0;
139}
140
141// Emit fchannel with RGB changed to contrast effectively against bchannel.
142static uint32_t
143highcontrast(const tinfo* ti, uint32_t bchannel){
144 unsigned r, g, b;
145 if(ncchannel_default_p(bchannel)){
146 // FIXME what if we couldn't identify the background color?
147 r = ncchannel_r(ti->bg_collides_default);
148 g = ncchannel_g(ti->bg_collides_default);
149 b = ncchannel_b(ti->bg_collides_default);
150 }else{
151 // FIXME need to handle palette-indexed
152 r = ncchannel_r(bchannel);
153 g = ncchannel_g(bchannel);
154 b = ncchannel_b(bchannel);
155 }
156 uint32_t conrgb = 0;
157 if(r + g + b < 320){
158 ncchannel_set(&conrgb, 0xffffff);
159 }else{
160 ncchannel_set(&conrgb, 0);
161 }
162 return conrgb;
163}
164
165// wants coordinates within the sprixel, not absolute
166// FIXME if plane is not wholly on-screen, probably need to toss plane,
167// at least for this rendering cycle
168static void
169paint_sprixel(ncplane* p, struct crender* rvec, int starty, int startx,
170 int offy, int offx, int dstleny, int dstlenx){
171 const notcurses* nc = ncplane_notcurses_const(p);
172 sprixel* s = p->sprite;
173 int dimy = s->dimy;
174 int dimx = s->dimx;
175//fprintf(stderr, "START: %d/%d DIM: %d/%d dim(p): %d/%d off: %d/%d DSTLEN %d/%d %p\n", starty, startx, dimy, dimx, ncplane_dim_y(p), ncplane_dim_x(p), offy, offx, dstleny, dstlenx, rvec);
176 if(s->invalidated == SPRIXEL_HIDE){ // no need to do work if we're killing it
177 return;
178 }
179 for(int y = starty ; y < dimy ; ++y){
180 const int absy = y + offy;
181 // once we've passed the physical screen's bottom, we're done
182 if(absy >= dstleny || absy < 0){
183 break;
184 }
185 for(int x = startx ; x < dimx ; ++x){ // iteration for each cell
186 const int absx = x + offx;
187 if(absx >= dstlenx || absx < 0){
188 break;
189 }
190 sprixcell_e state = sprixel_state(s, absy, absx);
191 struct crender* crender = &rvec[fbcellidx(absy, dstlenx, absx)];
192//fprintf(stderr, "presprixel: %p preid: %d state: %d\n", rvec->sprixel, rvec->sprixel ? rvec->sprixel->id : 0, s->invalidated);
193 // if we already have a glyph solved (meaning said glyph is above this
194 // sprixel), and we run into a bitmap cell, we need to null that cell out
195 // of the bitmap.
196 if(crender->p || crender->s.bgblends){
197 // if sprite_wipe_cell() fails, we presumably do not have the
198 // ability to wipe, and must reprint the character
199 if(sprite_wipe(nc, p->sprite, y, x) < 0){
200//fprintf(stderr, "damaging due to wipe [%s] %d/%d\n", nccell_extended_gcluster(crender->p, &crender->c), absy, absx);
201 crender->s.damaged = 1;
202 }
204 }else if(!crender->p && !crender->s.bgblends){
205 // if we are a bitmap, and above a cell that has changed (and
206 // will thus be printed), we'll need redraw the sprixel.
207 if(crender->sprixel == NULL){
208 crender->sprixel = s;
209 }
210 if(state == SPRIXCELL_ANNIHILATED || state == SPRIXCELL_ANNIHILATED_TRANS){
211//fprintf(stderr, "REBUILDING AT %d/%d\n", y, x);
212 sprite_rebuild(nc, s, y, x);
213//fprintf(stderr, "damaging due to rebuild [%s] %d/%d\n", nccell_extended_gcluster(crender->p, &crender->c), absy, absx);
214 }
215 }
216 }
217 }
218}
219
220// Paints a single ncplane 'p' into the provided scratch framebuffer 'fb' (we
221// can't always write directly into lastframe, because we need build state to
222// solve certain cells, and need compare their solved result to the last frame).
223//
224// dstleny: leny of target rendering area described by rvec
225// dstlenx: lenx of target rendering area described by rvec
226// dstabsy: absy of target rendering area (relative to terminal)
227// dstabsx: absx of target rendering area (relative to terminal)
228//
229// only those cells where 'p' intersects with the target rendering area are
230// rendered.
231//
232// the sprixelstack orders sprixels of the plane (so we needn't keep them
233// ordered between renders). each time we meet a sprixel, extract it from
234// the pile's sprixel list, and update the sprixelstack.
235__attribute__ ((nonnull (1, 2, 7))) static void
236paint(ncplane* p, struct crender* rvec, int dstleny, int dstlenx,
237 int dstabsy, int dstabsx, sprixel** sprixelstack,
238 unsigned pgeo_changed){
239 unsigned y, x, dimy, dimx;
240 int offy, offx;
241 ncplane_dim_yx(p, &dimy, &dimx);
242 offy = p->absy - dstabsy;
243 offx = p->absx - dstabsx;
244//fprintf(stderr, "PLANE %p %d %d %d %d %d %d %p\n", p, dimy, dimx, offy, offx, dstleny, dstlenx, p->sprite);
245 // skip content above or to the left of the physical screen
246 unsigned starty, startx;
247 if(offy < 0){
248 starty = -offy;
249 }else{
250 starty = 0;
251 }
252 if(offx < 0){
253 startx = -offx;
254 }else{
255 startx = 0;
256 }
257 // if we're a sprixel, we must not register ourselves as the active
258 // glyph, but we *do* need to null out any cellregions that we've
259 // scribbled upon.
260 if(p->sprite){
261 if(pgeo_changed){
262 // do what on failure? FIXME
263 sprixel_rescale(p->sprite, ncplane_pile(p)->cellpxy, ncplane_pile(p)->cellpxx);
264 }
265 paint_sprixel(p, rvec, starty, startx, offy, offx, dstleny, dstlenx);
266 // decouple from the pile's sixel list
267 if(p->sprite->next){
268 p->sprite->next->prev = p->sprite->prev;
269 }
270 if(p->sprite->prev){
271 p->sprite->prev->next = p->sprite->next;
272 }else{
273 ncplane_pile(p)->sprixelcache = p->sprite->next;
274 }
275 // stick on the head of the running list: top sprixel is at end
276 if(*sprixelstack){
277 (*sprixelstack)->prev = p->sprite;
278 }
279 p->sprite->next = *sprixelstack;
280 p->sprite->prev = NULL;
281 *sprixelstack = p->sprite;
282 return;
283 }
284 for(y = starty ; y < dimy ; ++y){
285 const int absy = y + offy;
286 // once we've passed the physical screen's bottom, we're done
287 if(absy >= dstleny || absy < 0){
288 break;
289 }
290 for(x = startx ; x < dimx ; ++x){ // iteration for each cell
291 const int absx = x + offx;
292 if(absx >= dstlenx || absx < 0){
293 break;
294 }
295 struct crender* crender = &rvec[fbcellidx(absy, dstlenx, absx)];
296//fprintf(stderr, "p: %p damaged: %u %d/%d\n", p, crender->s.damaged, y, x);
297 nccell* targc = &crender->c;
298 if(nccell_wide_right_p(targc)){
299 continue;
300 }
301
302 if(nccell_fg_alpha(targc) > NCALPHA_OPAQUE){
303 const nccell* vis = &p->fb[nfbcellidx(p, y, x)];
304 if(nccell_fg_default_p(vis)){
305 vis = &p->basecell;
306 }
307 if(nccell_fg_alpha(vis) == NCALPHA_HIGHCONTRAST){
308 crender->s.highcontrast = true;
310 crender->hcfg = cell_fchannel(targc);
311 }
312 unsigned fgblends = crender->s.fgblends;
313 cell_blend_fchannel(ncplane_notcurses(p), targc, cell_fchannel(vis), &fgblends);
315 // crender->highcontrast can only be true if we just set it, since we're
316 // about to set targc opaque based on crender->highcontrast (and this
317 // entire stanza is conditional on targc not being NCALPHA_OPAQUE).
319 nccell_set_fg_alpha(targc, NCALPHA_OPAQUE);
320 }
321 }
322
323 // Background color takes effect independently of whether we have a
324 // glyph. If we've already locked in the background, it has no effect.
325 // If it's transparent, it has no effect. Otherwise, update the
326 // background channel and balpha.
327 // Evaluate the background first, in case we have HIGHCONTRAST fg text.
328 if(nccell_bg_alpha(targc) > NCALPHA_OPAQUE){
329 const nccell* vis = &p->fb[nfbcellidx(p, y, x)];
330 // to be on the blitter stacking path, we need
331 // 1) crender->s.blittedquads to be non-zero (we're below semigraphics)
332 // 2) cell_blittedquadrants(vis) to be non-zero (we're semigraphics)
333 // 3) somewhere crender is 0, blittedquads is 1 (we're visible)
334 if(!crender->s.blittedquads || !((~crender->s.blittedquads) & cell_blittedquadrants(vis))){
335 if(nccell_bg_default_p(vis)){
336 vis = &p->basecell;
337 }
338 unsigned bgblends = crender->s.bgblends;
339 cell_blend_bchannel(ncplane_notcurses(p), targc, cell_bchannel(vis), &bgblends);
341 }else{ // use the local foreground; we're stacking blittings
342 if(nccell_fg_default_p(vis)){
343 vis = &p->basecell;
344 }
345 unsigned bgblends = crender->s.bgblends;
346 cell_blend_bchannel(ncplane_notcurses(p), targc, cell_fchannel(vis), &bgblends);
349 }
350 }
351
352 // if we never loaded any content into the cell (or obliterated it by
353 // writing in a zero), use the plane's base cell.
354 // if we have no character in this cell, we continue to look for a
355 // character, but our foreground color will still be used unless it's
356 // been set to transparent. if that foreground color is transparent, we
357 // still use a character we find here, but its color will come entirely
358 // from cells underneath us.
359 if(!crender->p){
360 const nccell* vis = &p->fb[nfbcellidx(p, y, x)];
361 if(vis->gcluster == 0 && !nccell_double_wide_p(vis)){
362 vis = &p->basecell;
363 }
364 // if the following is true, we're a real glyph, and not the right-hand
365 // side of a wide glyph (nor the null codepoint).
366 if( (targc->gcluster = vis->gcluster) ){ // index copy only
368//fprintf(stderr, "damaged due to hide %d/%d\n", y, x);
369 crender->s.damaged = 1;
370 }
371 crender->s.blittedquads = cell_blittedquadrants(vis);
372 // we can't plop down a wide glyph if the next cell is beyond the
373 // screen, nor if we're bisected by a higher plane.
374 if(nccell_double_wide_p(vis)){
375 // are we on the last column of the real screen? if so, 0x20 us
376 if(absx >= dstlenx - 1){
377 targc->gcluster = htole(' ');
378 targc->width = 1;
379 // is the next cell occupied? if so, 0x20 us
380 }else if(crender[1].c.gcluster){
381//fprintf(stderr, "NULLING out %d/%d (%d/%d) due to %u\n", y, x, absy, absx, crender[1].c.gcluster);
382 targc->gcluster = htole(' ');
383 targc->width = 1;
384 }else{
385 targc->stylemask = vis->stylemask;
386 targc->width = vis->width;
387 }
388 }else{
389 targc->stylemask = vis->stylemask;
390 targc->width = vis->width;
391 }
392 crender->p = p;
393 }else if(nccell_wide_right_p(vis)){
394 crender->p = p;
395 targc->width = 0;
396 }
397 }
398 }
399 }
400}
401
402// it's not a pure memset(), because NCALPHA_OPAQUE is the zero value, and
403// we need NCALPHA_TRANSPARENT
404static inline void
405init_rvec(struct crender* rvec, int totalcells){
406 struct crender c = {0};
407 nccell_set_fg_alpha(&c.c, NCALPHA_TRANSPARENT);
408 nccell_set_bg_alpha(&c.c, NCALPHA_TRANSPARENT);
409 for(int t = 0 ; t < totalcells ; ++t){
410 memcpy(&rvec[t], &c, sizeof(c));
411 }
412}
413
414// adjust an otherwise locked-in cell if highcontrast has been requested. this
415// should be done at the end of rendering the cell, so that contrast is solved
416// against the real background.
417static inline void
418lock_in_highcontrast(notcurses* nc, const tinfo* ti, nccell* targc, struct crender* crender){
419 if(nccell_fg_alpha(targc) == NCALPHA_TRANSPARENT){
420 nccell_set_fg_default(targc);
421 }
422 if(nccell_bg_alpha(targc) == NCALPHA_TRANSPARENT){
423 nccell_set_bg_default(targc);
424 }
426 // highcontrast weighs the original at 1/4 and the contrast at 3/4
427 if(!nccell_fg_default_p(targc)){
428 unsigned fgblends = 3;
429 uint32_t fchan = cell_fchannel(targc);
430 uint32_t bchan = cell_bchannel(targc);
431 uint32_t hchan = channels_blend(nc, highcontrast(ti, bchan), fchan,
433 cell_set_fchannel(targc, hchan);
435 hchan = channels_blend(nc, hchan, crender->hcfg, &fgblends,
436 nc->tcache.fg_default);
437 cell_set_fchannel(targc, hchan);
438 }else{
439 nccell_set_fg_rgb(targc, highcontrast(ti, cell_bchannel(targc)));
440 }
441 }
442}
443
444// Postpaint a single cell (multiple if it is a multicolumn EGC). This means
445// checking for and locking in high-contrast, checking for damage, and updating
446// 'lastframe' for any cells which are damaged.
447static inline void
448postpaint_cell(notcurses* nc, const tinfo* ti, nccell* lastframe, unsigned dimx,
449 struct crender* crender, egcpool* pool, unsigned y, unsigned* x){
450 nccell* targc = &crender->c;
451 lock_in_highcontrast(nc, ti, targc, crender);
452 nccell* prevcell = &lastframe[fbcellidx(y, dimx, *x)];
453 if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc) > 0){
454//fprintf(stderr, "damaging due to cmp [%s] %d %d\n", nccell_extended_gcluster(crender->p, &crender->c), y, *x);
455 if(crender->sprixel){
456 sprixcell_e state = sprixel_state(crender->sprixel, y, *x);
457//fprintf(stderr, "state under candidate sprixel: %d %d/%d\n", state, y, *x);
458 // we don't need to change it when under an opaque cell, because
459 // that's always printed on top.
461 if(state != SPRIXCELL_OPAQUE_SIXEL){
462//fprintf(stderr, "damaged due to opaque %d/%d\n", y, *x);
463 crender->s.damaged = 1;
464 }
465 }
466 }else{
467//fprintf(stderr, "damaged due to lastframe disagree %d/%d\n", y, *x);
468 crender->s.damaged = 1;
469 }
470 assert(!nccell_wide_right_p(targc));
471 const int width = targc->width;
472 for(int i = 1 ; i < width ; ++i){
473 const ncplane* tmpp = crender->p;
474 ++crender;
475 crender->p = tmpp;
476 ++*x;
477 ++prevcell;
478 targc = &crender->c;
479 targc->gcluster = 0;
480 targc->channels = crender[-i].c.channels;
481 targc->stylemask = crender[-i].c.stylemask;
482 if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc) > 0){
483//fprintf(stderr, "damaging due to cmp2 %d/%d\n", y, *x);
484 crender->s.damaged = 1;
485 }
486 }
487 }
488}
489
490
491// iterate over the rendered frame, adjusting the foreground colors for any
492// cells marked NCALPHA_HIGHCONTRAST, and clearing any cell covered by a
493// wide glyph to its left.
494//
495// FIXME this cannot be performed at render time (we don't yet know the
496// lastframe, and thus can't compute damage), but we *could* unite it
497// with rasterization--factor out the single cell iteration...
498// FIXME can we not do the blend a single time here, if we track sums in
499// paint()? tried this before and didn't get a win...
500static void
501postpaint(notcurses* nc, const tinfo* ti, nccell* lastframe,
502 unsigned dimy, unsigned dimx, struct crender* rvec, egcpool* pool){
503//fprintf(stderr, "POSTPAINT BEGINS! %zu %p %d/%d\n", sizeof(*rvec), rvec, dimy, dimx);
504 for(unsigned y = 0 ; y < dimy ; ++y){
505 for(unsigned x = 0 ; x < dimx ; ++x){
506 struct crender* crender = &rvec[fbcellidx(y, dimx, x)];
507 postpaint_cell(nc, ti, lastframe, dimx, crender, pool, y, &x);
508 }
509 }
510}
511
512// merging one plane down onto another is basically just performing a render
513// using only these two planes, with the result written to the lower plane.
514int ncplane_mergedown(ncplane* restrict src, ncplane* restrict dst,
515 int begsrcy, int begsrcx, unsigned leny, unsigned lenx,
516 int dsty, int dstx){
517//fprintf(stderr, "Merging down %d/%d @ %d/%d to %d/%d\n", leny, lenx, begsrcy, begsrcx, dsty, dstx);
518 if(dsty < 0){
519 if(dsty != -1){
520 logerror("invalid dsty %d", dsty);
521 return -1;
522 }
523 dsty = dst->y;
524 }
525 if(dstx < 0){
526 if(dstx != -1){
527 logerror("invalid dstx %d", dstx);
528 return -1;
529 }
530 dstx = dst->x;
531 }
532 if((unsigned)dsty >= dst->leny || (unsigned)dstx >= dst->lenx){
533 logerror("dest origin %u/%u ≥ dest dimensions %d/%d",
534 dsty, dstx, dst->leny, dst->lenx);
535 return -1;
536 }
537 if(begsrcy < 0){
538 if(begsrcy != -1){
539 logerror("invalid begsrcy %d", begsrcy);
540 return -1;
541 }
542 begsrcy = src->y;
543 }
544 if(begsrcx < 0){
545 if(begsrcx != -1){
546 logerror("invalid begsrcx %d", begsrcx);
547 return -1;
548 }
549 begsrcx = src->x;
550 }
551 if((unsigned)begsrcy >= src->leny || (unsigned)begsrcx >= src->lenx){
552 logerror("source origin %u/%u ≥ source dimensions %d/%d",
553 begsrcy, begsrcx, src->leny, src->lenx);
554 return -1;
555 }
556 if(leny == 0){
557 if((leny = src->leny - begsrcy) == 0){
558 logerror("source area was zero height");
559 return -1;
560 }
561 }
562 if(lenx == 0){
563 if((lenx = src->lenx - begsrcx) == 0){
564 logerror("source area was zero width");
565 return -1;
566 }
567 }
568 if(dst->leny - leny < (unsigned)dsty || dst->lenx - lenx < (unsigned)dstx){
569 logerror("dest len %u/%u ≥ dest dimensions %d/%d",
570 leny, lenx, dst->leny, dst->lenx);
571 return -1;
572 }
573 if(src->leny - leny < (unsigned)begsrcy || src->lenx - lenx < (unsigned)begsrcx){
574 logerror("source len %u/%u ≥ source dimensions %d/%d",
575 leny, lenx, src->leny, src->lenx);
576 return -1;
577 }
578 if(src->sprite || dst->sprite){
579 logerror("can't merge sprixel planes");
580 return -1;
581 }
582 const int totalcells = dst->leny * dst->lenx;
583 nccell* rendfb = calloc(totalcells, sizeof(*rendfb));
584 const size_t crenderlen = sizeof(struct crender) * totalcells;
585 struct crender* rvec = malloc(crenderlen);
586 if(!rendfb || !rvec){
587 logerror("error allocating render state for %ux%u", leny, lenx);
588 free(rendfb);
589 free(rvec);
590 return -1;
591 }
592 init_rvec(rvec, totalcells);
593 sprixel* s = NULL;
594 paint(src, rvec, dst->leny, dst->lenx, dst->absy, dst->absx, &s, 0);
595 assert(NULL == s);
596 paint(dst, rvec, dst->leny, dst->lenx, dst->absy, dst->absx, &s, 0);
597 assert(NULL == s);
598//fprintf(stderr, "Postpaint start (%dx%d)\n", dst->leny, dst->lenx);
599 const struct tinfo* ti = &ncplane_notcurses_const(dst)->tcache;
600 postpaint(ncplane_notcurses(dst), ti, rendfb, dst->leny, dst->lenx, rvec, &dst->pool);
601//fprintf(stderr, "Postpaint done (%dx%d)\n", dst->leny, dst->lenx);
602 free(dst->fb);
603 dst->fb = rendfb;
604 free(rvec);
605 return 0;
606}
607
608int ncplane_mergedown_simple(ncplane* restrict src, ncplane* restrict dst){
609 return ncplane_mergedown(src, dst, 0, 0, 0, 0, 0, 0);
610}
611
612// write the nccell's UTF-8 extended grapheme cluster to the provided fbuf.
613static inline int
614term_putc(fbuf* f, const egcpool* e, const nccell* c){
615 if(cell_simple_p(c)){
616//fprintf(stderr, "[%.4s] %08x\n", (const char*)&c->gcluster, c->gcluster); }
617 // we must not have any 'cntrl' characters at this point, except for
618 // nil or newline
619 if(c->gcluster == 0 || c->gcluster == '\n'){
620 if(fbuf_putc(f, ' ') < 0){
621 return -1;
622 }
623 }else if(fbuf_puts(f, (const char*)&c->gcluster) == EOF){
624 return -1;
625 }
626 }else{
627 if(fbuf_puts(f, egcpool_extended_gcluster(e, c)) == EOF){
628 return -1;
629 }
630 }
631 return 0;
632}
633
634// write any escape sequences necessary to set the desired style
635static inline int
636term_setstyles(fbuf* f, notcurses* nc, const nccell* c){
637 unsigned normalized = false;
638 int ret = coerce_styles(f, &nc->tcache, &nc->rstate.curattr,
639 nccell_styles(c), &normalized);
640 if(normalized){
641 nc->rstate.fgdefelidable = true;
642 nc->rstate.bgdefelidable = true;
643 nc->rstate.bgelidable = false;
644 nc->rstate.fgelidable = false;
645 nc->rstate.bgpalelidable = false;
646 nc->rstate.fgpalelidable = false;
647 }
648 return ret;
649}
650
651// u8->str lookup table used in term_esc_rgb below
652static const char* const NUMBERS[] = {
653"0;", "1;", "2;", "3;", "4;", "5;", "6;", "7;", "8;", "9;", "10;", "11;", "12;", "13;", "14;", "15;", "16;",
654"17;", "18;", "19;", "20;", "21;", "22;", "23;", "24;", "25;", "26;", "27;", "28;", "29;", "30;", "31;", "32;",
655"33;", "34;", "35;", "36;", "37;", "38;", "39;", "40;", "41;", "42;", "43;", "44;", "45;", "46;", "47;", "48;",
656"49;", "50;", "51;", "52;", "53;", "54;", "55;", "56;", "57;", "58;", "59;", "60;", "61;", "62;", "63;", "64;",
657"65;", "66;", "67;", "68;", "69;", "70;", "71;", "72;", "73;", "74;", "75;", "76;", "77;", "78;", "79;", "80;",
658"81;", "82;", "83;", "84;", "85;", "86;", "87;", "88;", "89;", "90;", "91;", "92;", "93;", "94;", "95;", "96;",
659"97;", "98;", "99;", "100;", "101;", "102;", "103;", "104;", "105;", "106;", "107;", "108;", "109;", "110;", "111;", "112;",
660"113;", "114;", "115;", "116;", "117;", "118;", "119;", "120;", "121;", "122;", "123;", "124;", "125;", "126;", "127;", "128;",
661"129;", "130;", "131;", "132;", "133;", "134;", "135;", "136;", "137;", "138;", "139;", "140;", "141;", "142;", "143;", "144;",
662"145;", "146;", "147;", "148;", "149;", "150;", "151;", "152;", "153;", "154;", "155;", "156;", "157;", "158;", "159;", "160;",
663"161;", "162;", "163;", "164;", "165;", "166;", "167;", "168;", "169;", "170;", "171;", "172;", "173;", "174;", "175;", "176;",
664"177;", "178;", "179;", "180;", "181;", "182;", "183;", "184;", "185;", "186;", "187;", "188;", "189;", "190;", "191;", "192;",
665"193;", "194;", "195;", "196;", "197;", "198;", "199;", "200;", "201;", "202;", "203;", "204;", "205;", "206;", "207;", "208;",
666"209;", "210;", "211;", "212;", "213;", "214;", "215;", "216;", "217;", "218;", "219;", "220;", "221;", "222;", "223;", "224;",
667"225;", "226;", "227;", "228;", "229;", "230;", "231;", "232;", "233;", "234;", "235;", "236;", "237;", "238;", "239;", "240;",
668"241;", "242;", "243;", "244;", "245;", "246;", "247;", "248;", "249;", "250;", "251;", "252;", "253;", "254;", "255;", };
669
670static inline int
671term_esc_rgb(fbuf* f, bool foreground, unsigned r, unsigned g, unsigned b){
672 // The correct way to do this is using tiparm+tputs, but doing so (at least
673 // as of terminfo 6.1.20191019) both emits ~3% more bytes for a run of 'rgb'
674 // and gives rise to some inaccurate colors (possibly due to special handling
675 // of values < 256; I'm not at this time sure). So we just cons up our own.
676 /*if(esc == 4){
677 return fbuf_emit(f, "setab", tiparm(nc->setab, (int)((r << 16u) | (g << 8u) | b)));
678 }else if(esc == 3){
679 return fbuf_emit(f, "setaf", tiparm(nc->setaf, (int)((r << 16u) | (g << 8u) | b)));
680 }else{
681 return -1;
682 }*/
683 #define RGBESC1 "\x1b" "["
684 // we'd like to use the proper ITU T.416 colon syntax i.e. "8:2::", but it is
685 // not supported by several terminal emulators :/.
686 #define RGBESC2 "8;2;"
687 // fprintf() was sitting atop our profiles, so we put the effort into a fast solution
688 // here. assemble a buffer using constants and a lookup table.
689 fbuf_putn(f, RGBESC1, strlen(RGBESC1));
690 fbuf_putc(f, foreground ? '3' : '4');
691 fbuf_putn(f, RGBESC2, strlen(RGBESC2));
692 const char* s = NUMBERS[r];
693 while(*s){
694 fbuf_putc(f, *s++);
695 }
696 s = NUMBERS[g];
697 while(*s){
698 fbuf_putc(f, *s++);
699 }
700 s = NUMBERS[b];
701 while(*s != ';'){
702 fbuf_putc(f, *s++);
703 }
704 fbuf_putc(f, 'm');
705 return 0;
706}
707
708static inline int
709term_bg_rgb8(const tinfo* ti, fbuf* f, unsigned r, unsigned g, unsigned b){
710 // We typically want to use tputs() and tiperm() to acquire and write the
711 // escapes, as these take into account terminal-specific delays, padding,
712 // etc. For the case of DirectColor, there is no suitable terminfo entry, but
713 // we're also in that case working with hopefully more robust terminals.
714 // If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
715 if(ti->caps.rgb){
716 if((ti->bg_collides_default & 0xff000000) == 0x01000000){
717 if((r == ncchannel_r(ti->bg_collides_default)) &&
718 (g == ncchannel_g(ti->bg_collides_default)) &&
719 (b == ncchannel_b(ti->bg_collides_default))){
720 // the human eye has fewer blue cones than red or green. toggle
721 // the last bit in the blue component to avoid a collision.
722 b ^= 0x00000001;
723 }
724 }
725 return term_esc_rgb(f, false, r, g, b);
726 }else{
727 const char* setab = get_escape(ti, ESCAPE_SETAB);
728 if(setab){
729 // For 256-color indexed mode, start constructing a palette based off
730 // the inputs *if we can change the palette*. If more than 256 are used on
731 // a single screen, start... combining close ones? For 8-color mode, simple
732 // interpolation. I have no idea what to do for 88 colors. FIXME
733 if(ti->caps.colors >= 256){
734 return fbuf_emit(f, tiparm(setab, rgb_quantize_256(r, g, b)));
735 }else if(ti->caps.colors >= 8){
736 return fbuf_emit(f, tiparm(setab, rgb_quantize_8(r, g, b)));
737 }
738 }
739 }
740 return 0;
741}
742
743int term_fg_rgb8(const tinfo* ti, fbuf* f, unsigned r, unsigned g, unsigned b){
744 // We typically want to use tputs() and tiperm() to acquire and write the
745 // escapes, as these take into account terminal-specific delays, padding,
746 // etc. For the case of DirectColor, there is no suitable terminfo entry, but
747 // we're also in that case working with hopefully more robust terminals.
748 // If it doesn't work, eh, it doesn't work. Fuck the world; save yourself.
749 if(ti->caps.rgb){
750 return term_esc_rgb(f, true, r, g, b);
751 }else{
752 const char* setaf = get_escape(ti, ESCAPE_SETAF);
753 if(setaf){
754 // For 256-color indexed mode, start constructing a palette based off
755 // the inputs *if we can change the palette*. If more than 256 are used on
756 // a single screen, start... combining close ones? For 8-color mode, simple
757 // interpolation. I have no idea what to do for 88 colors. FIXME
758 if(ti->caps.colors >= 256){
759 return fbuf_emit(f, tiparm(setaf, rgb_quantize_256(r, g, b)));
760 }else if(ti->caps.colors >= 8){
761 return fbuf_emit(f, tiparm(setaf, rgb_quantize_8(r, g, b)));
762 }
763 }
764 }
765 return 0;
766}
767
768static inline int
769update_palette(notcurses* nc, fbuf* f){
771 const char* initc = get_escape(&nc->tcache, ESCAPE_INITC);
772 for(size_t damageidx = 0 ; damageidx < sizeof(nc->palette.chans) / sizeof(*nc->palette.chans) ; ++damageidx){
773 unsigned r, g, b;
774 if(nc->palette_damage[damageidx]){
775 nc->touched_palette = true;
776 ncchannel_rgb8(nc->palette.chans[damageidx], &r, &g, &b);
777 // Need convert RGB values [0..256) to [0..1000], ugh
778 // FIXME need handle HSL case also
779 r = r * 1000 / 255;
780 g = g * 1000 / 255;
781 b = b * 1000 / 255;
782 if(fbuf_emit(f, tiparm(initc, damageidx, r, g, b)) < 0){
783 return -1;
784 }
785 nc->palette_damage[damageidx] = false;
786 }
787 }
788 }
789 return 0;
790}
791
792// at least one of the foreground and background are the default. emit the
793// necessary return to default (if one is necessary), and update rstate.
794static inline int
795raster_defaults(notcurses* nc, bool fgdef, bool bgdef, fbuf* f){
796 const char* op = get_escape(&nc->tcache, ESCAPE_OP);
797 if(op == NULL){ // if we don't have op, we don't have fgop/bgop
798 return 0;
799 }
800 const char* fgop = get_escape(&nc->tcache, ESCAPE_FGOP);
801 const char* bgop = get_escape(&nc->tcache, ESCAPE_BGOP);
802 bool mustsetfg = fgdef && !nc->rstate.fgdefelidable;
803 bool mustsetbg = bgdef && !nc->rstate.bgdefelidable;
804 if(!mustsetfg && !mustsetbg){ // needn't emit anything
805 ++nc->stats.s.defaultelisions;
806 return 0;
807 }else if((mustsetfg && mustsetbg) || !fgop || !bgop){
808 if(fbuf_emit(f, op)){
809 return -1;
810 }
811 nc->rstate.fgdefelidable = true;
812 nc->rstate.bgdefelidable = true;
813 nc->rstate.fgelidable = false;
814 nc->rstate.bgelidable = false;
815 nc->rstate.fgpalelidable = false;
816 nc->rstate.bgpalelidable = false;
817 }else if(mustsetfg){ // if we reach here, we must have fgop
818 if(fbuf_emit(f, fgop)){
819 return -1;
820 }
821 nc->rstate.fgdefelidable = true;
822 nc->rstate.fgelidable = false;
823 nc->rstate.fgpalelidable = false;
824 }else{ // mustsetbg and !mustsetfg and bgop != NULL
825 if(fbuf_emit(f, bgop)){
826 return -1;
827 }
828 nc->rstate.bgdefelidable = true;
829 nc->rstate.bgelidable = false;
830 nc->rstate.bgpalelidable = false;
831 }
833 return 0;
834}
835
836// these are unlikely, so we leave it uninlined
837static int
838emit_fg_palindex(notcurses* nc, fbuf* f, const nccell* srccell){
839 unsigned palfg = nccell_fg_palindex(srccell);
840 // we overload lastr for the palette index; both are 8 bits
841 if(nc->rstate.fgpalelidable && nc->rstate.lastr == palfg){
842 ++nc->stats.s.fgelisions;
843 }else{
844 if(term_fg_palindex(nc, f, palfg)){
845 return -1;
846 }
847 ++nc->stats.s.fgemissions;
848 nc->rstate.fgpalelidable = true;
849 }
850 nc->rstate.lastr = palfg;
851 nc->rstate.fgdefelidable = false;
852 nc->rstate.fgelidable = false;
853 return 0;
854}
855
856static int
857emit_bg_palindex(notcurses* nc, fbuf* f, const nccell* srccell){
858 unsigned palbg = nccell_bg_palindex(srccell);
859 if(nc->rstate.bgpalelidable && nc->rstate.lastbr == palbg){
860 ++nc->stats.s.bgelisions;
861 }else{
862 if(term_bg_palindex(nc, f, palbg)){
863 return -1;
864 }
865 ++nc->stats.s.bgemissions;
866 nc->rstate.bgpalelidable = true;
867 }
868 nc->rstate.lastr = palbg;
869 nc->rstate.bgdefelidable = false;
870 nc->rstate.bgelidable = false;
871 return 0;
872}
873
874// this first phase of sprixel rasterization is responsible for:
875// 1) invalidating all QUIESCENT sprixels if the pile has changed (because
876// it would have been destroyed when switching away from our pile).
877// for the same reason, invalidate all MOVE sprixels in this case.
878// 2) damaging all cells under a HIDE sixel, so text phase 1 consumes it
879// (not necessary for kitty graphics)
880// 3) damaging uncovered cells under a MOVE (not necessary for kitty)
881// 4) drawing invalidated sixels and loading invalidated kitty graphics
882// (new kitty graphics are *not* yet made visible)
883// by the end of this pass, all sixels are *complete*. all kitty graphics
884// are loaded, but old kitty graphics remain visible, and new/updated kitty
885// graphics are not yet visible, and they have not moved.
886static int64_t
887clean_sprixels(notcurses* nc, ncpile* p, fbuf* f, int scrolls){
888 sprixel* s;
889 sprixel** parent = &p->sprixelcache;
890 int64_t bytesemitted = 0;
891 while( (s = *parent) ){
892 loginfo("phase 1 sprixel %u state %d loc %d/%d", s->id,
893 s->invalidated, s->n ? s->n->absy : -1, s->n ? s->n->absx : -1);
895 if(p != nc->last_pile){
897 }
898 }else if(s->invalidated == SPRIXEL_HIDE){
899//fprintf(stderr, "OUGHT HIDE %d [%dx%d] %p\n", s->id, s->dimy, s->dimx, s);
900 int r = sprite_scrub(nc, p, s);
901 if(r < 0){
902 return -1;
903 }else if(r > 0){
904 if( (*parent = s->next) ){
905 s->next->prev = s->prev;
906 }
907 sprixel_free(s);
908 // need to avoid the rest of the iteration, as s is dead
909 continue; // don't account as an elision
910 }
911 }
913 nc->tcache.pixel_refresh(p, s);
914 }else if(s->invalidated == SPRIXEL_MOVED ||
917//fprintf(stderr, "1 MOVING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, y + nc->margin_t, x + nc->margin_l, s->n);
918 if(s->invalidated == SPRIXEL_MOVED){
919 if(p != nc->last_pile){
921 }else{
922 if(s->n->absx == s->movedfromx){
923 if(s->movedfromy - s->n->absy == scrolls){
925 continue;
926 }
927 }
928 }
929 }
930 // otherwise it's a new pile, so we couldn't have been on-screen
931 int r = sprite_redraw(nc, p, s, f, nc->margin_t, nc->margin_l);
932 if(r < 0){
933 return -1;
934 }
935 bytesemitted += r;
937 }else{
938 ++nc->stats.s.sprixelelisions;
939 }
940 parent = &s->next;
941//fprintf(stderr, "SPRIXEL STATE: %d\n", s->invalidated);
942 }
943 return bytesemitted;
944}
945
946// scroll the lastframe data |rows| up, to reflect scrolling reality.
947// FIXME we could virtualize this as we do for scrolling planes; this
948// method involves a lot of unnecessary copying.
949static void
950scroll_lastframe(notcurses* nc, unsigned rows){
951 // the top |rows| rows need be released (though not more than the actual
952 // number of rows!)
953 if(rows > nc->lfdimy){
954 rows = nc->lfdimy;
955 }
956 for(unsigned targy = 0 ; targy < rows ; ++targy){
957 for(unsigned targx = 0 ; targx < nc->lfdimx ; ++targx){
958 const size_t damageidx = targy * nc->lfdimx + targx;
959 nccell* c = &nc->lastframe[damageidx];
960 pool_release(&nc->pool, c);
961 }
962 }
963 // now for all rows subsequent, up through lfdimy - rows, move them back.
964 // if we scrolled all rows, we will not move anything (and we just
965 // released everything).
966 for(unsigned targy = 0 ; targy < nc->lfdimy - rows ; ++targy){
967 const size_t dstidx = targy * nc->lfdimx;
968 nccell* dst = &nc->lastframe[dstidx];
969 const size_t srcidx = dstidx + rows * nc->lfdimx;
970 const nccell* src = &nc->lastframe[srcidx];
971 memcpy(dst, src, sizeof(*dst) * nc->lfdimx);
972 }
973 // now for the last |rows| rows, initialize them to 0.
974 unsigned targy = nc->lfdimy - rows;
975 while(targy < nc->lfdimy){
976 const size_t dstidx = targy * nc->lfdimx;
977 nccell* dst = &nc->lastframe[dstidx];
978 memset(dst, 0, sizeof(*dst) * nc->lfdimx);
979 ++targy;
980 }
981}
982
983// "%d tardies to work off, by far the most in the class!\n", p->scrolls
984static int
985rasterize_scrolls(const ncpile* p, fbuf* f){
986 int scrolls = p->scrolls;
987 if(scrolls == 0){
988 return 0;
989 }
990 logdebug("order-%d scroll", scrolls);
991 /*if(p->nc->rstate.logendy >= 0){
992 p->nc->rstate.logendy -= scrolls;
993 if(p->nc->rstate.logendy < 0){
994 p->nc->rstate.logendy = 0;
995 p->nc->rstate.logendx = 0;
996 }
997 }*/
998 // FIXME probably need this to take place at the end of cycle...
999 if(p->nc->tcache.pixel_scroll){
1000 p->nc->tcache.pixel_scroll(p, &p->nc->tcache, scrolls);
1001 }
1002 if(goto_location(p->nc, f, p->dimy, 0, NULL)){
1003 return -1;
1004 }
1005 // terminals advertising 'bce' will scroll in the current background color;
1006 // switch back to the default explicitly.
1007 if(p->nc->tcache.bce){
1008 if(raster_defaults(p->nc, false, true, f)){
1009 return -1;
1010 }
1011 }
1012 return emit_scrolls_track(p->nc, scrolls, f);
1013}
1014
1015// second sprixel pass in rasterization. by this time, all sixels are handled
1016// (and in the QUIESCENT state); only persistent kitty graphics still require
1017// operation. responsibilities of this second pass include:
1018//
1019// 1) if we're a different pile, issue the kitty universal clear
1020// 2) first, hide all sprixels in the HIDE state
1021// 3) then, make allo LOADED sprixels visible
1022//
1023// don't account for sprixelemissions here, as they were already counted.
1024static int64_t
1025rasterize_sprixels(notcurses* nc, ncpile* p, fbuf* f){
1026 int64_t bytesemitted = 0;
1027 sprixel* s;
1028 sprixel** parent = &p->sprixelcache;
1029 while( (s = *parent) ){
1030//fprintf(stderr, "raster YARR HARR HARR SPIRXLE %u STATE %d\n", s->id, s->invalidated);
1032//fprintf(stderr, "3 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, nc->margin_t, nc->margin_l, s->n);
1033 int r = sprite_draw(&nc->tcache, p, s, f, nc->margin_t, nc->margin_l);
1034 if(r < 0){
1035 return -1;
1036 }else if(r > 0){
1037 bytesemitted += r;
1038 nc->rstate.y = -1;
1039 nc->rstate.x = -1;
1040 ++nc->stats.s.sprixelemissions;
1041 }
1042 }else if(s->invalidated == SPRIXEL_LOADED){
1043 if(nc->tcache.pixel_commit){
1044 int y, x;
1045 ncplane_abs_yx(s->n, &y, &x);
1046 if(goto_location(nc, f, y + nc->margin_t, x + nc->margin_l, NULL)){
1047 return -1;
1048 }
1049 if(sprite_commit(&nc->tcache, f, s, false)){
1050 return -1;
1051 }
1052 nc->rstate.y = -1;
1053 nc->rstate.x = -1;
1054 }
1055 }else if(s->invalidated == SPRIXEL_HIDE){
1056 if(nc->tcache.pixel_remove){
1057 if(nc->tcache.pixel_remove(s->id, f) < 0){
1058 return -1;
1059 }
1060 if( (*parent = s->next) ){
1061 s->next->prev = s->prev;
1062 }
1063 sprixel_free(s);
1064 continue;
1065 }
1066 }
1067 parent = &s->next;
1068 }
1069 return bytesemitted;
1070}
1071
1072// bitmap backends which don't use the bytestream (currently only fbcon)
1073// need go at the very end, following writeout. pass again, invoking
1074// pixel_draw_late if defined.
1075static int64_t
1076rasterize_sprixels_post(notcurses* nc, ncpile* p){
1077 if(!nc->tcache.pixel_draw_late){
1078 return 0;
1079 }
1080 int64_t bytesemitted = 0;
1081 sprixel* s;
1082 sprixel** parent = &p->sprixelcache;
1083 while( (s = *parent) ){
1084//fprintf(stderr, "YARR HARR HARR SPIRXLE %u STATE %d\n", s->id, s->invalidated);
1086 int offy, offx;
1087 ncplane_abs_yx(s->n, &offy, &offx);
1088//fprintf(stderr, "5 DRAWING BITMAP %d STATE %d AT %d/%d for %p\n", s->id, s->invalidated, nc->margin_t + offy, nc->margin_l + offx, s->n);
1089 int r = nc->tcache.pixel_draw_late(&nc->tcache, s, nc->margin_t + offy, nc->margin_l + offx);
1090 if(r < 0){
1091 return -1;
1092 }
1093 bytesemitted += r;
1094 }
1095 parent = &s->next;
1096 }
1097 return bytesemitted;
1098}
1099
1100// Producing the frame requires three steps:
1101// * render -- build up a flat framebuffer from a set of ncplanes
1102// * rasterize -- build up a UTF-8/ASCII stream of escapes and EGCs
1103// * refresh -- write the stream to the emulator
1104
1105// Takes a rendered frame (a flat framebuffer, where each cell has the desired
1106// EGC, attribute, and channels), which has been written to nc->lastframe, and
1107// spits out an optimal sequence of terminal-appropriate escapes and EGCs. There
1108// should be an rvec entry for each cell, but only the 'damaged' field is used.
1109// lastframe has *not yet been written to the screen*, i.e. it's only about to
1110// *become* the last frame rasterized.
1111static int
1112rasterize_core(notcurses* nc, const ncpile* p, fbuf* f, unsigned phase){
1113 struct crender* rvec = p->crender;
1114 // we only need to emit a coordinate if it was damaged. the damagemap is a
1115 // bit per coordinate, one per struct crender.
1116 for(unsigned y = nc->margin_t; y < p->dimy + nc->margin_t ; ++y){
1117 const int innery = y - nc->margin_t;
1118 bool saw_linefeed = 0;
1119 for(unsigned x = nc->margin_l ; x < p->dimx + nc->margin_l ; ++x){
1120 const int innerx = x - nc->margin_l;
1121 const size_t damageidx = innery * nc->lfdimx + innerx;
1122 unsigned r, g, b, br, bg, bb;
1123 nccell* srccell = &nc->lastframe[damageidx];
1124 if(!rvec[damageidx].s.damaged){
1125 // no need to emit a cell; what we rendered appears to already be
1126 // here. no updates are performed to elision state nor lastframe.
1127 ++nc->stats.s.cellelisions;
1128 if(nccell_wide_left_p(srccell)){
1129 ++x;
1130 }
1131 }else if(phase != 0 || !rvec[damageidx].s.p_beats_sprixel){
1132//fprintf(stderr, "phase %u damaged at %d/%d %d\n", phase, innery, innerx, x);
1133 // in the first text phase, we draw only those glyphs where the glyph
1134 // was not above a sprixel (and the cell is damaged). in the second
1135 // phase, we draw everything that remains damaged.
1136 ++nc->stats.s.cellemissions;
1137 if(goto_location(nc, f, y, x, rvec[damageidx].p)){
1138 return -1;
1139 }
1140 // set the style. this can change the color back to the default; if it
1141 // does, we need update our elision possibilities.
1142 if(term_setstyles(f, nc, srccell)){
1143 return -1;
1144 }
1145 // if our cell has a default foreground *or* background, we can elide
1146 // the default set iff one of:
1147 // * we are a partial glyph, and the previous was default on both, or
1148 // * we are a no-foreground glyph, and the previous was default background, or
1149 // * we are a no-background glyph, and the previous was default foreground
1150 bool nobackground = nccell_nobackground_p(srccell);
1151 bool rgbequal = nccell_rgbequal_p(srccell);
1152 if((nccell_fg_default_p(srccell)) || (!nobackground && nccell_bg_default_p(srccell))){
1153 if(raster_defaults(nc, nccell_fg_default_p(srccell),
1154 !nobackground && nccell_bg_default_p(srccell), f)){
1155 return -1;
1156 }
1157 }
1158 if(nccell_fg_palindex_p(srccell)){ // palette-indexed foreground
1159 if(emit_fg_palindex(nc, f, srccell)){
1160 return -1;
1161 }
1162 }else if(!nccell_fg_default_p(srccell)){ // rgb foreground
1163 // if our cell has a non-default foreground, we can elide the
1164 // non-default foreground set iff either:
1165 // * the previous was non-default, and matches what we have now, or
1166 // * we are a no-foreground glyph (iswspace() is true) FIXME
1167 nccell_fg_rgb8(srccell, &r, &g, &b);
1168 if(nc->rstate.fgelidable && nc->rstate.lastr == r && nc->rstate.lastg == g && nc->rstate.lastb == b){
1169 ++nc->stats.s.fgelisions;
1170 }else{
1171 if(!rgbequal){ // if rgbequal, no need to set fg
1172 if(term_fg_rgb8(&nc->tcache, f, r, g, b)){
1173 return -1;
1174 }
1175 ++nc->stats.s.fgemissions;
1176 nc->rstate.fgelidable = true;
1177 }else{
1178 r = nc->rstate.lastr; g = nc->rstate.lastg; b = nc->rstate.lastb;
1179 }
1180 }
1181 nc->rstate.lastr = r; nc->rstate.lastg = g; nc->rstate.lastb = b;
1182 nc->rstate.fgdefelidable = false;
1183 nc->rstate.fgpalelidable = false;
1184 }
1185 if(nobackground){
1186 ++nc->stats.s.bgelisions;
1187 }else if(nccell_bg_palindex_p(srccell)){ // palette-indexed background
1188 if(emit_bg_palindex(nc, f, srccell)){
1189 return -1;
1190 }
1191 }else if(!nccell_bg_default_p(srccell)){ // rgb background
1192 // if our cell has a non-default background, we can elide the
1193 // non-default background set iff either:
1194 // * we do not use the background, because the cell is all-foreground,
1195 // * the previous was non-default, and matches what we have now, or
1196 nccell_bg_rgb8(srccell, &br, &bg, &bb);
1197 if(nc->rstate.bgelidable && nc->rstate.lastbr == br && nc->rstate.lastbg == bg && nc->rstate.lastbb == bb){
1198 ++nc->stats.s.bgelisions;
1199 }else{
1200 if(term_bg_rgb8(&nc->tcache, f, br, bg, bb)){
1201 return -1;
1202 }
1203 ++nc->stats.s.bgemissions;
1204 nc->rstate.bgelidable = true;
1205 }
1206 nc->rstate.lastbr = br; nc->rstate.lastbg = bg; nc->rstate.lastbb = bb;
1207 nc->rstate.bgdefelidable = false;
1208 nc->rstate.bgpalelidable = false;
1209 // FIXME see above -- if we don't have to change the fg, but do need
1210 // to change the bg, emit full block and no rgb at all
1211 if(rgbequal){
1212 // FIXME need one per column of original glyph
1213 pool_load_direct(&nc->pool, srccell, " ", 1, 1);
1214 }
1215 }
1216//fprintf(stderr, "RAST %08x [%s] to %d/%d cols: %u %016" PRIx64 "\n", srccell->gcluster, pool_extended_gcluster(&nc->pool, srccell), y, x, srccell->width, srccell->channels);
1217 // this is used to invalidate the sprixel in the first text round,
1218 // which is only necessary for sixel, not kitty.
1219 if(rvec[damageidx].sprixel){
1220 sprixcell_e scstate = sprixel_state(rvec[damageidx].sprixel, y - nc->margin_t, x - nc->margin_l);
1221 if((scstate == SPRIXCELL_MIXED_SIXEL || scstate == SPRIXCELL_OPAQUE_SIXEL)
1222 && !rvec[damageidx].s.p_beats_sprixel){
1223//fprintf(stderr, "INVALIDATING at %d/%d (%u)\n", y, x, rvec[damageidx].s.p_beats_sprixel);
1224 sprixel_invalidate(rvec[damageidx].sprixel, y - nc->margin_t, x - nc->margin_l);
1225 }
1226 }
1227 if(term_putc(f, &nc->pool, srccell)){
1228 return -1;
1229 }
1230 if(srccell->gcluster == '\n'){
1231 saw_linefeed = true;
1232 }
1233 rvec[damageidx].s.damaged = 0;
1234 rvec[damageidx].s.p_beats_sprixel = 0;
1235 nc->rstate.x += srccell->width;
1236 if(srccell->width){ // check only necessary when undamaged; be safe
1237 x += srccell->width - 1;
1238 }else{
1239 ++nc->rstate.x;
1240 }
1241 if((int)y > nc->rstate.logendy || ((int)y == nc->rstate.logendy && (int)x > nc->rstate.logendx)){
1242 if((int)y > nc->rstate.logendy){
1243//fprintf(stderr, "**************8NATURAL PLACEMENT AT %u/ %u\n", y, x);
1244 nc->rstate.logendy = y;
1245 nc->rstate.logendx = 0;
1246 }
1247 if(x >= p->dimx + nc->margin_l - 1){
1248 if(nc->rstate.logendy < (int)(p->dimy + nc->margin_t - 1)){
1249 ++nc->rstate.logendy;
1250 }
1251 nc->rstate.logendx = 0;
1252 saw_linefeed = false;
1253//fprintf(stderr, "**********8SCROLLING PLACEMENT AT %u %u\n", nc->rstate.logendy, nc->rstate.logendx);
1254 }else if((int)x >= nc->rstate.logendx){
1255 nc->rstate.logendx = x;
1256//fprintf(stderr, "**********HORIZ PLACEMENT AT %u %u\n", nc->rstate.logendy, nc->rstate.logendx);
1257 }
1258 }
1259 }
1260//fprintf(stderr, "damageidx: %ld\n", damageidx);
1261 }
1262 // shouldn't this happen only if the linefeed was on the last line? FIXME
1263 if(saw_linefeed){
1264 nc->rstate.logendx = 0;
1265 }
1266 }
1267 return 0;
1268}
1269
1270// 'asu' on input is non-0 if application-synchronized updates are permitted
1271// (they are not, for instance, when rendering to a non-tty). on output,
1272// assuming success, it is non-0 if application-synchronized updates are
1273// desired; in this case, a SUM footer is present at the end of the buffer.
1274static int
1275notcurses_rasterize_inner(notcurses* nc, ncpile* p, fbuf* f, unsigned* asu){
1276 logdebug("pile %p ymax: %d xmax: %d", p, p->dimy + nc->margin_t, p->dimx + nc->margin_l);
1277 // don't write a clearscreen. we only update things that have been changed.
1278 // we explicitly move the cursor at the beginning of each output line, so no
1279 // need to home it expliticly.
1280 update_palette(nc, f);
1281 int scrolls = p->scrolls;
1282 logdebug("sprixel phase 1");
1283 int64_t sprixelbytes = clean_sprixels(nc, p, f, scrolls);
1284 if(sprixelbytes < 0){
1285 return -1;
1286 }
1287 logdebug("glyph phase 1");
1288 if(rasterize_core(nc, p, f, 0)){
1289 return -1;
1290 }
1291 logdebug("sprixel phase 2");
1292 int64_t rasprixelbytes = rasterize_sprixels(nc, p, f);
1293 if(rasprixelbytes < 0){
1294 return -1;
1295 }
1296 sprixelbytes += rasprixelbytes;
1297 pthread_mutex_lock(&nc->stats.lock);
1298 nc->stats.s.sprixelbytes += sprixelbytes;
1299 pthread_mutex_unlock(&nc->stats.lock);
1300 logdebug("glyph phase 2");
1301 if(rasterize_scrolls(p, f)){
1302 return -1;
1303 }
1304 p->scrolls = 0;
1305 if(rasterize_core(nc, p, f, 1)){
1306 return -1;
1307 }
1308#define MIN_SUMODE_SIZE BUFSIZ
1309 if(*asu){
1310 if(nc->rstate.f.used >= MIN_SUMODE_SIZE){
1311 const char* endasu = get_escape(&nc->tcache, ESCAPE_ESUM);
1312 if(endasu){
1313 if(fbuf_puts(f, endasu) < 0){
1314 *asu = 0;
1315 }
1316 }else{
1317 *asu = 0;
1318 }
1319 }else{
1320 *asu = 0;
1321 }
1322 }
1323#undef MIN_SUMODE_SIZE
1324 return nc->rstate.f.used;
1325}
1326
1327// rasterize the rendered frame, and blockingly write it out to the terminal.
1328static int
1329raster_and_write(notcurses* nc, ncpile* p, fbuf* f){
1330 fbuf_reset(f);
1331 // will we be using application-synchronized updates? if this comes back as
1332 // non-zero, we are, and must emit the header. no SUM without a tty, and we
1333 // can't have the escape without being connected to one...
1334 const char* basu = get_escape(&nc->tcache, ESCAPE_BSUM);
1335 unsigned useasu = basu ? 1 : 0;
1336 // if we have SUM support, emit a BSU speculatively. if we do so, but don't
1337 // actually use an ESU, this BSUM must be skipped on write.
1338 if(useasu){
1339 if(fbuf_puts(f, basu) < 0){
1340 return -1;
1341 }
1342 }
1343 if(notcurses_rasterize_inner(nc, p, f, &useasu) < 0){
1344 return -1;
1345 }
1346 // if we loaded a BSU into the front, but don't actually want to use it,
1347 // we start printing after the BSU.
1348 size_t moffset = 0;
1349 if(basu){
1350 if(useasu){
1351 ++nc->stats.s.appsync_updates;
1352 }else{
1353 moffset = strlen(basu);
1354 }
1355 }
1356 int ret = 0;
1357 sigset_t oldmask;
1358 block_signals(&oldmask);
1359 if(blocking_write(fileno(nc->ttyfp), nc->rstate.f.buf + moffset,
1360 nc->rstate.f.used - moffset)){
1361 ret = -1;
1362 }
1363 unblock_signals(&oldmask);
1364 rasterize_sprixels_post(nc, p);
1365//fprintf(stderr, "%lu/%lu %lu/%lu %lu/%lu %d\n", nc->stats.defaultelisions, nc->stats.defaultemissions, nc->stats.fgelisions, nc->stats.fgemissions, nc->stats.bgelisions, nc->stats.bgemissions, ret);
1366 if(ret < 0){
1367 return ret;
1368 }
1369 return nc->rstate.f.used;
1370}
1371
1372// if the cursor is enabled, store its location and disable it. then, once done
1373// rasterizing, enable it afresh, moving it to the stored location. if left on
1374// during rasterization, we'll get grotesque flicker. 'out' is a memstream
1375// used to collect a buffer.
1376static inline int
1377notcurses_rasterize(notcurses* nc, ncpile* p, fbuf* f){
1378 const int cursory = nc->cursory;
1379 const int cursorx = nc->cursorx;
1380 if(cursory >= 0){ // either both are good, or neither is
1382 }
1383 int ret = raster_and_write(nc, p, f);
1384 fbuf_reset(f);
1385 if(cursory >= 0){
1386 notcurses_cursor_enable(nc, cursory, cursorx);
1387 }else if(nc->rstate.logendy >= 0){
1388 goto_location(nc, f, nc->rstate.logendy, nc->rstate.logendx, nc->rstate.lastsrcp);
1389 if(fbuf_flush(f, nc->ttyfp)){
1390 ret = -1;
1391 }
1392 }
1393 nc->last_pile = p;
1394 return ret;
1395}
1396
1397// get the cursor to the upper-left corner by one means or another, clearing
1398// the screen while doing so.
1400 // clear clears the screen and homes the cursor by itself
1401 const char* clearscr = get_escape(ti, ESCAPE_CLEAR);
1402 if(clearscr){
1403 if(fbuf_emit(f, clearscr) == 0){
1404 nc->rstate.x = nc->rstate.y = 0;
1405 return 0;
1406 }
1407 }
1408 if(emit_scrolls_track(nc, ncplane_dim_y(notcurses_stdplane_const(nc)), f)){
1409 return -1;
1410 }
1411 if(goto_location(nc, f, 0, 0, NULL)){
1412 return -1;
1413 }
1414 return 0;
1415}
1416
1417int notcurses_refresh(notcurses* nc, unsigned* restrict dimy, unsigned* restrict dimx){
1418 if(notcurses_resize(nc, dimy, dimx)){
1419 return -1;
1420 }
1421 fbuf_reset(&nc->rstate.f);
1422 if(clear_and_home(nc, &nc->tcache, &nc->rstate.f)){
1423 return -1;
1424 }
1425 if(fbuf_flush(&nc->rstate.f, nc->ttyfp)){
1426 return -1;
1427 }
1428 if(nc->lfdimx == 0 || nc->lfdimy == 0){
1429 return 0;
1430 }
1431 ncpile p = {0};
1432 p.dimy = nc->lfdimy;
1433 p.dimx = nc->lfdimx;
1434 const int count = p.dimy * p.dimx;
1435 p.crender = malloc(count * sizeof(*p.crender));
1436 if(p.crender == NULL){
1437 return -1;
1438 }
1439 init_rvec(p.crender, count);
1440 for(int i = 0 ; i < count ; ++i){
1441 p.crender[i].s.damaged = 1;
1442 }
1443 int ret = notcurses_rasterize(nc, &p, &nc->rstate.f);
1444 free(p.crender);
1445 if(ret < 0){
1446 return -1;
1447 }
1448 ++nc->stats.s.refreshes;
1449 return 0;
1450}
1451
1454 ncpile* p = ncplane_pile(n);
1455 if(nc->lfdimx == 0 || nc->lfdimy == 0){
1456 return 0;
1457 }
1458 fbuf f = {0};
1459 if(fbuf_init(&f)){
1460 return -1;
1461 }
1462 const unsigned count = (nc->lfdimx > p->dimx ? nc->lfdimx : p->dimx) *
1463 (nc->lfdimy > p->dimy ? nc->lfdimy : p->dimy);
1464 p->crender = malloc(count * sizeof(*p->crender));
1465 if(p->crender == NULL){
1466 fbuf_free(&f);
1467 return -1;
1468 }
1469 init_rvec(p->crender, count);
1470 for(unsigned i = 0 ; i < count ; ++i){
1471 p->crender[i].s.damaged = 1;
1472 }
1473 int ret = raster_and_write(nc, p, &f);
1474 free(p->crender);
1475 if(ret > 0){
1476 if(fwrite(f.buf, f.used, 1, fp) == 1){
1477 ret = 0;
1478 }else{
1479 ret = -1;
1480 }
1481 }
1482 fbuf_free(&f);
1483 return ret;
1484}
1485
1486// We execute the painter's algorithm, starting from our topmost plane. The
1487// damagevector should be all zeros on input. On success, it will reflect
1488// which cells were changed. We solve for each coordinate's cell by walking
1489// down the z-buffer, looking at intersections with ncplanes. This implies
1490// locking down the EGC, the attributes, and the channels for each cell.
1491// if |pgeo_changed|, the cell-pixel geometry for the pile has changed
1492// since the last render, and thus all sprixels need be rescaled.
1493static void
1494ncpile_render_internal(ncpile* p, unsigned pgeo_changed){
1495 struct crender* rvec = p->crender;
1496//fprintf(stderr, "rendering %dx%d\n", p->dimy, p->dimx);
1497 ncplane* pl = p->top;
1498 sprixel* sprixel_list = NULL;
1499 while(pl){
1500 paint(pl, rvec, p->dimy, p->dimx, 0, 0, &sprixel_list, pgeo_changed);
1501 pl = pl->below;
1502 }
1503 if(sprixel_list){
1504 if(p->sprixelcache){
1505 sprixel* s = sprixel_list;
1506 while(s->next){
1507 s = s->next;
1508 }
1509 if( (s->next = p->sprixelcache) ){
1510 p->sprixelcache->prev = s;
1511 }
1512 }
1513 p->sprixelcache = sprixel_list;
1514 }
1515}
1516
1518 struct timespec start, rasterdone, writedone;
1519 clock_gettime(CLOCK_MONOTONIC, &start);
1520 ncpile* pile = ncplane_pile(n);
1521 struct notcurses* nc = ncpile_notcurses(pile);
1522 const struct tinfo* ti = &ncplane_notcurses_const(n)->tcache;
1523 postpaint(nc, ti, nc->lastframe, pile->dimy, pile->dimx, pile->crender, &nc->pool);
1524 clock_gettime(CLOCK_MONOTONIC, &rasterdone);
1525 int bytes = notcurses_rasterize(nc, pile, &nc->rstate.f);
1526 clock_gettime(CLOCK_MONOTONIC, &writedone);
1527 pthread_mutex_lock(&nc->stats.lock);
1528 // accepts negative |bytes| as an indication of failure
1529 update_raster_bytes(&nc->stats.s, bytes);
1530 update_raster_stats(&rasterdone, &start, &nc->stats.s);
1531 update_write_stats(&writedone, &rasterdone, &nc->stats.s, bytes);
1532 pthread_mutex_unlock(&nc->stats.lock);
1533 // we want to refresh if the screen geometry changed (or if we were just
1534 // woken up from SIGSTOP), but we mustn't do so until after rasterizing
1535 // the solved rvec, since this might result in a geometry update.
1539 }
1540 if(bytes < 0){
1541 return -1;
1542 }
1543 return 0;
1544}
1545
1546// ensure the crender vector of 'n' is properly sized for 'n'->dimy x 'n'->dimx,
1547// and initialize the rvec afresh for a new render.
1548static int
1549engorge_crender_vector(ncpile* p){
1550 if(p->dimy <= 0 || p->dimx <= 0){
1551 return -1;
1552 }
1553 const size_t crenderlen = p->dimy * p->dimx; // desired size
1554//fprintf(stderr, "crlen: %d y: %d x:%d\n", crenderlen, dimy, dimx);
1555 if(crenderlen != p->crenderlen){
1556 loginfo("resizing rvec (%" PRIuPTR ") for %p to %" PRIuPTR,
1557 p->crenderlen, p, crenderlen);
1558 struct crender* tmp = realloc(p->crender, sizeof(*tmp) * crenderlen);
1559 if(tmp == NULL){
1560 return -1;
1561 }
1562 p->crender = tmp;
1563 p->crenderlen = crenderlen;
1564 }
1565 init_rvec(p->crender, crenderlen);
1566 return 0;
1567}
1568
1570 scroll_lastframe(ncplane_notcurses(n), ncplane_pile(n)->scrolls);
1571 struct timespec start, renderdone;
1572 clock_gettime(CLOCK_MONOTONIC, &start);
1574 ncpile* pile = ncplane_pile(n);
1575 // update our notion of screen geometry, and render against that
1576 unsigned pgeo_changed = 0;
1577 notcurses_resize_internal(n, NULL, NULL);
1578 if(pile->cellpxy != nc->tcache.cellpxy || pile->cellpxx != nc->tcache.cellpxx){
1579 pile->cellpxy = nc->tcache.cellpxy;
1580 pile->cellpxx = nc->tcache.cellpxx;
1581 pgeo_changed = 1;
1582 }
1583 if(engorge_crender_vector(pile)){
1584 return -1;
1585 }
1586 ncpile_render_internal(pile, pgeo_changed);
1587 clock_gettime(CLOCK_MONOTONIC, &renderdone);
1588 pthread_mutex_lock(&nc->stats.lock);
1589 update_render_stats(&renderdone, &start, &nc->stats.s);
1590 pthread_mutex_unlock(&nc->stats.lock);
1591 return 0;
1592}
1593
1594// run the top half of notcurses_render(), and steal the buffer from rstate.
1595int ncpile_render_to_buffer(ncplane* p, char** buf, size_t* buflen){
1596 if(ncpile_render(p)){
1597 return -1;
1598 }
1600 unsigned useasu = false; // no SUM with file
1601 fbuf_reset(&nc->rstate.f);
1602 int bytes = notcurses_rasterize_inner(nc, ncplane_pile(p), &nc->rstate.f, &useasu);
1603 pthread_mutex_lock(&nc->stats.lock);
1604 update_raster_bytes(&nc->stats.s, bytes);
1605 pthread_mutex_unlock(&nc->stats.lock);
1606 if(bytes < 0){
1607 return -1;
1608 }
1609 *buf = nc->rstate.f.buf;
1610 *buflen = nc->rstate.f.used;
1611 fbuf_reset(&nc->rstate.f);
1612 return 0;
1613}
1614
1615// copy the UTF8-encoded EGC out of the cell, whether simple or complex. the
1616// result is not tied to the ncplane, and persists across erases / destruction.
1617static inline char*
1618pool_egc_copy(const egcpool* e, const nccell* c){
1619 if(cell_simple_p(c)){
1620 return strdup((const char*)&c->gcluster);
1621 }
1622 return strdup(egcpool_extended_gcluster(e, c));
1623}
1624
1625char* notcurses_at_yx(notcurses* nc, unsigned yoff, unsigned xoff, uint16_t* stylemask, uint64_t* channels){
1626 if(nc->lastframe == NULL){
1627 logerror("haven't yet rendered");
1628 return NULL;
1629 }
1630 if(yoff >= nc->lfdimy){
1631 logerror("invalid coordinates: %u/%u", yoff, xoff);
1632 return NULL;
1633 }
1634 if(xoff >= nc->lfdimx){
1635 logerror("invalid coordinates: %u/%u", yoff, xoff);
1636 return NULL;
1637 }
1638 const nccell* srccell = &nc->lastframe[yoff * nc->lfdimx + xoff];
1639 if(nccell_wide_right_p(srccell)){
1640 return notcurses_at_yx(nc, yoff, xoff - 1, stylemask, channels);
1641 }
1642 if(stylemask){
1643 *stylemask = srccell->stylemask;
1644 }
1645 if(channels){
1646 *channels = srccell->channels;
1647 }
1648//fprintf(stderr, "COPYING: %d from %p\n", srccell->gcluster, &nc->pool);
1649 return pool_egc_copy(&nc->pool, srccell);
1650}
1651
1652int ncdirect_set_bg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f){
1653 if(rgb > 0xffffffu){
1654 return -1;
1655 }
1656 if(!ncdirect_bg_default_p(nc) && !ncdirect_bg_palindex_p(nc)
1657 && ncchannels_bg_rgb(nc->channels) == rgb){
1658 return 0;
1659 }
1660 if(term_bg_rgb8(&nc->tcache, f, (rgb & 0xff0000u) >> 16u, (rgb & 0xff00u) >> 8u, rgb & 0xffu)){
1661 return -1;
1662 }
1663 ncchannels_set_bg_rgb(&nc->channels, rgb);
1664 return 0;
1665}
1666
1667int ncdirect_set_bg_rgb(ncdirect* nc, unsigned rgb){
1668 fbuf f = {0};
1669 if(fbuf_init_small(&f)){
1670 return -1;
1671 }
1672 if(ncdirect_set_bg_rgb_f(nc, rgb, &f)){
1673 fbuf_free(&f);
1674 return -1;
1675 }
1676 if(fbuf_finalize(&f, nc->ttyfp) < 0){
1677 return -1;
1678 }
1679 return 0;
1680}
1681
1682int ncdirect_set_fg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f){
1683 if(rgb > 0xffffffu){
1684 return -1;
1685 }
1686 if(!ncdirect_fg_default_p(nc) && !ncdirect_fg_palindex_p(nc)
1687 && ncchannels_fg_rgb(nc->channels) == rgb){
1688 return 0;
1689 }
1690 if(term_fg_rgb8(&nc->tcache, f, (rgb & 0xff0000u) >> 16u, (rgb & 0xff00u) >> 8u, rgb & 0xffu)){
1691 return -1;
1692 }
1693 ncchannels_set_fg_rgb(&nc->channels, rgb);
1694 return 0;
1695}
1696
1697int ncdirect_set_fg_rgb(ncdirect* nc, unsigned rgb){
1698 fbuf f = {0};
1699 if(fbuf_init_small(&f)){
1700 return -1;
1701 }
1702 if(ncdirect_set_fg_rgb_f(nc, rgb, &f)){
1703 fbuf_free(&f);
1704 return -1;
1705 }
1706 if(fbuf_finalize(&f, nc->ttyfp) < 0){
1707 return -1;
1708 }
1709 return 0;
1710}
1711
1712int notcurses_default_foreground(const struct notcurses* nc, uint32_t* fg){
1713 const tinfo* ti = &nc->tcache;
1714 if(ti->fg_default & 0x80000000){
1715 logerror("default foreground could not be determined");
1716 return -1;
1717 }
1718 *fg = ti->fg_default & NC_BG_RGB_MASK;
1719 return 0;
1720}
1721
1722int notcurses_default_background(const struct notcurses* nc, uint32_t* bg){
1723 const tinfo* ti = &nc->tcache;
1724 if(ti->bg_collides_default & 0x80000000){
1725 logerror("default background could not be determined");
1726 return -1;
1727 }
1729 return 0;
1730}
1731
1732int notcurses_cursor_yx(const notcurses* nc, int* y, int* x){
1733 *y = nc->rstate.y;
1734 *x = nc->rstate.x;
1735 return 0;
1736}
1737
1739 if(y < 0 || x < 0){
1740 logerror("illegal cursor placement: %d, %d", y, x);
1741 return -1;
1742 }
1743 // if we're already at the demanded location, we must already be visible, and
1744 // we needn't move the cursor -- return success immediately.
1745 if(nc->cursory == y && nc->cursorx == x){
1746 return 0;
1747 }
1748 fbuf f = {0};
1749 if(fbuf_init_small(&f)){
1750 return -1;
1751 }
1752 // updates nc->rstate.cursor{y,x}
1753 if(goto_location(nc, &f, y + nc->margin_t, x + nc->margin_l, nc->rstate.lastsrcp)){
1754 fbuf_free(&f);
1755 return -1;
1756 }
1757 if(nc->cursory < 0){ // we weren't visible before, need cnorm
1758 const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
1759 if(!cnorm || fbuf_emit(&f, cnorm)){
1760 fbuf_free(&f);
1761 return -1;
1762 }
1763 }
1764 if(fbuf_finalize(&f, nc->ttyfp)){
1765 return -1;
1766 }
1767 nc->cursory = y;
1768 nc->cursorx = x;
1769 return 0;
1770}
1771
1773 if(nc->cursorx < 0 || nc->cursory < 0){
1774 logerror("cursor is not enabled");
1775 return -1;
1776 }
1777 const char* cinvis = get_escape(&nc->tcache, ESCAPE_CIVIS);
1778 if(cinvis){
1779 if(!tty_emit(cinvis, nc->tcache.ttyfd) && !ncflush(nc->ttyfp)){
1780 nc->cursory = -1;
1781 nc->cursorx = -1;
1782 return 0;
1783 }
1784 }
1785 return -1;
1786}
assert(false)
const nccell * c
Definition egcpool.h:296
free(duplicated)
int r
Definition fbuf.h:226
void update_render_stats(const struct timespec *time1, const struct timespec *time0, ncstats *stats)
Definition stats.c:40
int update_term_dimensions(unsigned *rows, unsigned *cols, tinfo *tcache, int margin_b, unsigned *cgeo_changed, unsigned *pgeo_changed) __attribute__((nonnull(3
void update_write_stats(const struct timespec *time1, const struct timespec *time0, ncstats *stats, int bytes)
Definition stats.c:5
int sprite_wipe(const notcurses *nc, sprixel *s, int y, int x)
Definition sprite.c:170
void sprixel_invalidate(sprixel *s, int y, int x)
Definition sprite.c:103
void update_raster_stats(const struct timespec *time1, const struct timespec *time0, ncstats *stats)
Definition stats.c:58
void sprixel_free(sprixel *s)
Definition sprite.c:38
void update_raster_bytes(ncstats *stats, int bytes)
Definition stats.c:27
#define logerror(fmt,...)
Definition logging.h:32
#define loginfo(fmt,...)
Definition logging.h:42
#define logdebug(fmt,...)
Definition logging.h:52
#define htole(x)
Definition ncport.h:36
ncplane * notcurses_stdplane(notcurses *nc)
Definition notcurses.c:699
void ncplane_abs_yx(const ncplane *n, int *RESTRICT y, int *RESTRICT x)
Definition notcurses.c:2642
const notcurses * ncplane_notcurses_const(const ncplane *n)
Definition notcurses.c:2630
int ncplane_resize_maximize(ncplane *n)
Definition notcurses.c:2753
const ncplane * notcurses_stdplane_const(const notcurses *nc)
Definition notcurses.c:703
notcurses * ncplane_notcurses(const ncplane *n)
Definition notcurses.c:2626
void ncplane_dim_yx(const ncplane *n, unsigned *rows, unsigned *cols)
Definition notcurses.c:301
int y
Definition notcurses.h:1905
#define NCALPHA_OPAQUE
Definition notcurses.h:108
#define NCALPHA_TRANSPARENT
Definition notcurses.h:106
vopts n
Definition notcurses.h:3502
int int x
Definition notcurses.h:1905
#define NCALPHA_HIGHCONTRAST
Definition notcurses.h:105
#define NC_BG_RGB_MASK
Definition notcurses.h:120
int notcurses_cursor_yx(const notcurses *nc, int *y, int *x)
Definition render.c:1732
int ncdirect_set_bg_rgb_f(ncdirect *nc, unsigned rgb, fbuf *f)
Definition render.c:1652
int ncdirect_set_fg_rgb_f(ncdirect *nc, unsigned rgb, fbuf *f)
Definition render.c:1682
int notcurses_cursor_enable(notcurses *nc, int y, int x)
Definition render.c:1738
char * notcurses_at_yx(notcurses *nc, unsigned yoff, unsigned xoff, uint16_t *stylemask, uint64_t *channels)
Definition render.c:1625
#define RGBESC1
int term_fg_rgb8(const tinfo *ti, fbuf *f, unsigned r, unsigned g, unsigned b)
Definition render.c:743
void nccell_release(ncplane *n, nccell *c)
Definition render.c:128
#define RGBESC2
int ncpile_render(ncplane *n)
Definition render.c:1569
int ncplane_mergedown(ncplane *restrict src, ncplane *restrict dst, int begsrcy, int begsrcx, unsigned leny, unsigned lenx, int dsty, int dstx)
Definition render.c:514
int notcurses_default_foreground(const struct notcurses *nc, uint32_t *fg)
Definition render.c:1712
int ncpile_rasterize(ncplane *n)
Definition render.c:1517
int ncdirect_set_bg_rgb(ncdirect *nc, unsigned rgb)
Definition render.c:1667
int ncdirect_set_fg_rgb(ncdirect *nc, unsigned rgb)
Definition render.c:1697
int ncpile_render_to_buffer(ncplane *p, char **buf, size_t *buflen)
Definition render.c:1595
int notcurses_cursor_disable(notcurses *nc)
Definition render.c:1772
int nccell_duplicate(ncplane *n, nccell *targ, const nccell *c)
Definition render.c:133
int ncpile_render_to_file(ncplane *n, FILE *fp)
Definition render.c:1452
sig_atomic_t sigcont_seen_for_render
Definition render.c:7
__attribute__((nonnull(1, 2, 7)))
Definition render.c:235
int ncplane_mergedown_simple(ncplane *restrict src, ncplane *restrict dst)
Definition render.c:608
#define MIN_SUMODE_SIZE
int notcurses_refresh(notcurses *nc, unsigned *restrict dimy, unsigned *restrict dimx)
Definition render.c:1417
int notcurses_default_background(const struct notcurses *nc, uint32_t *bg)
Definition render.c:1722
int clear_and_home(notcurses *nc, tinfo *ti, fbuf *f)
Definition render.c:1399
int sprixel_rescale(sprixel *spx, unsigned ncellpxy, unsigned ncellpxx)
Definition sprite.c:225
sprixcell_e
Definition sprite.h:114
@ SPRIXCELL_ANNIHILATED
Definition sprite.h:120
@ SPRIXCELL_ANNIHILATED_TRANS
Definition sprite.h:121
@ SPRIXCELL_MIXED_SIXEL
Definition sprite.h:118
@ SPRIXCELL_OPAQUE_SIXEL
Definition sprite.h:116
@ SPRIXEL_UNSEEN
Definition sprite.h:22
@ SPRIXEL_LOADED
Definition sprite.h:23
@ SPRIXEL_QUIESCENT
Definition sprite.h:21
@ SPRIXEL_INVALIDATED
Definition sprite.h:24
@ SPRIXEL_HIDE
Definition sprite.h:25
@ SPRIXEL_MOVED
Definition sprite.h:26
struct crender::@2 s
unsigned damaged
Definition internal.h:283
nccell c
Definition internal.h:266
unsigned fgblends
Definition internal.h:287
unsigned bgblends
Definition internal.h:288
sprixel * sprixel
Definition internal.h:268
const ncplane * p
Definition internal.h:267
unsigned hcfgblends
Definition internal.h:292
unsigned highcontrast
Definition internal.h:286
unsigned p_beats_sprixel
Definition internal.h:294
unsigned blittedquads
Definition internal.h:282
uint32_t hcfg
Definition internal.h:269
Definition fbuf.h:25
char * buf
Definition fbuf.h:28
uint64_t used
Definition fbuf.h:27
unsigned colors
Definition notcurses.h:1637
bool can_change_colors
Definition notcurses.h:1640
uint8_t width
Definition notcurses.h:702
uint64_t channels
Definition notcurses.h:723
uint16_t stylemask
Definition notcurses.h:703
uint32_t gcluster
Definition notcurses.h:693
uint64_t channels
Definition internal.h:254
tinfo tcache
Definition internal.h:253
FILE * ttyfp
Definition internal.h:252
uint32_t chans[NCPALETTESIZE]
Definition notcurses.h:1585
unsigned cellpxx
Definition internal.h:326
sprixel * sprixelcache
Definition internal.h:328
unsigned dimx
Definition internal.h:325
size_t crenderlen
Definition internal.h:324
struct notcurses * nc
Definition internal.h:322
ncplane * roots
Definition internal.h:320
unsigned dimy
Definition internal.h:325
struct crender * crender
Definition internal.h:321
int scrolls
Definition internal.h:327
unsigned cellpxy
Definition internal.h:326
struct ncplane * below
Definition internal.h:94
sprixel * sprite
Definition internal.h:105
nccell basecell
Definition internal.h:110
int absy
Definition internal.h:83
int absx
Definition internal.h:83
nccell * fb
Definition internal.h:77
ncstats s
Definition internal.h:247
pthread_mutex_t lock
Definition internal.h:246
uint64_t cellemissions
Definition notcurses.h:1800
uint64_t refreshes
Definition notcurses.h:1807
uint64_t defaultelisions
Definition notcurses.h:1805
uint64_t appsync_updates
Definition notcurses.h:1811
uint64_t cellelisions
Definition notcurses.h:1799
uint64_t bgemissions
Definition notcurses.h:1804
uint64_t fgemissions
Definition notcurses.h:1802
uint64_t defaultemissions
Definition notcurses.h:1806
uint64_t sprixelemissions
Definition notcurses.h:1808
uint64_t sprixelelisions
Definition notcurses.h:1809
uint64_t fgelisions
Definition notcurses.h:1801
uint64_t bgelisions
Definition notcurses.h:1803
uint64_t sprixelbytes
Definition notcurses.h:1810
rasterstate rstate
Definition internal.h:336
ncpalette palette
Definition internal.h:366
ncpile * last_pile
Definition internal.h:347
bool palette_damage[NCPALETTESIZE]
Definition internal.h:367
nccell * lastframe
Definition internal.h:341
int cursorx
Definition internal.h:354
int margin_l
Definition internal.h:364
ncsharedstats stats
Definition internal.h:356
tinfo tcache
Definition internal.h:360
unsigned lfdimx
Definition internal.h:350
int cursory
Definition internal.h:353
bool touched_palette
Definition internal.h:368
unsigned lfdimy
Definition internal.h:351
FILE * ttyfp
Definition internal.h:359
int margin_t
Definition internal.h:364
egcpool pool
Definition internal.h:348
uint16_t curattr
Definition internal.h:157
bool fgelidable
Definition internal.h:159
unsigned lastbb
Definition internal.h:147
bool fgpalelidable
Definition internal.h:161
unsigned lastb
Definition internal.h:144
unsigned lastg
Definition internal.h:143
bool bgpalelidable
Definition internal.h:162
unsigned lastbg
Definition internal.h:146
unsigned lastbr
Definition internal.h:145
bool bgelidable
Definition internal.h:160
unsigned lastr
Definition internal.h:142
bool fgdefelidable
Definition internal.h:163
bool bgdefelidable
Definition internal.h:164
const ncplane * lastsrcp
Definition internal.h:140
struct sprixel * prev
Definition sprite.h:145
unsigned dimx
Definition sprite.h:146
int movedfromx
Definition sprite.h:151
int movedfromy
Definition sprite.h:150
unsigned dimy
Definition sprite.h:146
struct sprixel * next
Definition sprite.h:144
struct ncplane * n
Definition sprite.h:142
uint32_t id
Definition sprite.h:139
sprixel_e invalidated
Definition sprite.h:143
unsigned cellpxx
Definition termdesc.h:117
int(* pixel_draw_late)(const struct tinfo *, struct sprixel *s, int yoff, int xoff)
Definition termdesc.h:149
int(* pixel_remove)(int id, fbuf *f)
Definition termdesc.h:145
bool bce
Definition termdesc.h:218
uint32_t bg_collides_default
Definition termdesc.h:127
int(* pixel_commit)(fbuf *f, struct sprixel *s, unsigned noscroll)
Definition termdesc.h:155
nccapabilities caps
Definition termdesc.h:111
void(* pixel_refresh)(const struct ncpile *p, struct sprixel *s)
Definition termdesc.h:144
uint32_t fg_default
Definition termdesc.h:130
void(* pixel_scroll)(const struct ncpile *p, struct tinfo *, int rows)
Definition termdesc.h:157
unsigned cellpxy
Definition termdesc.h:116
int ttyfd
Definition termdesc.h:109
return NULL
Definition termdesc.h:229
@ ESCAPE_CIVIS
Definition termdesc.h:54
@ ESCAPE_CNORM
Definition termdesc.h:55
@ ESCAPE_SETAB
Definition termdesc.h:49
@ ESCAPE_ESUM
Definition termdesc.h:86
@ ESCAPE_OP
Definition termdesc.h:50
@ ESCAPE_FGOP
Definition termdesc.h:51
@ ESCAPE_BSUM
Definition termdesc.h:85
@ ESCAPE_CLEAR
Definition termdesc.h:80
@ ESCAPE_INITC
Definition termdesc.h:81
@ ESCAPE_SETAF
Definition termdesc.h:48
@ ESCAPE_BGOP
Definition termdesc.h:52
static escape_e e
Definition termdesc.h:224
int block_signals(sigset_t *old_blocked_signals)
Definition unixsig.c:78
int unblock_signals(const sigset_t *old_blocked_signals)
Definition unixsig.c:85