Notcurses 3.0.13
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){
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 return ret;
866}
867
868ncdirect* ncdirect_core_init(const char* termtype, FILE* outfp, uint64_t flags){
869 if(outfp == NULL){
870 outfp = stdout;
871 }
872 if(flags > (NCDIRECT_OPTION_DRAIN_INPUT << 1)){ // allow them through with warning
873 logwarn("Passed unsupported flags 0x%016" PRIx64 "\n", flags);
874 }
875 if(termtype){
876 if(putenv_term(termtype)){
877 return NULL;
878 }
879 }
880 ncdirect* ret = malloc(sizeof(ncdirect));
881 if(ret == NULL){
882 return ret;
883 }
884 memset(ret, 0, sizeof(*ret));
885 if(pthread_mutex_init(&ret->stats.lock, NULL)){
886 free(ret);
887 return NULL;
888 }
889 ret->flags = flags;
890 ret->ttyfp = outfp;
892 init_lang();
893 }
894 const char* encoding = nl_langinfo(CODESET);
895 bool utf8 = false;
896 if(encoding && encoding_is_utf8(encoding)){
897 utf8 = true;
899 }
901 true, ncdirect_stop_minimal)){
902 pthread_mutex_destroy(&ret->stats.lock);
903 free(ret);
904 return NULL;
905 }
906 // don't set the loglevel until we've locked in signal handling, lest we
907 // change the loglevel out from under a running instance.
910 }else if(flags & NCDIRECT_OPTION_VERBOSE){
912 }else{
914 }
916 int cursor_y = -1;
917 int cursor_x = -1;
918 if(interrogate_terminfo(&ret->tcache, ret->ttyfp, utf8, 1,
920 0, &cursor_y, &cursor_x, &ret->stats, 0, 0, 0, 0,
922 goto err;
923 }
924 if(cursor_y >= 0){
925 // the u7 led the queries so that we would get a cursor position
926 // unaffected by any query spill (unconsumed control sequences). move
927 // us back to that location, in case there was any such spillage.
928 if(ncdirect_cursor_move_yx(ret, cursor_y, cursor_x)){
930 goto err;
931 }
932 }
935 goto err;
936 }
937 unsigned cgeo, pgeo; // both are don't-cares
938 update_term_dimensions(NULL, NULL, &ret->tcache, 0, &cgeo, &pgeo);
939 ncdirect_set_styles(ret, 0);
940 return ret;
941
942err:{
943 void* altstack;
944 if(ret->tcache.ttyfd >= 0){
945 (void)tcsetattr(ret->tcache.ttyfd, TCSANOW, ret->tcache.tpreserved);
946 }
947 drop_signals(ret, &altstack);
948 pthread_mutex_destroy(&ret->stats.lock);
949 free(ret);
950 }
951 return NULL;
952}
953
955 int ret = 0;
956 if(nc){
957 void* altstack;
958 ret |= ncdirect_stop_minimal(nc, &altstack);
960 if(nc->tcache.ttyfd >= 0){
961 ret |= close(nc->tcache.ttyfd);
962 }
963 pthread_mutex_destroy(&nc->stats.lock);
964 free(nc);
965 free(altstack);
966 }
967 return ret;
968}
969
970// our new input system is fundamentally incompatible with libreadline, so we
971// have to fake it ourselves. at least it saves us the dependency.
972//
973// if NCDIRECT_OPTION_INHIBIT_CBREAK is in play, we're not going to get the
974// text until cooked mode has had its way with it, and we are essentially
975// unable to do anything clever. text will be echoed, and there will be no
976// line-editing keybindings, save any implemented in the line discipline.
977//
978// otherwise, we control echo. whenever we emit output, get our position. if
979// we've changed line, assume the prompt has scrolled up, and account for
980// that. we return to the prompt, clear any affected lines, and reprint what
981// we have.
982char* ncdirect_readline(ncdirect* n, const char* prompt){
983 const char* u7 = get_escape(&n->tcache, ESCAPE_U7);
984 if(!u7){ // we probably *can*, but it would be a pita; screw it
985 logerror("can't readline without u7");
986 return NULL;
987 }
988 if(n->eof){
989 logerror("already got EOF");
990 return NULL;
991 }
992 if(fprintf(n->ttyfp, "%s", prompt) < 0){
993 return NULL;
994 }
995 unsigned dimx = ncdirect_dim_x(n);
996 if(dimx == 0){
997 return NULL;
998 }
999 // FIXME what if we're reading from redirected input, not a terminal?
1000 unsigned y, xstart;
1001 if(cursor_yx_get(n, u7, &y, &xstart)){
1002 return NULL;
1003 }
1004 int tline = y;
1005 unsigned bline = y;
1006 wchar_t* str;
1007 int wspace = BUFSIZ / sizeof(*str);
1008 if((str = malloc(wspace * sizeof(*str))) == NULL){
1009 return NULL;
1010 }
1011 int wpos = 0; // cursor location (single-dimensional)
1012 int wused = 0; // number used
1013 str[wused++] = L'\0';
1014 ncinput ni;
1015 uint32_t id;
1016 unsigned oldx = xstart;
1017 while((id = ncdirect_get_blocking(n, &ni)) != (uint32_t)-1){
1018 if(ni.evtype == NCTYPE_RELEASE){
1019 continue;
1020 }
1021 if(id == NCKEY_EOF || id == NCKEY_ENTER || (ncinput_ctrl_p(&ni) && id == 'D')){
1022 if(id == NCKEY_ENTER){
1023 if(fputc('\n', n->ttyfp) < 0){
1024 free(str);
1025 return NULL;
1026 }
1027 }else{
1028 n->eof = 1;
1029 if(wused == 1){ // NCKEY_EOF without input returns NULL
1030 free(str);
1031 return NULL;
1032 }
1033 }
1034 char* ustr = ncwcsrtombs(str);
1035 free(str);
1036 return ustr;
1037 }else if(id == NCKEY_BACKSPACE){
1038 if(wused > 1){
1039 str[wused - 2] = L'\0';
1040 --wused;
1041 }
1042 --wpos;
1043 }else if(id == NCKEY_LEFT){
1044 --wpos;
1045 }else if(id == NCKEY_RIGHT){
1046 ++wpos;
1047 }else if(id == NCKEY_UP){
1048 wpos -= dimx;
1049 }else if(id == NCKEY_DOWN){
1050 wpos += dimx;
1051 }else if(id == 'A' && ncinput_ctrl_p(&ni)){
1052 wpos = 1;
1053 }else if(id == 'E' && ncinput_ctrl_p(&ni)){
1054 wpos = wused - 1;
1055 }else if(nckey_synthesized_p(ni.id)){
1056 continue;
1057 }else{
1058 if(wspace - 1 < wused){
1059 wspace += BUFSIZ;
1060 wchar_t* tmp = realloc(str, wspace * sizeof(*str));
1061 if(tmp == NULL){
1062 free(str);
1063 return NULL;
1064 }
1065 str = tmp;
1066 }
1067 if(wpos < wused - 1){
1068 memmove(str + wpos + 1, str + wpos, (wused - wpos) * sizeof(*str));
1069 str[wpos] = id;
1070 ++wused;
1071 ++wpos;
1072 }else{
1073 str[wused - 1] = id;
1074 ++wused;
1075 ++wpos;
1076 str[wused - 1] = L'\0';
1077 }
1078 // FIXME check modifiers
1079 unsigned x;
1080 if(cursor_yx_get(n, u7, &y, &x)){
1081 break;
1082 }
1083 if(x < oldx){
1084 oldx = x;
1085 if(--tline < 0){
1086 tline = 0;
1087 }
1088 }
1089 if(y > bline){
1090 bline = y;
1091 }
1092 }
1093 if(wpos < 0){
1094 wpos = 0;
1095 }else if(wpos > wused - 1){
1096 wpos = wused - 1;
1097 }
1098 // clear to end of line(s)
1099 const char* el = get_escape(&n->tcache, ESCAPE_EL);
1100 for(int i = bline ; i >= tline ; --i){
1101 if(ncdirect_cursor_move_yx(n, i, i > tline ? 0 : xstart)){
1102 break;
1103 }
1104 if(term_emit(el, n->ttyfp, false)){
1105 break;
1106 }
1107 }
1108 if(fprintf(n->ttyfp, "%ls", str) < 0){
1109 break;
1110 }
1111 if(wpos != wused){
1112 int linear = xstart + wpos;
1113 int ylin = linear / dimx;
1114 int xlin = linear % dimx;
1115 if(ncdirect_cursor_move_yx(n, tline + ylin, xlin)){
1116 break;
1117 }
1118 }
1119 if(fflush(n->ttyfp)){
1120 break;
1121 }
1122 }
1123 free(str);
1124 return NULL;
1125}
1126
1127static inline int
1128ncdirect_style_emit(ncdirect* n, unsigned stylebits, fbuf* f){
1129 unsigned normalized = 0;
1130 int r = coerce_styles(f, &n->tcache, &n->stylemask, stylebits, &normalized);
1131 // sgr0 resets colors, so set them back up if not defaults and it was used
1132 if(normalized){
1133 // emitting an sgr resets colors. if we want to be default, that's no
1134 // problem, and our channels remain correct. otherwise, clear our
1135 // channel, and set them back up.
1136 if(!ncdirect_fg_default_p(n)){
1137 if(!ncdirect_fg_palindex_p(n)){
1138 uint32_t fg = ncchannels_fg_rgb(n->channels);
1139 ncchannels_set_fg_default(&n->channels);
1140 r |= ncdirect_set_fg_rgb(n, fg);
1141 }else{ // palette-indexed
1142 uint32_t fg = ncchannels_fg_palindex(n->channels);
1143 ncchannels_set_fg_default(&n->channels);
1145 }
1146 }
1147 if(!ncdirect_bg_default_p(n)){
1148 if(!ncdirect_bg_palindex_p(n)){
1149 uint32_t bg = ncchannels_bg_rgb(n->channels);
1150 ncchannels_set_bg_default(&n->channels);
1151 r |= ncdirect_set_bg_rgb(n, bg);
1152 }else{ // palette-indexed
1153 uint32_t bg = ncchannels_bg_palindex(n->channels);
1154 ncchannels_set_bg_default(&n->channels);
1156 }
1157 }
1158 }
1159 return r;
1160}
1161
1162int ncdirect_on_styles(ncdirect* n, unsigned stylebits){
1163 if((stylebits & n->tcache.supported_styles) < stylebits){ // unsupported styles
1164 return -1;
1165 }
1166 uint32_t stylemask = n->stylemask | stylebits;
1167 fbuf f = {0};
1168 if(fbuf_init_small(&f)){
1169 return -1;
1170 }
1171 if(ncdirect_style_emit(n, stylemask, &f)){
1172 fbuf_free(&f);
1173 return -1;
1174 }
1175 if(fbuf_finalize(&f, n->ttyfp)){
1176 return -1;
1177 }
1178 return 0;
1179}
1180
1181uint16_t ncdirect_styles(const ncdirect* n){
1182 return n->stylemask;
1183}
1184
1185// turn off any specified stylebits
1186int ncdirect_off_styles(ncdirect* n, unsigned stylebits){
1187 uint32_t stylemask = n->stylemask & ~stylebits;
1188 fbuf f = {0};
1189 if(fbuf_init_small(&f)){
1190 return -1;
1191 }
1192 if(ncdirect_style_emit(n, stylemask, &f)){
1193 fbuf_free(&f);
1194 return -1;
1195 }
1196 if(fbuf_finalize(&f, n->ttyfp)){
1197 return -1;
1198 }
1199 return 0;
1200}
1201
1202// set the current stylebits to exactly those provided
1203int ncdirect_set_styles(ncdirect* n, unsigned stylebits){
1204 if((stylebits & n->tcache.supported_styles) < stylebits){ // unsupported styles
1205 return -1;
1206 }
1207 uint32_t stylemask = stylebits;
1208 fbuf f = {0};
1209 if(fbuf_init_small(&f)){
1210 return -1;
1211 }
1212 if(ncdirect_style_emit(n, stylemask, &f)){
1213 fbuf_free(&f);
1214 return -1;
1215 }
1216 if(fbuf_finalize(&f, n->ttyfp)){
1217 return -1;
1218 }
1219 return 0;
1220}
1221
1223 return ncdirect_capabilities(nc)->colors;
1224}
1225
1227 if(ncdirect_fg_default_p(nc)){
1228 return 0;
1229 }
1230 const char* esc;
1231 if((esc = get_escape(&nc->tcache, ESCAPE_FGOP)) != NULL){
1232 if(term_emit(esc, nc->ttyfp, false)){
1233 return -1;
1234 }
1235 }else if((esc = get_escape(&nc->tcache, ESCAPE_OP)) != NULL){
1236 if(term_emit(esc, nc->ttyfp, false)){
1237 return -1;
1238 }
1239 if(!ncdirect_bg_default_p(nc)){
1240 if(ncdirect_set_bg_rgb(nc, ncchannels_bg_rgb(nc->channels))){
1241 return -1;
1242 }
1243 }
1244 }
1245 ncchannels_set_fg_default(&nc->channels);
1246 return 0;
1247}
1248
1250 if(ncdirect_bg_default_p(nc)){
1251 return 0;
1252 }
1253 const char* esc;
1254 if((esc = get_escape(&nc->tcache, ESCAPE_BGOP)) != NULL){
1255 if(term_emit(esc, nc->ttyfp, false)){
1256 return -1;
1257 }
1258 }else if((esc = get_escape(&nc->tcache, ESCAPE_OP)) != NULL){
1259 if(term_emit(esc, nc->ttyfp, false)){
1260 return -1;
1261 }
1262 if(!ncdirect_fg_default_p(nc)){
1263 if(ncdirect_set_fg_rgb(nc, ncchannels_fg_rgb(nc->channels))){
1264 return -1;
1265 }
1266 }
1267 }
1268 ncchannels_set_bg_default(&nc->channels);
1269 return 0;
1270}
1271
1272int ncdirect_hline_interp(ncdirect* n, const char* egc, unsigned len,
1273 uint64_t c1, uint64_t c2){
1274 if(len == 0){
1275 logerror("passed zero length\n");
1276 return -1;
1277 }
1278 unsigned ur, ug, ub;
1279 int r1, g1, b1, r2, g2, b2;
1280 int br1, bg1, bb1, br2, bg2, bb2;
1281 ncchannels_fg_rgb8(c1, &ur, &ug, &ub);
1282 r1 = ur; g1 = ug; b1 = ub;
1283 ncchannels_fg_rgb8(c2, &ur, &ug, &ub);
1284 r2 = ur; g2 = ug; b2 = ub;
1285 ncchannels_bg_rgb8(c1, &ur, &ug, &ub);
1286 br1 = ur; bg1 = ug; bb1 = ub;
1287 ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
1288 br2 = ur; bg2 = ug; bb2 = ub;
1289 int deltr = r2 - r1;
1290 int deltg = g2 - g1;
1291 int deltb = b2 - b1;
1292 int deltbr = br2 - br1;
1293 int deltbg = bg2 - bg1;
1294 int deltbb = bb2 - bb1;
1295 unsigned ret;
1296 bool fgdef = false, bgdef = false;
1297 if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
1299 return -1;
1300 }
1301 fgdef = true;
1302 }
1303 if(ncchannels_bg_default_p(c1) && ncchannels_bg_default_p(c2)){
1305 return -1;
1306 }
1307 bgdef = true;
1308 }
1309 for(ret = 0 ; ret < len ; ++ret){
1310 int r = (deltr * (int)ret) / (int)len + r1;
1311 int g = (deltg * (int)ret) / (int)len + g1;
1312 int b = (deltb * (int)ret) / (int)len + b1;
1313 int br = (deltbr * (int)ret) / (int)len + br1;
1314 int bg = (deltbg * (int)ret) / (int)len + bg1;
1315 int bb = (deltbb * (int)ret) / (int)len + bb1;
1316 if(!fgdef){
1317 ncdirect_set_fg_rgb8(n, r, g, b);
1318 }
1319 if(!bgdef){
1320 ncdirect_set_bg_rgb8(n, br, bg, bb);
1321 }
1322 if(fprintf(n->ttyfp, "%s", egc) < 0){
1323 logerror("error emitting egc [%s]\n", egc);
1324 return -1;
1325 }
1326 }
1327 return ret;
1328}
1329
1330int ncdirect_vline_interp(ncdirect* n, const char* egc, unsigned len,
1331 uint64_t c1, uint64_t c2){
1332 if(len == 0){
1333 logerror("passed zero length\n");
1334 return -1;
1335 }
1336 unsigned ur, ug, ub;
1337 int r1, g1, b1, r2, g2, b2;
1338 int br1, bg1, bb1, br2, bg2, bb2;
1339 ncchannels_fg_rgb8(c1, &ur, &ug, &ub);
1340 r1 = ur; g1 = ug; b1 = ub;
1341 ncchannels_fg_rgb8(c2, &ur, &ug, &ub);
1342 r2 = ur; g2 = ug; b2 = ub;
1343 ncchannels_bg_rgb8(c1, &ur, &ug, &ub);
1344 br1 = ur; bg1 = ug; bb1 = ub;
1345 ncchannels_bg_rgb8(c2, &ur, &ug, &ub);
1346 br2 = ur; bg2 = ug; bb2 = ub;
1347 int deltr = (r2 - r1) / ((int)len + 1);
1348 int deltg = (g2 - g1) / ((int)len + 1);
1349 int deltb = (b2 - b1) / ((int)len + 1);
1350 int deltbr = (br2 - br1) / ((int)len + 1);
1351 int deltbg = (bg2 - bg1) / ((int)len + 1);
1352 int deltbb = (bb2 - bb1) / ((int)len + 1);
1353 unsigned ret;
1354 bool fgdef = false, bgdef = false;
1355 if(ncchannels_fg_default_p(c1) && ncchannels_fg_default_p(c2)){
1357 return -1;
1358 }
1359 fgdef = true;
1360 }
1361 if(ncchannels_bg_default_p(c1) && ncchannels_bg_default_p(c2)){
1363 return -1;
1364 }
1365 bgdef = true;
1366 }
1367 for(ret = 0 ; ret < len ; ++ret){
1368 r1 += deltr;
1369 g1 += deltg;
1370 b1 += deltb;
1371 br1 += deltbr;
1372 bg1 += deltbg;
1373 bb1 += deltbb;
1374 uint64_t channels = 0;
1375 if(!fgdef){
1376 ncchannels_set_fg_rgb8(&channels, r1, g1, b1);
1377 }
1378 if(!bgdef){
1379 ncchannels_set_bg_rgb8(&channels, br1, bg1, bb1);
1380 }
1381 if(ncdirect_putstr(n, channels, egc) == EOF){
1382 return -1;
1383 }
1384 if(len - ret > 1){
1386 return -1;
1387 }
1388 }
1389 }
1390 return ret;
1391}
1392
1393// wchars: wchar_t[6] mapping to UL, UR, BL, BR, HL, VL.
1394// they cannot be complex EGCs, but only a single wchar_t, alas.
1395int ncdirect_box(ncdirect* n, uint64_t ul, uint64_t ur,
1396 uint64_t ll, uint64_t lr, const wchar_t* wchars,
1397 unsigned ylen, unsigned xlen, unsigned ctlword){
1398 if(xlen < 2 || ylen < 2){
1399 return -1;
1400 }
1401 char hl[MB_LEN_MAX + 1];
1402 char vl[MB_LEN_MAX + 1];
1403 unsigned edges;
1404 edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_LEFT);
1405 // FIXME rewrite all fprintfs as ncdirect_putstr()!
1406 if(edges >= box_corner_needs(ctlword)){
1407 if(activate_channels(n, ul)){
1408 return -1;
1409 }
1410 if(fprintf(n->ttyfp, "%lc", wchars[0]) < 0){
1411 logerror("error emitting %lc\n", wchars[0]);
1412 return -1;
1413 }
1414 }else{
1416 }
1417 mbstate_t ps = {0};
1418 size_t bytes;
1419 if((bytes = wcrtomb(hl, wchars[4], &ps)) == (size_t)-1){
1420 logerror("error converting %lc\n", wchars[4]);
1421 return -1;
1422 }
1423 hl[bytes] = '\0';
1424 memset(&ps, 0, sizeof(ps));
1425 if((bytes = wcrtomb(vl, wchars[5], &ps)) == (size_t)-1){
1426 logerror("error converting %lc\n", wchars[5]);
1427 return -1;
1428 }
1429 vl[bytes] = '\0';
1430 if(!(ctlword & NCBOXMASK_TOP)){ // draw top border, if called for
1431 if(xlen > 2){
1432 if(ncdirect_hline_interp(n, hl, xlen - 2, ul, ur) < 0){
1433 return -1;
1434 }
1435 }
1436 }else{
1438 }
1439 edges = !(ctlword & NCBOXMASK_TOP) + !(ctlword & NCBOXMASK_RIGHT);
1440 if(edges >= box_corner_needs(ctlword)){
1441 if(activate_channels(n, ur)){
1442 return -1;
1443 }
1444 if(fprintf(n->ttyfp, "%lc", wchars[1]) < 0){
1445 return -1;
1446 }
1448 }else{
1450 }
1452 // middle rows (vertical lines)
1453 if(ylen > 2){
1454 if(!(ctlword & NCBOXMASK_LEFT)){
1455 if(ncdirect_vline_interp(n, vl, ylen - 2, ul, ll) < 0){
1456 return -1;
1457 }
1460 }else{
1462 }
1463 if(!(ctlword & NCBOXMASK_RIGHT)){
1464 if(ncdirect_vline_interp(n, vl, ylen - 2, ur, lr) < 0){
1465 return -1;
1466 }
1468 }else{
1470 }
1472 }
1473 // bottom line
1474 edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_LEFT);
1475 if(edges >= box_corner_needs(ctlword)){
1476 if(activate_channels(n, ll)){
1477 return -1;
1478 }
1479 if(fprintf(n->ttyfp, "%lc", wchars[2]) < 0){
1480 return -1;
1481 }
1482 }else{
1484 }
1485 if(!(ctlword & NCBOXMASK_BOTTOM)){
1486 if(xlen > 2){
1487 if(ncdirect_hline_interp(n, hl, xlen - 2, ll, lr) < 0){
1488 return -1;
1489 }
1490 }
1491 }else{
1493 }
1494 edges = !(ctlword & NCBOXMASK_BOTTOM) + !(ctlword & NCBOXMASK_RIGHT);
1495 if(edges >= box_corner_needs(ctlword)){
1496 if(activate_channels(n, lr)){
1497 return -1;
1498 }
1499 if(fprintf(n->ttyfp, "%lc", wchars[3]) < 0){
1500 return -1;
1501 }
1502 }
1503 return 0;
1504}
1505
1506int ncdirect_rounded_box(ncdirect* n, uint64_t ul, uint64_t ur,
1507 uint64_t ll, uint64_t lr,
1508 unsigned ylen, unsigned xlen, unsigned ctlword){
1509 return ncdirect_box(n, ul, ur, ll, lr, NCBOXROUNDW, ylen, xlen, ctlword);
1510}
1511
1512int ncdirect_double_box(ncdirect* n, uint64_t ul, uint64_t ur,
1513 uint64_t ll, uint64_t lr,
1514 unsigned ylen, unsigned xlen, unsigned ctlword){
1515 return ncdirect_box(n, ul, ur, ll, lr, NCBOXDOUBLEW, ylen, xlen, ctlword);
1516}
1517
1518// Is our encoding UTF-8? Requires LANG being set to a UTF8 locale.
1520 return n->tcache.caps.utf8;
1521}
1522
1524 return ncflush(nc->ttyfp);
1525}
1526
1528 if(n->tcache.pixel_draw || n->tcache.pixel_draw_late){
1529 return 1;
1530 }
1531 return 0;
1532}
1533
1534int ncdirect_stream(ncdirect* n, const char* filename, ncstreamcb streamer,
1535 struct ncvisual_options* vopts, void* curry){
1536 ncvisual* ncv = ncvisual_from_file(filename);
1537 if(ncv == NULL){
1538 return -1;
1539 }
1540 // starting position *after displaying one frame* so as to effect any
1541 // necessary scrolling.
1542 unsigned y = 0, x = 0;
1543 int lastid = -1;
1544 int thisid = -1;
1545 do{
1546 if(y > 0){
1547 if(x == ncdirect_dim_x(n)){
1548 x = 0;
1549 ++y;
1550 }
1551 ncdirect_cursor_up(n, y - 1);
1552 }
1553 if(x > 0){
1555 }
1556 ncdirectv* v = ncdirect_render_visual(n, ncv, vopts);
1557 if(v == NULL){
1559 return -1;
1560 }
1561 ncplane_dim_yx(v, &y, &x);
1562 if(v->sprite){
1563 thisid = v->sprite->id;
1564 }
1567 return -1;
1568 }
1569 if(lastid > -1){
1570 if(n->tcache.pixel_remove){
1571 fbuf f = {0};
1572 fbuf_init_small(&f);
1573 if(n->tcache.pixel_remove(lastid, &f)){
1574 fbuf_free(&f);
1576 return -1;
1577 }
1578 if(fbuf_finalize(&f, n->ttyfp) < 0){
1580 return -1;
1581 }
1582 }
1583 }
1584 streamer(ncv, vopts, NULL, curry);
1585 lastid = thisid;
1586 }while(ncvisual_decode(ncv) == 0);
1589 return 0;
1590}
1591
1593 const char* filename){
1594 return ncvisual_from_file(filename);
1595}
1596
1598 ncvisual_destroy(frame);
1599}
1600
1602 return ncdirect_render_visual(n, frame, vopts);
1603}
1604
1606 const struct ncvisual_options* vopts, ncvgeom* geom){
1607 const struct blitset* bset;
1608 unsigned disppxy, disppxx, outy, outx;
1609 int placey, placex;
1610 return ncvisual_geom_inner(&n->tcache, frame, vopts, geom, &bset,
1611 &disppxy, &disppxx, &outy, &outx,
1612 &placey, &placex);
1613}
1614
1616 return term_supported_styles(&nc->tcache);
1617}
1618
1620 return termdesc_longterm(&nc->tcache);
1621}
1622
1624 return &n->tcache.caps;
1625}
1626
1628 if(get_escape(&n->tcache, ESCAPE_U7) == NULL){
1629 return false;
1630 }
1631 if(n->tcache.ttyfd < 0){
1632 return false;
1633 }
1634 return true;
1635}
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:1222
uint16_t ncdirect_styles(const ncdirect *n)
Definition direct.c:1181
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:1506
int ncdirect_off_styles(ncdirect *n, unsigned stylebits)
Definition direct.c:1186
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:1527
ncdirect * ncdirect_core_init(const char *termtype, FILE *outfp, uint64_t flags)
Definition direct.c:868
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:1605
void ncdirectf_free(ncdirectf *frame)
Definition direct.c:1597
int ncdirect_set_fg_default(ncdirect *nc)
Definition direct.c:1226
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:1627
ncdirectf * ncdirectf_from_file(ncdirect *n __attribute__((unused)), const char *filename)
Definition direct.c:1592
int ncdirect_set_styles(ncdirect *n, unsigned stylebits)
Definition direct.c:1203
uint16_t ncdirect_supported_styles(const ncdirect *nc)
Definition direct.c:1615
int ncdirect_hline_interp(ncdirect *n, const char *egc, unsigned len, uint64_t c1, uint64_t c2)
Definition direct.c:1272
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:1601
int ncdirect_cursor_disable(ncdirect *nc)
Definition direct.c:182
const nccapabilities * ncdirect_capabilities(const ncdirect *n)
Definition direct.c:1623
int ncdirect_set_bg_default(ncdirect *nc)
Definition direct.c:1249
int ncdirect_stop(ncdirect *nc)
Definition direct.c:954
char * ncdirect_readline(ncdirect *n, const char *prompt)
Definition direct.c:982
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:1523
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:1330
int ncdirect_on_styles(ncdirect *n, unsigned stylebits)
Definition direct.c:1162
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:1395
int ncdirect_stream(ncdirect *n, const char *filename, ncstreamcb streamer, struct ncvisual_options *vopts, void *curry)
Definition direct.c:1534
char * ncdirect_detected_terminal(const ncdirect *nc)
Definition direct.c:1619
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:1512
bool ncdirect_canutf8(const ncdirect *n)
Definition direct.c:1519
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
const char * egc
Definition egcpool.h:173
const nccell * c
Definition egcpool.h:296
free(duplicated)
__attribute__((nonnull(1, 2))) static inline int egcpool_stash(egcpool *pool
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:2785
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:557
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:1632
int ncvisual_init(int loglevel)
Definition visual.c:23
void free_plane(ncplane *p)
Definition notcurses.c:464
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:1094
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:2087
#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:214
int ncstrwidth(const char *egcs, int *validbytes, int *validwidth)
Definition notcurses.c:3309
void ncplane_dim_yx(const ncplane *n, unsigned *rows, unsigned *cols)
Definition notcurses.c:301
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:2601
const struct ncplane_options struct ncvisual * ncv
Definition notcurses.h:3484
const struct ncplane_options struct ncvisual struct ncvisual_options * vopts
Definition notcurses.h:3484
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:2604
@ 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:3341
vopts n
Definition notcurses.h:3502
#define NCBOXMASK_BOTTOM
Definition notcurses.h:2603
@ 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:3497
#define NCBOXMASK_RIGHT
Definition notcurses.h:2602
int(* ncstreamcb)(struct ncvisual *, struct ncvisual_options *, const struct timespec *, void *)
Definition notcurses.h:3533
API int API int const nccell unsigned len
Definition notcurses.h:2588
#define NCVISUAL_OPTION_HORALIGNED
Definition notcurses.h:3339
#define NCVISUAL_OPTION_NODEGRADE
Definition notcurses.h:3337
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:1294
char * termdesc_longterm(const tinfo *ti)
Definition termdesc.c:1535
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 **))
Definition unixsig.c:188
int drop_signals(void *nc, void **altstack)
Definition unixsig.c:100
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:1225