Notcurses 3.0.16
a blingful library for TUIs and character graphics
Loading...
Searching...
No Matches
direct.c
Go to the documentation of this file.
1#include "version.h"
2#include "builddef.h"
3#include <stdio.h>
4#include <fcntl.h>
5#include <errno.h>
6#include <string.h>
7#include <unistd.h>
8#include "version.h"
9#include "visual-details.h"
10#include "notcurses/direct.h"
11#include "internal.h"
12#include "unixsig.h"
13
14// conform to the foreground and background channels of 'channels'
15static int
16activate_channels(ncdirect* nc, uint64_t channels){
17 if(ncchannels_fg_default_p(channels)){
19 return -1;
20 }
21 }else if(ncchannels_fg_palindex_p(channels)){
22 if(ncdirect_set_fg_palindex(nc, ncchannels_fg_palindex(channels))){
23 return -1;
24 }
25 }else if(ncdirect_set_fg_rgb(nc, ncchannels_fg_rgb(channels))){
26 return -1;
27 }
28 if(ncchannels_bg_default_p(channels)){
30 return -1;
31 }
32 }else if(ncchannels_bg_palindex_p(channels)){
33 if(ncdirect_set_bg_palindex(nc, ncchannels_bg_palindex(channels))){
34 return -1;
35 }
36 }else if(ncdirect_set_bg_rgb(nc, ncchannels_bg_rgb(channels))){
37 return -1;
38 }
39 return 0;
40}
41
42int ncdirect_putstr(ncdirect* nc, uint64_t channels, const char* utf8){
43 if(activate_channels(nc, channels)){
44 return -1;
45 }
46 return ncfputs(utf8, nc->ttyfp);
47}
48
49int ncdirect_putegc(ncdirect* nc, uint64_t channels, const char* utf8,
50 int* sbytes){
51 int cols;
52 int bytes = utf8_egc_len(utf8, &cols);
53 if(bytes < 0){
54 return -1;
55 }
56 if(sbytes){
57 *sbytes = bytes;
58 }
59 if(activate_channels(nc, channels)){
60 return -1;
61 }
62 if(fprintf(nc->ttyfp, "%.*s", bytes, utf8) < 0){
63 return -1;
64 }
65 return cols;
66}
67
68int ncdirect_cursor_up(ncdirect* nc, int num){
69 if(num < 0){
70 logerror("requested negative move %d\n", num);
71 return -1;
72 }
73 if(num == 0){
74 return 0;
75 }
76 const char* cuu = get_escape(&nc->tcache, ESCAPE_CUU);
77 if(cuu){
78 return term_emit(tiparm(cuu, num), nc->ttyfp, false);
79 }
80 return -1;
81}
82
84 if(num < 0){
85 logerror("requested negative move %d\n", num);
86 return -1;
87 }
88 if(num == 0){
89 return 0;
90 }
91 const char* cub = get_escape(&nc->tcache, ESCAPE_CUB);
92 if(cub){
93 return term_emit(tiparm(cub, num), nc->ttyfp, false);
94 }
95 return -1;
96}
97
99 if(num < 0){
100 logerror("requested negative move %d\n", num);
101 return -1;
102 }
103 if(num == 0){
104 return 0;
105 }
106 const char* cuf = get_escape(&nc->tcache, ESCAPE_CUF);
107 if(cuf){
108 return term_emit(tiparm(cuf, num), nc->ttyfp, false);
109 }
110 return -1; // FIXME fall back to cuf1?
111}
112
113// if we're on the last line, we need some scrolling action. rather than
114// merely using cud (which doesn't reliably scroll), we emit vertical tabs.
115// this has the peculiar property (in all terminals tested) of scrolling when
116// necessary but performing no carriage return -- a pure line feed.
118 if(num < 0){
119 logerror("requested negative move %d\n", num);
120 return -1;
121 }
122 if(num == 0){
123 return 0;
124 }
125 int ret = 0;
126 while(num--){
127 if(ncfputc('\v', nc->ttyfp) == EOF){
128 ret = -1;
129 break;
130 }
131 }
132 return ret;
133}
134
135static inline int
136ncdirect_cursor_down_f(ncdirect* nc, int num, fbuf* f){
137 return emit_scrolls(&nc->tcache, num, f);
138}
139
141 const char* clearscr = get_escape(&nc->tcache, ESCAPE_CLEAR);
142 if(clearscr){
143 return term_emit(clearscr, nc->ttyfp, true);
144 }
145 return -1;
146}
147
149 unsigned x;
150 if(nc->tcache.ttyfd >= 0){
151 unsigned cgeo, pgeo; // don't care about either
152 if(update_term_dimensions(NULL, &x, &nc->tcache, 0, &cgeo, &pgeo) == 0){
153 return x;
154 }
155 }else{
156 return 80; // lol
157 }
158 return 0;
159}
160
162 unsigned y;
163 if(nc->tcache.ttyfd >= 0){
164 unsigned cgeo, pgeo; // don't care about either
165 if(update_term_dimensions(&y, NULL, &nc->tcache, 0, &cgeo, &pgeo) == 0){
166 return y;
167 }
168 }else{
169 return 24; // lol
170 }
171 return 0;
172}
173
175 const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
176 if(cnorm){
177 return term_emit(cnorm, nc->ttyfp, true);
178 }
179 return -1;
180}
181
183 const char* cinvis = get_escape(&nc->tcache, ESCAPE_CIVIS);
184 if(cinvis){
185 return term_emit(cinvis, nc->ttyfp, true);
186 }
187 return -1;
188}
189
190static int
191cursor_yx_get(ncdirect* n, const char* u7, unsigned* y, unsigned* x){
192 struct inputctx* ictx = n->tcache.ictx;
193 if(ncdirect_flush(n)){
194 return -1;
195 }
196 unsigned fakey, fakex;
197 if(y == NULL){
198 y = &fakey;
199 }
200 if(x == NULL){
201 x = &fakex;
202 }
203 if(get_cursor_location(ictx, u7, y, x)){
204 logerror("couldn't get cursor position");
205 return -1;
206 }
207 loginfo("cursor at y=%u x=%u\n", *y, *x);
208 return 0;
209}
210
211// if we're lacking hpa/vpa, *and* -1 is passed for one of x/y, *and* we've
212// not got a real ttyfd, we're pretty fucked. we just punt and substitute
213// 0 for that case, which hopefully only happens when running headless unit
214// tests under TERM=vt100. if we need to truly rigourize things, we could
215// cub/cub1 the width or cuu/cuu1 the height, then cuf/cub back? FIXME
217 const char* hpa = get_escape(&n->tcache, ESCAPE_HPA);
218 const char* vpa = get_escape(&n->tcache, ESCAPE_VPA);
219 const char* u7 = get_escape(&n->tcache, ESCAPE_U7);
220 if(y == -1){ // keep row the same, horizontal move only
221 if(hpa){
222 return term_emit(tiparm(hpa, x), n->ttyfp, false);
223 }else if(n->tcache.ttyfd >= 0 && u7){
224 unsigned yprime;
225 if(cursor_yx_get(n, u7, &yprime, NULL)){
226 return -1;
227 }
228 y = yprime;
229 }else{
230 y = 0;
231 }
232 }else if(x == -1){ // keep column the same, vertical move only
233 if(!vpa){
234 return term_emit(tiparm(vpa, y), n->ttyfp, false);
235 }else if(n->tcache.ttyfd >= 0 && u7){
236 unsigned xprime;
237 if(cursor_yx_get(n, u7, NULL, &xprime)){
238 return -1;
239 }
240 x = xprime;
241 }else{
242 x = 0;
243 }
244 }
245 const char* cup = get_escape(&n->tcache, ESCAPE_CUP);
246 if(cup){
247 return term_emit(tiparm(cup, y, x), n->ttyfp, false);
248 }else if(vpa && hpa){
249 if(term_emit(tiparm(hpa, x), n->ttyfp, false) == 0 &&
250 term_emit(tiparm(vpa, y), n->ttyfp, false) == 0){
251 return 0;
252 }
253 }
254 return -1; // we will not be moving the cursor today
255}
256
257/*
258// an algorithm to detect inverted cursor reporting on terminals 2x2 or larger:
259// * get initial cursor position / push cursor position
260// * move right using cursor-independent routines
261// * move up using cursor-independent routines
262// * get cursor position
263// * if cursor position is unchanged, either cursor reporting is broken, or
264// we started in the upper-right corner. determine the latter by checking
265// terminal dimensions. if we were in the upper-right corner, move somewhere
266// else and retry.
267// * if cursor coordinate changed in only one dimension, we were either on the
268// right side, or along the top row, but not both. determine which one, and
269// determine whether we're inverted.
270// * if both dimensions changed, determine whether we're inverted by checking
271// the change. the row ought have decreased; the column ought have increased.
272// * move back to initial position / pop cursor position
273static int
274detect_cursor_inversion(ncdirect* n, const char* u7, int rows, int cols, int* y, int* x){
275 if(rows <= 1 || cols <= 1){ // FIXME can this be made to work in 1 dimension?
276 return -1;
277 }
278 if(cursor_yx_get(n->tcache.ttyfd, u7, y, x)){
279 return -1;
280 }
281 // do not use normal ncdirect_cursor_*() commands, because those go to ttyfp
282 // instead of tcache.ttyfd. since we always talk directly to the terminal, we need
283 // to move the cursor directly via the terminal.
284 // FIXME since we're always moving 1, we could also just use cuu1 etc (which
285 // i believe to be the only form implemented by Windows Terminal?...)
286 const char* cuu = get_escape(&n->tcache, ESCAPE_CUU);
287 const char* cuf = get_escape(&n->tcache, ESCAPE_CUF);
288 const char* cub = get_escape(&n->tcache, ESCAPE_CUB);
289 // FIXME do we want to use cud here, or \v like above?
290 const char* cud = get_escape(&n->tcache, ESCAPE_CUD);
291 if(!cud || !cub || !cuf || !cuu){
292 return -1;
293 }
294 int movex;
295 int movey;
296 if(*x == cols && *y == 1){
297 if(tty_emit(tiparm(cud, 1), n->tcache.ttyfd)){
298 return -1;
299 }
300 if(tty_emit(tiparm(cub, 1), n->tcache.ttyfd)){
301 return -1;
302 }
303 movex = 1;
304 movey = -1;
305 }else{
306 if(tty_emit(tiparm(cuu, 1), n->tcache.ttyfd)){
307 return -1;
308 }
309 if(tty_emit(tiparm(cuf, 1), n->tcache.ttyfd)){
310 return -1;
311 }
312 movex = -1;
313 movey = 1;
314 }
315 int newy, newx;
316 if(cursor_yx_get(n->tcache.ttyfd, u7, &newy, &newx)){
317 return -1;
318 }
319 if(*x == cols && *y == 1){ // need to swap values, since we moved opposite
320 *x = newx;
321 newx = cols;
322 *y = newy;
323 newy = 1;
324 }
325 if(tty_emit(tiparm(movex == 1 ? cuf : cub, 1), n->tcache.ttyfd)){
326 return -1;
327 }
328 if(tty_emit(tiparm(movey == 1 ? cud : cuu, 1), n->tcache.ttyfd)){
329 return -1;
330 }
331 if(*y == newy && *x == newx){
332 return -1; // hopelessly broken
333 }else if(*x == newx){
334 // we only changed one, supposedly the number of rows. if we were on the
335 // top row before, the reply is inverted.
336 if(*y == 0){
337 n->tcache.inverted_cursor = true;
338 }
339 }else if(*y == newy){
340 // we only changed one, supposedly the number of columns. if we were on the
341 // rightmost column before, the reply is inverted.
342 if(*x == cols){
343 n->tcache.inverted_cursor = true;
344 }
345 }else{
346 // the row ought have decreased, and the column ought have increased. if it
347 // went the other way, the reply is inverted.
348 if(newy > *y && newx < *x){
349 n->tcache.inverted_cursor = true;
350 }
351 }
352 n->tcache.detected_cursor_inversion = true;
353 return 0;
354}
355
356static int
357detect_cursor_inversion_wrapper(ncdirect* n, const char* u7, int* y, int* x){
358 // if we're not on a real terminal, there's no point in running this
359 if(n->tcache.ttyfd < 0){
360 return 0;
361 }
362 const int toty = ncdirect_dim_y(n);
363 const int totx = ncdirect_dim_x(n);
364 // there's an argument to be made that this ought be wrapped in sc/rc
365 // (push/pop cursor), rather than undoing itself. problem is, some
366 // terminals lack sc/rc (they need cursor moves to run the detection
367 // algorithm in the first place), and our versions go to ttyfp instead
368 // of ttyfd, as needed by cursor interrogation.
369 return detect_cursor_inversion(n, u7, toty, totx, y, x);
370}
371*/
372
373// no terminfo capability for this. dangerous--it involves writing controls to
374// the terminal, and then reading a response.
375int ncdirect_cursor_yx(ncdirect* n, unsigned* y, unsigned* x){
376 // this is only meaningful for real terminals
377 if(n->tcache.ttyfd < 0){
378 return -1;
379 }
380 const char* u7 = get_escape(&n->tcache, ESCAPE_U7);
381 if(u7 == NULL){
382 fprintf(stderr, "Terminal doesn't support cursor reporting\n");
383 return -1;
384 }
385 unsigned yval, xval;
386 if(!y){
387 y = &yval;
388 }
389 if(!x){
390 x = &xval;
391 }
392 return cursor_yx_get(n, u7, y, x);
393}
394
396 const char* sc = get_escape(&n->tcache, ESCAPE_SC);
397 if(sc){
398 return term_emit(sc, n->ttyfp, false);
399 }
400 return -1;
401}
402
404 const char* rc = get_escape(&n->tcache, ESCAPE_RC);
405 if(rc){
406 return term_emit(rc, n->ttyfp, false);
407 }
408 return -1;
409}
410
411static inline int
412ncdirect_align(struct ncdirect* n, ncalign_e align, unsigned c){
413 if(align == NCALIGN_LEFT){
414 return 0;
415 }
416 unsigned cols = ncdirect_dim_x(n);
417 if(c > cols){
418 return 0;
419 }
420 if(align == NCALIGN_CENTER){
421 return (cols - c) / 2;
422 }else if(align == NCALIGN_RIGHT){
423 return cols - c;
424 }
425 return INT_MAX;
426}
427
428// y is an out-only param, indicating the location where drawing started
429static int
430ncdirect_dump_sprixel(ncdirect* n, const ncplane* np, int xoff, unsigned* y, fbuf* f){
431 unsigned dimy, dimx;
432 ncplane_dim_yx(np, &dimy, &dimx);
433 const unsigned toty = ncdirect_dim_y(n);
434 // flush our FILE*, as we're about to use UNIX I/O (since we can't rely on
435 // stdio to transfer large amounts at once).
436 if(ncdirect_flush(n)){
437 return -1;
438 }
439 if(ncdirect_cursor_yx(n, y, NULL)){
440 return -1;
441 }
442 if(toty - dimy < *y){
443 int scrolls = *y - 1;
444 if(toty <= dimy){
445 *y = 0;
446 }else{
447 *y = toty - dimy;
448 }
449 scrolls -= *y;
450 // perform our scrolling outside of the fbuf framework, as we need it
451 // to happen immediately for fbcon
452 if(ncdirect_cursor_move_yx(n, *y, xoff)){
453 return -1;
454 }
455 if(emit_scrolls(&n->tcache, scrolls, f) < 0){
456 return -1;
457 }
458 }
459 if(sprite_draw(&n->tcache, NULL, np->sprite, f, *y, xoff) < 0){
460 return -1;
461 }
462 if(sprite_commit(&n->tcache, f, np->sprite, true)){
463 return -1;
464 }
465 return 0;
466}
467
468static int
469ncdirect_set_bg_default_f(ncdirect* nc, fbuf* f){
470 if(ncdirect_bg_default_p(nc)){
471 return 0;
472 }
473 const char* esc;
474 if((esc = get_escape(&nc->tcache, ESCAPE_BGOP)) != NULL){
475 if(fbuf_emit(f, esc) < 0){
476 return -1;
477 }
478 }else if((esc = get_escape(&nc->tcache, ESCAPE_OP)) != NULL){
479 if(fbuf_emit(f, esc) < 0){
480 return -1;
481 }
482 if(!ncdirect_fg_default_p(nc)){
483 if(ncdirect_set_fg_rgb_f(nc, ncchannels_fg_rgb(nc->channels), f)){
484 return -1;
485 }
486 }
487 }
488 ncchannels_set_bg_default(&nc->channels);
489 return 0;
490}
491
492static int
493ncdirect_set_fg_default_f(ncdirect* nc, fbuf* f){
494 if(ncdirect_fg_default_p(nc)){
495 return 0;
496 }
497 const char* esc;
498 if((esc = get_escape(&nc->tcache, ESCAPE_FGOP)) != NULL){
499 if(fbuf_emit(f, esc) < 0){
500 return -1;
501 }
502 }else if((esc = get_escape(&nc->tcache, ESCAPE_OP)) != NULL){
503 if(fbuf_emit(f, esc) < 0){
504 return -1;
505 }
506 if(!ncdirect_bg_default_p(nc)){
507 if(ncdirect_set_bg_rgb_f(nc, ncchannels_bg_rgb(nc->channels), f)){
508 return -1;
509 }
510 }
511 }
512 ncchannels_set_fg_default(&nc->channels);
513 return 0;
514}
515
516static int
517ncdirect_dump_cellplane(ncdirect* n, const ncplane* np, fbuf* f, int xoff){
518 unsigned dimy, dimx;
519 ncplane_dim_yx(np, &dimy, &dimx);
520 const unsigned toty = ncdirect_dim_y(n);
521 // save the existing style and colors
522 const bool fgdefault = ncdirect_fg_default_p(n);
523 const bool bgdefault = ncdirect_bg_default_p(n);
524 const uint32_t fgrgb = ncchannels_fg_rgb(n->channels);
525 const uint32_t bgrgb = ncchannels_bg_rgb(n->channels);
526 for(unsigned y = 0 ; y < dimy ; ++y){
527 for(unsigned x = 0 ; x < dimx ; ++x){
528 uint16_t stylemask;
529 uint64_t channels;
530 char* egc = ncplane_at_yx(np, y, x, &stylemask, &channels);
531 if(egc == NULL){
532 return -1;
533 }
534 if(ncchannels_fg_alpha(channels) == NCALPHA_TRANSPARENT){
535 ncdirect_set_fg_default_f(n, f);
536 }else{
537 ncdirect_set_fg_rgb_f(n, ncchannels_fg_rgb(channels), f);
538 }
539 if(ncchannels_bg_alpha(channels) == NCALPHA_TRANSPARENT){
540 ncdirect_set_bg_default_f(n, f);
541 }else{
542 ncdirect_set_bg_rgb_f(n, ncchannels_bg_rgb(channels), f);
543 }
544//fprintf(stderr, "%03d/%03d [%s] (%03dx%03d)\n", y, x, egc, dimy, dimx);
545 size_t egclen = strlen(egc);
546 if(fbuf_putn(f, egclen == 0 ? " " : egc, egclen == 0 ? 1 : egclen) < 0){
547 free(egc);
548 return -1;
549 }
550 free(egc);
551 }
552 // yes, we want to reset colors and emit an explicit new line following
553 // each line of output; this is necessary if our output is lifted out and
554 // used in something e.g. paste(1).
555 // FIXME replace with a SGR clear
556 ncdirect_set_fg_default_f(n, f);
557 ncdirect_set_bg_default_f(n, f);
558 if(fbuf_printf(f, "\n%*.*s", xoff, xoff, "") < 0){
559 return -1;
560 }
561 if(y == toty){
562 if(ncdirect_cursor_down_f(n, 1, f)){
563 return -1;
564 }
565 }
566 }
567 // restore the previous colors
568 if(fgdefault){
569 ncdirect_set_fg_default_f(n, f);
570 }else{
571 ncdirect_set_fg_rgb_f(n, fgrgb, f);
572 }
573 if(bgdefault){
574 ncdirect_set_bg_default_f(n, f);
575 }else{
576 ncdirect_set_bg_rgb_f(n, bgrgb, f);
577 }
578 return 0;
579}
580
581static int
582ncdirect_dump_plane(ncdirect* n, const ncplane* np, int xoff){
583//fprintf(stderr, "rasterizing %dx%d+%d\n", dimy, dimx, xoff);
584 if(xoff){
585 if(ncdirect_cursor_move_yx(n, -1, xoff)){
586 return -1;
587 }
588 }
589 fbuf f;
590 if(fbuf_init(&f)){
591 return -1;
592 }
593 if(np->sprite){
594 unsigned y;
595 if(ncdirect_dump_sprixel(n, np, xoff, &y, &f)){
596 fbuf_free(&f);
597 return -1;
598 }
599 if(fbuf_finalize(&f, n->ttyfp)){
600 return -1;
601 }
602 if(n->tcache.pixel_draw_late){
603 if(n->tcache.pixel_draw_late(&n->tcache, np->sprite, y, xoff) < 0){
604 return -1;
605 }
606 }
607 int targy = y + ncplane_dim_y(np);
608 const int toty = ncdirect_dim_y(n);
609 if(targy > toty){
610 targy = toty;
611 }
612 if(ncdirect_cursor_move_yx(n, targy, xoff)){
613 return -1;
614 }
615 }else{
616 if(ncdirect_dump_cellplane(n, np, &f, xoff)){
617 fbuf_free(&f);
618 return -1;
619 }
620 if(fbuf_finalize(&f, n->ttyfp)){
621 return -1;
622 }
623 }
624 return 0;
625}
626
628 int lenx = ncplane_dim_x(ncdv);
629 int xoff = ncdirect_align(n, align, lenx);
630 int r = ncdirect_dump_plane(n, ncdv, xoff);
631 free_plane(ncdv);
632 return r;
633}
634
635static ncdirectv*
636ncdirect_render_visual(ncdirect* n, ncvisual* ncv,
637 const struct ncvisual_options* vopts){
638 struct ncvisual_options defvopts = {0};
639 if(!vopts){
640 vopts = &defvopts;
641 }
642//fprintf(stderr, "OUR DATA: %p rows/cols: %d/%d outsize: %d/%d %d/%d\n", ncv->data, ncv->pixy, ncv->pixx, dimy, dimx, ymax, xmax);
643//fprintf(stderr, "render %d/%d to scaling: %d\n", ncv->pixy, ncv->pixx, vopts->scaling);
644 const struct blitset* bset = rgba_blitter_low(&n->tcache, vopts->scaling,
646 vopts->blitter);
647 if(!bset){
648 return NULL;
649 }
650 unsigned ymax = vopts->leny / bset->height;
651 unsigned xmax = vopts->lenx / bset->width;
652 unsigned dimy = vopts->leny > 0 ? ymax : ncdirect_dim_y(n);
653 unsigned dimx = vopts->lenx > 0 ? xmax : ncdirect_dim_x(n);
654 unsigned disprows, dispcols, outy;
655 if(vopts->scaling != NCSCALE_NONE && vopts->scaling != NCSCALE_NONE_HIRES){
656 if(bset->geom != NCBLIT_PIXEL){
657 dispcols = dimx * encoding_x_scale(&n->tcache, bset);
658 disprows = dimy * encoding_y_scale(&n->tcache, bset) - 1;
659 outy = disprows;
660 }else{
661 dispcols = dimx * n->tcache.cellpxx;
662 disprows = dimy * n->tcache.cellpxy;
663 clamp_to_sixelmax(&n->tcache, &disprows, &dispcols, &outy, vopts->scaling);
664 }
665 if(vopts->scaling == NCSCALE_SCALE || vopts->scaling == NCSCALE_SCALE_HIRES){
666 scale_visual(ncv, &disprows, &dispcols);
667 outy = disprows;
668 if(bset->geom == NCBLIT_PIXEL){
669 clamp_to_sixelmax(&n->tcache, &disprows, &dispcols, &outy, vopts->scaling);
670 }
671 }
672 }else{
673 disprows = ncv->pixy;
674 dispcols = ncv->pixx;
675 if(bset->geom == NCBLIT_PIXEL){
676 clamp_to_sixelmax(&n->tcache, &disprows, &dispcols, &outy, vopts->scaling);
677 }else{
678 outy = disprows;
679 }
680 }
681 if(bset->geom == NCBLIT_PIXEL){
682 while((outy + n->tcache.cellpxy - 1) / n->tcache.cellpxy > dimy){
683 outy -= n->tcache.sprixel_scale_height;
684 disprows = outy;
685 }
686 }
687//fprintf(stderr, "max: %d/%d out: %d/%d\n", ymax, xmax, outy, dispcols);
688//fprintf(stderr, "render: %d/%d stride %u %p\n", ncv->pixy, ncv->pixx, ncv->rowstride, ncv->data);
689 ncplane_options nopts = {
690 .y = 0,
691 .x = 0,
692 .rows = outy / encoding_y_scale(&n->tcache, bset),
693 .cols = dispcols / encoding_x_scale(&n->tcache, bset),
694 .userptr = NULL,
695 .name = "fake",
696 .resizecb = NULL,
697 .flags = 0,
698 };
699 if(bset->geom == NCBLIT_PIXEL){
700 nopts.rows = outy / n->tcache.cellpxy + !!(outy % n->tcache.cellpxy);
701 nopts.cols = dispcols / n->tcache.cellpxx + !!(dispcols % n->tcache.cellpxx);
702 }
703 if(ymax && nopts.rows > ymax){
704 nopts.rows = ymax;
705 }
706 if(xmax && nopts.cols > xmax){
707 nopts.cols = xmax;
708 }
709 struct ncplane* ncdv = ncplane_new_internal(NULL, NULL, &nopts);
710 if(!ncdv){
711 return NULL;
712 }
713 if((ncdv->tam = create_tam(ncplane_dim_y(ncdv), ncplane_dim_x(ncdv))) == NULL){
714 free_plane(ncdv);
715 return NULL;
716 }
717 blitterargs bargs = {0};
718 bargs.flags = vopts->flags;
720 bargs.transcolor = vopts->transcolor | 0x1000000ull;
721 }
722 if(bset->geom == NCBLIT_PIXEL){
723 bargs.u.pixel.colorregs = n->tcache.color_registers;
724 bargs.u.pixel.cellpxy = n->tcache.cellpxy;
725 bargs.u.pixel.cellpxx = n->tcache.cellpxx;
726 if((bargs.u.pixel.spx = sprixel_alloc(ncdv, nopts.rows, nopts.cols)) == NULL){
727 free_plane(ncdv);
728 return NULL;
729 }
730 ncdv->sprite = bargs.u.pixel.spx;
731 }
732 if(ncvisual_blit_internal(ncv, disprows, dispcols, ncdv, bset, &bargs)){
733 free_plane(ncdv);
734 return NULL;
735 }
736 return ncdv;
737}
738
740 ncblitter_e blitfxn, ncscale_e scale,
741 int ymax, int xmax){
742 if(ymax < 0 || xmax < 0){
743 return NULL;
744 }
746 if(ncv == NULL){
747 return NULL;
748 }
749 struct ncvisual_options vopts = {0};
750 const struct blitset* bset = rgba_blitter_low(&n->tcache, scale, true, blitfxn);
751 if(!bset){
752 return NULL;
753 }
754 vopts.blitter = bset->geom;
756 vopts.scaling = scale;
757 if(ymax > 0){
758 if((vopts.leny = ymax * bset->height) > ncv->pixy){
759 vopts.leny = 0;
760 }
761 }
762 if(xmax > 0){
763 if((vopts.lenx = xmax * bset->width) > ncv->pixx){
764 vopts.lenx = 0;
765 }
766 }
769 return v;
770}
771
772int ncdirect_render_image(ncdirect* n, const char* file, ncalign_e align,
773 ncblitter_e blitfxn, ncscale_e scale){
774 ncdirectv* faken = ncdirect_render_frame(n, file, blitfxn, scale, 0, 0);
775 if(!faken){
776 return -1;
777 }
778 return ncdirect_raster_frame(n, faken, align);
779}
780
782 const char* setaf = get_escape(&nc->tcache, ESCAPE_SETAF);
783 if(!setaf){
784 return -1;
785 }
786 if(ncchannels_set_fg_palindex(&nc->channels, pidx) < 0){
787 return -1;
788 }
789 return term_emit(tiparm(setaf, pidx), nc->ttyfp, false);
790}
791
793 const char* setab = get_escape(&nc->tcache, ESCAPE_SETAB);
794 if(!setab){
795 return -1;
796 }
797 if(ncchannels_set_bg_palindex(&nc->channels, pidx) < 0){
798 return -1;
799 }
800 return term_emit(tiparm(setab, pidx), nc->ttyfp, false);
801}
802
803int ncdirect_vprintf_aligned(ncdirect* n, int y, ncalign_e align, const char* fmt, va_list ap){
804 char* r = ncplane_vprintf_prep(fmt, ap);
805 if(r == NULL){
806 return -1;
807 }
808 const int len = ncstrwidth(r, NULL, NULL);
809 if(len < 0){
810 free(r);
811 return -1;
812 }
813 const int x = ncdirect_align(n, align, len);
815 free(r);
816 return -1;
817 }
818 int ret = puts(r);
819 free(r);
820 if(ret == EOF){
821 return -1;
822 }
823 return ret;
824}
825
826int ncdirect_printf_aligned(ncdirect* n, int y, ncalign_e align, const char* fmt, ...){
827 va_list va;
828 va_start(va, fmt);
829 int ret = ncdirect_vprintf_aligned(n, y, align, fmt, va);
830 va_end(va);
831 return ret;
832}
833
834static int
835ncdirect_stop_minimal(void* vnc, void** altstack, int errret){
836 ncdirect* nc = vnc;
837 int ret = drop_signals(nc, altstack);
838 fbuf f = {0};
839 if(fbuf_init_small(&f) == 0){
840 ret |= reset_term_attributes(&nc->tcache, &f);
841 ret |= fbuf_finalize(&f, stdout);
842 }
843 if(nc->tcache.ttyfd >= 0){
845 if(nc->tcache.kbdlevel){
846 if(tty_emit(KKEYBOARD_POP, nc->tcache.ttyfd)){
847 ret = -1;
848 }
849 }else{
850 if(tty_emit(XTMODKEYSUNDO, nc->tcache.ttyfd)){
851 ret = -1;
852 }
853 }
854 }
855 const char* cnorm = get_escape(&nc->tcache, ESCAPE_CNORM);
856 if(cnorm && tty_emit(cnorm, nc->tcache.ttyfd)){
857 ret = -1;
858 }
859 ret |= tcsetattr(nc->tcache.ttyfd, TCSANOW, nc->tcache.tpreserved);
860 }
861 ret |= ncdirect_flush(nc);
862#ifndef __MINGW32__
863 del_curterm(cur_term);
864#endif
865 if(errret){
866 ret = errret;
867 }
868 return ret;
869}
870
871ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
872 if(outfp == NULL){
873 outfp = stdout;
874 }
875 if(flags > (NCDIRECT_OPTION_DRAIN_INPUT << 1)){ // allow them through with warning
876 logwarn("Passed unsupported flags 0x%016" PRIx64 "\n", flags);
877 }
878 if(termtype){
879 if(putenv_term(termtype)){
880 return NULL;
881 }
882 }
883 ncdirect* ret = malloc(sizeof(ncdirect));
884 if(ret == NULL){
885 return ret;
886 }
887 memset(ret, 0, sizeof(*ret));
888 if(pthread_mutex_init(&ret->stats.lock, NULL)){
889 free(ret);
890 return NULL;
891 }
892 ret->flags = flags;
893 ret->ttyfp = outfp;
895 init_lang();
896 }
897 const char* encoding = nl_langinfo(CODESET);
898 bool utf8 = false;
899 if(encoding && encoding_is_utf8(encoding)){
900 utf8 = true;
902 }
904 true, ncdirect_stop_minimal)){
905 pthread_mutex_destroy(&ret->stats.lock);
906 free(ret);
907 return NULL;
908 }
909 // don't set the loglevel until we've locked in signal handling, lest we
910 // change the loglevel out from under a running instance.
913 }else if(flags & NCDIRECT_OPTION_VERBOSE){
915 }else{
917 }
919 int cursor_y = -1;
920 int cursor_x = -1;
921 if(interrogate_terminfo(&ret->tcache, ret->ttyfp, utf8, 1,
923 0, &cursor_y, &cursor_x, &ret->stats, 0, 0, 0, 0,
925 goto err;
926 }
927 if(cursor_y >= 0){
928 // the u7 led the queries so that we would get a cursor position
929 // unaffected by any query spill (unconsumed control sequences). move
930 // us back to that location, in case there was any such spillage.
931 if(ncdirect_cursor_move_yx(ret, cursor_y, cursor_x)){
933 goto err;
934 }
935 }
938 goto err;
939 }
940 unsigned cgeo, pgeo; // both are don't-cares
941 update_term_dimensions(NULL, NULL, &ret->tcache, 0, &cgeo, &pgeo);
942 ncdirect_set_styles(ret, 0);
943 return ret;
944
945err:{
946 void* altstack;
947 if(ret->tcache.ttyfd >= 0){
948 (void)tcsetattr(ret->tcache.ttyfd, TCSANOW, ret->tcache.tpreserved);
949 }
950 drop_signals(ret, &altstack);
951 pthread_mutex_destroy(&ret->stats.lock);
952 free(ret);
953 }
954 return NULL;
955}
956
958 int ret = 0;
959 if(nc){
960 void* altstack;
961 ret |= ncdirect_stop_minimal(nc, &altstack, 0);
963 if(nc->tcache.ttyfd >= 0){
964 ret |= close(nc->tcache.ttyfd);
965 }
966 pthread_mutex_destroy(&nc->stats.lock);
967 free(nc);
968 free(altstack);
969 }
970 return ret;
971}
972
973// our new input system is fundamentally incompatible with libreadline, so we
974// have to fake it ourselves. at least it saves us the dependency.
975//
976// if NCDIRECT_OPTION_INHIBIT_CBREAK is in play, we're not going to get the
977// text until cooked mode has had its way with it, and we are essentially
978// unable to do anything clever. text will be echoed, and there will be no
979// line-editing keybindings, save any implemented in the line discipline.
980//
981// otherwise, we control echo. whenever we emit output, get our position. if
982// we've changed line, assume the prompt has scrolled up, and account for
983// that. we return to the prompt, clear any affected lines, and reprint what
984// we have.
985char* ncdirect_readline(ncdirect* n, const char* prompt){
986 const char* u7 = get_escape(&n->tcache, ESCAPE_U7);
987 if(!u7){ // we probably *can*, but it would be a pita; screw it
988 logerror("can't readline without u7");
989 return NULL;
990 }
991 if(n->eof){
992 logerror("already got EOF");
993 return NULL;
994 }
995 if(fprintf(n->ttyfp, "%s", prompt) < 0){
996 return NULL;
997 }
998 unsigned dimx = ncdirect_dim_x(n);
999 if(dimx == 0){
1000 return NULL;
1001 }
1002 // FIXME what if we're reading from redirected input, not a terminal?
1003 unsigned y, xstart;
1004 if(cursor_yx_get(n, u7, &y, &xstart)){
1005 return NULL;
1006 }
1007 int tline = y;
1008 unsigned bline = y;
1009 wchar_t* str;
1010 int wspace = BUFSIZ / sizeof(*str);
1011 if((str = malloc(wspace * sizeof(*str))) == NULL){
1012 return NULL;
1013 }
1014 int wpos = 0; // cursor location (single-dimensional)
1015 int wused = 0; // number used
1016 str[wused++] = L'\0';
1017 ncinput ni;
1018 uint32_t id;
1019 unsigned oldx = xstart;
1020 while((id = ncdirect_get_blocking(n, &ni)) != (uint32_t)-1){
1021 if(ni.evtype == NCTYPE_RELEASE){
1022 continue;
1023 }
1024 if(id == NCKEY_EOF || id == NCKEY_ENTER || (ncinput_ctrl_p(&ni) && id == 'D')){
1025 if(id == NCKEY_ENTER){
1026 if(fputc('\n', n->ttyfp) < 0){
1027 free(str);
1028 return NULL;
1029 }
1030 }else{
1031 n->eof = 1;
1032 if(wused == 1){ // NCKEY_EOF without input returns NULL
1033 free(str);
1034 return NULL;
1035 }
1036 }
1037 char* ustr = ncwcsrtombs(str);
1038 free(str);
1039 return ustr;
1040 }else if(id == NCKEY_BACKSPACE){
1041 if(wused > 1){
1042 str[wused - 2] = L'\0';
1043 --wused;
1044 }
1045 --wpos;
1046 }else if(id == NCKEY_LEFT){
1047 --wpos;
1048 }else if(id == NCKEY_RIGHT){
1049 ++wpos;
1050 }else if(id == NCKEY_UP){
1051 wpos -= dimx;
1052 }else if(id == NCKEY_DOWN){
1053 wpos += dimx;
1054 }else if(id == 'A' && ncinput_ctrl_p(&ni)){
1055 wpos = 1;
1056 }else if(id == 'E' && ncinput_ctrl_p(&ni)){
1057 wpos = wused - 1;
1058 }else if(nckey_synthesized_p(ni.id)){
1059 continue;
1060 }else{
1061 if(wspace - 1 < wused){
1062 wspace += BUFSIZ;
1063 wchar_t* tmp = realloc(str, wspace * sizeof(*str));
1064 if(tmp == NULL){
1065 free(str);
1066 return NULL;
1067 }
1068 str = tmp;
1069 }
1070 if(wpos < wused - 1){
1071 memmove(str + wpos + 1, str + wpos, (wused - wpos) * sizeof(*str));
1072 str[wpos] = id;
1073 ++wused;
1074 ++wpos;
1075 }else{
1076 str[wused - 1] = id;
1077 ++wused;
1078 ++wpos;
1079 str[wused - 1] = L'\0';
1080 }
1081 // FIXME check modifiers
1082 unsigned x;
1083 if(cursor_yx_get(n, u7, &y, &x)){
1084 break;
1085 }
1086 if(x < oldx){
1087 oldx = x;
1088 if(--tline < 0){
1089 tline = 0;
1090 }
1091 }
1092 if(y > bline){
1093 bline = y;
1094 }
1095 }
1096 if(wpos < 0){
1097 wpos = 0;
1098 }else if(wpos > wused - 1){
1099 wpos = wused - 1;
1100 }
1101 // clear to end of line(s)
1102 const char* el = get_escape(&n->tcache, ESCAPE_EL);
1103 for(int i = bline ; i >= tline ; --i){
1104 if(ncdirect_cursor_move_yx(n, i, i > tline ? 0 : xstart)){
1105 break;
1106 }
1107 if(term_emit(el, n->ttyfp, false)){
1108 break;
1109 }
1110 }
1111 if(fprintf(n->ttyfp, "%ls", str) < 0){
1112 break;
1113 }
1114 if(wpos != wused){
1115 int linear = xstart + wpos;
1116 int ylin = linear / dimx;
1117 int xlin = linear % dimx;
1118 if(ncdirect_cursor_move_yx(n, tline + ylin, xlin)){
1119 break;
1120 }
1121 }
1122 if(fflush(n->ttyfp)){
1123 break;
1124 }
1125 }
1126 free(str);
1127 return NULL;
1128}
1129
1130static inline int
1131ncdirect_style_emit(ncdirect* n, unsigned stylebits, fbuf* f){
1132 unsigned normalized = 0;
1133 int r = coerce_styles(f, &n->tcache, &n->stylemask, stylebits, &normalized);
1134 // sgr0 resets colors, so set them back up if not defaults and it was used
1135 if(normalized){
1136 // emitting an sgr resets colors. if we want to be default, that's no
1137 // problem, and our channels remain correct. otherwise, clear our
1138 // channel, and set them back up.
1139 if(!ncdirect_fg_default_p(n)){
1140 if(!ncdirect_fg_palindex_p(n)){
1141 uint32_t fg = ncchannels_fg_rgb(n->channels);
1142 ncchannels_set_fg_default(&n->channels);
1143 r |= ncdirect_set_fg_rgb(n, fg);
1144 }else{ // palette-indexed
1145 uint32_t fg = ncchannels_fg_palindex(n->channels);
1146 ncchannels_set_fg_default(&n->channels);
1148 }
1149 }
1150 if(!ncdirect_bg_default_p(n)){
1151 if(!ncdirect_bg_palindex_p(n)){
1152 uint32_t bg = ncchannels_bg_rgb(n->channels);
1153 ncchannels_set_bg_default(&n->channels);
1154 r |= ncdirect_set_bg_rgb(n, bg);
1155 }else{ // palette-indexed
1156 uint32_t bg = ncchannels_bg_palindex(n->channels);
1157 ncchannels_set_bg_default(&n->channels);
1159 }
1160 }
1161 }
1162 return r;
1163}
1164
1165int ncdirect_on_styles(ncdirect* n, unsigned stylebits){
1166 if((stylebits & n->tcache.supported_styles) < stylebits){ // unsupported styles
1167 return -1;
1168 }
1169 uint32_t stylemask = n->stylemask | stylebits;
1170 fbuf f = {0};
1171 if(fbuf_init_small(&f)){
1172 return -1;
1173 }
1174 if(ncdirect_style_emit(n, stylemask, &f)){
1175 fbuf_free(&f);
1176 return -1;
1177 }
1178 if(fbuf_finalize(&f, n->ttyfp)){
1179 return -1;
1180 }
1181 return 0;
1182}
1183
1184uint16_t ncdirect_styles(const ncdirect* n){
1185 return n->stylemask;
1186}
1187
1188// turn off any specified stylebits
1189int ncdirect_off_styles(ncdirect* n, unsigned stylebits){
1190 uint32_t stylemask = n->stylemask & ~stylebits;
1191 fbuf f = {0};
1192 if(fbuf_init_small(&f)){
1193 return -1;
1194 }
1195 if(ncdirect_style_emit(n, stylemask, &f)){
1196 fbuf_free(&f);
1197 return -1;
1198 }
1199 if(fbuf_finalize(&f, n->ttyfp)){
1200 return -1;
1201 }
1202 return 0;
1203}
1204
1205// set the current stylebits to exactly those provided
1206int ncdirect_set_styles(ncdirect* n, unsigned stylebits){
1207 if((stylebits & n->tcache.supported_styles) < stylebits){ // unsupported styles
1208 return -1;
1209 }
1210 uint32_t stylemask = stylebits;
1211 fbuf f = {0};
1212 if(fbuf_init_small(&f)){
1213 return -1;
1214 }
1215 if(ncdirect_style_emit(n, stylemask, &f)){
1216 fbuf_free(&f);
1217 return -1;
1218 }
1219 if(fbuf_finalize(&f, n->ttyfp)){
1220 return -1;
1221 }
1222 return 0;
1223}
1224
1226 return ncdirect_capabilities(nc)->colors;
1227}
1228
1230 if(ncdirect_fg_default_p(nc)){
1231 return 0;
1232 }
1233 const char* esc;
1234 if((esc = get_escape(&nc->tcache, ESCAPE_FGOP)) != NULL){
1235 if(term_emit(esc, nc->ttyfp, false)){
1236 return -1;
1237 }
1238 }else if((esc = get_escape(&nc->tcache, ESCAPE_OP)) != NULL){
1239 if(term_emit(esc, nc->ttyfp, false)){
1240 return -1;
1241 }
1242 if(!ncdirect_bg_default_p(nc)){
1243 if(ncdirect_set_bg_rgb(nc, ncchannels_bg_rgb(nc->channels))){
1244 return -1;
1245 }
1246 }
1247 }
1248 ncchannels_set_fg_default(&nc->channels);
1249 return 0;
1250}
1251
1253 if(ncdirect_bg_default_p(nc)){
1254 return 0;
1255 }
1256 const char* esc;
1257 if((esc = get_escape(&nc->tcache, ESCAPE_BGOP)) != NULL){
1258 if(term_emit(esc, nc->ttyfp, false)){
1259 return -1;
1260 }
1261 }else if((esc = get_escape(&nc->tcache, ESCAPE_OP)) != NULL){
1262 if(term_emit(esc, nc->ttyfp, false)){
1263 return -1;
1264 }
1265 if(!ncdirect_fg_default_p(nc)){
1266 if(ncdirect_set_fg_rgb(nc, ncchannels_fg_rgb(nc->channels))){
1267 return -1;
1268 }
1269 }
1270 }
1271 ncchannels_set_bg_default(&nc->channels);
1272 return 0;
1273}
1274
1275int ncdirect_hline_interp(ncdirect* n, const char* egc, unsigned len,
1276 uint64_t c1, uint64_t c2){
1277 if(len == 0){
1278 logerror("passed zero length\n");
1279 return -1;
1280 }
1281 unsigned ur, ug, ub;
1282 int r1, g1, b1, r2, g2, b2;
1283 int br1, bg1, bb1, br2, bg2, bb2;
1284 ncchannels_fg_rgb8(c1, &ur, &ug, &ub);
1285 r1 = ur; g1 = ug; b1 = ub;
1286 ncchannels_fg_rgb8(c2, &ur, &ug, &ub);
1287 r2 = ur; g2 = ug; b2 = ub;
1288 ncchannels_bg_rgb8(c1, &ur, &ug, &ub);
1289 br1 = ur; bg1 = ug; bb1 = ub;
1290 ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
1291 br2 = ur; bg2 = ug; bb2 = ub;
1292 int deltr = r2 - r1;
1293 int deltg = g2 - g1;
1294 int deltb = b2 - b1;
1295 int deltbr = br2 - br1;
1296 int deltbg = bg2 - bg1;
1297 int deltbb = bb2 - bb1;
1298 unsigned ret;
1299 bool fgdef = false, bgdef = false;
1300 if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
1302 return -1;
1303 }
1304 fgdef = true;
1305 }
1306 if(ncchannels_bg_default_p(c1) && ncchannels_bg_default_p(c2)){
1308 return -1;
1309 }
1310 bgdef = true;
1311 }
1312 for(ret = 0 ; ret < len ; ++ret){
1313 int r = (deltr * (int)ret) / (int)len + r1;
1314 int g = (deltg * (int)ret) / (int)len + g1;
1315 int b = (deltb * (int)ret) / (int)len + b1;
1316 int br = (deltbr * (int)ret) / (int)len + br1;
1317 int bg = (deltbg * (int)ret) / (int)len + bg1;
1318 int bb = (deltbb * (int)ret) / (int)len + bb1;
1319 if(!fgdef){
1320 ncdirect_set_fg_rgb8(n, r, g, b);
1321 }
1322 if(!bgdef){
1323 ncdirect_set_bg_rgb8(n, br, bg, bb);
1324 }
1325 if(fprintf(n->ttyfp, "%s", egc) < 0){
1326 logerror("error emitting egc [%s]\n", egc);
1327 return -1;
1328 }
1329 }
1330 return ret;
1331}
1332
1333int ncdirect_vline_interp(ncdirect* n, const char* egc, unsigned len,
1334 uint64_t c1, uint64_t c2){
1335 if(len == 0){
1336 logerror("passed zero length\n");
1337 return -1;
1338 }
1339 unsigned ur, ug, ub;
1340 int r1, g1, b1, r2, g2, b2;
1341 int br1, bg1, bb1, br2, bg2, bb2;
1342 ncchannels_fg_rgb8(c1, &ur, &ug, &ub);
1343 r1 = ur; g1 = ug; b1 = ub;
1344 ncchannels_fg_rgb8(c2, &ur, &ug, &ub);
1345 r2 = ur; g2 = ug; b2 = ub;
1346 ncchannels_bg_rgb8(c1, &ur, &ug, &ub);
1347 br1 = ur; bg1 = ug; bb1 = ub;
1348 ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
1349 br2 = ur; bg2 = ug; bb2 = ub;
1350 int deltr = (r2 - r1) / ((int)len + 1);
1351 int deltg = (g2 - g1) / ((int)len + 1);
1352 int deltb = (b2 - b1) / ((int)len + 1);
1353 int deltbr = (br2 - br1) / ((int)len + 1);
1354 int deltbg = (bg2 - bg1) / ((int)len + 1);
1355 int deltbb = (bb2 - bb1) / ((int)len + 1);
1356 unsigned ret;
1357 bool fgdef = false, bgdef = false;
1358 if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
1360 return -1;
1361 }
1362 fgdef = true;
1363 }
1364 if(ncchannels_bg_default_p(c1) && ncchannels_bg_default_p(c2)){
1366 return -1;
1367 }
1368 bgdef = true;
1369 }
1370 for(ret = 0 ; ret < len ; ++ret){
1371 r1 += deltr;
1372 g1 += deltg;
1373 b1 += deltb;
1374 br1 += deltbr;
1375 bg1 += deltbg;
1376 bb1 += deltbb;
1377 uint64_t channels = 0;
1378 if(!fgdef){
1379 ncchannels_set_fg_rgb8(&channels, r1, g1, b1);
1380 }
1381 if(!bgdef){
1382 ncchannels_set_bg_rgb8(&channels, br1, bg1, bb1);
1383 }
1384 if(ncdirect_putstr(n, channels, egc) == EOF){
1385 return -1;
1386 }
1387 if(len - ret > 1){
1389 return -1;
1390 }
1391 }
1392 }
1393 return ret;
1394}
1395
1396// wchars: wchar_t[6] mapping to UL, UR, BL, BR, HL, VL.
1397// they cannot be complex EGCs, but only a single wchar_t, alas.
1398int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur,
1399 uint64_t ll, uint64_t lr, const wchar_t* wchars,
1400 unsigned ylen, unsigned xlen, unsigned ctlword){
1401 if(xlen < 2 || ylen < 2){
1402 return -1;
1403 }
1404 char hl[MB_LEN_MAX + 1];
1405 char vl[MB_LEN_MAX + 1];
1406 unsigned edges;
1407 edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_LEFT);
1408 // FIXME rewrite all fprintfs as ncdirect_putstr()!
1409 if(edges >= box_corner_needs(ctlword)){
1410 if(activate_channels(n, ul)){
1411 return -1;
1412 }
1413 if(fprintf(n->ttyfp, "%lc", wchars[0]) < 0){
1414 logerror("error emitting %lc\n", wchars[0]);
1415 return -1;
1416 }
1417 }else{
1419 }
1420 mbstate_t ps = {0};
1421 size_t bytes;
1422 if((bytes = wcrtomb(hl, wchars[4], &ps)) == (size_t)-1){
1423 logerror("error converting %lc\n", wchars[4]);
1424 return -1;
1425 }
1426 hl[bytes] = '\0';
1427 memset(&ps, 0, sizeof(ps));
1428 if((bytes = wcrtomb(vl, wchars[5], &ps)) == (size_t)-1){
1429 logerror("error converting %lc\n", wchars[5]);
1430 return -1;
1431 }
1432 vl[bytes] = '\0';
1433 if(!(ctlword & NCBOXMASK_TOP)){ // draw top border, if called for
1434 if(xlen > 2){
1435 if(ncdirect_hline_interp(n, hl, xlen - 2, ul, ur) < 0){
1436 return -1;
1437 }
1438 }
1439 }else{
1441 }
1442 edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_RIGHT);
1443 if(edges >= box_corner_needs(ctlword)){
1444 if(activate_channels(n, ur)){
1445 return -1;
1446 }
1447 if(fprintf(n->ttyfp, "%lc", wchars[1]) < 0){
1448 return -1;
1449 }
1451 }else{
1453 }
1455 // middle rows (vertical lines)
1456 if(ylen > 2){
1457 if(!(ctlword & NCBOXMASK_LEFT)){
1458 if(ncdirect_vline_interp(n, vl, ylen - 2, ul, ll) < 0){
1459 return -1;
1460 }
1463 }else{
1465 }
1466 if(!(ctlword & NCBOXMASK_RIGHT)){
1467 if(ncdirect_vline_interp(n, vl, ylen - 2, ur, lr) < 0){
1468 return -1;
1469 }
1471 }else{
1473 }
1475 }
1476 // bottom line
1477 edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_LEFT);
1478 if(edges >= box_corner_needs(ctlword)){
1479 if(activate_channels(n, ll)){
1480 return -1;
1481 }
1482 if(fprintf(n->ttyfp, "%lc", wchars[2]) < 0){
1483 return -1;
1484 }
1485 }else{
1487 }
1488 if(!(ctlword & NCBOXMASK_BOTTOM)){
1489 if(xlen > 2){
1490 if(ncdirect_hline_interp(n, hl, xlen - 2, ll, lr) < 0){
1491 return -1;
1492 }
1493 }
1494 }else{
1496 }
1497 edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_RIGHT);
1498 if(edges >= box_corner_needs(ctlword)){
1499 if(activate_channels(n, lr)){
1500 return -1;
1501 }
1502 if(fprintf(n->ttyfp, "%lc", wchars[3]) < 0){
1503 return -1;
1504 }
1505 }
1506 return 0;
1507}
1508
1509int ncdirect_rounded_box(ncdirect* n, uint64_t ul, uint64_t ur,
1510 uint64_t ll, uint64_t lr,
1511 unsigned ylen, unsigned xlen, unsigned ctlword){
1512 return ncdirect_box(n, ul, ur, ll, lr, NCBOXROUNDW, ylen, xlen, ctlword);
1513}
1514
1515int ncdirect_double_box(ncdirect* n, uint64_t ul, uint64_t ur,
1516 uint64_t ll, uint64_t lr,
1517 unsigned ylen, unsigned xlen, unsigned ctlword){
1518 return ncdirect_box(n, ul, ur, ll, lr, NCBOXDOUBLEW, ylen, xlen, ctlword);
1519}
1520
1521// Is our encoding UTF-8? Requires LANG being set to a UTF8 locale.
1523 return n->tcache.caps.utf8;
1524}
1525
1527 return ncflush(nc->ttyfp);
1528}
1529
1531 if(n->tcache.pixel_draw || n->tcache.pixel_draw_late){
1532 return 1;
1533 }
1534 return 0;
1535}
1536
1537int ncdirect_stream(ncdirect* n, const char* filename, ncstreamcb streamer,
1538 struct ncvisual_options* vopts, void* curry){
1539 ncvisual* ncv = ncvisual_from_file(filename);
1540 if(ncv == NULL){
1541 return -1;
1542 }
1543 // starting position *after displaying one frame* so as to effect any
1544 // necessary scrolling.
1545 unsigned y = 0, x = 0;
1546 int lastid = -1;
1547 int thisid = -1;
1548 do{
1549 if(y > 0){
1550 if(x == ncdirect_dim_x(n)){
1551 x = 0;
1552 ++y;
1553 }
1554 ncdirect_cursor_up(n, y - 1);
1555 }
1556 if(x > 0){
1558 }
1559 ncdirectv* v = ncdirect_render_visual(n, ncv, vopts);
1560 if(v == NULL){
1562 return -1;
1563 }
1564 ncplane_dim_yx(v, &y, &x);
1565 if(v->sprite){
1566 thisid = v->sprite->id;
1567 }
1570 return -1;
1571 }
1572 if(lastid > -1){
1573 if(n->tcache.pixel_remove){
1574 fbuf f = {0};
1575 fbuf_init_small(&f);
1576 if(n->tcache.pixel_remove(lastid, &f)){
1577 fbuf_free(&f);
1579 return -1;
1580 }
1581 if(fbuf_finalize(&f, n->ttyfp) < 0){
1583 return -1;
1584 }
1585 }
1586 }
1587 streamer(ncv, vopts, NULL, curry);
1588 lastid = thisid;
1589 }while(ncvisual_decode(ncv) == 0);
1592 return 0;
1593}
1594
1596 const char* filename){
1597 return ncvisual_from_file(filename);
1598}
1599
1601 ncvisual_destroy(frame);
1602}
1603
1605 return ncdirect_render_visual(n, frame, vopts);
1606}
1607
1609 const struct ncvisual_options* vopts, ncvgeom* geom){
1610 const struct blitset* bset;
1611 unsigned disppxy, disppxx, outy, outx;
1612 int placey, placex;
1613 return ncvisual_geom_inner(&n->tcache, frame, vopts, geom, &bset,
1614 &disppxy, &disppxx, &outy, &outx,
1615 &placey, &placex);
1616}
1617
1619 return term_supported_styles(&nc->tcache);
1620}
1621
1623 return termdesc_longterm(&nc->tcache);
1624}
1625
1627 return &n->tcache.caps;
1628}
1629
1631 if(get_escape(&n->tcache, ESCAPE_U7) == NULL){
1632 return false;
1633 }
1634 if(n->tcache.ttyfd < 0){
1635 return false;
1636 }
1637 return true;
1638}
ncloglevel_e loglevel
Definition debug.c:3
int ncdirect_render_image(ncdirect *n, const char *file, ncalign_e align, ncblitter_e blitfxn, ncscale_e scale)
Definition direct.c:772
unsigned ncdirect_palette_size(const ncdirect *nc)
Definition direct.c:1225
uint16_t ncdirect_styles(const ncdirect *n)
Definition direct.c:1184
int ncdirect_rounded_box(ncdirect *n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, unsigned ylen, unsigned xlen, unsigned ctlword)
Definition direct.c:1509
int ncdirect_off_styles(ncdirect *n, unsigned stylebits)
Definition direct.c:1189
int ncdirect_set_fg_palindex(ncdirect *nc, int pidx)
Definition direct.c:781
int ncdirect_set_bg_palindex(ncdirect *nc, int pidx)
Definition direct.c:792
ncdirectv * ncdirect_render_frame(ncdirect *n, const char *file, ncblitter_e blitfxn, ncscale_e scale, int ymax, int xmax)
Definition direct.c:739
unsigned ncdirect_dim_x(ncdirect *nc)
Definition direct.c:148
int ncdirect_check_pixel_support(const ncdirect *n)
Definition direct.c:1530
ncdirect * ncdirect_core_init(const char *termtype, FILE *outfp, uint64_t flags)
Definition direct.c:871
int ncdirect_cursor_right(ncdirect *nc, int num)
Definition direct.c:98
int ncdirect_cursor_down(ncdirect *nc, int num)
Definition direct.c:117
int ncdirectf_geom(ncdirect *n, ncdirectf *frame, const struct ncvisual_options *vopts, ncvgeom *geom)
Definition direct.c:1608
void ncdirectf_free(ncdirectf *frame)
Definition direct.c:1600
int ncdirect_set_fg_default(ncdirect *nc)
Definition direct.c:1229
int ncdirect_putegc(ncdirect *nc, uint64_t channels, const char *utf8, int *sbytes)
Definition direct.c:49
bool ncdirect_canget_cursor(const ncdirect *n)
Definition direct.c:1630
ncdirectf * ncdirectf_from_file(ncdirect *n __attribute__((unused)), const char *filename)
Definition direct.c:1595
int ncdirect_set_styles(ncdirect *n, unsigned stylebits)
Definition direct.c:1206
uint16_t ncdirect_supported_styles(const ncdirect *nc)
Definition direct.c:1618
int ncdirect_hline_interp(ncdirect *n, const char *egc, unsigned len, uint64_t c1, uint64_t c2)
Definition direct.c:1275
int ncdirect_putstr(ncdirect *nc, uint64_t channels, const char *utf8)
Definition direct.c:42
unsigned ncdirect_dim_y(ncdirect *nc)
Definition direct.c:161
int ncdirect_cursor_up(ncdirect *nc, int num)
Definition direct.c:68
int ncdirect_cursor_yx(ncdirect *n, unsigned *y, unsigned *x)
Definition direct.c:375
int ncdirect_cursor_push(ncdirect *n)
Definition direct.c:395
ncdirectv * ncdirectf_render(ncdirect *n, ncdirectf *frame, const struct ncvisual_options *vopts)
Definition direct.c:1604
int ncdirect_cursor_disable(ncdirect *nc)
Definition direct.c:182
const nccapabilities * ncdirect_capabilities(const ncdirect *n)
Definition direct.c:1626
int ncdirect_set_bg_default(ncdirect *nc)
Definition direct.c:1252
int ncdirect_stop(ncdirect *nc)
Definition direct.c:957
char * ncdirect_readline(ncdirect *n, const char *prompt)
Definition direct.c:985
int ncdirect_vprintf_aligned(ncdirect *n, int y, ncalign_e align, const char *fmt, va_list ap)
Definition direct.c:803
int ncdirect_cursor_move_yx(ncdirect *n, int y, int x)
Definition direct.c:216
int ncdirect_clear(ncdirect *nc)
Definition direct.c:140
int ncdirect_cursor_enable(ncdirect *nc)
Definition direct.c:174
int ncdirect_cursor_left(ncdirect *nc, int num)
Definition direct.c:83
int ncdirect_flush(const ncdirect *nc)
Definition direct.c:1526
int ncdirect_printf_aligned(ncdirect *n, int y, ncalign_e align, const char *fmt,...)
Definition direct.c:826
int ncdirect_raster_frame(ncdirect *n, ncdirectv *ncdv, ncalign_e align)
Definition direct.c:627
int ncdirect_vline_interp(ncdirect *n, const char *egc, unsigned len, uint64_t c1, uint64_t c2)
Definition direct.c:1333
int ncdirect_on_styles(ncdirect *n, unsigned stylebits)
Definition direct.c:1165
int ncdirect_box(ncdirect *n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, const wchar_t *wchars, unsigned ylen, unsigned xlen, unsigned ctlword)
Definition direct.c:1398
int ncdirect_stream(ncdirect *n, const char *filename, ncstreamcb streamer, struct ncvisual_options *vopts, void *curry)
Definition direct.c:1537
char * ncdirect_detected_terminal(const ncdirect *nc)
Definition direct.c:1622
int ncdirect_double_box(ncdirect *n, uint64_t ul, uint64_t ur, uint64_t ll, uint64_t lr, unsigned ylen, unsigned xlen, unsigned ctlword)
Definition direct.c:1515
bool ncdirect_canutf8(const ncdirect *n)
Definition direct.c:1522
int ncdirect_cursor_pop(ncdirect *n)
Definition direct.c:403
API int API int API int uint64_t uint64_t uint64_t uint64_t lr
Definition direct.h:216
#define NCDIRECT_OPTION_INHIBIT_SETLOCALE
Definition direct.h:29
#define NCDIRECT_OPTION_INHIBIT_CBREAK
Definition direct.h:33
#define NCDIRECT_OPTION_DRAIN_INPUT
Definition direct.h:38
#define NCDIRECT_OPTION_NO_QUIT_SIGHANDLERS
Definition direct.h:43
const char * prompt
Definition direct.h:68
#define NCDIRECT_OPTION_VERBOSE
Definition direct.h:46
API int API int API int uint64_t uint64_t uint64_t uint64_t unsigned unsigned unsigned ctlword
Definition direct.h:217
API int API int API int uint64_t uint64_t uint64_t ll
Definition direct.h:216
API int API int API int uint64_t uint64_t uint64_t uint64_t unsigned unsigned xlen
Definition direct.h:217
API int API int API int uint64_t uint64_t uint64_t uint64_t unsigned ylen
Definition direct.h:217
#define NCDIRECT_OPTION_VERY_VERBOSE
Definition direct.h:50
API int API int API int uint64_t ul
Definition direct.h:215
API int API int API int uint64_t uint64_t ur
Definition direct.h:215
__attribute__((nonnull(1)))
Definition egcpool.c:4
const char * egc
Definition egcpool.h:162
const nccell * c
Definition egcpool.h:207
const char * fmt
Definition fbuf.h:220
va_end(va)
const char va_start(va, fmt)
int r
Definition fbuf.h:226
int get_cursor_location(inputctx *ictx, const char *u7, unsigned *y, unsigned *x)
Definition in.c:2810
int ncdirect_set_bg_rgb_f(ncdirect *nc, unsigned rgb, fbuf *f)
Definition render.c:1652
ncplane * ncplane_new_internal(notcurses *nc, ncplane *n, const ncplane_options *nopts)
Definition notcurses.c:560
int update_term_dimensions(unsigned *rows, unsigned *cols, tinfo *tcache, int margin_b, unsigned *cgeo_changed, unsigned *pgeo_changed) __attribute__((nonnull(3
int ncdirect_set_fg_rgb_f(ncdirect *nc, unsigned rgb, fbuf *f)
Definition render.c:1682
int putenv_term(const char *termname) __attribute__((nonnull(1)))
Definition termdesc.c:1655
int ncvisual_init(int loglevel)
Definition visual.c:23
void free_plane(ncplane *p)
Definition notcurses.c:467
int set_loglevel_from_env(ncloglevel_e *loglevel) __attribute__((nonnull(1)))
Definition util.c:19
int ncvisual_blit_internal(const struct ncvisual *ncv, int rows, int cols, ncplane *n, const struct blitset *bset, const blitterargs *bargs)
void init_lang(void)
Definition notcurses.c:1097
int reset_term_attributes(const tinfo *ti, fbuf *f)
Definition notcurses.c:60
void ncmetric_use_utf8(void)
Definition metric.c:24
sprixel * sprixel_alloc(ncplane *n, int dimy, int dimx)
Definition sprite.c:117
int ncvisual_geom_inner(const tinfo *ti, const struct ncvisual *n, const struct ncvisual_options *vopts, ncvgeom *geom, const struct blitset **bset, unsigned *disppxy, unsigned *disppxx, unsigned *outy, unsigned *outx, int *placey, int *placex)
ALLOC char * ncplane_vprintf_prep(const char *format, va_list ap)
Definition notcurses.c:2093
#define logerror(fmt,...)
Definition logging.h:32
#define loginfo(fmt,...)
Definition logging.h:42
#define logwarn(fmt,...)
Definition logging.h:37
#define NCKEY_UP
Definition nckeys.h:37
#define NCKEY_BACKSPACE
Definition nckeys.h:43
#define NCKEY_EOF
Definition nckeys.h:183
#define NCKEY_DOWN
Definition nckeys.h:39
#define NCKEY_RIGHT
Definition nckeys.h:38
#define NCKEY_ENTER
Definition nckeys.h:110
#define NCKEY_LEFT
Definition nckeys.h:40
#define NCBOXDOUBLEW
Definition ncseqs.h:12
#define NCBOXROUNDW
Definition ncseqs.h:11
char * ncplane_at_yx(const ncplane *n, int y, int x, uint16_t *stylemask, uint64_t *channels)
Definition notcurses.c:217
int ncstrwidth(const char *egcs, int *validbytes, int *validwidth)
Definition notcurses.c:3311
void ncplane_dim_yx(const ncplane *n, unsigned *rows, unsigned *cols)
Definition notcurses.c:304
ncscale_e
Definition notcurses.h:96
@ NCSCALE_SCALE_HIRES
Definition notcurses.h:101
@ NCSCALE_SCALE
Definition notcurses.h:98
@ NCSCALE_NONE
Definition notcurses.h:97
@ NCSCALE_NONE_HIRES
Definition notcurses.h:100
int y
Definition notcurses.h:1905
#define NCBOXMASK_TOP
Definition notcurses.h:2605
const struct ncplane_options struct ncvisual * ncv
Definition notcurses.h:3488
const struct ncplane_options struct ncvisual struct ncvisual_options * vopts
Definition notcurses.h:3488
ncalign_e
Definition notcurses.h:80
@ NCALIGN_LEFT
Definition notcurses.h:82
@ NCALIGN_CENTER
Definition notcurses.h:83
@ NCALIGN_RIGHT
Definition notcurses.h:84
#define NCBOXMASK_LEFT
Definition notcurses.h:2608
@ NCTYPE_RELEASE
Definition notcurses.h:1198
ncblitter_e
Definition notcurses.h:65
@ NCBLIT_PIXEL
Definition notcurses.h:73
#define NCALPHA_TRANSPARENT
Definition notcurses.h:106
#define NCVISUAL_OPTION_ADDALPHA
Definition notcurses.h:3345
vopts n
Definition notcurses.h:3506
#define NCBOXMASK_BOTTOM
Definition notcurses.h:2607
@ NCLOGLEVEL_WARNING
Definition notcurses.h:973
@ NCLOGLEVEL_SILENT
Definition notcurses.h:969
@ NCLOGLEVEL_TRACE
Definition notcurses.h:977
int int x
Definition notcurses.h:1905
struct ncvisual_options v
Definition notcurses.h:3501
#define NCBOXMASK_RIGHT
Definition notcurses.h:2606
int(* ncstreamcb)(struct ncvisual *, struct ncvisual_options *, const struct timespec *, void *)
Definition notcurses.h:3537
API int API int const nccell unsigned len
Definition notcurses.h:2592
#define NCVISUAL_OPTION_HORALIGNED
Definition notcurses.h:3343
#define NCVISUAL_OPTION_NODEGRADE
Definition notcurses.h:3341
int ncdirect_set_bg_rgb(ncdirect *nc, unsigned rgb)
Definition render.c:1667
int ncdirect_set_fg_rgb(ncdirect *nc, unsigned rgb)
Definition render.c:1697
ncblitter_e geom
Definition internal.h:399
unsigned width
Definition internal.h:400
unsigned height
Definition internal.h:401
struct blitterargs::@3::@5 pixel
uint64_t flags
Definition internal.h:379
sprixel * spx
Definition internal.h:388
union blitterargs::@3 u
uint32_t transcolor
Definition internal.h:380
int colorregs
Definition internal.h:387
Definition fbuf.h:25
Definition in.c:52
unsigned colors
Definition notcurses.h:1637
uint64_t channels
Definition internal.h:254
uint64_t flags
Definition internal.h:256
ncsharedstats stats
Definition internal.h:257
tinfo tcache
Definition internal.h:253
FILE * ttyfp
Definition internal.h:252
ncintype_e evtype
Definition notcurses.h:1218
uint32_t id
Definition notcurses.h:1210
sprixel * sprite
Definition internal.h:105
tament * tam
Definition internal.h:106
pthread_mutex_t lock
Definition internal.h:246
unsigned kbdlevel
Definition termdesc.h:216
struct termios * tpreserved
Definition termdesc.h:180
int ttyfd
Definition termdesc.h:109
void free_terminfo_cache(tinfo *ti)
Definition termdesc.c:197
int interrogate_terminfo(tinfo *ti, FILE *out, unsigned utf8, unsigned noaltscreen, unsigned nocbreak, unsigned nonewfonts, int *cursor_y, int *cursor_x, ncsharedstats *stats, int lmargin, int tmargin, int rmargin, int bmargin, unsigned draininput)
Definition termdesc.c:1317
char * termdesc_longterm(const tinfo *ti)
Definition termdesc.c:1558
return NULL
Definition termdesc.h:229
@ ESCAPE_CUU
Definition termdesc.h:59
@ ESCAPE_CIVIS
Definition termdesc.h:54
@ ESCAPE_SC
Definition termdesc.h:78
@ ESCAPE_CUP
Definition termdesc.h:45
@ ESCAPE_CNORM
Definition termdesc.h:55
@ ESCAPE_SETAB
Definition termdesc.h:49
@ ESCAPE_CUF
Definition termdesc.h:61
@ ESCAPE_VPA
Definition termdesc.h:47
@ ESCAPE_OP
Definition termdesc.h:50
@ ESCAPE_FGOP
Definition termdesc.h:51
@ ESCAPE_CLEAR
Definition termdesc.h:80
@ ESCAPE_SETAF
Definition termdesc.h:48
@ ESCAPE_U7
Definition termdesc.h:82
@ ESCAPE_BGOP
Definition termdesc.h:52
@ ESCAPE_HPA
Definition termdesc.h:46
@ ESCAPE_RC
Definition termdesc.h:79
@ ESCAPE_EL
Definition termdesc.h:67
@ ESCAPE_CUB
Definition termdesc.h:60
#define XTMODKEYSUNDO
Definition termdesc.h:27
#define KKEYBOARD_POP
Definition termdesc.h:22
int setup_signals(void *vnc, bool no_quit_sigs, bool no_winch_sigs, int(*handler)(void *, void **, int))
Definition unixsig.c:195
int drop_signals(void *nc, void **altstack)
Definition unixsig.c:104
int ncvisual_decode(ncvisual *nc)
Definition visual.c:39
ncvisual * ncvisual_from_file(const char *filename)
Definition visual.c:53
void ncvisual_destroy(ncvisual *ncv)
Definition visual.c:1231