1 #ifndef NOTCURSES_INTERNAL
2 #define NOTCURSES_INTERNAL
3
4 #ifdef __cplusplus
5 extern "C" {
6 #endif
7
8 #include "version.h"
9 #include "builddef.h"
10 #include "compat/compat.h"
11 #include "notcurses/ncport.h"
12 #include "notcurses/notcurses.h"
13 #include "notcurses/direct.h"
14
15 #ifndef __MINGW64__
16 #define API __attribute__((visibility("default")))
17 #else
18 #define API __declspec(dllexport)
19 #endif
20 #define ALLOC __attribute__((malloc)) __attribute__((warn_unused_result))
21
22 // KEY_EVENT is defined by both ncurses.h (prior to 6.3) and wincon.h. since we
23 // don't use either definition, kill it before inclusion of ncurses.h.
24 #undef KEY_EVENT
25 #include <term.h>
26 #include <time.h>
27 #include <term.h>
28 #include <stdio.h>
29 #include <stdint.h>
30 #include <unistd.h>
31 #include <stdlib.h>
32 #include <stdarg.h>
33 #include <string.h>
34 #include <signal.h>
35 #include <wctype.h>
36 #include <pthread.h>
37 #include <stdbool.h>
38 #include <inttypes.h>
39 #include <unictype.h>
40 #ifndef __MINGW64__
41 #include <langinfo.h>
42 #endif
43 #include "lib/termdesc.h"
44 #include "lib/egcpool.h"
45 #include "lib/sprite.h"
46 #include "lib/fbuf.h"
47 #include "lib/gpm.h"
48
49 struct sixelmap;
50 struct ncvisual_details;
51
52 // Was this glyph drawn as part of an ncvisual? If so, we need to honor
53 // blitter stacking rather than the standard trichannel solver.
54 #define NC_BLITTERSTACK_MASK NC_NOBACKGROUND_MASK
55
56 // we can't define multipart ncvisual here, because OIIO requires C++ syntax,
57 // and we can't go throwing C++ syntax into this header. so it goes.
58
59 // A plane is memory for some rectilinear virtual window, plus current cursor
60 // state for that window, and part of a pile. Each pile has a total order along
61 // its z-axis. Functions update these virtual planes over a series of API
62 // calls. Eventually, notcurses_render() is called. We then do a depth buffer
63 // blit of updated cells. A cell is updated if the topmost plane including that
64 // cell updates it, not simply if any plane updates it.
65 //
66 // A plane may be partially or wholly offscreen--this might occur if the
67 // screen is resized, for example. Offscreen portions will not be rendered.
68 // Accesses beyond the borders of a panel, however, are errors.
69 //
70 // The framebuffer 'fb' is a set of rows. For scrolling, we interpret it as a
71 // circular buffer of rows. 'logrow' is the index of the row at the logical top
72 // of the plane. It only changes from 0 if the plane is scrollable.
73 typedef struct ncplane {
74 nccell* fb; // "framebuffer" of character cells
75 int logrow; // logical top row, starts at 0, add one for each scroll
76 unsigned x, y; // current cursor location within this plane
77 // ncplane_yx() etc. use coordinates relative to the plane to which this
78 // plane is bound, but absx/absy are always relative to the terminal origin.
79 // they must thus be translated by any function which moves a parent plane.
80 int absx, absy; // origin of the plane relative to the pile's origin
81 // also used as left and top margin on resize by
82 // ncplane_resize_marginalized()
83 unsigned lenx, leny; // size of the plane, [0..len{x,y}) is addressable
84 egcpool pool; // attached storage pool for UTF-8 EGCs
85 uint64_t channels; // works the same way as cells
86
87 // a notcurses context is made up of piles, each rooted by one or more root
88 // planes. each pile has its own (totally ordered) z-axis.
89 struct ncpile* pile; // pile of which we are a part
90 struct ncplane* above; // plane above us, NULL if we're on top
91 struct ncplane* below; // plane below us, NULL if we're on bottom
92
93 // every plane is bound to some other plane, unless it is a root plane of a
94 // pile. a pile has a set of one or more root planes, all of them siblings.
95 // root planes are bound to themselves. the standard plane is always a root
96 // plane (since it cannot be reparented). a path exists to a root plane.
97 struct ncplane* bnext; // next in the blist
98 struct ncplane** bprev; // blist link back to us
99 struct ncplane* blist; // head of list of bound planes
100 struct ncplane* boundto;// plane to which we are bound (ourself for roots)
101
102 sprixel* sprite; // pointer into the sprixel cache
103 tament* tam; // transparency-annihilation sprite matrix
104
105 void* userptr; // slot for the user to stick some opaque pointer
106 int (*resizecb)(struct ncplane*); // callback after parent is resized
107 nccell basecell; // cell written anywhere that fb[i].gcluster == 0
108 char* name; // used only for debugging
109 ncalign_e halign; // relative to parent plane, for automatic realignment
110 ncalign_e valign; // relative to parent plane, for automatic realignment
111 uint16_t stylemask; // same deal as in a cell
112 int margin_b, margin_r;// bottom and right margins, stored for resize
113 bool scrolling; // is scrolling enabled? always disabled by default
114 bool fixedbound; // are we fixed relative to the parent's scrolling?
115 bool autogrow; // do we grow to accommodate output?
116
117 // we need to track any widget to which we are bound, so that (1) we don't
118 // end up bound to two widgets and (2) we can clean them up on shutdown
119 // (assuming they weren't explicitly cleaned up by the client).
120 void* widget; // widget to which we are bound, can be NULL
121 void(*wdestruct)(void*); // widget destructor, NULL iff widget is NULL
122 } ncplane;
123
124 // current presentation state of the terminal. it is carried across render
125 // instances. initialize everything to 0 on a terminal reset / startup.
126 typedef struct rasterstate {
127 // we assemble the encoded (rasterized) output in an fbuf (a portable POSIX
128 // memstream, basically), and keep it around between uses. this could be a
129 // problem if it ever tremendously spiked, but that seems unlikely?
130 fbuf f; // buffer for preparing raster glyph/escape stream
131
132 // the current cursor position. this is independent of whether the cursor is
133 // visible. it is the cell at which the next write will take place. this is
134 // modified by: output, cursor moves, clearing the screen (during refresh).
135 int y, x;
136
137 const ncplane* lastsrcp; // last source plane (we emit hpa on plane changes)
138
139 unsigned lastr; // foreground rgb, overloaded for palindexed fg
140 unsigned lastg;
141 unsigned lastb;
142 unsigned lastbr; // background rgb, overloaded for palindexed bg
143 unsigned lastbg;
144 unsigned lastbb;
145
146 // used in CLI mode, these track the end of logical output, to place the
147 // cursor following each rasterization. they are tracked thusly:
148 // * initialized to the initial physical cursor position
149 // * when we write to a physical row greater than logendy, update both
150 // * when we scroll, subtract one from logendy
151 // * if logendy reaches -1, reset both to 0
152 int logendy, logendx;
153
154 uint16_t curattr; // current attributes set (does not include colors)
155 // we elide a color escape iff the color has not changed between two cells
156 bool fgelidable;
157 bool bgelidable;
158 bool fgpalelidable;
159 bool bgpalelidable;
160 bool fgdefelidable;
161 bool bgdefelidable;
162 } rasterstate;
163
164 // Tablets are the toplevel entitites within an ncreel. Each corresponds to
165 // a single, distinct ncplane.
166 typedef struct nctablet {
167 ncplane* p; // border plane, NULL when offscreen
168 ncplane* cbp; // data plane, NULL when offscreen
169 struct nctablet* next;
170 struct nctablet* prev;
171 tabletcb cbfxn; // application callback to draw cbp
172 void* curry; // application data provided to cbfxn
173 } nctablet;
174
175 typedef struct ncreel {
176 ncplane* p; // ncplane this ncreel occupies, under tablets
177 // doubly-linked list, a circular one when infinity scrolling is in effect.
178 // points at the focused tablet (when at least one tablet exists, one must be
179 // focused). it will be visibly focused following the next redraw.
180 nctablet* tablets;
181 nctablet* vft; // the visibly-focused tablet
182 enum {
183 LASTDIRECTION_UP,
184 LASTDIRECTION_DOWN,
185 } direction; // last direction of travel
186 int tabletcount; // could be derived, but we keep it o(1)
187 ncreel_options ropts; // copied in ncreel_create()
188 } ncreel;
189
190 typedef struct ncfdplane {
191 ncfdplane_callback cb; // invoked with fresh hot data
192 ncfdplane_done_cb donecb; // invoked on EOF (if !follow) or error
193 void* curry; // passed to the callbacks
194 int fd; // we take ownership of the fd, and close it
195 bool follow; // keep trying to read past the end (event-based)
196 ncplane* ncp; // bound ncplane
197 pthread_t tid; // thread servicing this i/o
198 bool destroyed; // set in ncfdplane_destroy() in our own context
199 } ncfdplane;
200
201 typedef struct ncsubproc {
202 ncfdplane* nfp;
203 pid_t pid; // subprocess
204 int pidfd; // for signalling/watching the subprocess
205 pthread_t waittid; // wait()ing thread if pidfd is not available
206 pthread_mutex_t lock; // guards waited
207 bool waited; // we've wait()ed on it, don't kill/wait further
208 } ncsubproc;
209
210 typedef struct ncreader {
211 ncplane* ncp; // always owned by ncreader
212 uint64_t tchannels; // channels for input text
213 uint32_t tattrs; // attributes for input text
214 ncplane* textarea; // grows as needed iff scrolling is enabled
215 int xproject; // virtual x location of ncp origin on textarea
216 bool horscroll; // is there horizontal panning?
217 bool no_cmd_keys; // are shortcuts disabled?
218 bool manage_cursor; // enable and place a virtual cursor
219 } ncreader;
220
221 typedef struct ncprogbar {
222 ncplane* ncp;
223 double progress; // on the range [0, 1]
224 uint32_t ulchannel, urchannel, blchannel, brchannel;
225 bool retrograde;
226 } ncprogbar;
227
228 typedef struct nctab {
229 struct nctabbed* nt; // The nctabbed this belongs to
230 tabcb cb; // tab callback
231 char* name; // tab name
232 int namecols; // tab name width in columns
233 void* curry; // user pointer
234 struct nctab* prev;
235 struct nctab* next;
236 } nctab;
237
238 // various moving parts within a notcurses context (and the user) might need to
239 // access the stats object, so throw a lock on it. we don't want the lock in
240 // the actual structure since (a) it's usually unnecessary and (b) it breaks
241 // memset() and memcpy().
242 typedef struct ncsharedstats {
243 pthread_mutex_t lock;
244 ncstats s;
245 } ncsharedstats;
246
247 typedef struct ncdirect {
248 ncpalette palette; // 256-indexed palette can be used instead of/with RGB
249 FILE* ttyfp; // FILE* for output tty
250 tinfo tcache; // terminfo cache
251 uint64_t channels; // current channels
252 uint16_t stylemask; // current styles
253 uint64_t flags; // copied in ncdirect_init() from param
254 ncsharedstats stats; // stats! not as broadly used as in notcurses
255 } ncdirect;
256
257 // Extracellular state for a cell during the render process. There is one
258 // crender per rendered cell, and they are initialized to all zeroes.
259 struct crender {
260 const ncplane *p; // source of glyph for this cell
261 nccell c;
262 uint32_t hcfg; // fg channel prior to HIGHCONTRAST (need full channel)
263 sprixel* sprixel; // bitmap encountered during traversal
264 struct {
265 // If the glyph we render is from an ncvisual, and has a transparent or
266 // blended background, blitter stacking is in effect. This is a complicated
267 // issue, but essentially, imagine a bottom block is rendered with a green
268 // bottom and transparent top. on a lower plane, a top block is rendered
269 // with a red foreground and blue background. Normally, this would result
270 // in a blue top and green bottom, but that's not what we ever wanted --
271 // what makes sense is a red top and green bottom. So ncvisual rendering
272 // sets bits from CELL_BLITTERSTACK_MASK when rendering a cell with a
273 // transparent background. When paint() selects a glyph, it checks for these
274 // bits. If they are set, any lower planes with CELL_BLITTERSTACK_MASK set
275 // take this into account when solving the background color.
276 unsigned blittedquads: 4;
277 unsigned damaged: 1; // only used in rasterization
278 // if NCALPHA_HIGHCONTRAST is in play, we apply the HSV flip once the
279 // background is locked in. set highcontrast to indicate this.
280 unsigned highcontrast: 1;
281 unsigned fgblends: 8;
282 unsigned bgblends: 8;
283 // we'll need recalculate the foreground relative to the solved background,
284 // and then reapply any foreground shading from above the highcontrast
285 // declaration. save the foreground state when we go highcontrast.
286 unsigned hcfgblends: 8; // number of foreground blends prior to HIGHCONTRAST
287 unsigned sprixeled: 1; // have we passed through a sprixel?
288 unsigned p_beats_sprixel: 1; // did we solve for our glyph above the bitmap?
289 } s;
290 };
291
292 // a pile is a collection of planes which will be rendered together. piles are
293 // completely distinct with regards to thread-safety; one can always operate
294 // concurrently on distinct piles (save rasterizing, of course). material from
295 // other piles will be blown away whenever a pile is rasterized. no ordering
296 // is meaningful between planes. when a new one is added, or one is destroyed,
297 // the pilelock (in struct notcurses) is taken. a pile is destroyed whenever
298 // its last plane is destroyed or reparented away. a pile contains a totally-
299 // ordered list of piles (ordered on the z axis) and a directed acyclic forest
300 // of bound planes, with each root plane rooting a DAG.
301 //
302 // the geometries are updated each time the pile is rendered. until a pile is
303 // rendered, its geometries might be out-of-date with regards to the terminal
304 // description in tcache. when it is rendered again, the geometries will be
305 // updated, sprixels will have their TAMs rebuilt (if necessary), and each
306 // root plane will have its resize callback invoked (possibly invoking its
307 // bound planes' resize callbacks in turn).
308 //
309 // at context start, there is one pile (the standard pile), containing one
310 // plane (the standard plane). each ncplane holds a pointer to its pile.
311 typedef struct ncpile {
312 ncplane* top; // topmost plane, never NULL
313 ncplane* bottom; // bottommost plane, never NULL
314 ncplane* roots; // head of root plane list
315 struct crender* crender; // array (rows * cols crender objects)
316 struct notcurses* nc; // notcurses context
317 struct ncpile *prev, *next; // circular list pointers
318 size_t crenderlen; // size of crender vector
319 unsigned dimy, dimx; // rows and cols at last render/creation
320 unsigned cellpxx, cellpxy; // cell-pixel geometry at last render/creation
321 int scrolls; // how many real lines need be scrolled at raster
322 sprixel* sprixelcache; // sorted list of sprixels, assembled during paint
323 } ncpile;
324
325 // the standard pile can be reached through ->stdplane.
326 typedef struct notcurses {
327 ncplane* stdplane; // standard plane, covers screen
328
329 // the style state of the terminal is carried across render runs
330 rasterstate rstate;
331
332 // we keep a copy of the last rendered frame. this facilitates O(1)
333 // notcurses_at_yx() and O(1) damage detection (at the cost of some memory).
334 // FIXME why isn't this just an ncplane rather than ~10 different members?
335 nccell* lastframe;// last rasterized framebuffer, NULL until first raster
336 // the last pile we rasterized. NULL until we've rasterized once. might
337 // be invalid due to the pile being destroyed; you are only allowed to
338 // evaluate it for equality to the pile being currently rasterized. when
339 // we switch piles, we need to clear all displayed sprixels, and
340 // invalidate the new pile's, pursuant to their display.
341 ncpile* last_pile;
342 egcpool pool; // egcpool for lastframe
343
344 unsigned lfdimx; // dimensions of lastframe, unchanged by screen resize
345 unsigned lfdimy; // lfdimx/lfdimy are 0 until first rasterization
346
347 int cursory; // desired cursor placement according to user.
348 int cursorx; // -1 is don't-care, otherwise moved here after each render.
349
350 ncsharedstats stats; // some statistics across the lifetime of the context
351 ncstats stashed_stats; // retain across a context reset, for closing banner
352
353 FILE* ttyfp; // FILE* for writing rasterized data
354 tinfo tcache; // terminfo cache
355 pthread_mutex_t pilelock; // guards pile list, locks resize in render
356
357 // desired margins (best-effort only), copied in from notcurses_options
358 int margin_t, margin_b, margin_r, margin_l;
359 int loglevel;
360 ncpalette palette; // 256-indexed palette can be used instead of/with RGB
361 bool palette_damage[NCPALETTESIZE];
362 bool touched_palette; // have we ever changed a palette entry?
363 uint64_t flags; // copied from notcurses_options
364 } notcurses;
365
366 typedef struct blitterargs {
367 // FIXME begy/begx are really only of interest to scaling; they ought be
368 // consumed there, and blitters ought always work with the scaled output.
369 int begy; // upper left start within visual
370 int begx;
371 int leny; // number of source pixels to use
372 int lenx;
373 uint64_t flags; // flags (as selected from ncvisual_options->flags)
374 uint32_t transcolor; // if non-zero, treat the lower 24 bits as a transparent color
375 union { // cell vs pixel-specific arguments
376 struct {
377 int placey; // placement within ncplane
378 int placex;
379 } cell; // for cells
380 struct {
381 int colorregs; // number of color registers
382 sprixel* spx; // sprixel object
383 int pxoffy; // pixel y-offset within origin cell
384 int pxoffx; // pixel x-offset within origin cell
385 int cellpxy; // targeted cell-pixel geometry. this ought come from the
386 int cellpxx; // target ncpile, or tcache in Direct Mode
387 } pixel; // for pixels
388 } u;
389 } blitterargs;
390
391 // scaledy and scaledx are output geometry from scaling; data is output data
392 // from scaling. we might actually need more pixels due to framing concerns,
393 // in which case just assume transparent input pixels where needed.
394 typedef int (*ncblitter)(struct ncplane* n, int linesize, const void* data,
395 int scaledy, int scaledx, const blitterargs* bargs);
396
397 // a system for rendering RGBA pixels as text glyphs or sixel/kitty bitmaps
398 struct blitset {
399 ncblitter_e geom;
400 unsigned width; // number of input pixels per output cell, width
401 unsigned height; // number of input pixels per output cell, height
402 // the EGCs which form the blitter. bits grow left to right, and then top to
403 // bottom. the first character is always a space, the last a full block.
404 const wchar_t* egcs;
405 // the EGCs which form the various levels of a given plotset. if the geometry
406 // is wide, things are arranged with the rightmost side increasing most
407 // quickly, i.e. it can be indexed as height arrays of 1 + height glyphs. i.e.
408 // the first five braille EGCs are all 0 on the left, [0..4] on the right.
409 const wchar_t* plotegcs;
410 ncblitter blit;
411 const char* name;
412 bool fill;
413 };
414
415 #include "blitset.h"
416
417 void reset_stats(ncstats* stats);
418 void summarize_stats(notcurses* nc);
419
420 void update_raster_stats(const struct timespec* time1, const struct timespec* time0, ncstats* stats);
421 void update_render_stats(const struct timespec* time1, const struct timespec* time0, ncstats* stats);
422 void update_raster_bytes(ncstats* stats, int bytes);
423 void update_write_stats(const struct timespec* time1, const struct timespec* time0, ncstats* stats, int bytes);
424
425 void sigwinch_handler(int signo);
426
427 void init_lang(void);
428
429 int reset_term_attributes(const tinfo* ti, fbuf* f);
430 int reset_term_palette(const tinfo* ti, fbuf* f, unsigned touchedpalette);
431
432 // if there were missing elements we wanted from terminfo, bitch about them here
433 void warn_terminfo(const notcurses* nc, const tinfo* ti);
434
435 int resize_callbacks_children(ncplane* n);
436
437 static inline ncpile*
ncplane_pile(const ncplane * n)438 ncplane_pile(const ncplane* n){
439 return n->pile;
440 }
441
442 static inline const ncpile*
ncplane_pile_const(const ncplane * n)443 ncplane_pile_const(const ncplane* n){
444 return n->pile;
445 }
446
447 static inline ncplane*
ncplane_stdplane(ncplane * n)448 ncplane_stdplane(ncplane* n){
449 return notcurses_stdplane(ncplane_notcurses(n));
450 }
451
452 static inline const ncplane*
ncplane_stdplane_const(const ncplane * n)453 ncplane_stdplane_const(const ncplane* n){
454 return notcurses_stdplane_const(ncplane_notcurses_const(n));
455 }
456
457 // set the plane's widget and wdestruct fields, returning non-zero if they're
458 // already non-NULL (i.e. if the plane is already bound), unless we pass NULL
459 // (which ought be done from the widget destructor, to avoid corecursion).
460 static inline int
ncplane_set_widget(ncplane * n,void * w,void (* wdestruct)(void *))461 ncplane_set_widget(ncplane* n, void* w, void(*wdestruct)(void*)){
462 if(n->widget){
463 if(w){
464 logerror("plane is already bound to a widget\n");
465 return -1;
466 }
467 }else if(w == NULL){
468 return -1;
469 }
470 n->widget = w;
471 n->wdestruct = wdestruct;
472 return 0;
473 }
474
475 // initialize visualization backend (ffmpeg/oiio)
476 int ncvisual_init(int loglevel);
477
478 static inline int
fbcellidx(int row,int rowlen,int col)479 fbcellidx(int row, int rowlen, int col){
480 //fprintf(stderr, "row: %d rowlen: %d col: %d\n", row, rowlen, col);
481 return row * rowlen + col;
482 }
483
484 // take a logical 'y' and convert it to the virtual 'y'. see HACKING.md.
485 static inline int
logical_to_virtual(const ncplane * n,int y)486 logical_to_virtual(const ncplane* n, int y){
487 //fprintf(stderr, "y: %d n->logrow: %d n->leny: %d\n", y, n->logrow, n->leny);
488 return (y + n->logrow) % n->leny;
489 }
490
491 int clear_and_home(notcurses* nc, tinfo* ti, fbuf* f);
492
493 static inline int
nfbcellidx(const ncplane * n,int row,int col)494 nfbcellidx(const ncplane* n, int row, int col){
495 return fbcellidx(logical_to_virtual(n, row), n->lenx, col);
496 }
497
498 // is the rgb value greyish? note that pure white and pure black are both
499 // considered greyish according to the definition of this function =].
500 static inline bool
rgb_greyish_p(unsigned r,unsigned g,unsigned b)501 rgb_greyish_p(unsigned r, unsigned g, unsigned b){
502 const unsigned GREYMASK = 0xf8;
503 if((r & GREYMASK) == (g & GREYMASK) && (g & GREYMASK) == (b & GREYMASK)){
504 return true;
505 }
506 return false;
507 }
508
509 // For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to
510 // ~2.4-bit 6x6x6 cube + greyscale (assumed on entry; I know no way to
511 // even semi-portably recover the palette) proceeds via: map each 8-bit to
512 // a 5-bit target grey. if all 3 components match, select that grey.
513 // otherwise, c / 42.7 to map to 6 values.
514 static inline int
rgb_quantize_256(unsigned r,unsigned g,unsigned b)515 rgb_quantize_256(unsigned r, unsigned g, unsigned b){
516 // if all 5 MSBs match, return grey from 24-member grey ramp or pure
517 // black/white from original 16 (0 and 15, respectively)
518 if(rgb_greyish_p(r, g, b)){
519 // 8 and 238 come from https://terminalguide.namepad.de/attr/fgcol256/,
520 // which suggests 10-nit intervals otherwise.
521 if(r < 8){
522 return 0;
523 }else if(r > 238){
524 return 15;
525 }
526 return 232 + (r - 8) / 10;
527 }
528 r /= 43;
529 g /= 43;
530 b /= 43;
531 return r * 36 + g * 6 + b + 16;
532 }
533
534 // We get black, red, green, yellow, blue, magenta, cyan, and white. Ugh. Under
535 // an additive model: G + R -> Y, G + B -> C, R + B -> M, R + G + B -> W
536 static inline int
rgb_quantize_8(unsigned r,unsigned g,unsigned b)537 rgb_quantize_8(unsigned r, unsigned g, unsigned b){
538 static const int BLACK = 0;
539 static const int RED = 1;
540 static const int GREEN = 2;
541 static const int YELLOW = 3;
542 static const int BLUE = 4;
543 static const int MAGENTA = 5;
544 static const int CYAN = 6;
545 static const int WHITE = 7;
546 if(rgb_greyish_p(r, g, b)){
547 if(r < 64){
548 return BLACK;
549 }
550 return WHITE;
551 }
552 if(r < 128){ // we have no red
553 if(g < 128){ // we have no green
554 if(b < 128){
555 return BLACK;
556 }
557 return BLUE;
558 } // we have green
559 if(b < 128){
560 return GREEN;
561 }
562 return CYAN;
563 }else if(g < 128){ // we have red, but not green
564 if(b < 128){
565 return RED;
566 }
567 return MAGENTA;
568 }else if(b < 128){ // we have red and green
569 return YELLOW;
570 }
571 return WHITE;
572 }
573
574 // Given r, g, and b values 0..255, do a weighted average per Rec. 601, and
575 // return the 8-bit greyscale value (this value will be the r, g, and b value
576 // for the new color).
577 static inline int
rgb_greyscale(int r,int g,int b)578 rgb_greyscale(int r, int g, int b){
579 if(r < 0 || r > 255){
580 return -1;
581 }
582 if(g < 0 || g > 255){
583 return -1;
584 }
585 if(b < 0 || b > 255){
586 return -1;
587 }
588 // Use Rec. 601 scaling plus linear approximation of gamma decompression
589 float fg = (0.299 * (r / 255.0) + 0.587 * (g / 255.0) + 0.114 * (b / 255.0));
590 return fg * 255;
591 }
592
593 static inline const char*
pool_extended_gcluster(const egcpool * pool,const nccell * c)594 pool_extended_gcluster(const egcpool* pool, const nccell* c){
595 if(cell_simple_p(c)){
596 return (const char*)&c->gcluster;
597 }
598 return egcpool_extended_gcluster(pool, c);
599 }
600
601 static inline nccell*
ncplane_cell_ref_yx(const ncplane * n,unsigned y,unsigned x)602 ncplane_cell_ref_yx(const ncplane* n, unsigned y, unsigned x){
603 return &n->fb[nfbcellidx(n, y, x)];
604 }
605
606 static inline void
cell_debug(const egcpool * p,const nccell * c)607 cell_debug(const egcpool* p, const nccell* c){
608 fprintf(stderr, "gcluster: %08x %s style: 0x%04x chan: 0x%016" PRIx64 "\n",
609 c->gcluster, egcpool_extended_gcluster(p, c), c->stylemask,
610 c->channels);
611 }
612
613 static inline void
plane_debug(const ncplane * n,bool details)614 plane_debug(const ncplane* n, bool details){
615 unsigned dimy, dimx;
616 ncplane_dim_yx(n, &dimy, &dimx);
617 fprintf(stderr, "p: %p dim: %d/%d poolsize: %d\n", n, dimy, dimx, n->pool.poolsize);
618 if(details){
619 for(unsigned y = 0 ; y < 1 ; ++y){
620 for(unsigned x = 0 ; x < 10 ; ++x){
621 const nccell* c = &n->fb[fbcellidx(y, dimx, x)];
622 fprintf(stderr, "[%03d/%03d] ", y, x);
623 cell_debug(&n->pool, c);
624 }
625 }
626 }
627 }
628
629 static inline notcurses*
ncpile_notcurses(ncpile * p)630 ncpile_notcurses(ncpile* p){
631 return p->nc;
632 }
633
634 static inline const notcurses*
ncpile_notcurses_const(const ncpile * p)635 ncpile_notcurses_const(const ncpile* p){
636 return p->nc;
637 }
638
639 static inline void
ncpile_debug(const ncpile * p,fbuf * f)640 ncpile_debug(const ncpile* p, fbuf* f){
641 fbuf_printf(f, " ************************* %16p pile ****************************\n", p);
642 const ncplane* n = p->top;
643 const ncplane* prev = NULL;
644 int planeidx = 0;
645 while(n){
646 fbuf_printf(f, "%04d off y: %3d x: %3d geom y: %3u x: %3u curs y: %3u x: %3u %p %.4s\n",
647 planeidx, n->absy, n->absx, n->leny, n->lenx, n->y, n->x, n, n->name);
648 if(n->boundto || n->bnext || n->bprev || n->blist){
649 fbuf_printf(f, " bound %p ← %p → %p binds %p\n",
650 n->boundto, n->bprev, n->bnext, n->blist);
651 }
652 if(n->bprev && (*n->bprev != n)){
653 fbuf_printf(f, " WARNING: expected *->bprev %p, got %p\n", n, *n->bprev);
654 }
655 if(n->above != prev){
656 fbuf_printf(f, " WARNING: expected ->above %p, got %p\n", prev, n->above);
657 }
658 if(ncplane_pile_const(n) != p){
659 fbuf_printf(f, " WARNING: expected pile %p, got %p\n", p, ncplane_pile_const(n));
660 }
661 prev = n;
662 n = n->below;
663 ++planeidx;
664 }
665 if(p->bottom != prev){
666 fbuf_printf(f, " WARNING: expected ->bottom %p, got %p\n", prev, p->bottom);
667 }
668 }
669
670 static inline void
notcurses_debug_fbuf(const notcurses * nc,fbuf * f)671 notcurses_debug_fbuf(const notcurses* nc, fbuf* f){
672 const ncpile* p = ncplane_pile(nc->stdplane);
673 fbuf_printf(f, " -------------------------- notcurses debug state -----------------------------\n");
674 const ncpile* p0 = p;
675 do{
676 ncpile_debug(p0, f);
677 const ncpile* prev = p0;
678 p0 = p0->next;
679 if(p0->prev != prev){
680 fbuf_printf(f, "WARNING: expected ->prev %p, got %p\n", prev, p0->prev);
681 }
682 }while(p != p0);
683 fbuf_printf(f, " ______________________________________________________________________________\n");
684 }
685
686 // cell coordinates *within the sprixel*, not absolute
687 int sprite_wipe(const notcurses* nc, sprixel* s, int y, int x);
688 void sprixel_free(sprixel* s);
689 void sprixel_hide(sprixel* s);
690
691 // dimy and dimx are cell geometry, not pixel.
692 sprixel* sprixel_alloc(ncplane* n, int dimy, int dimx);
693 sprixel* sprixel_recycle(ncplane* n);
694 int sprite_init(const tinfo* t, int fd);
695 int sprite_clear_all(const tinfo* t, fbuf* f);
696 // these three all use absolute coordinates
697 void sprixel_invalidate(sprixel* s, int y, int x);
698 void sprixel_movefrom(sprixel* s, int y, int x);
699 void sprixel_debug(const sprixel* s, FILE* out);
700 void sixelmap_free(struct sixelmap *s);
701
702 // update any necessary cells underneath the sprixel pursuant to its removal.
703 // for sixel, this *achieves* the removal, and is performed on every cell.
704 // returns 1 if the graphic can be immediately freed (which is equivalent to
705 // asking whether it was sixel and there were no errors).
706 static inline int
sprite_scrub(const notcurses * n,const ncpile * p,sprixel * s)707 sprite_scrub(const notcurses* n, const ncpile* p, sprixel* s){
708 //sprixel_debug(s, stderr);
709 logdebug("sprixel %u state %d\n", s->id, s->invalidated);
710 return n->tcache.pixel_scrub(p, s);
711 }
712
713 // precondition: s->invalidated is SPRIXEL_INVALIDATED or SPRIXEL_MOVED.
714 // returns -1 on error, or the number of bytes written.
715 static inline int
sprite_draw(const tinfo * ti,const ncpile * p,sprixel * s,fbuf * f,int yoff,int xoff)716 sprite_draw(const tinfo* ti, const ncpile* p, sprixel* s, fbuf* f,
717 int yoff, int xoff){
718 if(!ti->pixel_draw){
719 return 0;
720 }
721 //sprixel_debug(s, stderr);
722 logdebug("sprixel %u state %d\n", s->id, s->invalidated);
723 return ti->pixel_draw(ti, p, s, f, yoff, xoff);
724 }
725
726 // precondition: s->invalidated is SPRIXEL_MOVED or SPRIXEL_INVALIDATED
727 // returns -1 on error, or the number of bytes written.
728 static inline int
sprite_redraw(notcurses * nc,const ncpile * p,sprixel * s,fbuf * f,int y,int x)729 sprite_redraw(notcurses* nc, const ncpile* p, sprixel* s, fbuf* f, int y, int x){
730 //sprixel_debug(s, stderr);
731 const tinfo* ti = &nc->tcache;
732 logdebug("sprixel %u state %d\n", s->id, s->invalidated);
733 if(s->invalidated == SPRIXEL_MOVED && ti->pixel_move){
734 // if we are kitty prior to 0.20.0, C=1 isn't available to us, and we must
735 // not emit it. we use sixel_maxy_pristine as a side channel to encode
736 // this version information.
737 bool noscroll = !ti->sixel_maxy_pristine;
738 return ti->pixel_move(s, f, noscroll, y, x);
739 }else{
740 if(!ti->pixel_draw){
741 return 0;
742 }
743 int r = ti->pixel_draw(ti, p, s, f, y, x);
744 // different terminals put the cursor at different places following
745 // emission of a bitmap graphic. just reset y/x.
746 nc->rstate.y = -1;
747 nc->rstate.x = -1;
748 return r;
749 }
750 }
751
752 // present a loaded graphic. only defined for kitty.
753 static inline int
sprite_commit(tinfo * ti,fbuf * f,sprixel * s,unsigned forcescroll)754 sprite_commit(tinfo* ti, fbuf* f, sprixel* s, unsigned forcescroll){
755 if(ti->pixel_commit){
756 // if we are kitty prior to 0.20.0, C=1 isn't available to us, and we must
757 // not emit it. we use sixel_maxy_pristine as a side channel to encode
758 // this version information. direct mode, meanwhile, sets forcescroll.
759 bool noscroll = !ti->sixel_maxy_pristine && !forcescroll;
760 if(ti->pixel_commit(f, s, noscroll) < 0){
761 return -1;
762 }
763 }
764 return 0;
765 }
766
767 static inline void
cleanup_tam(tament * tam,int ydim,int xdim)768 cleanup_tam(tament* tam, int ydim, int xdim){
769 for(int y = 0 ; y < ydim ; ++y){
770 for(int x = 0 ; x < xdim ; ++x){
771 free(tam[y * xdim + x].auxvector);
772 tam[y * xdim + x].auxvector = NULL;
773 }
774 }
775 }
776
777 static inline void
destroy_tam(ncplane * p)778 destroy_tam(ncplane* p){
779 if(p->tam){
780 cleanup_tam(p->tam, p->leny, p->lenx);
781 free(p->tam);
782 p->tam = NULL;
783 }
784 }
785
786 static inline int
sprite_rebuild(const notcurses * nc,sprixel * s,int ycell,int xcell)787 sprite_rebuild(const notcurses* nc, sprixel* s, int ycell, int xcell){
788 logdebug("rebuilding %d %d/%d\n", s->id, ycell, xcell);
789 const int idx = s->dimx * ycell + xcell;
790 int ret = 0;
791 // special case the transition back to SPRIXCELL_TRANSPARENT; this can be
792 // done in O(1), since the actual glyph needn't change.
793 if(s->n->tam[idx].state == SPRIXCELL_ANNIHILATED_TRANS){
794 s->n->tam[idx].state = SPRIXCELL_TRANSPARENT;
795 }else if(s->n->tam[idx].state == SPRIXCELL_ANNIHILATED){
796 uint8_t* auxvec = (uint8_t*)s->n->tam[idx].auxvector;
797 assert(auxvec);
798 // sets the new state itself
799 ret = nc->tcache.pixel_rebuild(s, ycell, xcell, auxvec);
800 if(ret > 0){
801 free(auxvec);
802 s->n->tam[idx].auxvector = NULL;
803 }
804 }else{
805 return 0;
806 }
807 // don't upset SPRIXEL_MOVED
808 if(s->invalidated == SPRIXEL_QUIESCENT){
809 if(s->n->tam[idx].state != SPRIXCELL_TRANSPARENT &&
810 s->n->tam[idx].state != SPRIXCELL_ANNIHILATED &&
811 s->n->tam[idx].state != SPRIXCELL_ANNIHILATED_TRANS){
812 s->invalidated = SPRIXEL_INVALIDATED;
813 }
814 }
815 return ret;
816 }
817
818 // |y| and |x| are scaled geometry on input, and clamped scaled geometry on
819 // output. |outy| is output geometry on output, and unused on input. output
820 // geometry is derived from scaled geometry and output requirements (that Sixel
821 // must be a multiple of six pixels tall). output width is always equal to
822 // scaled width. all are pixels.
823 // happy fact: common reported values for maximum sixel height are 256, 1024,
824 // and 4096...not a single goddamn one of which is divisible by six. augh.
825 static inline void
clamp_to_sixelmax(const tinfo * t,unsigned * y,unsigned * x,unsigned * outy,ncscale_e scaling)826 clamp_to_sixelmax(const tinfo* t, unsigned* y, unsigned* x, unsigned* outy, ncscale_e scaling){
827 if(t->sixel_maxy && *y > t->sixel_maxy){
828 *y = t->sixel_maxy;
829 }
830 *outy = *y;
831 if(*outy % t->sprixel_scale_height){
832 *outy += t->sprixel_scale_height - (*outy % t->sprixel_scale_height);
833 // FIXME use closed form
834 while(t->sixel_maxy && *outy > t->sixel_maxy){
835 *outy -= t->sprixel_scale_height;
836 }
837 if(scaling == NCSCALE_STRETCH || *y > *outy){
838 *y = *outy;
839 }
840 }
841 if(t->sixel_maxx && *x > t->sixel_maxx){
842 *x = t->sixel_maxx;
843 }
844 }
845
846 // any sprixcell which does not cover the entirety of the underlying cell
847 // cannot be SPRIXCELL_OPAQUE. this postprocesses the TAM, flipping any
848 // such sprixcells to SPRIXCELL_MIXED. |leny| and |lenx| are output geometry
849 // in pixels. |cdimy| and |cdimx| are output coverage in cells.
850 static inline void
scrub_tam_boundaries(tament * tam,int leny,int lenx,int cdimy,int cdimx)851 scrub_tam_boundaries(tament* tam, int leny, int lenx, int cdimy, int cdimx){
852 // any sprixcells which don't cover the full cell underneath them cannot
853 // be SPRIXCELL_OPAQUE
854 const int cols = (lenx + cdimx - 1) / cdimx;
855 if(lenx % cdimx){
856 for(int y = 0 ; y < (leny + cdimy - 1) / cdimy ; ++y){
857 if(tam[y * cols + cols - 1].state == SPRIXCELL_OPAQUE_KITTY){
858 tam[y * cols + cols - 1].state = SPRIXCELL_MIXED_KITTY;
859 }else if(tam[y * cols + cols - 1].state == SPRIXCELL_OPAQUE_SIXEL){
860 tam[y * cols + cols - 1].state = SPRIXCELL_MIXED_SIXEL;
861 }
862 }
863 }
864 if(leny % cdimy){
865 const int y = (leny + cdimy - 1) / cdimy - 1;
866 for(int x = 0 ; x < cols ; ++x){
867 if(tam[y * cols + x].state == SPRIXCELL_OPAQUE_KITTY){
868 tam[y * cols + x].state = SPRIXCELL_MIXED_KITTY;
869 }else if(tam[y * cols + x].state == SPRIXCELL_OPAQUE_SIXEL){
870 tam[y * cols + x].state = SPRIXCELL_MIXED_SIXEL;
871 }
872 }
873 }
874 }
875
876 // get the TAM entry for these (absolute) coordinates
877 static inline sprixcell_e
sprixel_state(const sprixel * s,int y,int x)878 sprixel_state(const sprixel* s, int y, int x){
879 const ncplane* stdn = notcurses_stdplane_const(ncplane_notcurses_const(s->n));
880 int localy = y - (s->n->absy - stdn->absy);
881 int localx = x - (s->n->absx - stdn->absx);
882 //fprintf(stderr, "TAM %d at %d/%d (%d/%d, %d/%d)\n", s->n->tam[localy * s->dimx + localx].state, localy, localx, y, x, s->dimy, s->dimx);
883 assert(localy >= 0);
884 assert(localy < (int)s->dimy);
885 assert(localx >= 0);
886 assert(localx < (int)s->dimx);
887 return s->n->tam[localy * s->dimx + localx].state;
888 }
889
890 static inline void
pool_release(egcpool * pool,nccell * c)891 pool_release(egcpool* pool, nccell* c){
892 if(cell_extended_p(c)){
893 egcpool_release(pool, cell_egc_idx(c));
894 }
895 c->gcluster = 0; // don't subject ourselves to double-release problems
896 c->width = 0; // don't subject ourselves to geometric ambiguities
897 }
898
899 // set the nccell 'c' to point into the egcpool at location 'eoffset'
900 static inline void
set_gcluster_egc(nccell * c,int eoffset)901 set_gcluster_egc(nccell* c, int eoffset){
902 c->gcluster = htole(0x01000000ul) + htole(eoffset);
903 }
904
905 // Duplicate one nccell onto another, possibly crossing ncplanes.
906 static inline int
cell_duplicate_far(egcpool * tpool,nccell * targ,const ncplane * splane,const nccell * c)907 cell_duplicate_far(egcpool* tpool, nccell* targ, const ncplane* splane, const nccell* c){
908 pool_release(tpool, targ);
909 targ->stylemask = c->stylemask;
910 targ->channels = c->channels;
911 targ->width = c->width;
912 if(!cell_extended_p(c)){
913 targ->gcluster = c->gcluster;
914 return 0;
915 }
916 const char* egc = nccell_extended_gcluster(splane, c);
917 size_t ulen = strlen(egc);
918 int eoffset = egcpool_stash(tpool, egc, ulen);
919 if(eoffset < 0){
920 return -1;
921 }
922 set_gcluster_egc(targ, eoffset);
923 return 0;
924 }
925
926 int ncplane_resize_internal(ncplane* n, int keepy, int keepx,
927 unsigned keepleny, unsigned keeplenx,
928 int yoff, int xoff,
929 unsigned ylen, unsigned xlen);
930
931 int update_term_dimensions(unsigned* rows, unsigned* cols, tinfo* tcache, int margin_b,
932 unsigned* cgeo_changed, unsigned* pgeo_changed)
933 __attribute__ ((nonnull (3, 5, 6)));
934
935 ALLOC static inline void*
memdup(const void * src,size_t len)936 memdup(const void* src, size_t len){
937 void* ret = malloc(len);
938 if(ret){
939 memcpy(ret, src, len);
940 }
941 return ret;
942 }
943
944 ALLOC void* bgra_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha);
945 ALLOC void* rgb_loose_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha);
946 ALLOC void* rgb_packed_to_rgba(const void* data, int rows, int* rowstride, int cols, int alpha);
947
948 // find the "center" cell of two lengths. in the case of even rows/columns, we
949 // place the center on the top/left. in such a case there will be one more
950 // cell to the bottom/right of the center.
951 static inline void
center_box(int * RESTRICT y,int * RESTRICT x)952 center_box(int* RESTRICT y, int* RESTRICT x){
953 if(y){
954 *y = (*y - 1) / 2;
955 }
956 if(x){
957 *x = (*x - 1) / 2;
958 }
959 }
960
961 // find the "center" cell of a plane. in the case of even rows/columns, we
962 // place the center on the top/left. in such a case there will be one more
963 // cell to the bottom/right of the center.
964 static inline void
ncplane_center(const ncplane * n,int * RESTRICT y,int * RESTRICT x)965 ncplane_center(const ncplane* n, int* RESTRICT y, int* RESTRICT x){
966 *y = n->leny;
967 *x = n->lenx;
968 center_box(y, x);
969 }
970
971 int ncvisual_bounding_box(const struct ncvisual* ncv, int* leny, int* lenx,
972 int* offy, int* offx);
973
974 // Our gradient is a 2d lerp among the four corners of the region. We start
975 // with the observation that each corner ought be its exact specified corner,
976 // and the middle ought be the exact average of all four corners' components.
977 // Another observation is that if all four corners are the same, every cell
978 // ought be the exact same color. From this arises the observation that a
979 // perimeter element is not affected by the other three sides:
980 //
981 // a corner element is defined by itself
982 // a perimeter element is defined by the two points on its side
983 // an internal element is defined by all four points
984 //
985 // 2D equation of state: solve for each quadrant's contribution (min 2x2):
986 //
987 // X' = (xlen - 1) - X
988 // Y' = (ylen - 1) - Y
989 // TLC: X' * Y' * TL
990 // TRC: X * Y' * TR
991 // BLC: X' * Y * BL
992 // BRC: X * Y * BR
993 // steps: (xlen - 1) * (ylen - 1) [maximum steps away from origin]
994 //
995 // Then add TLC + TRC + BLC + BRC + steps / 2, and divide by steps (the
996 // steps / 2 is to work around truncate-towards-zero).
997 static int
calc_gradient_component(unsigned tl,unsigned tr,unsigned bl,unsigned br,unsigned y,unsigned x,unsigned ylen,unsigned xlen)998 calc_gradient_component(unsigned tl, unsigned tr, unsigned bl, unsigned br,
999 unsigned y, unsigned x, unsigned ylen, unsigned xlen){
1000 const int avm = (ylen - 1) - y;
1001 const int ahm = (xlen - 1) - x;
1002 if(xlen < 2){
1003 if(ylen < 2){
1004 return tl;
1005 }
1006 return (tl * avm + bl * y) / (ylen - 1);
1007 }
1008 if(ylen < 2){
1009 return (tl * ahm + tr * x) / (xlen - 1);
1010 }
1011 const int tlc = ahm * avm * tl;
1012 const int blc = ahm * y * bl;
1013 const int trc = x * avm * tr;
1014 const int brc = y * x * br;
1015 const int divisor = (ylen - 1) * (xlen - 1);
1016 return ((tlc + blc + trc + brc) + divisor / 2) / divisor;
1017 }
1018
1019 // calculate one of the channels of a gradient at a particular point.
1020 static inline uint32_t
calc_gradient_channel(uint32_t ul,uint32_t ur,uint32_t ll,uint32_t lr,unsigned y,unsigned x,unsigned ylen,unsigned xlen)1021 calc_gradient_channel(uint32_t ul, uint32_t ur, uint32_t ll, uint32_t lr,
1022 unsigned y, unsigned x, unsigned ylen, unsigned xlen){
1023 uint32_t chan = 0;
1024 ncchannel_set_rgb8_clipped(&chan,
1025 calc_gradient_component(ncchannel_r(ul), ncchannel_r(ur),
1026 ncchannel_r(ll), ncchannel_r(lr),
1027 y, x, ylen, xlen),
1028 calc_gradient_component(ncchannel_g(ul), ncchannel_g(ur),
1029 ncchannel_g(ll), ncchannel_g(lr),
1030 y, x, ylen, xlen),
1031 calc_gradient_component(ncchannel_b(ul), ncchannel_b(ur),
1032 ncchannel_b(ll), ncchannel_b(lr),
1033 y, x, ylen, xlen));
1034 ncchannel_set_alpha(&chan, ncchannel_alpha(ul)); // precondition: all αs are equal
1035 return chan;
1036 }
1037
1038 // calculate both channels of a gradient at a particular point, storing them
1039 // into `channels'. x and y ought be the location within the gradient.
1040 static inline void
calc_gradient_channels(uint64_t * channels,uint64_t ul,uint64_t ur,uint64_t ll,uint64_t lr,unsigned y,unsigned x,unsigned ylen,unsigned xlen)1041 calc_gradient_channels(uint64_t* channels, uint64_t ul, uint64_t ur,
1042 uint64_t ll, uint64_t lr, unsigned y, unsigned x,
1043 unsigned ylen, unsigned xlen){
1044 if(!ncchannels_fg_default_p(ul)){
1045 ncchannels_set_fchannel(channels,
1046 calc_gradient_channel(ncchannels_fchannel(ul),
1047 ncchannels_fchannel(ur),
1048 ncchannels_fchannel(ll),
1049 ncchannels_fchannel(lr),
1050 y, x, ylen, xlen));
1051 }else{
1052 ncchannels_set_fg_default(channels);
1053 }
1054 if(!ncchannels_bg_default_p(ul)){
1055 ncchannels_set_bchannel(channels,
1056 calc_gradient_channel(ncchannels_bchannel(ul),
1057 ncchannels_bchannel(ur),
1058 ncchannels_bchannel(ll),
1059 ncchannels_bchannel(lr),
1060 y, x, ylen, xlen));
1061 }else{
1062 ncchannels_set_bg_default(channels);
1063 }
1064 }
1065
1066 // ncdirect needs to "fake" an isolated ncplane as a drawing surface for
1067 // ncvisual_blit(), and thus calls these low-level internal functions.
1068 // they are not for general use -- check ncplane_new() and ncplane_destroy().
1069 ncplane* ncplane_new_internal(notcurses* nc, ncplane* n, const ncplane_options* nopts);
1070
1071 void free_plane(ncplane* p);
1072
1073 // heap-allocated formatted output
1074 ALLOC char* ncplane_vprintf_prep(const char* format, va_list ap);
1075
1076 // Resize the provided ncvisual to the specified 'rows' x 'cols', but do not
1077 // change the internals of the ncvisual. Uses oframe.
1078 int ncvisual_blit_internal(struct ncvisual* ncv, int rows, int cols,
1079 ncplane* n, const struct blitset* bset,
1080 const blitterargs* bargs);
1081
1082 static inline int
tty_emit(const char * seq,int fd)1083 tty_emit(const char* seq, int fd){
1084 if(!seq){
1085 return -1;
1086 }
1087 size_t slen = strlen(seq);
1088 if(blocking_write(fd, seq, slen)){
1089 return -1;
1090 }
1091 return 0;
1092 }
1093
1094 int set_fd_nonblocking(int fd, unsigned state, unsigned* oldstate);
1095
1096 static inline int
term_bg_palindex(const notcurses * nc,fbuf * f,unsigned pal)1097 term_bg_palindex(const notcurses* nc, fbuf* f, unsigned pal){
1098 const char* setab = get_escape(&nc->tcache, ESCAPE_SETAB);
1099 if(setab){
1100 return fbuf_emit(f, tiparm(setab, pal));
1101 }
1102 return 0;
1103 }
1104
1105 static inline int
term_fg_palindex(const notcurses * nc,fbuf * f,unsigned pal)1106 term_fg_palindex(const notcurses* nc, fbuf* f, unsigned pal){
1107 const char* setaf = get_escape(&nc->tcache, ESCAPE_SETAF);
1108 if(setaf){
1109 return fbuf_emit(f, tiparm(setaf, pal));
1110 }
1111 return 0;
1112 }
1113
1114 // check the current and target style bitmasks against the specified 'stylebit'.
1115 // if they are different, and we have the necessary capability, write the
1116 // applicable terminfo entry to 'out'. returns -1 only on a true error.
1117 static int
term_setstyle(fbuf * f,unsigned cur,unsigned targ,unsigned stylebit,const char * ton,const char * toff)1118 term_setstyle(fbuf* f, unsigned cur, unsigned targ, unsigned stylebit,
1119 const char* ton, const char* toff){
1120 int ret = 0;
1121 unsigned curon = cur & stylebit;
1122 unsigned targon = targ & stylebit;
1123 if(curon != targon){
1124 if(targon){
1125 if(ton){
1126 ret = fbuf_emit(f, ton);
1127 }
1128 }else{
1129 if(toff){ // how did this happen? we can turn it on, but not off?
1130 ret = fbuf_emit(f, toff);
1131 }
1132 }
1133 }
1134 if(ret < 0){
1135 return -1;
1136 }
1137 return 0;
1138 }
1139
1140 // emit escapes such that the current style is equal to newstyle. if this
1141 // required an sgr0 (which resets colors), normalized will be non-zero upon
1142 // a successful return.
1143 static inline int
coerce_styles(fbuf * f,const tinfo * ti,uint16_t * curstyle,uint16_t newstyle,unsigned * normalized)1144 coerce_styles(fbuf* f, const tinfo* ti, uint16_t* curstyle,
1145 uint16_t newstyle, unsigned* normalized){
1146 *normalized = 0; // we never currently use sgr0
1147 int ret = 0;
1148 ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_BOLD,
1149 get_escape(ti, ESCAPE_BOLD), get_escape(ti, ESCAPE_NOBOLD));
1150 ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_ITALIC,
1151 get_escape(ti, ESCAPE_SITM), get_escape(ti, ESCAPE_RITM));
1152 ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_STRUCK,
1153 get_escape(ti, ESCAPE_SMXX), get_escape(ti, ESCAPE_RMXX));
1154 // underline and undercurl are exclusive. if we set one, don't go unsetting
1155 // the other.
1156 if(newstyle & NCSTYLE_UNDERLINE){ // turn on underline, or do nothing
1157 ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_UNDERLINE,
1158 get_escape(ti, ESCAPE_SMUL), get_escape(ti, ESCAPE_RMUL));
1159 }else if(newstyle & NCSTYLE_UNDERCURL){ // turn on undercurl, or do nothing
1160 ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_UNDERCURL,
1161 get_escape(ti, ESCAPE_SMULX), get_escape(ti, ESCAPE_SMULNOX));
1162 }else{ // turn off any underlining
1163 ret |= term_setstyle(f, *curstyle, newstyle, NCSTYLE_UNDERCURL | NCSTYLE_UNDERLINE,
1164 NULL, get_escape(ti, ESCAPE_RMUL));
1165 }
1166 *curstyle = newstyle;
1167 return ret;
1168 }
1169
1170 // DEC private mode set (DECSET) parameters (and corresponding XTerm resources)
1171 #define SET_X10_MOUSE_PROT "9" // outdated, do not use, use x11
1172 // we can combine 1000--1004, and then use 1005/1006/1015 for extended coordinates
1173 #define SET_X11_MOUSE_PROT "1000" // for button events
1174 #define SET_HILITE_MOUSE_PROT "1001" // for highlight tracking
1175 #define SET_BTN_EVENT_MOUSE "1002" // for motion events with buttons
1176 #define SET_ALL_EVENT_MOUSE "1003" // for motion events without buttons
1177 #define SET_FOCUS_EVENT_MOUSE "1004" // for focus events
1178 #define SET_UTF8_MOUSE_PROT "1005" // utf8-style extended coordinates
1179 #define SET_SGR_MOUSE_PROT "1006" // sgr-style extended coordinates
1180 #define SET_ALTERNATE_SCROLL "1007" // scroll in alternate screen (alternateScroll)
1181 #define SET_TTYOUTPUT_SCROLL "1010" // scroll on tty output (scrollTtyOutput)
1182 #define SET_KEYPRESS_SCROLL "1011" // scroll on keypress (scrollKey)
1183 #define SET_URXVT_MOUSE_PROT "1015" // urxvt-style extended coordinates
1184 #define SET_PIXEL_MOUSE_PROT "1016" // sgr-style, using pixles rather than cells
1185 #define SET_ENABLE_ALTSCREEN "1046" // enable alternate screen (*sets* titeInhibit)
1186 #define SET_ALTERNATE_SCREEN "1047" // replaces 47 (conflict w/DECGRPM) (titeInhibit)
1187 #define SET_SAVE_CURSOR "1048" // save cursor ala DECSC (titeInhibit)
1188 #define SET_SMCUP "1049" // 1047+1048 (titeInhibit)
1189 // DECSET/DECRSTs can be chained with semicolons; can we generalize this? FIXME
1190 #define DECSET(p) "\x1b[?" p "h"
1191 #define DECRST(p) "\x1b[?" p "l"
1192
1193 int mouse_setup(tinfo* ti, unsigned eventmask);
1194
1195 // sync the drawing position to the specified location with as little overhead
1196 // as possible (with nothing, if already at the right location). we prefer
1197 // absolute horizontal moves (hpa) to relative ones, in the rare event that
1198 // our understanding of our horizontal location is faulty. if we're moving from
1199 // one plane to another, we emit an hpa no matter what.
1200 // FIXME fall back to synthesized moves in the absence of capabilities (i.e.
1201 // textronix lacks cup; fake it with horiz+vert moves)
1202 // if hardcursorpos is non-zero, we always perform a cup. this is done when we
1203 // don't know where the cursor currently is =].
1204 static inline int
goto_location(notcurses * nc,fbuf * f,int y,int x,const ncplane * srcp)1205 goto_location(notcurses* nc, fbuf* f, int y, int x, const ncplane* srcp){
1206 //fprintf(stderr, "going to %d/%d from %d/%d hard: %u\n", y, x, nc->rstate.y, nc->rstate.x, nc->rstate.hardcursorpos);
1207 int ret = 0;
1208 // if we don't have hpa, force a cup even if we're only 1 char away. the only
1209 // TERM i know supporting cup sans hpa is vt100, and vt100 can suck it.
1210 // you can't use cuf for backwards moves anyway; again, vt100 can suck it.
1211 const char* hpa = get_escape(&nc->tcache, ESCAPE_HPA);
1212 if(nc->rstate.y == y && hpa){ // only need move x
1213 if(nc->rstate.x == x){
1214 if(nc->rstate.lastsrcp == srcp || !nc->tcache.gratuitous_hpa){
1215 return 0; // needn't move shit
1216 }
1217 ++nc->stats.s.hpa_gratuitous;
1218 }
1219 if(fbuf_emit(f, tiparm(hpa, x))){
1220 return -1;
1221 }
1222 }else{
1223 // cup is required, no need to verify existence
1224 const char* cup = get_escape(&nc->tcache, ESCAPE_CUP);
1225 if(fbuf_emit(f, tiparm(cup, y, x))){
1226 return -1;
1227 }
1228 }
1229 nc->rstate.x = x;
1230 nc->rstate.y = y;
1231 nc->rstate.lastsrcp = srcp;
1232 return ret;
1233 }
1234
1235 // how many edges need touch a corner for it to be printed?
1236 static inline unsigned
box_corner_needs(unsigned ctlword)1237 box_corner_needs(unsigned ctlword){
1238 return (ctlword & NCBOXCORNER_MASK) >> NCBOXCORNER_SHIFT;
1239 }
1240
1241 // True if the cell does not generate background pixels (i.e., the cell is a
1242 // solid or shaded block, or certain emoji).
1243 static inline bool
nccell_nobackground_p(const nccell * c)1244 nccell_nobackground_p(const nccell* c){
1245 // needs to match all four bits of NC_NOBACKGROUND_MASK, not just one
1246 return (c->channels & NC_NOBACKGROUND_MASK) == NC_NOBACKGROUND_MASK;
1247 }
1248
1249 // True iff the foreground and background color are both RGB, and equal.
1250 static inline bool
nccell_rgbequal_p(const nccell * c)1251 nccell_rgbequal_p(const nccell* c){
1252 if(nccell_fg_default_p(c) || nccell_fg_palindex_p(c)){
1253 return false;
1254 }
1255 if(nccell_bg_default_p(c) || nccell_bg_palindex_p(c)){
1256 return false;
1257 }
1258 return nccell_fg_rgb(c) == nccell_bg_rgb(c);
1259 }
1260
1261 // Returns a number 0 <= n <= 15 representing the four quadrants, and which (if
1262 // any) are occupied due to blitting with a transparent background. The mapping
1263 // is {tl, tr, bl, br}.
1264 static inline unsigned
cell_blittedquadrants(const nccell * c)1265 cell_blittedquadrants(const nccell* c){
1266 return ((c->channels & 0x8000000000000000ull) ? 1 : 0) |
1267 ((c->channels & 0x0400000000000000ull) ? 2 : 0) |
1268 ((c->channels & 0x0200000000000000ull) ? 4 : 0) |
1269 ((c->channels & 0x0100000000000000ull) ? 8 : 0);
1270 }
1271
1272 // Set this whenever blitting an ncvisual, when we have a transparent
1273 // background. In such cases, ncvisuals underneath the cell must be rendered
1274 // slightly differently.
1275 static inline void
cell_set_blitquadrants(nccell * c,unsigned tl,unsigned tr,unsigned bl,unsigned br)1276 cell_set_blitquadrants(nccell* c, unsigned tl, unsigned tr, unsigned bl, unsigned br){
1277 // FIXME want a static assert that these four constants OR together to
1278 // equal CELL_BLITTERSTACK_MASK, bah
1279 uint64_t newval = (tl ? 0x8000000000000000ull : 0) |
1280 (tr ? 0x0400000000000000ull : 0) |
1281 (bl ? 0x0200000000000000ull : 0) |
1282 (br ? 0x0100000000000000ull : 0);
1283 c->channels = ((c->channels & ~NC_BLITTERSTACK_MASK) | newval);
1284 }
1285
1286 // Destroy a plane and all its bound descendants.
1287 int ncplane_destroy_family(ncplane *ncp);
1288
1289 // Extract the 32-bit background channel from a cell.
1290 static inline uint32_t
cell_bchannel(const nccell * cl)1291 cell_bchannel(const nccell* cl){
1292 return ncchannels_bchannel(cl->channels);
1293 }
1294
1295 // Extract those elements of the channel which are common to both foreground
1296 // and background channel representations.
1297 static inline uint32_t
channel_common(uint32_t channel)1298 channel_common(uint32_t channel){
1299 return channel & (NC_BGDEFAULT_MASK | NC_BG_RGB_MASK |
1300 NC_BG_PALETTE | NC_BG_ALPHA_MASK);
1301 }
1302
1303 // Extract those elements of the background channel which may be freely swapped
1304 // with the foreground channel (alpha and coloring info).
1305 static inline uint32_t
cell_bchannel_common(const nccell * cl)1306 cell_bchannel_common(const nccell* cl){
1307 return channel_common(cell_bchannel(cl));
1308 }
1309
1310 // Extract the 32-bit foreground channel from a cell.
1311 static inline uint32_t
cell_fchannel(const nccell * cl)1312 cell_fchannel(const nccell* cl){
1313 return ncchannels_fchannel(cl->channels);
1314 }
1315
1316 // Extract those elements of the foreground channel which may be freely swapped
1317 // with the background channel (alpha and coloring info).
1318 static inline uint32_t
cell_fchannel_common(const nccell * cl)1319 cell_fchannel_common(const nccell* cl){
1320 return channel_common(cell_fchannel(cl));
1321 }
1322
1323 // Set the 32-bit background channel of an nccell.
1324 static inline uint64_t
cell_set_bchannel(nccell * cl,uint32_t channel)1325 cell_set_bchannel(nccell* cl, uint32_t channel){
1326 return ncchannels_set_bchannel(&cl->channels, channel);
1327 }
1328
1329 // Set the 32-bit foreground channel of an nccell.
1330 static inline uint64_t
cell_set_fchannel(nccell * cl,uint32_t channel)1331 cell_set_fchannel(nccell* cl, uint32_t channel){
1332 return ncchannels_set_fchannel(&cl->channels, channel);
1333 }
1334
1335 // Returns the result of blending two channels. 'blends' indicates how heavily
1336 // 'c1' ought be weighed. If 'blends' is 0, 'c1' will be entirely replaced by
1337 // 'c2'. If 'c1' is otherwise the default color, 'c1' will not be touched,
1338 // since we can't blend default colors. Likewise, if 'c2' is a default color,
1339 // it will not be used (unless 'blends' is 0).
1340 static inline unsigned
channels_blend(notcurses * nc,unsigned c1,unsigned c2,unsigned * blends)1341 channels_blend(notcurses* nc, unsigned c1, unsigned c2, unsigned* blends){
1342 if(ncchannel_alpha(c2) == NCALPHA_TRANSPARENT){
1343 return c1; // do *not* increment *blends
1344 }
1345 bool c2default = ncchannel_default_p(c2);
1346 bool c2palette = ncchannel_palindex_p(c2);
1347 if(*blends == 0){
1348 // don't just return c2, or you set wide status and all kinds of crap
1349 if(c2default){
1350 ncchannel_set_default(&c1);
1351 }else if(c2palette){
1352 ncchannel_set_palindex(&c1, ncchannel_palindex(c2));
1353 }else{
1354 ncchannel_set(&c1, c2 & NC_BG_RGB_MASK);
1355 }
1356 ncchannel_set_alpha(&c1, ncchannel_alpha(c2));
1357 }else if(!c2default && !ncchannel_default_p(c1)){
1358 unsigned rsum, gsum, bsum;
1359 if(c2palette){
1360 uint32_t rgb = nc->palette.chans[ncchannel_palindex(c2)];
1361 bsum = rgb & 0xff;
1362 gsum = (rgb >> 8u) & 0xff;
1363 rsum = (rgb >> 16u) & 0xff;
1364 }else{
1365 ncchannel_rgb8(c2, &rsum, &gsum, &bsum);
1366 }
1367 rsum = (ncchannel_r(c1) * *blends + rsum) / (*blends + 1);
1368 gsum = (ncchannel_g(c1) * *blends + gsum) / (*blends + 1);
1369 bsum = (ncchannel_b(c1) * *blends + bsum) / (*blends + 1);
1370 ncchannel_set_rgb8(&c1, rsum, gsum, bsum);
1371 ncchannel_set_alpha(&c1, ncchannel_alpha(c2));
1372 }
1373 ++*blends;
1374 return c1;
1375 }
1376
1377 // do not pass palette-indexed channels!
1378 static inline uint64_t
cell_blend_fchannel(notcurses * nc,nccell * cl,unsigned channel,unsigned * blends)1379 cell_blend_fchannel(notcurses* nc, nccell* cl, unsigned channel, unsigned* blends){
1380 return cell_set_fchannel(cl, channels_blend(nc, cell_fchannel(cl), channel, blends));
1381 }
1382
1383 static inline uint64_t
cell_blend_bchannel(notcurses * nc,nccell * cl,unsigned channel,unsigned * blends)1384 cell_blend_bchannel(notcurses* nc, nccell* cl, unsigned channel, unsigned* blends){
1385 return cell_set_bchannel(cl, channels_blend(nc, cell_bchannel(cl), channel, blends));
1386 }
1387
1388 // a sprixel occupies the entirety of its associated plane, usually an entirely
1389 // new, purpose-specific plane. |leny| and |lenx| are output geometry in pixels.
1390 static inline int
plane_blit_sixel(sprixel * spx,fbuf * f,int leny,int lenx,int parse_start,tament * tam,sprixel_e state)1391 plane_blit_sixel(sprixel* spx, fbuf* f, int leny, int lenx,
1392 int parse_start, tament* tam, sprixel_e state){
1393 if(sprixel_load(spx, f, leny, lenx, parse_start, state)){
1394 return -1;
1395 }
1396 ncplane* n = spx->n;
1397 if(n){
1398 //fprintf(stderr, "TAM WAS: %p NOW: %p\n", n->tam, tam);
1399 if(n->tam != tam){
1400 destroy_tam(n);
1401 }
1402 n->tam = tam;
1403 n->sprite = spx;
1404 }
1405 return 0;
1406 }
1407
1408 // is it a control character? check C0 and C1, but don't count empty strings,
1409 // nor single-byte strings containing only a NUL character.
1410 static inline bool
is_control_egc(const unsigned char * egc,int bytes)1411 is_control_egc(const unsigned char* egc, int bytes){
1412 if(bytes == 1){
1413 if(*egc && iscntrl(*egc)){
1414 return true;
1415 }
1416 }else if(bytes == 2){
1417 // 0xc2 followed by 0x80--0x9f are controls. 0xc2 followed by <0x80 is
1418 // simply invalid utf8.
1419 if(egc[0] == 0xc2){
1420 if(egc[1] < 0xa0){
1421 return true;
1422 }
1423 }
1424 }
1425 return false;
1426 }
1427
1428 // lowest level of cell+pool setup. if the EGC changes the output to RTL, it
1429 // must be suffixed with a LTR-forcing character by now. The four bits of
1430 // NC_BLITTERSTACK_MASK ought already be initialized. If gcluster is four
1431 // bytes or fewer, this function cannot fail.
1432 static inline int
pool_blit_direct(egcpool * pool,nccell * c,const char * gcluster,int bytes,int cols)1433 pool_blit_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){
1434 pool_release(pool, c);
1435 if(bytes < 0 || cols < 0){
1436 return -1;
1437 }
1438 // we allow newlines to be blitted into the pool, as they're picked up and
1439 // given special semantics by paint() and rasterization.
1440 if(*gcluster != '\n' && is_control_egc((const unsigned char*)gcluster, bytes)){
1441 logerror("not loading control character %u\n", *(const unsigned char*)gcluster);
1442 return -1;
1443 }
1444 c->width = cols;
1445 if(bytes <= 4){
1446 c->gcluster = 0;
1447 memcpy(&c->gcluster, gcluster, bytes);
1448 }else{
1449 int eoffset = egcpool_stash(pool, gcluster, bytes);
1450 if(eoffset < 0){
1451 return -1;
1452 }
1453 set_gcluster_egc(c, eoffset);
1454 }
1455 return bytes;
1456 }
1457
1458 // Reset the quadrant occupancy bits, and pass the cell down to
1459 // pool_blit_direct(). Returns the number of bytes loaded.
1460 static inline int
pool_load_direct(egcpool * pool,nccell * c,const char * gcluster,int bytes,int cols)1461 pool_load_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){
1462 c->channels &= ~NC_NOBACKGROUND_MASK;
1463 return pool_blit_direct(pool, c, gcluster, bytes, cols);
1464 }
1465
1466 static inline int
cell_load_direct(ncplane * n,nccell * c,const char * gcluster,int bytes,int cols)1467 cell_load_direct(ncplane* n, nccell* c, const char* gcluster, int bytes, int cols){
1468 return pool_load_direct(&n->pool, c, gcluster, bytes, cols);
1469 }
1470
1471 // increment y by 1 and rotate the framebuffer up one line. x moves to 0.
1472 void scroll_down(ncplane* n);
1473
1474 static inline bool
islinebreak(wchar_t wchar)1475 islinebreak(wchar_t wchar){
1476 // UC_LINE_SEPARATOR + UC_PARAGRAPH_SEPARATOR
1477 if(wchar == L'\n' || wchar == L'\v' || wchar == L'\f'){
1478 return true;
1479 }
1480 const uint32_t mask = UC_CATEGORY_MASK_Zl | UC_CATEGORY_MASK_Zp;
1481 return uc_is_general_category_withtable(wchar, mask);
1482 }
1483
1484 static inline bool
iswordbreak(wchar_t wchar)1485 iswordbreak(wchar_t wchar){
1486 const uint32_t mask = UC_CATEGORY_MASK_Z |
1487 UC_CATEGORY_MASK_Zs;
1488 return uc_is_general_category_withtable(wchar, mask);
1489 }
1490
1491 // the heart of damage detection. compare two nccells (from two different
1492 // planes) for equality. if they are equal, return 0. otherwise, dup the second
1493 // onto the first and return non-zero.
1494 static inline int
cellcmp_and_dupfar(egcpool * dampool,nccell * damcell,const ncplane * srcplane,const nccell * srccell)1495 cellcmp_and_dupfar(egcpool* dampool, nccell* damcell,
1496 const ncplane* srcplane, const nccell* srccell){
1497 if(damcell->stylemask == srccell->stylemask){
1498 if(damcell->channels == srccell->channels){
1499 const char* srcegc = nccell_extended_gcluster(srcplane, srccell);
1500 const char* damegc = pool_extended_gcluster(dampool, damcell);
1501 if(strcmp(damegc, srcegc) == 0){
1502 return 0; // EGC match
1503 }
1504 }
1505 }
1506 cell_duplicate_far(dampool, damcell, srcplane, srccell);
1507 return 1;
1508 }
1509
1510 int get_tty_fd(FILE* ttyfp);
1511
1512 // Given the four channels arguments, verify that:
1513 //
1514 // - if any is default foreground, all are default foreground
1515 // - if any is default background, all are default background
1516 // - all foregrounds must have the same alpha
1517 // - all backgrounds must have the same alpha
1518 // - palette-indexed color must not be used
1519 //
1520 // If you only want to check n < 4 channels, just duplicate one.
1521 bool check_gradient_args(uint64_t ul, uint64_t ur, uint64_t bl, uint64_t br);
1522
1523 // takes a signed starting coordinate (where -1 indicates the cursor's
1524 // position), and an unsigned vector (where 0 indicates "everything
1525 // remaining", i.e. to the right and below). returns 0 iff everything
1526 // is valid and on the plane, filling in 'ystart'/'xstart' with the
1527 // (non-negative) starting coordinates and 'ylen'/'xlen with the
1528 // (positive) dimensions of the affected area.
1529 static inline int
check_geometry_args(const ncplane * n,int y,int x,unsigned * ylen,unsigned * xlen,unsigned * ystart,unsigned * xstart)1530 check_geometry_args(const ncplane* n, int y, int x,
1531 unsigned* ylen, unsigned* xlen,
1532 unsigned* ystart, unsigned* xstart){
1533 // handle the special -1 case for y/x, and reject other negatives
1534 if(y < 0){
1535 if(y != -1){
1536 logerror("invalid y: %d\n", y);
1537 return -1;
1538 }
1539 y = n->y;
1540 }
1541 if(x < 0){
1542 if(x != -1){
1543 logerror("invalid x: %d\n", x);
1544 return -1;
1545 }
1546 x = n->x;
1547 }
1548 // y and x are both now definitely positive, but might be off-plane.
1549 // lock in y and x as ystart and xstart for unsigned comparisons.
1550 *ystart = y;
1551 *xstart = x;
1552 unsigned ymax, xmax;
1553 ncplane_dim_yx(n, &ymax, &xmax);
1554 if(*ystart >= ymax || *xstart >= xmax){
1555 logerror("invalid starting coordinates: %u/%u\n", *ystart, *xstart);
1556 return -1;
1557 }
1558 // handle the special 0 case for ylen/xlen
1559 if(*ylen == 0){
1560 *ylen = ymax - *ystart;
1561 }
1562 if(*xlen == 0){
1563 *xlen = xmax - *xstart;
1564 }
1565 // ensure ylen/xlen are on-plane
1566 if(*ylen > ymax){
1567 logerror("ylen > dimy %u > %u\n", *ylen, ymax);
1568 return -1;
1569 }
1570 if(*xlen > xmax){
1571 logerror("xlen > dimx %u > %u\n", *xlen, xmax);
1572 return -1;
1573 }
1574 // ensure x + xlen and y + ylen are on-plane, without overflow
1575 if(ymax - *ylen < *ystart){
1576 logerror("y + ylen > ymax %u + %u > %u\n", *ystart, *ylen, ymax);
1577 return -1;
1578 }
1579 if(xmax - *xlen < *xstart){
1580 logerror("x + xlen > xmax %u + %u > %u\n", *xstart, *xlen, xmax);
1581 return -1;
1582 }
1583 return 0;
1584 }
1585
1586 void ncvisual_printbanner(fbuf* f);
1587
1588 // alpha comes to us 0--255, but we have only 3 alpha values to map them to
1589 // (opaque, blended, and transparent). it's necessary that we display
1590 // something for any non-zero alpha (see #1540), so the threshold is 1.
1591 // we might want to map this to blended, but we only use opaque and
1592 // transparent for now. if |transcolor| is non-zero, match its lower 24
1593 // bits against each pixel's RGB value, and treat a match as transparent.
1594 static inline bool
rgba_trans_p(uint32_t p,uint32_t transcolor)1595 rgba_trans_p(uint32_t p, uint32_t transcolor){
1596 if(ncpixel_a(p) < 192){
1597 return true;
1598 }
1599 if(transcolor &&
1600 (ncpixel_r(p) == (transcolor & 0xff0000ull) >> 16) &&
1601 (ncpixel_g(p) == (transcolor & 0xff00ull) >> 8) &&
1602 (ncpixel_b(p) == (transcolor & 0xffull))){
1603 return true;
1604 }
1605 return false;
1606 }
1607
1608 // get a non-negative "manhattan distance" between two rgb values
1609 static inline uint32_t
rgb_diff(unsigned r1,unsigned g1,unsigned b1,unsigned r2,unsigned g2,unsigned b2)1610 rgb_diff(unsigned r1, unsigned g1, unsigned b1, unsigned r2, unsigned g2, unsigned b2){
1611 uint32_t distance = 0;
1612 distance += r1 > r2 ? r1 - r2 : r2 - r1;
1613 distance += g1 > g2 ? g1 - g2 : g2 - g1;
1614 distance += b1 > b2 ? b1 - b2 : b2 - b1;
1615 //fprintf(stderr, "RGBDIFF %u %u %u %u %u %u: %u\n", r1, g1, b1, r2, g2, b2, distance);
1616 return distance;
1617 }
1618
1619 // returns non-zero iff the two planes intersect
1620 static inline unsigned
ncplanes_intersect_p(const ncplane * p1,const ncplane * p2)1621 ncplanes_intersect_p(const ncplane* p1, const ncplane* p2){
1622 int y1, x1, y2, x2;
1623 int b1, r1, b2, r2;
1624 ncplane_abs_yx(p1, &y1, &x1);
1625 b1 = y1 + ncplane_dim_y(p1) - 1;
1626 r1 = x1 + ncplane_dim_x(p1) - 1;
1627 ncplane_abs_yx(p2, &y2, &x2);
1628 b2 = y2 + ncplane_dim_y(p2) - 1;
1629 r2 = x2 + ncplane_dim_x(p2) - 1;
1630 if(b1 < y2){ // p1 is above p2, no intersection
1631 return 0;
1632 }
1633 if(b2 < y1){ // p2 is above p1, no intersection
1634 return 0;
1635 }
1636 if(r1 < x2){ // p1 is to the left of p2, no intersection
1637 return 0;
1638 }
1639 if(r2 < x1){ // p2 is to the left of p1, no intersection
1640 return 0;
1641 }
1642 return 1;
1643 }
1644
1645 static inline uint64_t
ncdirect_channels(const ncdirect * nc)1646 ncdirect_channels(const ncdirect* nc){
1647 return nc->channels;
1648 }
1649
1650 static inline bool
ncdirect_fg_default_p(const ncdirect * nc)1651 ncdirect_fg_default_p(const ncdirect* nc){
1652 return ncchannels_fg_default_p(ncdirect_channels(nc));
1653 }
1654
1655 static inline bool
ncdirect_bg_default_p(const ncdirect * nc)1656 ncdirect_bg_default_p(const ncdirect* nc){
1657 return ncchannels_bg_default_p(ncdirect_channels(nc));
1658 }
1659
1660 static inline bool
ncdirect_fg_palindex_p(const ncdirect * nc)1661 ncdirect_fg_palindex_p(const ncdirect* nc){
1662 return ncchannels_fg_palindex_p(ncdirect_channels(nc));
1663 }
1664
1665 static inline bool
ncdirect_bg_palindex_p(const ncdirect * nc)1666 ncdirect_bg_palindex_p(const ncdirect* nc){
1667 return ncchannels_bg_palindex_p(ncdirect_channels(nc));
1668 }
1669
1670 int ncdirect_set_fg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f);
1671 int ncdirect_set_bg_rgb_f(ncdirect* nc, unsigned rgb, fbuf* f);
1672 int term_fg_rgb8(const tinfo* ti, fbuf* f, unsigned r, unsigned g, unsigned b);
1673
1674 const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid, bool may_degrade);
1675
1676 static inline int
rgba_blit_dispatch(ncplane * nc,const struct blitset * bset,int linesize,const void * data,int leny,int lenx,const blitterargs * bargs)1677 rgba_blit_dispatch(ncplane* nc, const struct blitset* bset,
1678 int linesize, const void* data,
1679 int leny, int lenx, const blitterargs* bargs){
1680 return bset->blit(nc, linesize, data, leny, lenx, bargs);
1681 }
1682
1683 int ncvisual_geom_inner(const tinfo* ti, const struct ncvisual* n,
1684 const struct ncvisual_options* vopts, ncvgeom* geom,
1685 const struct blitset** bset,
1686 unsigned* disppxy, unsigned* disppxx,
1687 unsigned* outy, unsigned* outx,
1688 int* placey, int* placex);
1689
1690 static inline const struct blitset*
rgba_blitter_low(const tinfo * tcache,ncscale_e scale,bool maydegrade,ncblitter_e blitrec)1691 rgba_blitter_low(const tinfo* tcache, ncscale_e scale, bool maydegrade,
1692 ncblitter_e blitrec) {
1693 if(blitrec == NCBLIT_DEFAULT){
1694 blitrec = rgba_blitter_default(tcache, scale);
1695 }
1696 return lookup_blitset(tcache, blitrec, maydegrade);
1697 }
1698
1699 // RGBA visuals all use NCBLIT_2x1 by default (or NCBLIT_1x1 if not in
1700 // UTF-8 mode), but an alternative can be specified.
1701 static inline const struct blitset*
rgba_blitter(const struct tinfo * tcache,const struct ncvisual_options * opts)1702 rgba_blitter(const struct tinfo* tcache, const struct ncvisual_options* opts) {
1703 const bool maydegrade = !(opts && (opts->flags & NCVISUAL_OPTION_NODEGRADE));
1704 const ncscale_e scale = opts ? opts->scaling : NCSCALE_NONE;
1705 return rgba_blitter_low(tcache, scale, maydegrade, opts ? opts->blitter : NCBLIT_DEFAULT);
1706 }
1707
1708 // naive resize of |bmap| from |srows|x|scols| -> |drows|x|dcols|, suitable for
1709 // pixel art. we either select at a constant interval (for shrinking) or duplicate
1710 // at a constant ratio (for inflation). in the absence of a multimedia engine, this
1711 // is the only kind of resizing we support.
1712 static inline uint32_t*
resize_bitmap(const uint32_t * bmap,int srows,int scols,size_t sstride,int drows,int dcols,size_t dstride)1713 resize_bitmap(const uint32_t* bmap, int srows, int scols, size_t sstride,
1714 int drows, int dcols, size_t dstride){
1715 if(sstride < scols * sizeof(*bmap)){
1716 return NULL;
1717 }
1718 if(dstride < dcols * sizeof(*bmap)){
1719 return NULL;
1720 }
1721 // FIXME if parameters match current setup, do nothing, and return bmap
1722 size_t size = drows * dstride;
1723 uint32_t* ret = (uint32_t*)malloc(size);
1724 if(ret == NULL){
1725 return NULL;
1726 }
1727 float xrat = (float)dcols / scols;
1728 float yrat = (float)drows / srows;
1729 int dy = 0;
1730 for(int y = 0 ; y < srows ; ++y){
1731 float ytarg = (y + 1) * yrat;
1732 if(ytarg > drows){
1733 ytarg = drows;
1734 }
1735 while(ytarg > dy){
1736 int dx = 0;
1737 for(int x = 0 ; x < scols ; ++x){
1738 float xtarg = (x + 1) * xrat;
1739 if(xtarg > dcols){
1740 xtarg = dcols;
1741 }
1742 while(xtarg > dx){
1743 ret[dy * dstride / sizeof(*ret) + dx] = bmap[y * sstride / sizeof(*ret) + x];
1744 ++dx;
1745 }
1746 }
1747 ++dy;
1748 }
1749 }
1750 return ret;
1751 }
1752
1753 // a neighbor on which to polyfill. by the time we get to it, it might or
1754 // might not have been filled in. if so, discard immediately. otherwise,
1755 // check self, and if valid, push all neighbors.
1756 struct topolyfill {
1757 int y, x;
1758 struct topolyfill* next;
1759 };
1760
1761 static inline struct topolyfill*
create_polyfill_op(int y,int x,struct topolyfill ** stck)1762 create_polyfill_op(int y, int x, struct topolyfill** stck){
1763 // cast for the benefit of c++ callers
1764 struct topolyfill* n = (struct topolyfill*)malloc(sizeof(*n));
1765 if(n){
1766 n->y = y;
1767 n->x = x;
1768 n->next = *stck;
1769 *stck = n;
1770 }
1771 return n;
1772 }
1773
1774 // implemented by a multimedia backend (ffmpeg or oiio), and installed
1775 // prior to calling notcurses_core_init() (by notcurses_init()).
1776 typedef struct ncvisual_implementation {
1777 int (*visual_init)(int loglevel);
1778 void (*visual_printbanner)(fbuf* f);
1779 int (*visual_blit)(struct ncvisual* ncv, unsigned rows, unsigned cols, ncplane* n,
1780 const struct blitset* bset, const blitterargs* barg);
1781 struct ncvisual* (*visual_create)(void);
1782 struct ncvisual* (*visual_from_file)(const char* fname);
1783 // ncv constructors other than ncvisual_from_file() need to set up the
1784 // AVFrame* 'frame' according to their own data, which is assumed to
1785 // have been prepared already in 'ncv'.
1786 void (*visual_details_seed)(struct ncvisual* ncv);
1787 int (*visual_decode)(struct ncvisual* nc);
1788 int (*visual_decode_loop)(struct ncvisual* nc);
1789 int (*visual_stream)(notcurses* nc, struct ncvisual* ncv, float timescale,
1790 ncstreamcb streamer, const struct ncvisual_options* vopts, void* curry);
1791 ncplane* (*visual_subtitle)(ncplane* parent, const struct ncvisual* ncv);
1792 int rowalign; // rowstride base, can be 0 for no padding
1793 // do a persistent resize, changing the ncv itself
1794 int (*visual_resize)(struct ncvisual* ncv, unsigned rows, unsigned cols);
1795 void (*visual_destroy)(struct ncvisual* ncv);
1796 bool canopen_images;
1797 bool canopen_videos;
1798 } ncvisual_implementation;
1799
1800 // populated by libnotcurses.so if linked with multimedia
1801 API extern ncvisual_implementation* visual_implementation;
1802
1803 // prepend base with the Notcurses data directory as configured.
1804 static inline char*
prefix_data(const char * base)1805 prefix_data(const char* base){
1806 // need a byte for each of directory separator and nul terminator
1807 const size_t dlen = strlen(NOTCURSES_SHARE);
1808 size_t len = dlen + strlen(base) + 2;
1809 char* path = (char*)malloc(len); // cast for C++ includers
1810 if(path){
1811 memcpy(path, NOTCURSES_SHARE, dlen);
1812 path[dlen] = path_separator();
1813 strcpy(path + dlen + 1, base);
1814 }
1815 return path;
1816 }
1817
1818 // within unix, we can just use isatty(3). on windows, things work
1819 // differently. for a true Windows Terminal, we'll have HANDLE pointers
1820 // rather than file descriptors. in cygwin/msys2, isatty(3) always fails.
1821 // so for __MINGW64__, always return true. otherwise return isatty(fd).
1822 static inline int
tty_check(int fd)1823 tty_check(int fd){
1824 #ifdef __MINGW64__
1825 return _isatty(fd);
1826 #endif
1827 return isatty(fd);
1828 }
1829
1830 // attempt to cancel the specified thread (not an error if we can't; it might
1831 // have already exited), and then join it (an error here is propagated).
1832 static inline int
cancel_and_join(const char * name,pthread_t tid,void ** res)1833 cancel_and_join(const char* name, pthread_t tid, void** res){
1834 if(pthread_cancel(tid)){
1835 logerror("couldn't cancel %s thread\n", name); // tid might have died
1836 }
1837 if(pthread_join(tid, res)){
1838 logerror("error joining %s thread\n", name);
1839 return -1;
1840 }
1841 return 0;
1842 }
1843
1844 static inline int
emit_scrolls(const tinfo * ti,int count,fbuf * f)1845 emit_scrolls(const tinfo* ti, int count, fbuf* f){
1846 logdebug("emitting %d scrolls\n", count);
1847 if(count > 1){
1848 const char* indn = get_escape(ti, ESCAPE_INDN);
1849 if(indn){
1850 if(fbuf_emit(f, tiparm(indn, count)) < 0){
1851 return -1;
1852 }
1853 return 0;
1854 }
1855 }
1856 const char* ind = get_escape(ti, ESCAPE_IND);
1857 if(ind == NULL){
1858 ind = "\v";
1859 }
1860 // fell through if we had no indn
1861 while(count > 0){
1862 if(fbuf_emit(f, ind) < 0){
1863 return -1;
1864 }
1865 --count;
1866 }
1867 return 0;
1868 }
1869
1870 // both emit the |count > 0| scroll ops to |f|, and update the cursor
1871 // tracking in |nc|
1872 static inline int
emit_scrolls_track(notcurses * nc,int count,fbuf * f)1873 emit_scrolls_track(notcurses* nc, int count, fbuf* f){
1874 if(emit_scrolls(&nc->tcache, count, f)){
1875 return -1;
1876 }
1877 nc->rstate.y -= count;
1878 nc->rstate.x = 0;
1879 return 0;
1880 }
1881
1882 // replace or populate the TERM environment variable with 'termname'
1883 int putenv_term(const char* termname) __attribute__ ((nonnull (1)));
1884
1885 // check environment for NOTCURSES_LOGLEVEL, and use it if defined.
1886 int set_loglevel_from_env(ncloglevel_e* loglevel)
1887 __attribute__ ((nonnull (1)));
1888
1889 #undef API
1890 #undef ALLOC
1891
1892 #ifdef __cplusplus
1893 }
1894 #endif
1895
1896 #endif
1897