1 #include "linux.h"
2 #include "version.h"
3 #include "egcpool.h"
4 #include "internal.h"
5 #include <time.h>
6 #include <fcntl.h>
7 #include <errno.h>
8 #include <stdio.h>
9 #include <limits.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <stdlib.h>
13 #include <unistr.h>
14 #include <locale.h>
15 #include <uniwbrk.h>
16 #include <inttypes.h>
17 #include <notcurses/direct.h>
18 #include "compat/compat.h"
19 #include "unixsig.h"
20 #include "banner.h"
21
22 #define ESC "\x1b"
23
notcurses_version_components(int * major,int * minor,int * patch,int * tweak)24 void notcurses_version_components(int* major, int* minor, int* patch, int* tweak){
25 *major = NOTCURSES_VERNUM_MAJOR;
26 *minor = NOTCURSES_VERNUM_MINOR;
27 *patch = NOTCURSES_VERNUM_PATCH;
28 *tweak = atoi(NOTCURSES_VERSION_TWEAK);
29 }
30
notcurses_enter_alternate_screen(notcurses * nc)31 int notcurses_enter_alternate_screen(notcurses* nc){
32 if(nc->tcache.ttyfd < 0){
33 return -1;
34 }
35 if(enter_alternate_screen(nc->tcache.ttyfd, nc->ttyfp, &nc->tcache, nc->flags & NCOPTION_DRAIN_INPUT)){
36 return -1;
37 }
38 ncplane_set_scrolling(notcurses_stdplane(nc), false);
39 return 0;
40 }
41
notcurses_leave_alternate_screen(notcurses * nc)42 int notcurses_leave_alternate_screen(notcurses* nc){
43 if(nc->tcache.ttyfd < 0){
44 return -1;
45 }
46 if(leave_alternate_screen(nc->tcache.ttyfd, nc->ttyfp,
47 &nc->tcache, nc->flags & NCOPTION_DRAIN_INPUT)){
48 return -1;
49 }
50 // move to the end of our output
51 if(nc->rstate.logendy < 0){
52 return 0;
53 }
54 ncplane_cursor_move_yx(notcurses_stdplane(nc), nc->rstate.logendy, nc->rstate.logendx);
55 return 0;
56 }
57
58 // reset the current colors, styles, and palette. called on startup (to purge
59 // any preexisting styling) and shutdown (to not affect further programs).
reset_term_attributes(const tinfo * ti,fbuf * f)60 int reset_term_attributes(const tinfo* ti, fbuf* f){
61 int ret = 0;
62 const char* esc;
63 if((esc = get_escape(ti, ESCAPE_OP)) && fbuf_emit(f, esc)){
64 ret = -1;
65 }
66 if((esc = get_escape(ti, ESCAPE_SGR0)) && fbuf_emit(f, esc)){
67 ret = -1;
68 }
69 return ret;
70 }
71
72 // attempt to restore the palette. if XT{PUSH,POP}COLORS is supported, use
73 // XTPOPCOLORS. if we can program individual colors, and we read the palette,
74 // reload it from our initial capture. otherwise, use "oc" if available; this
75 // will blow away any preexisting palette in favor of the default. if we've
76 // never touched the palette, don't bother trying to restore it (unless we're
77 // using XTPOPCOLORS, since in that case we always used XTPUSHCOLORS).
reset_term_palette(const tinfo * ti,fbuf * f,unsigned touchedpalette)78 int reset_term_palette(const tinfo* ti, fbuf* f, unsigned touchedpalette){
79 int ret = 0;
80 const char* esc;
81 if((esc = get_escape(ti, ESCAPE_RESTORECOLORS))){
82 loginfo("restoring palette via xtpopcolors\n");
83 if(fbuf_emit(f, esc)){
84 ret = -1;
85 }
86 return ret;
87 }
88 if(!touchedpalette){
89 return 0;
90 }
91 if(ti->caps.can_change_colors && ti->maxpaletteread > -1){
92 loginfo("restoring saved palette (%d)\n", ti->maxpaletteread + 1);
93 esc = get_escape(ti, ESCAPE_INITC);
94 for(int z = 0 ; z < ti->maxpaletteread ; ++z){
95 unsigned r, g, b;
96 ncchannel_rgb8(ti->originalpalette.chans[z], &r, &g, &b);
97 // Need convert RGB values [0..256) to [0..1000], ugh
98 r = r * 1000 / 255;
99 g = g * 1000 / 255;
100 b = b * 1000 / 255;
101 if(fbuf_emit(f, tiparm(esc, z, r, g, b)) < 0){
102 return -1;
103 }
104 }
105 }else if((esc = get_escape(ti, ESCAPE_OC))){
106 loginfo("resetting palette\n");
107 if(fbuf_emit(f, esc)){
108 ret = -1;
109 }
110 }else{
111 logwarn("no method known to restore palette\n");
112 }
113 return ret;
114 }
115
116 // Do the minimum necessary stuff to restore the terminal, then return. This is
117 // the end of the line for fatal signal handlers. notcurses_stop() will go on
118 // to tear down and account for internal structures. note that we do lots of
119 // shit here that is unsafe within a signal handler =[ FIXME.
120 static int
notcurses_stop_minimal(void * vnc)121 notcurses_stop_minimal(void* vnc){
122 notcurses* nc = vnc;
123 int ret = 0;
124 ret |= drop_signals(nc);
125 // collect output into the memstream buffer, and then dump it directly using
126 // blocking_write(), to avoid problems with unreliable fflush().
127 fbuf* f = &nc->rstate.f;
128 fbuf_reset(f);
129 // be sure to write the restoration sequences *prior* to running rmcup, as
130 // they apply to the screen (alternate or otherwise) we're actually using.
131 const char* esc;
132 ret |= reset_term_palette(&nc->tcache, f, nc->touched_palette);
133 ret |= reset_term_attributes(&nc->tcache, f);
134 if((esc = get_escape(&nc->tcache, ESCAPE_RMKX)) && fbuf_emit(f, esc)){
135 ret = -1;
136 }
137 const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
138 if(cnorm && fbuf_emit(f, cnorm)){
139 ret = -1;
140 }
141 if(fbuf_flush(f, nc->ttyfp)){
142 ret = -1;
143 }
144 if(nc->tcache.ttyfd >= 0){
145 ret |= notcurses_mice_disable(nc);
146 if(nc->tcache.tpreserved){
147 ret |= tcsetattr(nc->tcache.ttyfd, TCSAFLUSH, nc->tcache.tpreserved);
148 }
149 // don't use use leave_alternate_screen() here; we need pop the keyboard
150 // whether we're in regular or alternate screen, and we need it done
151 // before returning to the regular screen if we're in the alternate. if
152 // we drained input, we never sent a keyboard modifier; send none now.
153 if(!(nc->flags & NCOPTION_DRAIN_INPUT)){
154 if(nc->tcache.kbdlevel){
155 if(tty_emit(KKEYBOARD_POP, nc->tcache.ttyfd)){
156 ret = -1;
157 }
158 }else{
159 if(tty_emit(XTMODKEYSUNDO, nc->tcache.ttyfd)){
160 ret = -1;
161 }
162 }
163 }
164 if(nc->tcache.in_alt_screen){
165 if((esc = get_escape(&nc->tcache, ESCAPE_RMCUP))){
166 if(tty_emit(esc, nc->tcache.ttyfd)){
167 ret = -1;
168 }
169 nc->tcache.in_alt_screen = 0;
170 }
171 }
172 }
173 logdebug("restored terminal, returning %d\n", ret);
174 return ret;
175 }
176
177 static const char NOTCURSES_VERSION[] =
178 NOTCURSES_VERSION_MAJOR "."
179 NOTCURSES_VERSION_MINOR "."
180 NOTCURSES_VERSION_PATCH;
181
notcurses_version(void)182 const char* notcurses_version(void){
183 return NOTCURSES_VERSION;
184 }
185
ncplane_set_userptr(ncplane * n,void * opaque)186 void* ncplane_set_userptr(ncplane* n, void* opaque){
187 void* ret = n->userptr;
188 n->userptr = opaque;
189 return ret;
190 }
191
ncplane_userptr(ncplane * n)192 void* ncplane_userptr(ncplane* n){
193 return n->userptr;
194 }
195
ncplane_userptr_const(const ncplane * n)196 const void* ncplane_userptr_const(const ncplane* n){
197 return n->userptr;
198 }
199
200 // is the cursor in an invalid position? it never should be, but it's probably
201 // better to make sure (it's cheap) than to read from/write to random crap.
202 static bool
cursor_invalid_p(const ncplane * n)203 cursor_invalid_p(const ncplane* n){
204 if(n->y >= n->leny || n->x >= n->lenx){
205 return true;
206 }
207 return false;
208 }
209
ncplane_at_cursor(ncplane * n,uint16_t * stylemask,uint64_t * channels)210 char* ncplane_at_cursor(ncplane* n, uint16_t* stylemask, uint64_t* channels){
211 return ncplane_at_yx(n, n->y, n->x, stylemask, channels);
212 }
213
ncplane_at_yx(const ncplane * n,int y,int x,uint16_t * stylemask,uint64_t * channels)214 char* ncplane_at_yx(const ncplane* n, int y, int x, uint16_t* stylemask, uint64_t* channels){
215 if(y < 0){
216 if(y != -1){
217 logerror("invalid y: %d\n", y);
218 return NULL;
219 }
220 y = n->y;
221 }
222 if(x < 0){
223 if(x != -1){
224 logerror("invalid x: %d\n", x);
225 return NULL;
226 }
227 x = n->x;
228 }
229 if((unsigned)y >= n->leny || (unsigned)x >= n->lenx){
230 logerror("invalid coordinates: %d/%d\n", y, x);
231 return NULL;
232 }
233 const nccell* yx = &n->fb[nfbcellidx(n, y, x)];
234 // if we're the right side of a wide glyph, we return the main glyph
235 if(nccell_wide_right_p(yx)){
236 return ncplane_at_yx(n, y, x - 1, stylemask, channels);
237 }
238 char* ret = nccell_extract(n, yx, stylemask, channels);
239 if(ret == NULL){
240 return NULL;
241 }
242 //fprintf(stderr, "GOT [%s]\n", ret);
243 if(strcmp(ret, "") == 0){
244 free(ret);
245 ret = nccell_strdup(n, &n->basecell);
246 if(ret == NULL){
247 return NULL;
248 }
249 if(stylemask){
250 *stylemask = n->basecell.stylemask;
251 }
252 }
253 // FIXME load basecell channels if appropriate
254 return ret;
255 }
256
ncplane_at_cursor_cell(ncplane * n,nccell * c)257 int ncplane_at_cursor_cell(ncplane* n, nccell* c){
258 return ncplane_at_yx_cell(n, n->y, n->x, c);
259 }
260
ncplane_at_yx_cell(ncplane * n,int y,int x,nccell * c)261 int ncplane_at_yx_cell(ncplane* n, int y, int x, nccell* c){
262 if(y < 0){
263 if(y != -1){
264 logerror("invalid y: %d\n", y);
265 return -1;
266 }
267 y = n->y;
268 }
269 if(x < 0){
270 if(x != -1){
271 logerror("invalid x: %d\n", x);
272 return -1;
273 }
274 x = n->x;
275 }
276 if((unsigned)y >= n->leny || (unsigned)x >= n->lenx){
277 logerror("invalid coordinates: %d/%d\n", y, x);
278 return -1;
279 }
280 nccell* targ = ncplane_cell_ref_yx(n, y, x);
281 if(nccell_duplicate(n, c, targ)){
282 return -1;
283 }
284 // FIXME take base cell into account where necessary!
285 return strlen(nccell_extended_gcluster(n, targ));
286 }
287
ncplane_dim_yx(const ncplane * n,unsigned * rows,unsigned * cols)288 void ncplane_dim_yx(const ncplane* n, unsigned* rows, unsigned* cols){
289 if(rows){
290 *rows = n->leny;
291 }
292 if(cols){
293 *cols = n->lenx;
294 }
295 }
296
297 // anyone calling this needs ensure the ncplane's framebuffer is updated
298 // to reflect changes in geometry. also called at startup for standard plane.
299 // sets |cgeo_changed| high iff the cell geometry changed (will happen on a
300 // resize, and on a font resize if the pixel geometry does not change).
301 // sets |pgeo_changed| high iff the cell-pixel geometry changed (will happen
302 // on a font resize).
update_term_dimensions(unsigned * rows,unsigned * cols,tinfo * tcache,int margin_b,unsigned * cgeo_changed,unsigned * pgeo_changed)303 int update_term_dimensions(unsigned* rows, unsigned* cols, tinfo* tcache,
304 int margin_b, unsigned* cgeo_changed, unsigned* pgeo_changed){
305 *pgeo_changed = 0;
306 *cgeo_changed = 0;
307 // if we're not a real tty, we presumably haven't changed geometry, return
308 if(tcache->ttyfd < 0){
309 if(rows){
310 *rows = tcache->default_rows;
311 }
312 if(cols){
313 *cols = tcache->default_cols;
314 }
315 tcache->cellpxy = 0;
316 tcache->cellpxx = 0;
317 return 0;
318 }
319 unsigned rowsafe, colsafe;
320 if(rows == NULL){
321 rows = &rowsafe;
322 rowsafe = tcache->dimy;
323 }
324 if(cols == NULL){
325 cols = &colsafe;
326 colsafe = tcache->dimx;
327 }
328 #ifndef __MINGW64__
329 struct winsize ws;
330 if(tiocgwinsz(tcache->ttyfd, &ws)){
331 return -1;
332 }
333 *rows = ws.ws_row;
334 *cols = ws.ws_col;
335 unsigned cpixy;
336 unsigned cpixx;
337 #ifdef __linux__
338 if(tcache->linux_fb_fd >= 0){
339 get_linux_fb_pixelgeom(tcache, &tcache->pixy, &tcache->pixx);
340 cpixy = tcache->pixy / *rows;
341 cpixx = tcache->pixx / *cols;
342 }else{
343 #else
344 {
345 #endif
346 // we might have the pixel geometry from CSI14t, so don't override a valid
347 // earlier response with 0s from the ioctl. we do want to fire off a fresh
348 // CSI14t in this case, though FIXME.
349 if(ws.ws_ypixel){
350 tcache->pixy = ws.ws_ypixel;
351 tcache->pixx = ws.ws_xpixel;
352 }
353 // update even if we didn't get values just now, because we need set
354 // cellpx{y,x} up from an initial CSI14n, which set only pix{y,x}.
355 cpixy = ws.ws_row ? tcache->pixy / ws.ws_row : 0;
356 cpixx = ws.ws_col ? tcache->pixx / ws.ws_col : 0;
357 }
358 if(tcache->cellpxy != cpixy){
359 tcache->cellpxy = cpixy;
360 *pgeo_changed = 1;
361 }
362 if(tcache->cellpxx != cpixx){
363 tcache->cellpxx = cpixx;
364 *pgeo_changed = 1;
365 }
366 if(tcache->cellpxy == 0 || tcache->cellpxx == 0){
367 tcache->pixel_draw = NULL; // disable support
368 }
369 #else
370 CONSOLE_SCREEN_BUFFER_INFO csbi;
371 // There is the buffer itself, which is similar in function to the scrollback
372 // buffer in a Linux terminal, and there is the display window, which is the
373 // visible view of that buffer. The addressable area (from a VT point of
374 // view) spans the width of the buffer, but the height of the display window.
375 //
376 // +--------------------------+ ^
377 // | | |
378 // | | |
379 // +-----+--------------+-----+ ^ w | b
380 // |XXXXX|XXXXXXXXXXXXXX|XXXXX| | i | u
381 // |XXXXX|XXXXXXXXXXXXXX|XXXXX| | n | f
382 // |XXXXX|XXXXXXXXXXXXXX|XXXXX| | d | f
383 // |XXXXX|XXXXXXXXXXXXXX|XXXXX| | o | e
384 // +-----+--------------+-----+ v w | r
385 // | | |
386 // | | |
387 // +--------------------------+ v
388 //
389 // <--- window --->
390 //
391 //<--------- buffer --------->
392 //
393 // Because the buffer extends past the bottom of the display window, a user
394 // can potentially scroll down beyond what would normally be thought of as the
395 // end of the buffer. Because the buffer can be wider than the display
396 // window, the user can scroll horizontally to view parts of the addressable
397 // area that aren't currently visible.
398 if(GetConsoleScreenBufferInfo(tcache->outhandle, &csbi)){
399 *cols = csbi.dwSize.X;
400 *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
401 }else{
402 *rows = tcache->default_rows;
403 *cols = tcache->default_cols;
404 }
405 #endif
406 if(tcache->dimy != *rows){
407 tcache->dimy = *rows;
408 *cgeo_changed = 1;
409 }
410 if(tcache->dimx != *cols){
411 tcache->dimx = *cols;
412 *cgeo_changed = 1;
413 }
414 if(tcache->sixel_maxy_pristine){
415 int sixelrows = *rows - 1;
416 // if the bottom margin is at least one row, we can draw into the last
417 // row of our visible area. we must leave the true bottom row alone.
418 if(margin_b){
419 ++sixelrows;
420 }
421 tcache->sixel_maxy = sixelrows * tcache->cellpxy;
422 if(tcache->sixel_maxy > tcache->sixel_maxy_pristine){
423 tcache->sixel_maxy = tcache->sixel_maxy_pristine;
424 }
425 }
426 return 0;
427 }
428
429 // destroy the sprixels of an ncpile (this will not hide the sprixels)
430 static void
431 free_sprixels(ncpile* n){
432 while(n->sprixelcache){
433 sprixel* tmp = n->sprixelcache->next;
434 sprixel_free(n->sprixelcache);
435 n->sprixelcache = tmp;
436 }
437 }
438
439 // destroy an empty ncpile. only call with pilelock held.
440 static void
441 ncpile_destroy(ncpile* pile){
442 if(pile){
443 pile->prev->next = pile->next;
444 pile->next->prev = pile->prev;
445 free_sprixels(pile);
446 free(pile->crender);
447 free(pile);
448 }
449 }
450
451 void free_plane(ncplane* p){
452 if(p){
453 // ncdirect fakes an ncplane with no ->pile
454 if(ncplane_pile(p)){
455 notcurses* nc = ncplane_notcurses(p);
456 pthread_mutex_lock(&nc->stats.lock);
457 --ncplane_notcurses(p)->stats.s.planes;
458 ncplane_notcurses(p)->stats.s.fbbytes -= sizeof(*p->fb) * p->leny * p->lenx;
459 pthread_mutex_unlock(&nc->stats.lock);
460 if(p->above == NULL && p->below == NULL){
461 pthread_mutex_lock(&nc->pilelock);
462 ncpile_destroy(ncplane_pile(p));
463 pthread_mutex_unlock(&nc->pilelock);
464 }
465 }
466 if(p->widget){
467 void* w = p->widget;
468 void (*wdestruct)(void*) = p->wdestruct;
469 p->widget = NULL;
470 p->wdestruct = NULL;
471 logdebug("calling widget destructor %p(%p)\n", wdestruct, w);
472 wdestruct(w);
473 logdebug("got the widget\n");
474 }
475 if(p->sprite){
476 sprixel_hide(p->sprite);
477 }
478 destroy_tam(p);
479 egcpool_dump(&p->pool);
480 free(p->name);
481 free(p->fb);
482 free(p);
483 }
484 }
485
486 // create a new ncpile. only call with pilelock held. the return value
487 // was assigned to n->pile.
488 __attribute__((malloc))
489 static ncpile*
490 make_ncpile(notcurses* nc, ncplane* n){
491 ncpile* ret = malloc(sizeof(*ret));
492 if(ret){
493 ret->nc = nc;
494 ret->top = n;
495 ret->bottom = n;
496 ret->roots = n;
497 n->bprev = &ret->roots;
498 if(nc->stdplane){ // stdplane (and thus stdpile) has already been created
499 ret->prev = ncplane_pile(nc->stdplane)->prev;
500 ncplane_pile(nc->stdplane)->prev->next = ret;
501 ret->next = ncplane_pile(nc->stdplane);
502 ncplane_pile(nc->stdplane)->prev = ret;
503 }else{
504 ret->prev = ret;
505 ret->next = ret;
506 }
507 n->above = NULL;
508 n->below = NULL;
509 ret->dimy = nc->tcache.dimy;
510 ret->dimx = nc->tcache.dimx;
511 ret->cellpxy = nc->tcache.cellpxy;
512 ret->cellpxx = nc->tcache.cellpxx;
513 ret->crender = NULL;
514 ret->crenderlen = 0;
515 ret->sprixelcache = NULL;
516 ret->scrolls = 0;
517 }
518 n->pile = ret;
519 return ret;
520 }
521
522 // create a new ncplane at the specified location (relative to the true screen,
523 // having origin at 0,0), having the specified size, and put it at the top of
524 // the planestack. its cursor starts at its origin; its style starts as null.
525 // a plane may exceed the boundaries of the screen, but must have positive
526 // size in both dimensions. bind the plane to 'n', which may be NULL to create
527 // a new pile. if bound to a plane instead, this plane moves when that plane
528 // moves, and coordinates to move to are relative to that plane.
529 // there are two denormalized case we also must handle, that of the "fake"
530 // isolated ncplane created by ncdirect for rendering visuals. in that case
531 // (and only in that case), nc is NULL (as is n). there's also creation of the
532 // initial standard plane, in which case nc is not NULL, but nc->stdplane *is*
533 // (as once more is n).
534 ncplane* ncplane_new_internal(notcurses* nc, ncplane* n,
535 const ncplane_options* nopts){
536 if(nopts->flags >= (NCPLANE_OPTION_FIXED << 1u)){
537 logwarn("provided unsupported flags %016" PRIx64 "\n", nopts->flags);
538 }
539 if(nopts->flags & NCPLANE_OPTION_HORALIGNED || nopts->flags & NCPLANE_OPTION_VERALIGNED){
540 if(n == NULL){
541 logerror("alignment requires a parent plane\n");
542 return NULL;
543 }
544 }
545 if(nopts->flags & NCPLANE_OPTION_MARGINALIZED){
546 if(nopts->rows != 0 || nopts->cols != 0){
547 logerror("geometry specified with margins (r=%d, c=%d)\n",
548 nopts->rows, nopts->cols);
549 return NULL;
550 }
551 }else if(nopts->rows <= 0 || nopts->cols <= 0){
552 logerror("won't create denormalized plane (r=%d, c=%d)\n",
553 nopts->rows, nopts->cols);
554 return NULL;
555 }
556 ncplane* p = malloc(sizeof(*p));
557 if(p == NULL){
558 return NULL;
559 }
560 p->scrolling = nopts->flags & NCPLANE_OPTION_VSCROLL;
561 p->fixedbound = nopts->flags & NCPLANE_OPTION_FIXED;
562 p->autogrow = nopts->flags & NCPLANE_OPTION_AUTOGROW;
563 p->widget = NULL;
564 p->wdestruct = NULL;
565 if(nopts->flags & NCPLANE_OPTION_MARGINALIZED){
566 p->margin_b = nopts->margin_b;
567 p->margin_r = nopts->margin_r;
568 if(n){ // use parent size
569 p->leny = ncplane_dim_y(n);
570 p->lenx = ncplane_dim_x(n);
571 }else{ // use pile size
572 notcurses_term_dim_yx(nc, &p->leny, &p->lenx);
573 }
574 if((p->leny -= p->margin_b) <= 0){
575 p->leny = 1;
576 }
577 if((p->lenx -= p->margin_r) <= 0){
578 p->lenx = 1;
579 }
580 }else{
581 p->leny = nopts->rows;
582 p->lenx = nopts->cols;
583 }
584 size_t fbsize = sizeof(*p->fb) * (p->leny * p->lenx);
585 if((p->fb = malloc(fbsize)) == NULL){
586 logerror("error allocating cellmatrix (r=%d, c=%d)\n",
587 p->leny, p->lenx);
588 free(p);
589 return NULL;
590 }
591 memset(p->fb, 0, fbsize);
592 p->x = p->y = 0;
593 p->logrow = 0;
594 p->sprite = NULL;
595 p->blist = NULL;
596 p->name = strdup(nopts->name ? nopts->name : "");
597 p->halign = NCALIGN_UNALIGNED;
598 p->valign = NCALIGN_UNALIGNED;
599 p->tam = NULL;
600 if(!n){ // new root/standard plane
601 p->absy = nopts->y;
602 p->absx = nopts->x;
603 p->bnext = NULL;
604 p->bprev = NULL;
605 p->boundto = p;
606 }else{ // bound to preexisting pile
607 if(nopts->flags & NCPLANE_OPTION_HORALIGNED){
608 p->absx = ncplane_halign(n, nopts->x, nopts->cols);
609 p->halign = nopts->x;
610 }else{
611 p->absx = nopts->x;
612 }
613 p->absx += n->absx;
614 if(nopts->flags & NCPLANE_OPTION_VERALIGNED){
615 p->absy = ncplane_valign(n, nopts->y, nopts->rows);
616 p->valign = nopts->y;
617 }else{
618 p->absy = nopts->y;
619 }
620 p->absy += n->absy;
621 if( (p->bnext = n->blist) ){
622 n->blist->bprev = &p->bnext;
623 }
624 p->bprev = &n->blist;
625 *p->bprev = p;
626 p->boundto = n;
627 }
628 // FIXME handle top/left margins
629 p->resizecb = nopts->resizecb;
630 p->stylemask = 0;
631 p->channels = 0;
632 egcpool_init(&p->pool);
633 nccell_init(&p->basecell);
634 p->userptr = nopts->userptr;
635 if(nc == NULL){ // fake ncplane backing ncdirect object
636 p->above = NULL;
637 p->below = NULL;
638 p->pile = NULL;
639 }else{
640 pthread_mutex_lock(&nc->pilelock);
641 ncpile* pile = n ? ncplane_pile(n) : NULL;
642 if( (p->pile = pile) ){ // existing pile
643 p->above = NULL;
644 if( (p->below = pile->top) ){ // always happens save initial plane
645 pile->top->above = p;
646 }else{
647 pile->bottom = p;
648 }
649 pile->top = p;
650 }else{ // new pile
651 make_ncpile(nc, p);
652 }
653 pthread_mutex_lock(&nc->stats.lock);
654 nc->stats.s.fbbytes += fbsize;
655 ++nc->stats.s.planes;
656 pthread_mutex_unlock(&nc->stats.lock);
657 pthread_mutex_unlock(&nc->pilelock);
658 }
659 loginfo("created new %dx%d plane \"%s\" @ %dx%d\n",
660 p->leny, p->lenx, p->name ? p->name : "", p->absy, p->absx);
661 return p;
662 }
663
664 // create an ncplane of the specified dimensions, but do not yet place it in
665 // the z-buffer. clear out all cells. this is for a wholly new context.
666 static ncplane*
667 create_initial_ncplane(notcurses* nc, int dimy, int dimx){
668 ncplane_options nopts = {
669 .y = 0, .x = 0,
670 .rows = dimy - (nc->margin_t + nc->margin_b),
671 .cols = dimx - (nc->margin_l + nc->margin_r),
672 .userptr = NULL,
673 .name = "std",
674 .resizecb = NULL,
675 .flags = 0,
676 };
677 return nc->stdplane = ncplane_new_internal(nc, NULL, &nopts);
678 }
679
680 ncplane* notcurses_stdplane(notcurses* nc){
681 return nc->stdplane;
682 }
683
684 const ncplane* notcurses_stdplane_const(const notcurses* nc){
685 return nc->stdplane;
686 }
687
688 ncplane* ncplane_create(ncplane* n, const ncplane_options* nopts){
689 return ncplane_new_internal(ncplane_notcurses(n), n, nopts);
690 }
691
692 ncplane* ncpile_create(notcurses* nc, const struct ncplane_options* nopts){
693 return ncplane_new_internal(nc, NULL, nopts);
694 }
695
696 void ncplane_home(ncplane* n){
697 n->x = 0;
698 n->y = 0;
699 }
700
701 int ncplane_cursor_move_yx(ncplane* n, int y, int x){
702 if(x < 0){
703 if(x < -1){
704 logerror("negative target x %d\n", x);
705 return -1;
706 }
707 }else if((unsigned)x >= n->lenx){
708 logerror("target x %d >= width %u\n", x, n->lenx);
709 return -1;
710 }else{
711 n->x = x;
712 }
713 if(y < 0){
714 if(y < -1){
715 logerror("negative target y %d\n", y);
716 return -1;
717 }
718 }else if((unsigned)y >= n->leny){
719 logerror("target y %d >= height %u\n", y, n->leny);
720 return -1;
721 }else{
722 n->y = y;
723 }
724 if(cursor_invalid_p(n)){
725 logerror("invalid cursor following move (%d/%d)\n", n->y, n->x);
726 return -1;
727 }
728 return 0;
729 }
730
731 int ncplane_cursor_move_rel(ncplane* n, int y, int x){
732 if((int)n->y + y == -1){
733 logerror("invalid target y -1\n");
734 return -1;
735 }else if((int)n->x + x == -1){
736 logerror("invalid target x -1\n");
737 return -1;
738 }else return ncplane_cursor_move_yx(n, n->y + y, n->x + x);
739 }
740
741 ncplane* ncplane_dup(const ncplane* n, void* opaque){
742 int dimy = n->leny;
743 int dimx = n->lenx;
744 const int placey = n->absy;
745 const int placex = n->absx;
746 struct ncplane_options nopts = {
747 .y = placey,
748 .x = placex,
749 .rows = dimy,
750 .cols = dimx,
751 .userptr = opaque,
752 .name = n->name,
753 .resizecb = ncplane_resizecb(n),
754 .flags = 0,
755 };
756 ncplane* newn = ncplane_create(n->boundto, &nopts);
757 if(newn == NULL){
758 return NULL;
759 }
760 // we don't duplicate sprites...though i'm unsure why not
761 size_t fbsize = sizeof(*n->fb) * dimx * dimy;
762 if(egcpool_dup(&newn->pool, &n->pool)){
763 ncplane_destroy(newn);
764 return NULL;
765 }
766 memmove(newn->fb, n->fb, fbsize);
767 // don't use ncplane_cursor_move_yx() here; the cursor could be in an
768 // invalid location, which will be disallowed, failing out.
769 newn->y = n->y;
770 newn->x = n->x;
771 newn->halign = n->halign;
772 newn->stylemask = ncplane_styles(n);
773 newn->channels = ncplane_channels(n);
774 // we dupd the egcpool, so just dup the goffset
775 newn->basecell = n->basecell;
776 return newn;
777 }
778
779 // call the resize callback for each bound child in turn. we only need to do
780 // the first generation; if they resize, they'll invoke
781 // ncplane_resize_internal(), leading to this function being called anew.
782 int resize_callbacks_children(ncplane* n){
783 int ret = 0;
784 for(struct ncplane* child = n->blist ; child ; child = child->bnext){
785 if(child->resizecb){
786 ret |= child->resizecb(child);
787 }
788 }
789 return ret;
790 }
791
792 // can be used on stdplane, unlike ncplane_resize() which prohibits it.
793 int ncplane_resize_internal(ncplane* n, int keepy, int keepx,
794 unsigned keepleny, unsigned keeplenx,
795 int yoff, int xoff,
796 unsigned ylen, unsigned xlen){
797 if(keepy < 0 || keepx < 0){ // can't start at negative origin
798 logerror("can't retain negative offset %dx%d\n", keepy, keepx);
799 return -1;
800 }
801 if((!keepleny && keeplenx) || (keepleny && !keeplenx)){ // both must be 0
802 logerror("can't retain null dimension %dx%d\n", keepleny, keeplenx);
803 return -1;
804 }
805 // can't be smaller than keep length
806 if(ylen < keepleny){
807 logerror("can't map in y dimension: %u < %d\n", ylen, keepleny);
808 return -1;
809 }
810 if(xlen < keeplenx){
811 logerror("can't map in x dimension: %u < %d\n", xlen, keeplenx);
812 return -1;
813 }
814 if(ylen <= 0 || xlen <= 0){ // can't resize to trivial or negative size
815 logerror("can't achieve meaningless size %ux%u\n", ylen, xlen);
816 return -1;
817 }
818 unsigned rows, cols;
819 ncplane_dim_yx(n, &rows, &cols);
820 if(keepleny + keepy > rows){
821 logerror("can't keep %d@%d rows from %d\n", keepleny, keepy, rows);
822 return -1;
823 }
824 if(keeplenx + keepx > cols){
825 logerror("can't keep %d@%d cols from %d\n", keeplenx, keepx, cols);
826 return -1;
827 }
828 loginfo("%dx%d @ %d/%d → %u/%u @ %d/%d (want %ux%u@%d/%d)\n", rows, cols, n->absy, n->absx, ylen, xlen, n->absy + keepy + yoff, n->absx + keepx + xoff, keepleny, keeplenx, keepy, keepx);
829 if(n->absy == n->absy + keepy && n->absx == n->absx + keepx &&
830 rows == ylen && cols == xlen){
831 return 0;
832 }
833 notcurses* nc = ncplane_notcurses(n);
834 if(n->sprite){
835 sprixel_hide(n->sprite);
836 }
837 // we're good to resize. we'll need alloc up a new framebuffer, and copy in
838 // those elements we're retaining, zeroing out the rest. alternatively, if
839 // we've shrunk, we will be filling the new structure.
840 int oldarea = rows * cols;
841 int keptarea = keepleny * keeplenx;
842 int newarea = ylen * xlen;
843 size_t fbsize = sizeof(nccell) * newarea;
844 // an important optimization is the case where nothing needs to be moved,
845 // true when either the keptarea is 0 or the old and new x dimensions are
846 // equal, and we're keeping the full x dimension, and any material we're
847 // keeping starts at the top. in this case, we try to realloc() and avoid
848 // any copying whatsoever (we otherwise incur at least one copy due to
849 // always using a new area). set realloced high in this event, so we don't
850 // free anything.
851 nccell* fb;
852 bool realloced = false;
853 if(keptarea == 0 || (cols == xlen && cols == keeplenx && keepy == 0)){
854 if((fb = realloc(n->fb, fbsize)) == NULL){
855 return -1;
856 }
857 realloced = true;
858 }else{
859 if((fb = malloc(fbsize)) == NULL){
860 return -1;
861 }
862 }
863 if(n->tam){
864 loginfo("TAM realloc to %d entries\n", newarea);
865 // FIXME first, free any disposed auxiliary vectors!
866 tament* tmptam = realloc(n->tam, sizeof(*tmptam) * newarea);
867 if(tmptam == NULL){
868 if(!realloced){
869 free(fb);
870 }
871 return -1;
872 }
873 n->tam = tmptam;
874 if(newarea > oldarea){
875 memset(n->tam + oldarea, 0, sizeof(*n->tam) * (newarea - oldarea));
876 }
877 }
878 // update the cursor, if it would otherwise be off-plane
879 if(n->y >= ylen){
880 n->y = ylen - 1;
881 }
882 if(n->x >= xlen){
883 n->x = xlen - 1;
884 }
885 nccell* preserved = n->fb;
886 pthread_mutex_lock(&nc->stats.lock);
887 ncplane_notcurses(n)->stats.s.fbbytes -= sizeof(*preserved) * (rows * cols);
888 ncplane_notcurses(n)->stats.s.fbbytes += fbsize;
889 pthread_mutex_unlock(&nc->stats.lock);
890 n->fb = fb;
891 const int oldabsy = n->absy;
892 // go ahead and move. we can no longer fail at this point. but don't yet
893 // resize, because n->len[xy] are used in fbcellidx() in the loop below. we
894 // don't use ncplane_move_yx(), because we want to planebinding-invariant.
895 n->absy += keepy + yoff;
896 n->absx += keepx + xoff;
897 //fprintf(stderr, "absx: %d keepx: %d xoff: %d\n", n->absx, keepx, xoff);
898 if(keptarea == 0){ // keep nothing, resize/move only.
899 // if we're keeping nothing, dump the old egcspool. otherwise, we go ahead
900 // and keep it. perhaps we ought compact it?
901 memset(fb, 0, sizeof(*fb) * newarea);
902 egcpool_dump(&n->pool);
903 }else if(realloced){
904 // the x dimensions are equal, and we're keeping across the width. only the
905 // y dimension changed. at worst, we need zero some out.
906 unsigned tozorch = (ylen - keepleny) * xlen * sizeof(*fb);
907 if(tozorch){
908 unsigned zorchoff = keepleny * xlen;
909 memset(fb + zorchoff, 0, tozorch);
910 }
911 }else{
912 // we currently have maxy rows of maxx cells each. we will be keeping rows
913 // keepy..keepy + keepleny - 1 and columns keepx..keepx + keeplenx - 1.
914 // anything else is zerod out. itery is the row we're writing *to*, and we
915 // must write to each (and every cell in each).
916 for(unsigned itery = 0 ; itery < ylen ; ++itery){
917 int truey = itery + n->absy;
918 int sourceoffy = truey - oldabsy;
919 //fprintf(stderr, "sourceoffy: %d keepy: %d ylen: %d\n", sourceoffy, keepy, ylen);
920 // if we have nothing copied to this line, zero it out in one go
921 if(sourceoffy < keepy || sourceoffy >= keepy + (int)keepleny){
922 //fprintf(stderr, "writing 0s to line %d of %d\n", itery, ylen);
923 memset(fb + (itery * xlen), 0, sizeof(*fb) * xlen);
924 }else{
925 int copyoff = itery * xlen; // our target at any given time
926 // we do have something to copy, and zero, one, or two regions to zero out
927 unsigned copied = 0;
928 if(xoff < 0){
929 memset(fb + copyoff, 0, sizeof(*fb) * -xoff);
930 copyoff += -xoff;
931 copied += -xoff;
932 }
933 const int sourceidx = nfbcellidx(n, sourceoffy, keepx);
934 //fprintf(stderr, "copying line %d (%d) to %d (%d)\n", sourceoffy, sourceidx, copyoff / xlen, copyoff);
935 memcpy(fb + copyoff, preserved + sourceidx, sizeof(*fb) * keeplenx);
936 copyoff += keeplenx;
937 copied += keeplenx;
938 if(xlen > copied){
939 memset(fb + copyoff, 0, sizeof(*fb) * (xlen - copied));
940 }
941 }
942 }
943 }
944 n->lenx = xlen;
945 n->leny = ylen;
946 if(!realloced){
947 free(preserved);
948 }
949 return resize_callbacks_children(n);
950 }
951
952 int ncplane_resize(ncplane* n, int keepy, int keepx,
953 unsigned keepleny, unsigned keeplenx,
954 int yoff, int xoff,
955 unsigned ylen, unsigned xlen){
956 if(n == ncplane_notcurses(n)->stdplane){
957 //fprintf(stderr, "Can't resize standard plane\n");
958 return -1;
959 }
960 return ncplane_resize_internal(n, keepy, keepx, keepleny, keeplenx,
961 yoff, xoff, ylen, xlen);
962 }
963
964 int ncplane_destroy(ncplane* ncp){
965 if(ncp == NULL){
966 return 0;
967 }
968 if(ncplane_notcurses(ncp)->stdplane == ncp){
969 logerror("Won't destroy standard plane\n");
970 return -1;
971 }
972 //notcurses_debug(ncplane_notcurses(ncp), stderr);
973 loginfo("Destroying %dx%d plane \"%s\" @ %dx%d\n",
974 ncp->leny, ncp->lenx, ncp->name ? ncp->name : NULL, ncp->absy, ncp->absx);
975 int ret = 0;
976 // dissolve our binding from behind (->bprev is either NULL, or its
977 // predecessor on the bound list's ->bnext, or &ncp->boundto->blist)
978 if(ncp->bprev){
979 if( (*ncp->bprev = ncp->bnext) ){
980 ncp->bnext->bprev = ncp->bprev;
981 }
982 }else if(ncp->bnext){
983 //assert(ncp->boundto->blist == ncp);
984 ncp->bnext->bprev = NULL;
985 }
986 // recursively reparent our children to the plane to which we are bound.
987 // this will extract each one from the sibling list.
988 struct ncplane* bound = ncp->blist;
989 while(bound){
990 struct ncplane* tmp = bound->bnext;
991 ncplane* bindto = ((ncp == ncp->boundto) ? bound : ncp->boundto);
992 if(ncplane_reparent_family(bound, bindto) == NULL){
993 ret = -1;
994 }
995 bound = tmp;
996 }
997 // extract ourselves from the z-axis. do this *after* reparenting, in case
998 // reparenting shifts up the z-axis somehow (though i don't think it can,
999 // at least not within a pile?).
1000 if(ncp->above){
1001 ncp->above->below = ncp->below;
1002 }else{
1003 ncplane_pile(ncp)->top = ncp->below;
1004 }
1005 if(ncp->below){
1006 ncp->below->above = ncp->above;
1007 }else{
1008 ncplane_pile(ncp)->bottom = ncp->above;
1009 }
1010 free_plane(ncp);
1011 return ret;
1012 }
1013
1014 int ncplane_destroy_family(ncplane *ncp){
1015 if(ncp == NULL){
1016 return 0;
1017 }
1018 if(ncplane_notcurses(ncp)->stdplane == ncp){
1019 logerror("Won't destroy standard plane\n");
1020 return -1;
1021 }
1022 int ret = 0;
1023 while(ncp->blist){
1024 ret |= ncplane_destroy_family(ncp->blist);
1025 }
1026 ret |= ncplane_destroy(ncp);
1027 return ret;
1028 }
1029
1030 // it's critical that we're using UTF-8 encoding if at all possible. since the
1031 // client might not have called setlocale(2) (if they weren't reading the
1032 // directions...), go ahead and try calling setlocale(LC_ALL, "") and then
1033 // setlocale(LC_CTYPE, "C.UTF-8") ourselves *iff* we're not using UTF-8 *and*
1034 // LANG is not explicitly set to "C" nor "POSIX". this still requires the user
1035 // to have a proper locale generated and available on disk. either way, they're
1036 // going to get a diagnostic (unless the user has explicitly configured a LANG
1037 // of "C" or "POSIX"). recommended practice is for the client code to have
1038 // called setlocale() themselves, and set the NCOPTION_INHIBIT_SETLOCALE flag.
1039 // if that flag is set, we take the locale and encoding as we get them.
1040 void init_lang(void){
1041 char* setret;
1042 #ifdef __MINGW64__
1043 if((setret = setlocale(LC_ALL, ".UTF8")) == NULL){
1044 logwarn("couldn't set LC_ALL to utf8\n");
1045 }
1046 #endif
1047 const char* encoding = nl_langinfo(CODESET);
1048 if(encoding && !strcmp(encoding, "UTF-8")){
1049 return; // already utf-8, great!
1050 }
1051 const char* lang = getenv("LANG");
1052 // if LANG was explicitly set to C/POSIX, life sucks, roll with it
1053 if(lang && (!strcmp(lang, "C") || !strcmp(lang, "POSIX"))){
1054 loginfo("LANG was explicitly set to %s, not changing locale\n", lang);
1055 return;
1056 }
1057 #ifndef __MINGW64__
1058 if((setret = setlocale(LC_ALL, "")) == NULL){
1059 logwarn("setting locale based on LANG failed\n");
1060 }
1061 #endif
1062 encoding = nl_langinfo(CODESET);
1063 if(encoding && !strcmp(encoding, "UTF-8")){
1064 loginfo("Set locale from LANG; client should call setlocale(2)!\n");
1065 return;
1066 }
1067 setlocale(LC_CTYPE, "C.UTF-8");
1068 encoding = nl_langinfo(CODESET);
1069 if(encoding && !strcmp(encoding, "UTF-8")){
1070 loginfo("Forced UTF-8 encoding; client should call setlocale(2)!\n");
1071 return;
1072 }
1073 }
1074
1075 // initialize a recursive mutex lock in a way that works on both glibc + musl
1076 static int
1077 recursive_lock_init(pthread_mutex_t *lock){
1078 #ifndef __GLIBC__
1079 #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE
1080 #endif
1081 pthread_mutexattr_t attr;
1082 if(pthread_mutexattr_init(&attr)){
1083 return -1;
1084 }
1085 if(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP)){
1086 pthread_mutexattr_destroy(&attr);
1087 return -1;
1088 }
1089 if(pthread_mutex_init(lock, &attr)){
1090 pthread_mutexattr_destroy(&attr);
1091 return -1;
1092 }
1093 pthread_mutexattr_destroy(&attr);
1094 return 0;
1095 #ifndef __GLIBC__
1096 #undef PTHREAD_MUTEX_RECURSIVE_NP
1097 #endif
1098 }
1099
1100 ncpixelimpl_e notcurses_check_pixel_support(const notcurses* nc){
1101 return nc->tcache.pixel_implementation;
1102 }
1103
1104 // the earliest stage of initialization. logging does not yet work; all
1105 // diagostics should be emitted with fprintf(stderr).
1106 // * validates |opts|, if not NULL
1107 // * creates and zeroes out the notcurses struct
1108 // * ensures that we're using ASCII or UTF8 and calls setlocale(3)
1109 // * checks the environment for NOTCURSES_LOGLEVEL and sets ret->loglevel
1110 // * writes TERM to the environment, if specified via opts->termtype
1111 //
1112 // iff we're using UTF8, |utf8| will be set to 1. it is otherwise set to 0.
1113 __attribute__ ((nonnull (2))) static notcurses*
1114 notcurses_early_init(const struct notcurses_options* opts, FILE* fp, unsigned* utf8){
1115 if(fwide(fp, 0) > 0){
1116 fprintf(stderr, "Error: output stream is wide-oriented\n");
1117 return NULL;
1118 }
1119 notcurses* ret = malloc(sizeof(*ret));
1120 if(ret == NULL){
1121 return ret;
1122 }
1123 memset(ret, 0, sizeof(*ret));
1124 if(opts){
1125 if(opts->flags >= (NCOPTION_DRAIN_INPUT << 1u)){
1126 fprintf(stderr, "Warning: unknown Notcurses options %016" PRIu64 "\n", opts->flags);
1127 }
1128 if(opts->termtype){
1129 if(putenv_term(opts->termtype)){
1130 free(ret);
1131 return NULL;
1132 }
1133 }
1134 ret->flags = opts->flags;
1135 ret->margin_t = opts->margin_t;
1136 ret->margin_b = opts->margin_b;
1137 ret->margin_l = opts->margin_l;
1138 ret->margin_r = opts->margin_r;
1139 ret->loglevel = opts->loglevel;
1140 }
1141 ret->last_pile = NULL;
1142 ret->rstate.f.buf = NULL;
1143 ret->lastframe = NULL;
1144 set_loglevel_from_env(&ret->loglevel);
1145 if(!(ret->flags & NCOPTION_INHIBIT_SETLOCALE)){
1146 init_lang();
1147 }
1148 //fprintf(stderr, "getenv LC_ALL: %s LC_CTYPE: %s\n", getenv("LC_ALL"), getenv("LC_CTYPE"));
1149 const char* encoding = nl_langinfo(CODESET);
1150 if(encoding && !strcmp(encoding, "UTF-8")){
1151 *utf8 = true;
1152 }else{
1153 *utf8 = false;
1154 if(encoding && (strcmp(encoding, "ANSI_X3.4-1968") &&
1155 strcmp(encoding, "US-ASCII") &&
1156 strcmp(encoding, "ASCII"))){
1157 fprintf(stderr, "Encoding (\"%s\") was neither ANSI_X3.4-1968 nor UTF-8, refusing to start\n Did you call setlocale()?\n",
1158 encoding ? encoding : "none found");
1159 free(ret);
1160 return NULL;
1161 }
1162 }
1163 ret->cursory = ret->cursorx = -1;
1164 reset_stats(&ret->stats.s);
1165 reset_stats(&ret->stashed_stats);
1166 ret->ttyfp = fp;
1167 egcpool_init(&ret->pool);
1168 if(ret->loglevel > NCLOGLEVEL_TRACE || ret->loglevel < NCLOGLEVEL_SILENT){
1169 fprintf(stderr, "Invalid loglevel %d\n", ret->loglevel);
1170 free(ret);
1171 return NULL;
1172 }
1173 if(recursive_lock_init(&ret->pilelock)){
1174 fprintf(stderr, "Couldn't initialize pile mutex\n");
1175 free(ret);
1176 return NULL;
1177 }
1178 if(pthread_mutex_init(&ret->stats.lock, NULL)){
1179 pthread_mutex_destroy(&ret->pilelock);
1180 free(ret);
1181 return NULL;
1182 }
1183 return ret;
1184 }
1185
1186 notcurses* notcurses_core_init(const notcurses_options* opts, FILE* outfp){
1187 if(outfp == NULL){
1188 outfp = stdout;
1189 }
1190 unsigned utf8;
1191 notcurses* ret = notcurses_early_init(opts, outfp, &utf8);
1192 if(ret == NULL){
1193 return NULL;
1194 }
1195 // the fbuf is needed by notcurses_stop_minimal, so this must be done
1196 // before registering fatal signal handlers.
1197 if(fbuf_init(&ret->rstate.f)){
1198 pthread_mutex_destroy(&ret->pilelock);
1199 pthread_mutex_destroy(&ret->stats.lock);
1200 free(ret);
1201 return NULL;
1202 }
1203 if(setup_signals(ret, (ret->flags & NCOPTION_NO_QUIT_SIGHANDLERS),
1204 (ret->flags & NCOPTION_NO_WINCH_SIGHANDLER),
1205 notcurses_stop_minimal)){
1206 fbuf_free(&ret->rstate.f);
1207 pthread_mutex_destroy(&ret->pilelock);
1208 pthread_mutex_destroy(&ret->stats.lock);
1209 free(ret);
1210 return NULL;
1211 }
1212 // don't set loglevel until we've acquired the signal handler, lest we
1213 // change the loglevel out from under a running instance
1214 loglevel = ret->loglevel;
1215 ret->rstate.logendy = -1;
1216 ret->rstate.logendx = -1;
1217 ret->rstate.x = ret->rstate.y = -1;
1218 int fakecursory = ret->rstate.logendy;
1219 int fakecursorx = ret->rstate.logendx;
1220 int* cursory = ret->flags & NCOPTION_PRESERVE_CURSOR ?
1221 &ret->rstate.logendy : &fakecursory;
1222 int* cursorx = ret->flags & NCOPTION_PRESERVE_CURSOR ?
1223 &ret->rstate.logendx : &fakecursorx;
1224 if(interrogate_terminfo(&ret->tcache, ret->ttyfp, utf8,
1225 ret->flags & NCOPTION_NO_ALTERNATE_SCREEN, 0,
1226 ret->flags & NCOPTION_NO_FONT_CHANGES,
1227 cursory, cursorx, &ret->stats,
1228 ret->margin_l, ret->margin_t,
1229 ret->margin_r, ret->margin_b,
1230 ret->flags & NCOPTION_DRAIN_INPUT)){
1231 fbuf_free(&ret->rstate.f);
1232 pthread_mutex_destroy(&ret->pilelock);
1233 pthread_mutex_destroy(&ret->stats.lock);
1234 drop_signals(ret);
1235 free(ret);
1236 return NULL;
1237 }
1238 if(ret->tcache.maxpaletteread > -1){
1239 memcpy(ret->palette.chans, ret->tcache.originalpalette.chans,
1240 sizeof(*ret->palette.chans) * (ret->tcache.maxpaletteread + 1));
1241 }
1242 if((ret->flags & NCOPTION_PRESERVE_CURSOR) ||
1243 (!(ret->flags & NCOPTION_SUPPRESS_BANNERS))){
1244 // the u7 led the queries so that we would get a cursor position
1245 // unaffected by any query spill (unconsumed control sequences). move
1246 // us back to that location, in case there was any such spillage.
1247 if(*cursory < 0 || *cursorx < 0){
1248 unsigned cy, cx;
1249 if(locate_cursor(&ret->tcache, &cy, &cx)){
1250 logwarn("couldn't preserve cursor\n");
1251 }else{
1252 *cursory = cy;
1253 *cursorx = cx;
1254 }
1255 }
1256 if(*cursory >= 0 && *cursorx >= 0){
1257 if(goto_location(ret, &ret->rstate.f, *cursory, *cursorx, NULL)){
1258 goto err;
1259 }
1260 }
1261 }
1262 unsigned dimy, dimx, cgeo, pgeo; // latter two are don't-cares
1263 if(update_term_dimensions(&dimy, &dimx, &ret->tcache, ret->margin_b, &cgeo, &pgeo)){
1264 goto err;
1265 }
1266 if(ncvisual_init(ret->loglevel)){
1267 goto err;
1268 }
1269 ret->stdplane = NULL;
1270 if((ret->stdplane = create_initial_ncplane(ret, dimy, dimx)) == NULL){
1271 logpanic("Couldn't create the initial plane (bad margins?)\n");
1272 goto err;
1273 }
1274 reset_term_attributes(&ret->tcache, &ret->rstate.f);
1275 const char* cinvis = get_escape(&ret->tcache, ESCAPE_CIVIS);
1276 if(cinvis && fbuf_emit(&ret->rstate.f, cinvis) < 0){
1277 free_plane(ret->stdplane);
1278 goto err;
1279 }
1280 const char* pushcolors = get_escape(&ret->tcache, ESCAPE_SAVECOLORS);
1281 if(pushcolors && fbuf_emit(&ret->rstate.f, pushcolors)){
1282 free_plane(ret->stdplane);
1283 goto err;
1284 }
1285 init_banner(ret, &ret->rstate.f);
1286 if(fbuf_flush(&ret->rstate.f, ret->ttyfp) < 0){
1287 free_plane(ret->stdplane);
1288 goto err;
1289 }
1290 if(ret->rstate.logendy >= 0){ // if either is set, both are
1291 if(!(ret->flags & NCOPTION_SUPPRESS_BANNERS) && ret->tcache.ttyfd >= 0){
1292 unsigned uendy, uendx;
1293 if(locate_cursor(&ret->tcache, &uendy, &uendx)){
1294 free_plane(ret->stdplane);
1295 goto err;
1296 }
1297 ret->rstate.logendy = uendy;
1298 ret->rstate.logendx = uendx;
1299 }
1300 if(ret->flags & NCOPTION_PRESERVE_CURSOR){
1301 ncplane_cursor_move_yx(ret->stdplane, ret->rstate.logendy, ret->rstate.logendx);
1302 }
1303 }
1304 if(!(ret->flags & NCOPTION_NO_ALTERNATE_SCREEN)){
1305 // perform an explicit clear since the alternate screen was requested
1306 // (smcup *might* clear, but who knows? and it might not have been
1307 // available in any case).
1308 if(clear_and_home(ret, &ret->tcache, &ret->rstate.f)){
1309 goto err;
1310 }
1311 // no need to reestablish a preserved cursor -- that only affects the
1312 // standard plane, not the physical cursor that was just disrupted.
1313 }
1314 // the sprite clear ought take place within the alternate screen, if it's
1315 // being used.
1316 if(!(ret->flags & NCOPTION_NO_CLEAR_BITMAPS)){
1317 if(sprite_clear_all(&ret->tcache, &ret->rstate.f)){
1318 goto err;
1319 }
1320 }
1321 if(ret->rstate.f.used){
1322 if(fbuf_flush(&ret->rstate.f, ret->ttyfp) < 0){
1323 goto err;
1324 }
1325 }
1326 return ret;
1327
1328 err:
1329 logpanic("Alas, you will not be going to space today.\n");
1330 notcurses_stop_minimal(ret);
1331 fbuf_free(&ret->rstate.f);
1332 if(ret->tcache.ttyfd >= 0 && ret->tcache.tpreserved){
1333 (void)tcsetattr(ret->tcache.ttyfd, TCSAFLUSH, ret->tcache.tpreserved);
1334 free(ret->tcache.tpreserved);
1335 }
1336 drop_signals(ret);
1337 del_curterm(cur_term);
1338 pthread_mutex_destroy(&ret->stats.lock);
1339 pthread_mutex_destroy(&ret->pilelock);
1340 free(ret);
1341 return NULL;
1342 }
1343
1344 // updates *pile to point at (*pile)->next, frees all but standard pile/plane
1345 static void
1346 ncpile_drop(notcurses* nc, ncpile** pile){
1347 bool sawstdplane = false;
1348 ncpile* next = (*pile)->next;
1349 ncplane* p = (*pile)->top;
1350 while(p){
1351 ncplane* tmp = p->below;
1352 logdebug("killing plane %p, next is %p\n", p, tmp);
1353 if(nc->stdplane != p){
1354 free_plane(p);
1355 }else{
1356 sawstdplane = true;
1357 }
1358 p = tmp;
1359 }
1360 *pile = next;
1361 if(sawstdplane){
1362 ncplane_pile(nc->stdplane)->top = nc->stdplane;
1363 ncplane_pile(nc->stdplane)->bottom = nc->stdplane;
1364 nc->stdplane->above = nc->stdplane->below = NULL;
1365 nc->stdplane->blist = NULL;
1366 }
1367 }
1368
1369 // drop all piles and all planes, save the standard plane and its pile
1370 void notcurses_drop_planes(notcurses* nc){
1371 logdebug("we have some planes\n");
1372 pthread_mutex_lock(&nc->pilelock);
1373 ncpile* p = ncplane_pile(nc->stdplane);
1374 ncpile* p0 = p;
1375 do{
1376 ncpile_drop(nc, &p);
1377 }while(p0 != p);
1378 pthread_mutex_unlock(&nc->pilelock);
1379 logdebug("all planes dropped\n");
1380 }
1381
1382 int notcurses_stop(notcurses* nc){
1383 logdebug("stopping notcurses\n");
1384 //notcurses_debug(nc, stderr);
1385 int ret = 0;
1386 if(nc){
1387 ret |= notcurses_stop_minimal(nc);
1388 // if we were not using the alternate screen, our cursor's wherever we last
1389 // wrote. move it to the furthest place to which it advanced.
1390 if(!get_escape(&nc->tcache, ESCAPE_SMCUP)){
1391 fbuf_reset(&nc->rstate.f);
1392 //fprintf(stderr, "CLOSING TO %d/%d\n", nc->rstate.logendy, nc->rstate.logendx);
1393 goto_location(nc, &nc->rstate.f, nc->rstate.logendy, nc->rstate.logendx, NULL);
1394 //fprintf(stderr, "***"); fflush(stderr);
1395 fbuf_finalize(&nc->rstate.f, stdout);
1396 }
1397 if(nc->stdplane){
1398 notcurses_drop_planes(nc);
1399 logdebug("say goodbye to the standard plane\n");
1400 free_plane(nc->stdplane);
1401 logdebug("get outta here\n");
1402 }
1403 if(nc->tcache.ttyfd >= 0){
1404 ret |= close(nc->tcache.ttyfd);
1405 }
1406 egcpool_dump(&nc->pool);
1407 free(nc->lastframe);
1408 // get any current stats loaded into stash_stats
1409 notcurses_stats_reset(nc, NULL);
1410 if(!(nc->flags & NCOPTION_SUPPRESS_BANNERS)){
1411 summarize_stats(nc);
1412 }
1413 #ifndef __MINGW64__
1414 del_curterm(cur_term);
1415 #endif
1416 ret |= pthread_mutex_destroy(&nc->stats.lock);
1417 ret |= pthread_mutex_destroy(&nc->pilelock);
1418 fbuf_free(&nc->rstate.f);
1419 free_terminfo_cache(&nc->tcache);
1420 free(nc);
1421 }
1422 return ret;
1423 }
1424
1425 uint64_t ncplane_channels(const ncplane* n){
1426 return n->channels;
1427 }
1428
1429 uint16_t ncplane_styles(const ncplane* n){
1430 return n->stylemask;
1431 }
1432
1433 void ncplane_set_channels(ncplane* n, uint64_t channels){
1434 n->channels = channels;
1435 }
1436
1437 void ncplane_set_fg_default(ncplane* n){
1438 ncchannels_set_fg_default(&n->channels);
1439 }
1440
1441 uint64_t ncplane_set_fchannel(ncplane* n, uint32_t channel){
1442 return ncchannels_set_fchannel(&n->channels, channel);
1443 }
1444
1445 uint64_t ncplane_set_bchannel(ncplane* n, uint32_t channel){
1446 return ncchannels_set_bchannel(&n->channels, channel);
1447 }
1448
1449 void ncplane_set_bg_default(ncplane* n){
1450 ncchannels_set_bg_default(&n->channels);
1451 }
1452
1453 void ncplane_set_bg_rgb8_clipped(ncplane* n, int r, int g, int b){
1454 ncchannels_set_bg_rgb8_clipped(&n->channels, r, g, b);
1455 }
1456
1457 int ncplane_set_bg_rgb8(ncplane* n, unsigned r, unsigned g, unsigned b){
1458 return ncchannels_set_bg_rgb8(&n->channels, r, g, b);
1459 }
1460
1461 void ncplane_set_fg_rgb8_clipped(ncplane* n, int r, int g, int b){
1462 ncchannels_set_fg_rgb8_clipped(&n->channels, r, g, b);
1463 }
1464
1465 int ncplane_set_fg_rgb8(ncplane* n, unsigned r, unsigned g, unsigned b){
1466 return ncchannels_set_fg_rgb8(&n->channels, r, g, b);
1467 }
1468
1469 int ncplane_set_fg_rgb(ncplane* n, unsigned channel){
1470 return ncchannels_set_fg_rgb(&n->channels, channel);
1471 }
1472
1473 int ncplane_set_bg_rgb(ncplane* n, unsigned channel){
1474 return ncchannels_set_bg_rgb(&n->channels, channel);
1475 }
1476
1477 int ncplane_set_fg_alpha(ncplane* n, int alpha){
1478 return ncchannels_set_fg_alpha(&n->channels, alpha);
1479 }
1480
1481 int ncplane_set_bg_alpha(ncplane *n, int alpha){
1482 return ncchannels_set_bg_alpha(&n->channels, alpha);
1483 }
1484
1485 int ncplane_set_fg_palindex(ncplane* n, int idx){
1486 return ncchannels_set_fg_palindex(&n->channels, idx);
1487 }
1488
1489 int ncplane_set_bg_palindex(ncplane* n, int idx){
1490 return ncchannels_set_bg_palindex(&n->channels, idx);
1491 }
1492
1493 int ncplane_set_base_cell(ncplane* ncp, const nccell* c){
1494 if(nccell_wide_right_p(c)){
1495 return -1;
1496 }
1497 return nccell_duplicate(ncp, &ncp->basecell, c);
1498 }
1499
1500 int ncplane_set_base(ncplane* ncp, const char* egc, uint16_t stylemask, uint64_t channels){
1501 return nccell_prime(ncp, &ncp->basecell, egc, stylemask, channels);
1502 }
1503
1504 int ncplane_base(ncplane* ncp, nccell* c){
1505 return nccell_duplicate(ncp, c, &ncp->basecell);
1506 }
1507
1508 const char* nccell_extended_gcluster(const ncplane* n, const nccell* c){
1509 if(cell_simple_p(c)){
1510 return (const char*)&c->gcluster;
1511 }
1512 return egcpool_extended_gcluster(&n->pool, c);
1513 }
1514
1515 // 'n' ends up above 'above'
1516 int ncplane_move_above(ncplane* restrict n, ncplane* restrict above){
1517 if(n == above){ // probably gets optimized out =/
1518 return -1;
1519 }
1520 ncpile* p = ncplane_pile(n);
1521 if(above == NULL){
1522 if(n->below){
1523 if( (n->below->above = n->above) ){
1524 n->above->below = n->below;
1525 }else{
1526 p->top = n->below;
1527 }
1528 n->below = NULL;
1529 if( (n->above = p->bottom) ){
1530 n->above->below = n;
1531 }
1532 p->bottom = n;
1533 }
1534 return 0;
1535 }
1536 if(n->below != above){
1537 if(p != ncplane_pile(above)){ // can't move among piles
1538 return -1;
1539 }
1540 // splice out 'n'
1541 if(n->below){
1542 n->below->above = n->above;
1543 }else{
1544 p->bottom = n->above;
1545 }
1546 if(n->above){
1547 n->above->below = n->below;
1548 }else{
1549 p->top = n->below;
1550 }
1551 if( (n->above = above->above) ){
1552 above->above->below = n;
1553 }else{
1554 p->top = n;
1555 }
1556 above->above = n;
1557 n->below = above;
1558 }
1559 return 0;
1560 }
1561
1562 // 'n' ends up below 'below', or on top if 'below' == NULL
1563 int ncplane_move_below(ncplane* restrict n, ncplane* restrict below){
1564 if(n == below){ // probably gets optimized out =/
1565 return -1;
1566 }
1567 ncpile* p = ncplane_pile(n);
1568 if(below == NULL){
1569 if(n->above){
1570 if( (n->above->below = n->below) ){
1571 n->below->above = n->above;
1572 }else{
1573 p->bottom = n->above;
1574 }
1575 n->above = NULL;
1576 if( (n->below = p->top) ){
1577 n->below->above = n;
1578 }
1579 p->top = n;
1580 }
1581 return 0;
1582 }
1583 if(n->above != below){
1584 if(p != ncplane_pile(below)){ // can't move among piles
1585 return -1;
1586 }
1587 if(n->below){
1588 n->below->above = n->above;
1589 }else{
1590 p->bottom = n->above;
1591 }
1592 if(n->above){
1593 n->above->below = n->below;
1594 }else{
1595 p->top = n->below;
1596 }
1597 if( (n->below = below->below) ){
1598 below->below->above = n;
1599 }else{
1600 p->bottom = n;
1601 }
1602 below->below = n;
1603 n->above = below;
1604 }
1605 return 0;
1606 }
1607
1608 // if above is NULL, we're moving to the bottom
1609 int ncplane_move_family_above(ncplane* restrict n, ncplane* restrict bpoint){
1610 ncplane* above = ncplane_above(n);
1611 ncplane* below = ncplane_below(n);
1612 if(ncplane_move_above(n, bpoint)){
1613 return -1;
1614 }
1615 // traverse the planes above n, until we hit NULL. do the planes above n
1616 // first, so that we know the topmost element of our new ensplicification.
1617 // at this point, n is the bottommost plane, and we're inserting above it.
1618 ncplane* targ = n;
1619 while(above){
1620 ncplane* tmp = ncplane_above(above);
1621 if(ncplane_descendant_p(above, n)){
1622 ncplane_move_above(above, targ);
1623 targ = above;
1624 }
1625 above = tmp;
1626 }
1627 // n remains the topmost plane, and we're inserting above it. we have to be
1628 // careful this time not to cross into any we moved below n.
1629 const ncplane* topmost = targ;
1630 targ = n;
1631 while(below && below != topmost){
1632 ncplane* tmp = ncplane_below(below);
1633 if(ncplane_descendant_p(below, n)){
1634 ncplane_move_below(below, targ);
1635 targ = below;
1636 }
1637 below = tmp;
1638 }
1639 return 0;
1640 }
1641
1642 // if below is NULL, we're moving to the top
1643 int ncplane_move_family_below(ncplane* restrict n, ncplane* restrict bpoint){
1644 ncplane* below = ncplane_below(n);
1645 ncplane* above = ncplane_above(n);
1646 if(ncplane_move_below(n, bpoint)){
1647 return -1;
1648 }
1649 // traverse the planes below n, until we hit NULL. do the planes below n
1650 // first, so that we know the bottommost element of our new ensplicification.
1651 // we're inserting below n...
1652 ncplane* targ = n;
1653 while(below){
1654 ncplane* tmp = ncplane_below(below);
1655 if(ncplane_descendant_p(below, n)){
1656 ncplane_move_below(below, targ);
1657 targ = below;
1658 }
1659 below = tmp;
1660 }
1661 // n remains the topmost plane, and we're inserting above it. we have to be
1662 // careful this time not to cross into any we moved below n.
1663 const ncplane* bottommost = targ;
1664 targ = n;
1665 while(above && above != bottommost){
1666 ncplane* tmp = ncplane_above(above);
1667 if(ncplane_descendant_p(above, n)){
1668 ncplane_move_above(above, targ);
1669 targ = above;
1670 }
1671 above = tmp;
1672 }
1673 return 0;
1674 }
1675
1676 void ncplane_cursor_yx(const ncplane* n, unsigned* y, unsigned* x){
1677 if(y){
1678 *y = n->y;
1679 }
1680 if(x){
1681 *x = n->x;
1682 }
1683 }
1684
1685 static inline void
1686 nccell_obliterate(ncplane* n, nccell* c){
1687 nccell_release(n, c);
1688 nccell_init(c);
1689 }
1690
1691 // increment y by 1 and rotate the framebuffer up one line. x moves to 0. any
1692 // non-fixed bound planes move up 1 line if they intersect the plane.
1693 void scroll_down(ncplane* n){
1694 //fprintf(stderr, "pre-scroll: %d/%d %d/%d log: %d scrolling: %u\n", n->y, n->x, n->leny, n->lenx, n->logrow, n->scrolling);
1695 n->x = 0;
1696 if(n->y == n->leny - 1){
1697 if(n->autogrow){
1698 ncplane_resize_simple(n, n->leny + 1, n->lenx);
1699 ncplane_cursor_move_yx(n, n->leny - 1, 0);
1700 return;
1701 }
1702 if(n == notcurses_stdplane(ncplane_notcurses(n))){
1703 ncplane_pile(n)->scrolls++;
1704 }
1705 n->logrow = (n->logrow + 1) % n->leny;
1706 nccell* row = n->fb + nfbcellidx(n, n->y, 0);
1707 for(unsigned clearx = 0 ; clearx < n->lenx ; ++clearx){
1708 nccell_release(n, &row[clearx]);
1709 }
1710 memset(row, 0, sizeof(*row) * n->lenx);
1711 for(struct ncplane* c = n->blist ; c ; c = c->bnext){
1712 if(!c->fixedbound){
1713 if(ncplanes_intersect_p(n, c)){
1714 ncplane_move_rel(c, -1, 0);
1715 }
1716 }
1717 }
1718 }else{
1719 ++n->y;
1720 }
1721 }
1722
1723 int ncplane_scrollup(ncplane* n, int r){
1724 if(!ncplane_scrolling_p(n)){
1725 logerror("can't scroll %d on non-scrolling plane\n", r);
1726 return -1;
1727 }
1728 if(r < 0){
1729 logerror("can't scroll %d lines\n", r);
1730 return -1;
1731 }
1732 while(r-- > 0){
1733 scroll_down(n);
1734 }
1735 return 0;
1736 }
1737
1738 // Scroll |n| up until |child| is no longer hidden beneath it. Returns an
1739 // error if |child| is not a child of |n|, or |n| is not scrolling, or |child|
1740 // is fixed. Returns the number of scrolling events otherwise (might be 0).
1741 int ncplane_scrollup_child(ncplane* n, const ncplane* child){
1742 if(ncplane_parent_const(child) != n){
1743 logerror("not a child of specified plane\n");
1744 return -1;
1745 }
1746 if(child->fixedbound){
1747 logerror("child plane is fixed\n");
1748 return -1;
1749 }
1750 int parend = ncplane_abs_y(n) + ncplane_dim_y(n) - 1; // where parent ends
1751 int chend = ncplane_abs_y(child) + ncplane_dim_y(child) - 1; // where child ends
1752 if(chend <= parend){
1753 return 0;
1754 }
1755 int r = chend - parend; // how many rows we need scroll parent
1756 ncplane_cursor_move_yx(n, ncplane_dim_y(n) - 1, 0);
1757 int ret = ncplane_scrollup(n, r);
1758 return ret;
1759 }
1760
1761 int nccell_load(ncplane* n, nccell* c, const char* gcluster){
1762 int cols;
1763 int bytes = utf8_egc_len(gcluster, &cols);
1764 return pool_load_direct(&n->pool, c, gcluster, bytes, cols);
1765 }
1766
1767 // where the magic happens. write the single EGC completely described by |egc|,
1768 // occupying |cols| columns, to the ncplane |n| at the coordinate |y|, |x|. if
1769 // either or both of |y|/|x| is -1, the current cursor location for that
1770 // dimension will be used. if the glyph cannot fit on the current line, it is
1771 // an error unless scrolling is enabled.
1772 static int
1773 ncplane_put(ncplane* n, int y, int x, const char* egc, int cols,
1774 uint16_t stylemask, uint64_t channels, int bytes){
1775 if(n->sprite){
1776 logerror("can't write [%s] to sprixelated plane\n", egc);
1777 return -1;
1778 }
1779 // reject any control character for output other than newline (and then only
1780 // on a scrolling plane).
1781 if(*egc == '\n'){
1782 // if we're not scrolling, autogrow would be to the right (as opposed to
1783 // down), and thus it still wouldn't apply to the case of a newline.
1784 if(!n->scrolling){
1785 logerror("rejecting newline on non-scrolling plane\n");
1786 return -1;
1787 }
1788 }else if(is_control_egc((const unsigned char*)egc, bytes)){
1789 logerror("rejecting %dB control character\n", bytes);
1790 return -1;
1791 }
1792 // check *before ncplane_cursor_move_yx()* whether we're past the end of the
1793 // line. if scrolling is enabled, move to the next line if so. if x or y are
1794 // specified, we must always try to print at exactly that location, and
1795 // there's no need to check the present location in that dimension.
1796 bool linesend = false;
1797 if(x < 0){
1798 // we checked x for all negatives, but only -1 is valid (our else clause is
1799 // predicated on a non-negative x).
1800 if(x == -1){
1801 if(n->x + cols - 1 >= n->lenx){
1802 linesend = true;
1803 }
1804 }
1805 }else{
1806 if((unsigned)x + cols - 1 >= n->lenx){
1807 linesend = true;
1808 }
1809 }
1810 if(linesend){
1811 if(n->scrolling){
1812 scroll_down(n);
1813 }else if(n->autogrow){
1814 ncplane_resize_simple(n, n->leny, n->lenx + cols);
1815 }else{
1816 logerror("target x %d [%.*s] > length %d\n", n->x, bytes, egc, n->lenx);
1817 return -1;
1818 }
1819 }
1820 // targets outside the plane will be rejected here.
1821 if(ncplane_cursor_move_yx(n, y, x)){
1822 return -1;
1823 }
1824 if(*egc == '\n'){
1825 scroll_down(n);
1826 }
1827 // A wide character obliterates anything to its immediate right (and marks
1828 // that cell as wide). Any character placed atop one cell of a wide character
1829 // obliterates all cells. Note that a two-cell glyph can thus obliterate two
1830 // other two-cell glyphs, totalling four columns.
1831 nccell* targ = ncplane_cell_ref_yx(n, n->y, n->x);
1832 // we're always starting on the leftmost cell of our output glyph. check the
1833 // target, and find the leftmost cell of the glyph it will be displacing.
1834 // obliterate as we go along.
1835 int idx = n->x;
1836 nccell* lmc = targ;
1837 while(nccell_wide_right_p(lmc)){
1838 nccell_obliterate(n, &n->fb[nfbcellidx(n, n->y, idx)]);
1839 lmc = ncplane_cell_ref_yx(n, n->y, --idx);
1840 }
1841 // we're now on the leftmost cell of the target glyph.
1842 int twidth = nccell_cols(lmc);
1843 nccell_release(n, lmc);
1844 twidth -= n->x - idx;
1845 while(--twidth > 0){
1846 nccell_obliterate(n, &n->fb[nfbcellidx(n, n->y, n->x + twidth)]);
1847 }
1848 targ->stylemask = stylemask;
1849 targ->channels = channels;
1850 if(cell_load_direct(n, targ, egc, bytes, cols) < 0){
1851 return -1;
1852 }
1853 //fprintf(stderr, "%08x %016lx %c %d %d\n", targ->gcluster, targ->channels, nccell_double_wide_p(targ) ? 'D' : 'd', bytes, cols);
1854 // must set our right hand sides wide, and check for further damage
1855 if(*egc != '\n'){
1856 ++n->x;
1857 for(int i = 1 ; i < cols ; ++i){
1858 nccell* candidate = &n->fb[nfbcellidx(n, n->y, n->x)];
1859 int off = nccell_cols(candidate);
1860 nccell_release(n, &n->fb[nfbcellidx(n, n->y, n->x)]);
1861 while(--off > 0){
1862 nccell_obliterate(n, &n->fb[nfbcellidx(n, n->y, n->x + off)]);
1863 }
1864 candidate->channels = targ->channels;
1865 candidate->stylemask = targ->stylemask;
1866 candidate->width = targ->width;
1867 ++n->x;
1868 }
1869 }
1870 return cols;
1871 }
1872
1873 int ncplane_putc_yx(ncplane* n, int y, int x, const nccell* c){
1874 const int cols = nccell_cols(c);
1875 // unfortunately, |c| comes from |n|. the act of writing |c| to |n| could
1876 // grow |n|'s egcpool, invalidating the reference represented by
1877 // nccell_extended_gcluster(). so we must copy and free it.
1878 char* egc = nccell_strdup(n, c);
1879 if(egc == NULL){
1880 logerror("coudln't duplicate cell\n");
1881 return -1;
1882 }
1883 int r = ncplane_put(n, y, x, egc, cols, c->stylemask, c->channels, strlen(egc));
1884 free(egc);
1885 return r;
1886 }
1887
1888 int ncplane_putegc_yx(ncplane* n, int y, int x, const char* gclust, size_t* sbytes){
1889 int cols;
1890 int bytes = utf8_egc_len(gclust, &cols);
1891 if(bytes < 0){
1892 return -1;
1893 }
1894 if(sbytes){
1895 *sbytes = bytes;
1896 }
1897 //fprintf(stderr, "glust: %s cols: %d wcs: %d\n", gclust, cols, bytes);
1898 return ncplane_put(n, y, x, gclust, cols, n->stylemask, n->channels, bytes);
1899 }
1900
1901 int ncplane_putchar_stained(ncplane* n, char c){
1902 uint64_t channels = n->channels;
1903 uint16_t stylemask = n->stylemask;
1904 const nccell* targ = &n->fb[nfbcellidx(n, n->y, n->x)];
1905 n->channels = targ->channels;
1906 n->stylemask = targ->stylemask;
1907 int ret = ncplane_putchar(n, c);
1908 n->channels = channels;
1909 n->stylemask = stylemask;
1910 return ret;
1911 }
1912
1913 int ncplane_putwegc_stained(ncplane* n, const wchar_t* gclust, size_t* sbytes){
1914 uint64_t channels = n->channels;
1915 uint16_t stylemask = n->stylemask;
1916 const nccell* targ = &n->fb[nfbcellidx(n, n->y, n->x)];
1917 n->channels = targ->channels;
1918 n->stylemask = targ->stylemask;
1919 int ret = ncplane_putwegc(n, gclust, sbytes);
1920 n->channels = channels;
1921 n->stylemask = stylemask;
1922 return ret;
1923 }
1924
1925 int ncplane_putegc_stained(ncplane* n, const char* gclust, size_t* sbytes){
1926 uint64_t channels = n->channels;
1927 uint16_t stylemask = n->stylemask;
1928 const nccell* targ = &n->fb[nfbcellidx(n, n->y, n->x)];
1929 n->channels = targ->channels;
1930 n->stylemask = targ->stylemask;
1931 int ret = ncplane_putegc(n, gclust, sbytes);
1932 n->channels = channels;
1933 n->stylemask = stylemask;
1934 return ret;
1935 }
1936
1937 int ncplane_cursor_at(const ncplane* n, nccell* c, char** gclust){
1938 if(n->y >= n->leny || n->x >= n->lenx){
1939 return -1;
1940 }
1941 const nccell* src = &n->fb[nfbcellidx(n, n->y, n->x)];
1942 memcpy(c, src, sizeof(*src));
1943 if(cell_simple_p(c)){
1944 *gclust = NULL;
1945 }else if((*gclust = strdup(nccell_extended_gcluster(n, src))) == NULL){
1946 return -1;
1947 }
1948 return 0;
1949 }
1950
1951 uint16_t notcurses_supported_styles(const notcurses* nc){
1952 return term_supported_styles(&nc->tcache);
1953 }
1954
1955 unsigned notcurses_palette_size(const notcurses* nc){
1956 return nc->tcache.caps.colors;
1957 }
1958
1959 char* notcurses_detected_terminal(const notcurses* nc){
1960 return termdesc_longterm(&nc->tcache);
1961 }
1962
1963 // conform to the specified stylebits
1964 void ncplane_set_styles(ncplane* n, unsigned stylebits){
1965 n->stylemask = (stylebits & NCSTYLE_MASK);
1966 }
1967
1968 // turn on any specified stylebits
1969 void ncplane_on_styles(ncplane* n, unsigned stylebits){
1970 n->stylemask |= (stylebits & NCSTYLE_MASK);
1971 }
1972
1973 // turn off any specified stylebits
1974 void ncplane_off_styles(ncplane* n, unsigned stylebits){
1975 n->stylemask &= ~(stylebits & NCSTYLE_MASK);
1976 }
1977
1978 // i hate the big allocation and two copies here, but eh what you gonna do?
1979 // well, for one, we don't need the huge allocation FIXME
1980 char* ncplane_vprintf_prep(const char* format, va_list ap){
1981 const size_t size = BUFSIZ; // healthy estimate, can embiggen below
1982 char* buf = malloc(size);
1983 if(buf == NULL){
1984 return NULL;
1985 }
1986 va_list vacopy;
1987 va_copy(vacopy, ap);
1988 int ret = vsnprintf(buf, size, format, ap);
1989 if(ret < 0){
1990 free(buf);
1991 va_end(vacopy);
1992 return NULL;
1993 }
1994 if((size_t)ret >= size){
1995 char* tmp = realloc(buf, ret + 1);
1996 if(tmp == NULL){
1997 free(buf);
1998 va_end(vacopy);
1999 return NULL;
2000 }
2001 buf = tmp;
2002 vsprintf(buf, format, vacopy);
2003 }
2004 va_end(vacopy);
2005 return buf;
2006 }
2007
2008 int ncplane_vprintf_yx(ncplane* n, int y, int x, const char* format, va_list ap){
2009 char* r = ncplane_vprintf_prep(format, ap);
2010 if(r == NULL){
2011 return -1;
2012 }
2013 int ret = ncplane_putstr_yx(n, y, x, r);
2014 free(r);
2015 return ret;
2016 }
2017
2018 int ncplane_vprintf_aligned(ncplane* n, int y, ncalign_e align,
2019 const char* format, va_list ap){
2020 char* r = ncplane_vprintf_prep(format, ap);
2021 if(r == NULL){
2022 return -1;
2023 }
2024 int ret = ncplane_putstr_aligned(n, y, align, r);
2025 free(r);
2026 return ret;
2027 }
2028
2029 int ncplane_vprintf_stained(struct ncplane* n, const char* format, va_list ap){
2030 char* r = ncplane_vprintf_prep(format, ap);
2031 if(r == NULL){
2032 return -1;
2033 }
2034 int ret = ncplane_putstr_stained(n, r);
2035 free(r);
2036 return ret;
2037 }
2038
2039 int ncplane_putnstr_aligned(struct ncplane* n, int y, ncalign_e align, size_t s, const char* str){
2040 char* chopped = strndup(str, s);
2041 int ret = ncplane_putstr_aligned(n, y, align, chopped);
2042 free(chopped);
2043 return ret;
2044 }
2045
2046 int ncplane_hline_interp(ncplane* n, const nccell* c, unsigned len,
2047 uint64_t c1, uint64_t c2){
2048 if(len <= 0){
2049 logerror("passed invalid length %u\n", len);
2050 return -1;
2051 }
2052 unsigned ur, ug, ub;
2053 int r1, g1, b1, r2, g2, b2;
2054 int br1, bg1, bb1, br2, bg2, bb2;
2055 ncchannels_fg_rgb8(c1, &ur, &ug, &ub);
2056 r1 = ur; g1 = ug; b1 = ub;
2057 ncchannels_fg_rgb8(c2, &ur, &ug, &ub);
2058 r2 = ur; g2 = ug; b2 = ub;
2059 ncchannels_bg_rgb8(c1, &ur, &ug, &ub);
2060 br1 = ur; bg1 = ug; bb1 = ub;
2061 ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
2062 br2 = ur; bg2 = ug; bb2 = ub;
2063 int deltr = r2 - r1;
2064 int deltg = g2 - g1;
2065 int deltb = b2 - b1;
2066 int deltbr = br2 - br1;
2067 int deltbg = bg2 - bg1;
2068 int deltbb = bb2 - bb1;
2069 unsigned ret;
2070 nccell dupc = NCCELL_TRIVIAL_INITIALIZER;
2071 if(nccell_duplicate(n, &dupc, c) < 0){
2072 return -1;
2073 }
2074 bool fgdef = false, bgdef = false;
2075 if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
2076 fgdef = true;
2077 }
2078 if(ncchannels_bg_default_p(c1) && ncchannels_bg_default_p(c2)){
2079 bgdef = true;
2080 }
2081 for(ret = 0 ; ret < len ; ++ret){
2082 int r = (deltr * (int)ret) / (int)len + r1;
2083 int g = (deltg * (int)ret) / (int)len + g1;
2084 int b = (deltb * (int)ret) / (int)len + b1;
2085 int br = (deltbr * (int)ret) / (int)len + br1;
2086 int bg = (deltbg * (int)ret) / (int)len + bg1;
2087 int bb = (deltbb * (int)ret) / (int)len + bb1;
2088 if(!fgdef){
2089 nccell_set_fg_rgb8(&dupc, r, g, b);
2090 }
2091 if(!bgdef){
2092 nccell_set_bg_rgb8(&dupc, br, bg, bb);
2093 }
2094 if(ncplane_putc(n, &dupc) <= 0){
2095 return -1;
2096 }
2097 }
2098 nccell_release(n, &dupc);
2099 return ret;
2100 }
2101
2102 int ncplane_vline_interp(ncplane* n, const nccell* c, unsigned len,
2103 uint64_t c1, uint64_t c2){
2104 if(len <= 0){
2105 logerror("passed invalid length %u\n", len);
2106 return -1;
2107 }
2108 unsigned ur, ug, ub;
2109 int r1, g1, b1, r2, g2, b2;
2110 int br1, bg1, bb1, br2, bg2, bb2;
2111 ncchannels_fg_rgb8(c1, &ur, &ug, &ub);
2112 r1 = ur; g1 = ug; b1 = ub;
2113 ncchannels_fg_rgb8(c2, &ur, &ug, &ub);
2114 r2 = ur; g2 = ug; b2 = ub;
2115 ncchannels_bg_rgb8(c1, &ur, &ug, &ub);
2116 br1 = ur; bg1 = ug; bb1 = ub;
2117 ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
2118 br2 = ur; bg2 = ug; bb2 = ub;
2119 int deltr = (r2 - r1) / ((int)len + 1);
2120 int deltg = (g2 - g1) / ((int)len + 1);
2121 int deltb = (b2 - b1) / ((int)len + 1);
2122 int deltbr = (br2 - br1) / ((int)len + 1);
2123 int deltbg = (bg2 - bg1) / ((int)len + 1);
2124 int deltbb = (bb2 - bb1) / ((int)len + 1);
2125 unsigned ypos, xpos;
2126 unsigned ret;
2127 ncplane_cursor_yx(n, &ypos, &xpos);
2128 nccell dupc = NCCELL_TRIVIAL_INITIALIZER;
2129 if(nccell_duplicate(n, &dupc, c) < 0){
2130 return -1;
2131 }
2132 bool fgdef = false, bgdef = false;
2133 if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
2134 fgdef = true;
2135 }
2136 if(ncchannels_bg_default_p(c1) && ncchannels_bg_default_p(c2)){
2137 bgdef = true;
2138 }
2139 for(ret = 0 ; ret < len ; ++ret){
2140 if(ncplane_cursor_move_yx(n, ypos + ret, xpos)){
2141 return -1;
2142 }
2143 r1 += deltr;
2144 g1 += deltg;
2145 b1 += deltb;
2146 br1 += deltbr;
2147 bg1 += deltbg;
2148 bb1 += deltbb;
2149 if(!fgdef){
2150 nccell_set_fg_rgb8(&dupc, r1, g1, b1);
2151 }
2152 if(!bgdef){
2153 nccell_set_bg_rgb8(&dupc, br1, bg1, bb1);
2154 }
2155 if(ncplane_putc(n, &dupc) <= 0){
2156 return -1;
2157 }
2158 }
2159 nccell_release(n, &dupc);
2160 return ret;
2161 }
2162
2163 // we must have at least 2x2, or it's an error
2164 int ncplane_box(ncplane* n, const nccell* ul, const nccell* ur,
2165 const nccell* ll, const nccell* lr, const nccell* hl,
2166 const nccell* vl, unsigned ystop, unsigned xstop,
2167 unsigned ctlword){
2168 unsigned yoff, xoff;
2169 ncplane_cursor_yx(n, &yoff, &xoff);
2170 // must be at least 2x2, with its upper-left corner at the current cursor
2171 if(ystop < yoff + 1){
2172 logerror("ystop (%u) insufficient for yoff (%d)\n", ystop, yoff);
2173 return -1;
2174 }
2175 if(xstop < xoff + 1){
2176 logerror("xstop (%u) insufficient for xoff (%d)\n", xstop, xoff);
2177 return -1;
2178 }
2179 unsigned ymax, xmax;
2180 ncplane_dim_yx(n, &ymax, &xmax);
2181 // must be within the ncplane
2182 if(xstop >= xmax || ystop >= ymax){
2183 logerror("Boundary (%ux%u) beyond plane (%dx%d)\n", ystop, xstop, ymax, xmax);
2184 return -1;
2185 }
2186 unsigned edges;
2187 edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_LEFT);
2188 if(edges >= box_corner_needs(ctlword)){
2189 if(ncplane_putc(n, ul) < 0){
2190 return -1;
2191 }
2192 }
2193 if(!(ctlword & NCBOXMASK_TOP)){ // draw top border, if called for
2194 if(xstop - xoff >= 2){
2195 if(ncplane_cursor_move_yx(n, yoff, xoff + 1)){
2196 return -1;
2197 }
2198 if(!(ctlword & NCBOXGRAD_TOP)){ // cell styling, hl
2199 if(ncplane_hline(n, hl, xstop - xoff - 1) < 0){
2200 return -1;
2201 }
2202 }else{ // gradient, ul -> ur
2203 if(ncplane_hline_interp(n, hl, xstop - xoff - 1, ul->channels, ur->channels) < 0){
2204 return -1;
2205 }
2206 }
2207 }
2208 }
2209 edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_RIGHT);
2210 if(edges >= box_corner_needs(ctlword)){
2211 if(ncplane_cursor_move_yx(n, yoff, xstop)){
2212 return -1;
2213 }
2214 if(ncplane_putc(n, ur) < 0){
2215 return -1;
2216 }
2217 }
2218 ++yoff;
2219 // middle rows (vertical lines)
2220 if(yoff < ystop){
2221 if(!(ctlword & NCBOXMASK_LEFT)){
2222 if(ncplane_cursor_move_yx(n, yoff, xoff)){
2223 return -1;
2224 }
2225 if((ctlword & NCBOXGRAD_LEFT)){ // grad styling, ul->ll
2226 if(ncplane_vline_interp(n, vl, ystop - yoff, ul->channels, ll->channels) < 0){
2227 return -1;
2228 }
2229 }else{
2230 if(ncplane_vline(n, vl, ystop - yoff) < 0){
2231 return -1;
2232 }
2233 }
2234 }
2235 if(!(ctlword & NCBOXMASK_RIGHT)){
2236 if(ncplane_cursor_move_yx(n, yoff, xstop)){
2237 return -1;
2238 }
2239 if((ctlword & NCBOXGRAD_RIGHT)){ // grad styling, ur->lr
2240 if(ncplane_vline_interp(n, vl, ystop - yoff, ur->channels, lr->channels) < 0){
2241 return -1;
2242 }
2243 }else{
2244 if(ncplane_vline(n, vl, ystop - yoff) < 0){
2245 return -1;
2246 }
2247 }
2248 }
2249 }
2250 // bottom line
2251 yoff = ystop;
2252 edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_LEFT);
2253 if(edges >= box_corner_needs(ctlword)){
2254 if(ncplane_cursor_move_yx(n, yoff, xoff)){
2255 return -1;
2256 }
2257 if(ncplane_putc(n, ll) < 0){
2258 return -1;
2259 }
2260 }
2261 if(!(ctlword & NCBOXMASK_BOTTOM)){
2262 if(xstop - xoff >= 2){
2263 if(ncplane_cursor_move_yx(n, yoff, xoff + 1)){
2264 return -1;
2265 }
2266 if(!(ctlword & NCBOXGRAD_BOTTOM)){ // cell styling, hl
2267 if(ncplane_hline(n, hl, xstop - xoff - 1) < 0){
2268 return -1;
2269 }
2270 }else{
2271 if(ncplane_hline_interp(n, hl, xstop - xoff - 1, ll->channels, lr->channels) < 0){
2272 return -1;
2273 }
2274 }
2275 }
2276 }
2277 edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_RIGHT);
2278 if(edges >= box_corner_needs(ctlword)){
2279 if(ncplane_cursor_move_yx(n, yoff, xstop)){
2280 return -1;
2281 }
2282 if(ncplane_putc(n, lr) < 0){
2283 return -1;
2284 }
2285 }
2286 return 0;
2287 }
2288
2289 // takes the head of a list of bound planes. performs a DFS on all planes bound
2290 // to 'n', and all planes down-list from 'n', moving all *by* 'dy' and 'dx'.
2291 static void
2292 move_bound_planes(ncplane* n, int dy, int dx){
2293 while(n){
2294 if(n->sprite){
2295 sprixel_movefrom(n->sprite, n->absy, n->absx);
2296 }
2297 n->absy += dy;
2298 n->absx += dx;
2299 move_bound_planes(n->blist, dy, dx);
2300 n = n->bnext;
2301 }
2302 }
2303
2304 int ncplane_move_yx(ncplane* n, int y, int x){
2305 if(n == ncplane_notcurses(n)->stdplane){
2306 return -1;
2307 }
2308 int dy, dx; // amount moved
2309 if(n->boundto == n){
2310 dy = y - n->absy;
2311 dx = x - n->absx;
2312 }else{
2313 dy = (n->boundto->absy + y) - n->absy;
2314 dx = (n->boundto->absx + x) - n->absx;
2315 }
2316 if(dy || dx){ // don't want to trigger sprixel_movefrom() if unneeded
2317 if(n->sprite){
2318 sprixel_movefrom(n->sprite, n->absy, n->absx);
2319 }
2320 n->absx += dx;
2321 n->absy += dy;
2322 move_bound_planes(n->blist, dy, dx);
2323 }
2324 return 0;
2325 }
2326
2327 int ncplane_y(const ncplane* n){
2328 if(n->boundto == n){
2329 return n->absy;
2330 }
2331 return n->absy - n->boundto->absy;
2332 }
2333
2334 int ncplane_x(const ncplane* n){
2335 if(n->boundto == n){
2336 return n->absx;
2337 }
2338 return n->absx - n->boundto->absx;
2339 }
2340
2341 void ncplane_yx(const ncplane* n, int* y, int* x){
2342 if(y){
2343 *y = ncplane_y(n);
2344 }
2345 if(x){
2346 *x = ncplane_x(n);
2347 }
2348 }
2349
2350 // special case of ncplane_erase_region()
2351 void ncplane_erase(ncplane* n){
2352 loginfo("erasing %dx%d plane\n", n->leny, n->lenx);
2353 if(n->sprite){
2354 sprixel_hide(n->sprite);
2355 destroy_tam(n);
2356 }
2357 // we must preserve the background, but a pure nccell_duplicate() would be
2358 // wiped out by the egcpool_dump(). do a duplication (to get the stylemask
2359 // and channels), and then reload.
2360 char* egc = nccell_strdup(n, &n->basecell);
2361 memset(n->fb, 0, sizeof(*n->fb) * n->leny * n->lenx);
2362 egcpool_dump(&n->pool);
2363 egcpool_init(&n->pool);
2364 // we need to zero out the EGC before handing this off to nccell_load, but
2365 // we don't want to lose the channels/attributes, so explicit gcluster load.
2366 n->basecell.gcluster = 0;
2367 nccell_load(n, &n->basecell, egc);
2368 free(egc);
2369 n->y = n->x = 0;
2370 }
2371
2372 int ncplane_erase_region(ncplane* n, int ystart, int xstart, int ylen, int xlen){
2373 if(ystart == -1){
2374 ystart = n->y;
2375 }
2376 if(xstart == -1){
2377 xstart = n->x;
2378 }
2379 if(ystart < 0 || xstart < 0){
2380 logerror("Illegal start of erase (%d, %d)\n", ystart, xstart);
2381 return -1;
2382 }
2383 if(ystart >= (int)ncplane_dim_y(n) || xstart >= (int)ncplane_dim_x(n)){
2384 logerror("Illegal start of erase (%d, %d)\n", ystart, xstart);
2385 return -1;
2386 }
2387 if(xlen < 0){
2388 if(xlen + 1 < -xstart){
2389 xlen = -xstart - 1;
2390 }
2391 xstart = xstart + xlen + 1;
2392 xlen = -xlen;
2393 }else if(xlen == 0){
2394 xstart = 0;
2395 xlen = ncplane_dim_x(n);
2396 }
2397 if(xlen > (int)ncplane_dim_x(n) || xstart + xlen > (int)ncplane_dim_x(n)){
2398 xlen = ncplane_dim_x(n) - xstart;
2399 }
2400 if(ylen < 0){
2401 if(ylen + 1 < -ystart){
2402 ylen = -ystart - 1;
2403 }
2404 ystart = ystart + ylen + 1;
2405 ylen = -ylen;
2406 }else if(ylen == 0){
2407 ystart = 0;
2408 ylen = ncplane_dim_y(n);
2409 }
2410 if(ylen > (int)ncplane_dim_y(n) || ystart + ylen > (int)ncplane_dim_y(n)){
2411 ylen = ncplane_dim_y(n) - ystart;
2412 }
2413 // special-case the full plane erasure, as it's powerfully optimized (O(1))
2414 if(ystart == 0 && xstart == 0 &&
2415 ylen == (int)ncplane_dim_y(n) && xlen == (int)ncplane_dim_x(n)){
2416 int tmpy = n->y; // preserve cursor location
2417 int tmpx = n->x;
2418 ncplane_erase(n);
2419 n->y = tmpy;
2420 n->x = tmpx;
2421 return 0;
2422 }
2423 loginfo("erasing %d/%d - %d/%d\n", ystart, xstart, ystart + ylen, xstart + xlen);
2424 for(int y = ystart ; y < ystart + ylen ; ++y){
2425 for(int x = xstart ; x < xstart + xlen ; ++x){
2426 nccell_release(n, &n->fb[nfbcellidx(n, y, x)]);
2427 nccell_init(&n->fb[nfbcellidx(n, y, x)]);
2428 }
2429 }
2430 return 0;
2431 }
2432
2433 ncplane* ncpile_top(ncplane* n){
2434 return ncplane_pile(n)->top;
2435 }
2436
2437 ncplane* ncpile_bottom(ncplane* n){
2438 return ncplane_pile(n)->bottom;
2439 }
2440
2441 ncplane* ncplane_below(ncplane* n){
2442 return n->below;
2443 }
2444
2445 ncplane* ncplane_above(ncplane* n){
2446 return n->above;
2447 }
2448
2449 int notcurses_mice_enable(notcurses* n, unsigned eventmask){
2450 if(mouse_setup(&n->tcache, eventmask)){
2451 return -1;
2452 }
2453 return 0;
2454 }
2455
2456 ncpalette* ncpalette_new(notcurses* nc){
2457 ncpalette* p = malloc(sizeof(*p));
2458 if(p){
2459 memcpy(p, &nc->palette, sizeof(*p));
2460 }
2461 return p;
2462 }
2463
2464 int ncpalette_use(notcurses* nc, const ncpalette* p){
2465 int ret = -1;
2466 if(!notcurses_canchangecolor(nc)){
2467 return -1;
2468 }
2469 for(size_t z = 0 ; z < sizeof(p->chans) / sizeof(*p->chans) ; ++z){
2470 if(nc->palette.chans[z] != p->chans[z]){
2471 nc->palette.chans[z] = p->chans[z];
2472 nc->palette_damage[z] = true;
2473 }
2474 }
2475 ret = 0;
2476 return ret;
2477 }
2478
2479 void ncpalette_free(ncpalette* p){
2480 free(p);
2481 }
2482
2483 bool ncplane_translate_abs(const ncplane* n, int* restrict y, int* restrict x){
2484 ncplane_translate(ncplane_stdplane_const(n), n, y, x);
2485 if(y){
2486 if(*y < 0){
2487 return false;
2488 }
2489 unsigned yval = *y;
2490 if(yval >= n->leny){
2491 return false;
2492 }
2493 }
2494 if(x){
2495 if(*x < 0){
2496 return false;
2497 }
2498 unsigned xval = *x;
2499 if(xval >= n->lenx){
2500 return false;
2501 }
2502 }
2503 return true;
2504 }
2505
2506 void ncplane_translate(const ncplane* src, const ncplane* dst,
2507 int* restrict y, int* restrict x){
2508 if(dst == NULL){
2509 dst = ncplane_stdplane_const(src);
2510 }
2511 if(y){
2512 *y = src->absy - dst->absy + *y;
2513 }
2514 if(x){
2515 *x = src->absx - dst->absx + *x;
2516 }
2517 }
2518
2519 notcurses* ncplane_notcurses(const ncplane* n){
2520 return ncplane_pile(n)->nc;
2521 }
2522
2523 const notcurses* ncplane_notcurses_const(const ncplane* n){
2524 return ncplane_pile_const(n)->nc;
2525 }
2526
2527 int ncplane_abs_y(const ncplane* n){
2528 return n->absy;
2529 }
2530
2531 int ncplane_abs_x(const ncplane* n){
2532 return n->absx;
2533 }
2534
2535 void ncplane_abs_yx(const ncplane* n, int* RESTRICT y, int* RESTRICT x){
2536 if(y){
2537 *y = ncplane_abs_y(n);
2538 }
2539 if(x){
2540 *x = ncplane_abs_x(n);
2541 }
2542 }
2543
2544 ncplane* ncplane_parent(ncplane* n){
2545 return n->boundto;
2546 }
2547
2548 const ncplane* ncplane_parent_const(const ncplane* n){
2549 return n->boundto;
2550 }
2551
2552 int ncplane_set_name(ncplane* n, const char* name){
2553 char* copy = name ? strdup(name) : NULL;
2554 if(copy == NULL && name != NULL){
2555 return -1;
2556 }
2557 free(n->name);
2558 n->name = copy;
2559 return 0;
2560 }
2561
2562 char* ncplane_name(const ncplane* n){
2563 return n->name ? strdup(n->name) : NULL;
2564 }
2565
2566 void ncplane_set_resizecb(ncplane* n, int(*resizecb)(ncplane*)){
2567 n->resizecb = resizecb;
2568 }
2569
2570 int (*ncplane_resizecb(const ncplane* n))(ncplane*){
2571 return n->resizecb;
2572 }
2573
2574 int ncplane_resize_placewithin(ncplane* n){
2575 if(n->boundto == n){
2576 return 0;
2577 }
2578 int absy = ncplane_abs_y(n);
2579 int absx = ncplane_abs_x(n);
2580 int ret = 0;
2581 if(absy + ncplane_dim_y(n) > ncplane_dim_y(n->boundto)){
2582 const int dy = (absy + ncplane_dim_y(n)) - ncplane_dim_y(n->boundto);
2583 logdebug("moving up %d\n", dy);
2584 if(ncplane_move_rel(n, -dy, 0)){
2585 ret = -1;
2586 }
2587 absy = ncplane_abs_y(n);
2588 }
2589 if(absx + ncplane_dim_x(n) > ncplane_dim_x(n->boundto)){
2590 const int dx = ncplane_dim_x(n->boundto) - (absx + ncplane_dim_x(n));
2591 logdebug("moving left %d\n", dx);
2592 if(ncplane_move_rel(n, 0, dx)){
2593 ret = -1;
2594 }
2595 absx = ncplane_abs_x(n);
2596 }
2597 // this will prefer upper-left material if the child plane is larger than
2598 // the parent. we might want a smarter rule, one based on origin?
2599 if(absy < 0){
2600 logdebug("moving down %d\n", -absy);
2601 // we're at least partially above our parent
2602 if(ncplane_move_rel(n, -absy, 0)){
2603 ret = -1;
2604 }
2605 absy = ncplane_abs_y(n);
2606 }
2607 if(absx < 0){
2608 logdebug("moving right %d\n", -absx);
2609 // we're at least partially to the left of our parent
2610 if(ncplane_move_rel(n, 0, -absx)){
2611 ret = -1;
2612 }
2613 absx = ncplane_abs_x(n);
2614 }
2615 return ret;
2616 }
2617
2618 int ncplane_resize_marginalized(ncplane* n){
2619 const ncplane* parent = ncplane_parent_const(n);
2620 // a marginalized plane cannot be larger than its oppressor plane =]
2621 unsigned maxy, maxx;
2622 if(parent == n){ // root plane, need to use pile size
2623 ncpile* p = ncplane_pile(n);
2624 maxy = p->dimy;
2625 maxx = p->dimx;
2626 }else{
2627 ncplane_dim_yx(parent, &maxy, &maxx);
2628 }
2629 if((maxy -= (n->margin_b + (n->absy - n->boundto->absy))) < 1){
2630 maxy = 1;
2631 }
2632 if((maxx -= (n->margin_r + (n->absx - n->boundto->absx))) < 1){
2633 maxx = 1;
2634 }
2635 unsigned oldy, oldx;
2636 ncplane_dim_yx(n, &oldy, &oldx); // current dimensions of 'n'
2637 unsigned keepleny = oldy > maxy ? maxy : oldy;
2638 unsigned keeplenx = oldx > maxx ? maxx : oldx;
2639 if(ncplane_resize_internal(n, 0, 0, keepleny, keeplenx, 0, 0, maxy, maxx)){
2640 return -1;
2641 }
2642 int targy = maxy - n->margin_b;
2643 int targx = maxx - n->margin_b;
2644 loginfo("marg %d/%d, pdim %d/%d, move %d/%d\n", n->margin_b, n->margin_r, maxy, maxx, targy, targx);
2645 return ncplane_move_yx(n, targy, targx);
2646 }
2647
2648 int ncplane_resize_maximize(ncplane* n){
2649 const ncpile* pile = ncplane_pile(n); // FIXME should be taken against parent
2650 const unsigned rows = pile->dimy;
2651 const unsigned cols = pile->dimx;
2652 unsigned oldy, oldx;
2653 ncplane_dim_yx(n, &oldy, &oldx); // current dimensions of 'n'
2654 unsigned keepleny = oldy > rows ? rows : oldy;
2655 unsigned keeplenx = oldx > cols ? cols : oldx;
2656 return ncplane_resize_internal(n, 0, 0, keepleny, keeplenx, 0, 0, rows, cols);
2657 }
2658
2659 int ncplane_resize_realign(ncplane* n){
2660 const ncplane* parent = ncplane_parent_const(n);
2661 if(parent == n){
2662 logerror("Can't realign a root plane\n");
2663 return 0;
2664 }
2665 if(n->halign == NCALIGN_UNALIGNED && n->valign == NCALIGN_UNALIGNED){
2666 logerror("Passed a non-aligned plane\n");
2667 return -1;
2668 }
2669 int xpos = ncplane_x(n);
2670 if(n->halign != NCALIGN_UNALIGNED){
2671 xpos = ncplane_halign(parent, n->halign, ncplane_dim_x(n));
2672 }
2673 int ypos = ncplane_y(n);
2674 if(n->valign != NCALIGN_UNALIGNED){
2675 ypos = ncplane_valign(parent, n->valign, ncplane_dim_y(n));
2676 }
2677 return ncplane_move_yx(n, ypos, xpos);
2678 }
2679
2680 // The standard plane cannot be reparented; we return NULL in that case.
2681 // If provided |newparent|==|n|, we are moving |n| to its own pile. If |n|
2682 // is already bound to |newparent|, this is a no-op, and we return |n|.
2683 // This is essentially a wrapper around ncplane_reparent_family() that first
2684 // reparents any children to the parent of 'n', or makes them root planes if
2685 // 'n' is a root plane.
2686 ncplane* ncplane_reparent(ncplane* n, ncplane* newparent){
2687 const notcurses* nc = ncplane_notcurses_const(n);
2688 if(n == nc->stdplane){
2689 logerror("won't reparent standard plane\n");
2690 return NULL; // can't reparent standard plane
2691 }
2692 if(n->boundto == newparent){
2693 loginfo("won't reparent plane to itself\n");
2694 return n; // don't return error, just a no-op
2695 }
2696 //notcurses_debug(ncplane_notcurses(n), stderr);
2697 if(n->blist){
2698 if(n->boundto == n){ // children become new root planes
2699 ncplane* lastlink;
2700 ncplane* child = n->blist;
2701 do{
2702 child->boundto = child;
2703 lastlink = child;
2704 child = child->bnext;
2705 }while(child); // n->blist != NULL -> lastlink != NULL
2706 if( (lastlink->bnext = ncplane_pile(n)->roots) ){
2707 lastlink->bnext->bprev = &lastlink->bnext;
2708 }
2709 n->blist->bprev = &ncplane_pile(n)->roots;
2710 ncplane_pile(n)->roots = n->blist;
2711 }else{ // children are rebound to current parent
2712 ncplane* lastlink;
2713 ncplane* child = n->blist;
2714 do{
2715 child->boundto = n->boundto;
2716 lastlink = child;
2717 child = child->bnext;
2718 }while(child); // n->blist != NULL -> lastlink != NULL
2719 if( (lastlink->bnext = n->boundto->blist) ){
2720 lastlink->bnext->bprev = &lastlink->bnext;
2721 }
2722 n->blist->bprev = &n->boundto->blist;
2723 n->boundto->blist = n->blist;
2724 }
2725 n->blist = NULL;
2726 }
2727 // FIXME would be nice to skip ncplane_descendant_p() on this call...:/
2728 return ncplane_reparent_family(n, newparent);
2729 }
2730
2731 // unsplice self from the z-axis, and then unsplice all children, recursively.
2732 // to be called before unbinding 'n' from old pile.
2733 static void
2734 unsplice_zaxis_recursive(ncplane* n){
2735 // might already have been unspliced, in which case ->above/->below are NULL
2736 if(ncplane_pile(n)->top == n){
2737 ncplane_pile(n)->top = n->below;
2738 }else if(n->above){
2739 n->above->below = n->below;
2740 }
2741 if(ncplane_pile(n)->bottom == n){
2742 ncplane_pile(n)->bottom = n->above;
2743 }else if(n->below){
2744 n->below->above = n->above;
2745 }
2746 for(ncplane* child = n->blist ; child ; child = child->bnext){
2747 unsplice_zaxis_recursive(child);
2748 }
2749 n->below = n->above = NULL;
2750 }
2751
2752 // unsplice our sprixel from the pile's sprixellist, and then unsplice all
2753 // children, recursively. call before unbinding. returns a doubly-linked
2754 // list of any sprixels found.
2755 static sprixel*
2756 unsplice_sprixels_recursive(ncplane* n, sprixel* prev){
2757 sprixel* s = n->sprite;
2758 if(s){
2759 if(s->prev){
2760 s->prev->next = s->next;
2761 }else{
2762 ncplane_pile(n)->sprixelcache = s->next;
2763 }
2764 if(s->next){
2765 s->next->prev = s->prev;
2766 }
2767 if( (s->prev = prev) ){
2768 prev->next = s;
2769 }
2770 s->next = NULL;
2771 prev = s;
2772 }
2773 for(ncplane* child = n->blist ; child ; child = child->bnext){
2774 unsplice_sprixels_recursive(child, prev);
2775 while(prev && prev->next){ // FIXME lame
2776 prev = prev->next;
2777 }
2778 }
2779 return prev;
2780 }
2781
2782 // recursively splice 'n' and children into the z-axis, above 'n->boundto'.
2783 // handles 'n' == 'n->boundto'. to be called after binding 'n' into new pile.
2784 static void
2785 splice_zaxis_recursive(ncplane* n, ncpile* p, unsigned ocellpxy, unsigned ocellpxx,
2786 unsigned ncellpxy, unsigned ncellpxx){
2787 n->pile = p;
2788 if(n != n->boundto){
2789 if((n->above = n->boundto->above) == NULL){
2790 n->pile->top = n;
2791 }else{
2792 n->boundto->above->below = n;
2793 }
2794 n->below = n->boundto;
2795 n->boundto->above = n;
2796 }
2797 if(n->sprite){
2798 if(ocellpxy != ncellpxy || ocellpxx != ncellpxx){
2799 sprixel_rescale(n->sprite, ncellpxy, ncellpxx);
2800 // FIXME do what on error?
2801 }
2802 }
2803 for(ncplane* child = n->blist ; child ; child = child->bnext){
2804 splice_zaxis_recursive(child, p, ocellpxy, ocellpxx, ncellpxy, ncellpxx);
2805 }
2806 }
2807
2808 ncplane* ncplane_reparent_family(ncplane* n, ncplane* newparent){
2809 // ncplane_notcurses() goes through ncplane_pile(). since we're possibly
2810 // destroying piles below, get the notcurses reference early on.
2811 notcurses* nc = ncplane_notcurses(n);
2812 if(n == nc->stdplane){
2813 return NULL; // can't reparent standard plane
2814 }
2815 if(n->boundto == newparent){ // no-op
2816 return n;
2817 }
2818 if(ncplane_descendant_p(newparent, n)){
2819 return NULL;
2820 }
2821 //notcurses_debug(ncplane_notcurses(n), stderr);
2822 if(n->bprev){ // extract from sibling list
2823 if( (*n->bprev = n->bnext) ){
2824 n->bnext->bprev = n->bprev;
2825 }
2826 }else if(n->bnext){
2827 n->bnext->bprev = NULL;
2828 }
2829 n->bprev = NULL;
2830 n->bnext = NULL;
2831 // if leaving a pile, extract n from the old zaxis, and also any sprixel
2832 sprixel* s = NULL;
2833 if(n == newparent || ncplane_pile(n) != ncplane_pile(newparent)){
2834 unsplice_zaxis_recursive(n);
2835 s = unsplice_sprixels_recursive(n, NULL);
2836 }
2837 const unsigned ocellpxy = ncplane_pile(n)->cellpxy;
2838 const unsigned ocellpxx = ncplane_pile(n)->cellpxx;
2839 n->boundto = newparent;
2840 if(n == n->boundto){ // we're a new root plane
2841 logdebug("reparenting new root plane %p\n", n);
2842 unsplice_zaxis_recursive(n);
2843 n->bnext = NULL;
2844 n->bprev = NULL;
2845 pthread_mutex_lock(&nc->pilelock);
2846 if(ncplane_pile(n)->top == NULL){ // did we just empty our pile?
2847 ncpile_destroy(ncplane_pile(n));
2848 }
2849 make_ncpile(nc, n);
2850 unsigned ncellpxy = ncplane_pile(n)->cellpxy;
2851 unsigned ncellpxx = ncplane_pile(n)->cellpxx;
2852 pthread_mutex_unlock(&nc->pilelock);
2853 if(ncplane_pile(n)){ // FIXME otherwise, we've got a problem...!
2854 splice_zaxis_recursive(n, ncplane_pile(n), ocellpxy, ocellpxx, ncellpxy, ncellpxx);
2855 }
2856 }else{ // establish ourselves as a sibling of new parent's children
2857 if( (n->bnext = newparent->blist) ){
2858 n->bnext->bprev = &n->bnext;
2859 }
2860 n->bprev = &newparent->blist;
2861 newparent->blist = n;
2862 // place it immediately above the new binding plane if crossing piles
2863 if(ncplane_pile(n) != ncplane_pile(n->boundto)){
2864 unsigned ncellpxy = ncplane_pile(n->boundto)->cellpxy;
2865 unsigned ncellpxx = ncplane_pile(n->boundto)->cellpxx;
2866 pthread_mutex_lock(&nc->pilelock);
2867 if(ncplane_pile(n)->top == NULL){ // did we just empty our pile?
2868 ncpile_destroy(ncplane_pile(n));
2869 }
2870 n->pile = ncplane_pile(n->boundto);
2871 pthread_mutex_unlock(&nc->pilelock);
2872 splice_zaxis_recursive(n, ncplane_pile(n), ocellpxy, ocellpxx, ncellpxy, ncellpxx);
2873 }
2874 }
2875 if(s){ // must be on new plane, with sprixels to donate
2876 sprixel* lame = s;
2877 while(lame->next){
2878 lame = lame->next;
2879 }
2880 if( (lame->next = n->pile->sprixelcache) ){
2881 n->pile->sprixelcache->prev = lame;
2882 }
2883 n->pile->sprixelcache = s;
2884 }
2885 return n;
2886 }
2887
2888 bool ncplane_set_scrolling(ncplane* n, unsigned scrollp){
2889 bool old = n->scrolling;
2890 n->scrolling = scrollp;
2891 return old;
2892 }
2893
2894 bool ncplane_scrolling_p(const ncplane* n){
2895 return n->scrolling;
2896 }
2897
2898 bool ncplane_set_autogrow(ncplane* n, unsigned growp){
2899 if(n == notcurses_stdplane_const(ncplane_notcurses_const(n))){
2900 logerror("can't set the standard plane autogrow\n");
2901 return false;
2902 }
2903 bool old = n->autogrow;
2904 n->autogrow = growp;
2905 return old;
2906 }
2907
2908 bool ncplane_autogrow_p(const ncplane* n){
2909 return n->autogrow;
2910 }
2911
2912 // extract an integer, which must be non-negative, and followed by either a
2913 // comma or a NUL terminator.
2914 static int
2915 lex_ulong(const char* op, unsigned* i, char** endptr){
2916 errno = 0;
2917 long l = strtol(op, endptr, 10);
2918 if(l < 0 || (l == LONG_MAX && errno == ERANGE) || (l > INT_MAX)){
2919 fprintf(stderr, "Invalid margin: %s\n", op);
2920 return -1;
2921 }
2922 if((**endptr != ',' && **endptr) || *endptr == op){
2923 fprintf(stderr, "Invalid margin: %s\n", op);
2924 return -1;
2925 }
2926 *i = l;
2927 return 0;
2928 }
2929
2930 int notcurses_lex_scalemode(const char* op, ncscale_e* scalemode){
2931 if(strcasecmp(op, "stretch") == 0){
2932 *scalemode = NCSCALE_STRETCH;
2933 }else if(strcasecmp(op, "scalehi") == 0){
2934 *scalemode = NCSCALE_SCALE_HIRES;
2935 }else if(strcasecmp(op, "hires") == 0){
2936 *scalemode = NCSCALE_NONE_HIRES;
2937 }else if(strcasecmp(op, "scale") == 0){
2938 *scalemode = NCSCALE_SCALE;
2939 }else if(strcasecmp(op, "none") == 0){
2940 *scalemode = NCSCALE_NONE;
2941 }else{
2942 return -1;
2943 }
2944 return 0;
2945 }
2946
2947 const char* notcurses_str_scalemode(ncscale_e scalemode){
2948 if(scalemode == NCSCALE_STRETCH){
2949 return "stretch";
2950 }else if(scalemode == NCSCALE_SCALE){
2951 return "scale";
2952 }else if(scalemode == NCSCALE_NONE){
2953 return "none";
2954 }else if(scalemode == NCSCALE_NONE_HIRES){
2955 return "hires";
2956 }else if(scalemode == NCSCALE_SCALE_HIRES){
2957 return "scalehi";
2958 }
2959 return NULL;
2960 }
2961
2962 int notcurses_lex_margins(const char* op, notcurses_options* opts){
2963 char* eptr;
2964 if(lex_ulong(op, &opts->margin_t, &eptr)){
2965 return -1;
2966 }
2967 if(!*eptr){ // allow a single value to be specified for all four margins
2968 opts->margin_r = opts->margin_l = opts->margin_b = opts->margin_t;
2969 return 0;
2970 }
2971 op = ++eptr; // once here, we require four values
2972 if(lex_ulong(op, &opts->margin_r, &eptr) || !*eptr){
2973 return -1;
2974 }
2975 op = ++eptr;
2976 if(lex_ulong(op, &opts->margin_b, &eptr) || !*eptr){
2977 return -1;
2978 }
2979 op = ++eptr;
2980 if(lex_ulong(op, &opts->margin_l, &eptr) || *eptr){ // must end in NUL
2981 return -1;
2982 }
2983 return 0;
2984 }
2985
2986 int notcurses_inputready_fd(notcurses* n){
2987 return inputready_fd(n->tcache.ictx);
2988 }
2989
2990 int ncdirect_inputready_fd(ncdirect* n){
2991 return inputready_fd(n->tcache.ictx);
2992 }
2993
2994 // FIXME speed this up, PoC
2995 // given an egc, get its index in the blitter's EGC set
2996 static int
2997 get_blitter_egc_idx(const struct blitset* bset, const char* egc){
2998 wchar_t wc;
2999 mbstate_t mbs = {};
3000 size_t sret = mbrtowc(&wc, egc, strlen(egc), &mbs);
3001 if(sret == (size_t)-1 || sret == (size_t)-2){
3002 return -1;
3003 }
3004 wchar_t* wptr = wcsrchr(bset->egcs, wc);
3005 if(wptr == NULL){
3006 //fprintf(stderr, "FAILED TO FIND [%s] (%lc) in [%ls]\n", egc, wc, bset->egcs);
3007 return -1;
3008 }
3009 //fprintf(stderr, "FOUND [%s] (%lc) in [%ls] (%zu)\n", egc, wc, bset->egcs, wptr - bset->egcs);
3010 return wptr - bset->egcs;
3011 }
3012
3013 static bool
3014 is_bg_p(int idx, int py, int px, int width){
3015 // bit increases to the right, and down
3016 const int bpos = py * width + px; // bit corresponding to pixel, 0..|egcs|-1
3017 const unsigned mask = 1u << bpos;
3018 if(idx & mask){
3019 return false;
3020 }
3021 return true;
3022 }
3023
3024 static inline uint32_t*
3025 ncplane_as_rgba_internal(const ncplane* nc, ncblitter_e blit,
3026 int begy, int begx, unsigned leny, unsigned lenx,
3027 unsigned* pxdimy, unsigned* pxdimx){
3028 const notcurses* ncur = ncplane_notcurses_const(nc);
3029 unsigned ystart, xstart;
3030 if(check_geometry_args(nc, begy, begx, &leny, &lenx, &ystart, &xstart)){
3031 return NULL;
3032 }
3033 if(blit == NCBLIT_PIXEL){ // FIXME extend this to support sprixels
3034 logerror("pixel blitter %d not yet supported\n", blit);
3035 return NULL;
3036 }
3037 if(blit == NCBLIT_DEFAULT){
3038 logerror("must specify exact blitter, not NCBLIT_DEFAULT\n");
3039 return NULL;
3040 }
3041 const struct blitset* bset = lookup_blitset(&ncur->tcache, blit, false);
3042 if(bset == NULL){
3043 logerror("blitter %d invalid in current environment\n", blit);
3044 return NULL;
3045 }
3046 //fprintf(stderr, "ALLOCATING %u %d %d %p\n", 4u * lenx * leny * 2, leny, lenx, bset);
3047 if(pxdimy){
3048 *pxdimy = leny * bset->height;
3049 }
3050 if(pxdimx){
3051 *pxdimx = lenx * bset->width;
3052 }
3053 uint32_t* ret = malloc(sizeof(*ret) * lenx * bset->width * leny * bset->height);
3054 //fprintf(stderr, "GEOM: %d/%d %d/%d ret: %p\n", bset->height, bset->width, *pxdimy, *pxdimx, ret);
3055 if(ret){
3056 for(unsigned y = ystart, targy = 0 ; y < ystart + leny ; ++y, targy += bset->height){
3057 for(unsigned x = xstart, targx = 0 ; x < xstart + lenx ; ++x, targx += bset->width){
3058 uint16_t stylemask;
3059 uint64_t channels;
3060 char* c = ncplane_at_yx(nc, y, x, &stylemask, &channels);
3061 if(c == NULL){
3062 free(ret);
3063 return NULL;
3064 }
3065 int idx = get_blitter_egc_idx(bset, c);
3066 if(idx < 0){
3067 free(ret);
3068 free(c);
3069 return NULL;
3070 }
3071 unsigned fr, fg, fb, br, bg, bb, fa, ba;
3072 ncchannels_fg_rgb8(channels, &fr, &fb, &fg);
3073 fa = ncchannels_fg_alpha(channels);
3074 ncchannels_bg_rgb8(channels, &br, &bb, &bg);
3075 ba = ncchannels_bg_alpha(channels);
3076 // handle each destination pixel from this cell
3077 for(unsigned py = 0 ; py < bset->height ; ++py){
3078 for(unsigned px = 0 ; px < bset->width ; ++px){
3079 uint32_t* p = &ret[(targy + py) * (lenx * bset->width) + (targx + px)];
3080 bool background = is_bg_p(idx, py, px, bset->width);
3081 if(background){
3082 if(ba){
3083 *p = 0;
3084 }else{
3085 ncpixel_set_a(p, 0xff);
3086 ncpixel_set_r(p, br);
3087 ncpixel_set_g(p, bb);
3088 ncpixel_set_b(p, bg);
3089 }
3090 }else{
3091 if(fa){
3092 *p = 0;
3093 }else{
3094 ncpixel_set_a(p, 0xff);
3095 ncpixel_set_r(p, fr);
3096 ncpixel_set_g(p, fb);
3097 ncpixel_set_b(p, fg);
3098 }
3099 }
3100 }
3101 }
3102 free(c);
3103 }
3104 }
3105 }
3106 return ret;
3107 }
3108
3109 uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit,
3110 int begy, int begx, unsigned leny,
3111 unsigned lenx, unsigned* pxdimy, unsigned* pxdimx){
3112 unsigned px, py;
3113 if(!pxdimy){
3114 pxdimy = &py;
3115 }
3116 if(!pxdimx){
3117 pxdimx = &px;
3118 }
3119 return ncplane_as_rgba_internal(nc, blit, begy, begx, leny, lenx, pxdimy, pxdimx);
3120 }
3121
3122 // return a heap-allocated copy of the contents
3123 char* ncplane_contents(ncplane* nc, int begy, int begx, unsigned leny, unsigned lenx){
3124 unsigned ystart, xstart;
3125 if(check_geometry_args(nc, begy, begx, &leny, &lenx, &ystart, &xstart)){
3126 return NULL;
3127 }
3128 size_t retlen = 1;
3129 char* ret = malloc(retlen);
3130 if(ret){
3131 for(unsigned y = ystart, targy = 0 ; y < ystart + leny ; ++y, targy += 2){
3132 for(unsigned x = xstart, targx = 0 ; x < xstart + lenx ; ++x, ++targx){
3133 nccell ncl = NCCELL_TRIVIAL_INITIALIZER;
3134 // we need ncplane_at_yx_cell() here instead of ncplane_at_yx(),
3135 // because we should only have one copy of each wide EGC.
3136 int clen;
3137 if((clen = ncplane_at_yx_cell(nc, y, x, &ncl)) < 0){
3138 free(ret);
3139 return NULL;
3140 }
3141 const char* c = nccell_extended_gcluster(nc, &ncl);
3142 if(clen){
3143 char* tmp = realloc(ret, retlen + clen);
3144 if(!tmp){
3145 free(ret);
3146 return NULL;
3147 }
3148 ret = tmp;
3149 memcpy(ret + retlen - 1, c, clen);
3150 retlen += clen;
3151 }
3152 }
3153 }
3154 ret[retlen - 1] = '\0';
3155 }
3156 return ret;
3157 }
3158
3159 // find the center coordinate of a plane, preferring the top/left in the
3160 // case of an even number of rows/columns (in such a case, there will be one
3161 // more cell to the bottom/right of the center than the top/left). the
3162 // center is then modified relative to the plane's origin.
3163 void ncplane_center_abs(const ncplane* n, int* RESTRICT y, int* RESTRICT x){
3164 ncplane_center(n, y, x);
3165 if(y){
3166 *y += n->absy;
3167 }
3168 if(x){
3169 *x += n->absx;
3170 }
3171 }
3172
3173 void nclog(const char* fmt, ...){
3174 va_list va;
3175 va_start(va, fmt);
3176 vfprintf(stderr, fmt, va);
3177 va_end(va);
3178 }
3179
3180 int ncplane_putwstr_stained(ncplane* n, const wchar_t* gclustarr){
3181 mbstate_t ps = {};
3182 const wchar_t** wset = &gclustarr;
3183 size_t mbytes = wcsrtombs(NULL, wset, 0, &ps);
3184 if(mbytes == (size_t)-1){
3185 logerror("error converting wide string\n");
3186 return -1;
3187 }
3188 ++mbytes;
3189 char* mbstr = malloc(mbytes);
3190 if(mbstr == NULL){
3191 return -1;
3192 }
3193 size_t s = wcsrtombs(mbstr, wset, mbytes, &ps);
3194 if(s == (size_t)-1){
3195 free(mbstr);
3196 return -1;
3197 }
3198 int r = ncplane_putstr_stained(n, mbstr);
3199 free(mbstr);
3200 return r;
3201 }
3202
3203 int notcurses_ucs32_to_utf8(const uint32_t* ucs32, unsigned ucs32count,
3204 unsigned char* resultbuf, size_t buflen){
3205 if(u32_to_u8(ucs32, ucs32count, resultbuf, &buflen) == NULL){
3206 return -1;
3207 }
3208 return buflen;
3209 }
3210
3211 int ncstrwidth(const char* egcs, int* validbytes, int* validwidth){
3212 int cols;
3213 if(validwidth == NULL){
3214 validwidth = &cols;
3215 }
3216 *validwidth = 0;
3217 int bytes;
3218 if(validbytes == NULL){
3219 validbytes = &bytes;
3220 }
3221 *validbytes = 0;
3222 do{
3223 int thesecols, thesebytes;
3224 thesebytes = utf8_egc_len(egcs, &thesecols);
3225 if(thesebytes < 0){
3226 return -1;
3227 }
3228 egcs += thesebytes;
3229 *validbytes += thesebytes;
3230 *validwidth += thesecols;
3231 }while(*egcs);
3232 return *validwidth;
3233 }
3234
3235 void ncplane_pixel_geom(const ncplane* n,
3236 unsigned* RESTRICT pxy, unsigned* RESTRICT pxx,
3237 unsigned* RESTRICT celldimy, unsigned* RESTRICT celldimx,
3238 unsigned* RESTRICT maxbmapy, unsigned* RESTRICT maxbmapx){
3239 const notcurses* nc = ncplane_notcurses_const(n);
3240 const ncpile* p = ncplane_pile_const(n);
3241 if(celldimy){
3242 *celldimy = p->cellpxy;
3243 }
3244 if(celldimx){
3245 *celldimx = p->cellpxx;
3246 }
3247 if(pxy){
3248 *pxy = p->cellpxy * ncplane_dim_y(n);
3249 }
3250 if(pxx){
3251 *pxx = p->cellpxx * ncplane_dim_x(n);
3252 }
3253 if(notcurses_check_pixel_support(nc) > 0){
3254 if(maxbmapy){
3255 *maxbmapy = p->cellpxy * ncplane_dim_y(n);
3256 if(*maxbmapy > nc->tcache.sixel_maxy && nc->tcache.sixel_maxy){
3257 *maxbmapy = nc->tcache.sixel_maxy;
3258 }
3259 }
3260 if(maxbmapx){
3261 *maxbmapx = p->cellpxx * ncplane_dim_x(n);
3262 if(*maxbmapx > nc->tcache.sixel_maxx && nc->tcache.sixel_maxx){
3263 *maxbmapx = nc->tcache.sixel_maxx;
3264 }
3265 }
3266 }else{
3267 if(maxbmapy){
3268 *maxbmapy = 0;
3269 }
3270 if(maxbmapx){
3271 *maxbmapx = 0;
3272 }
3273 }
3274 }
3275
3276 const nccapabilities* notcurses_capabilities(const notcurses* n){
3277 return &n->tcache.caps;
3278 }
3279