Move buffer related functions up
[dvtm.git] / vt.c
blobff3d267a41440e12a080ba9feecb9ced848add71
1 /*
2 * Copyright © 2004 Bruno T. C. de Oliveira
3 * Copyright © 2006 Pierre Habouzit
4 * Copyright © 2008-2013 Marc André Tanner
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <stdlib.h>
19 #include <stdint.h>
20 #include <unistd.h>
21 #include <ctype.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <langinfo.h>
25 #include <limits.h>
26 #include <signal.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <stddef.h>
30 #include <string.h>
31 #include <sys/ioctl.h>
32 #include <sys/types.h>
33 #include <termios.h>
34 #include <wchar.h>
35 #if defined(__linux__) || defined(__CYGWIN__)
36 # include <pty.h>
37 #elif defined(__FreeBSD__)
38 # include <libutil.h>
39 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
40 # include <util.h>
41 #endif
43 #include "vt.h"
45 #ifdef _AIX
46 # include "forkpty-aix.c"
47 #elif defined __sun
48 # include "forkpty-sunos.c"
49 #endif
51 #ifndef NCURSES_ATTR_SHIFT
52 # define NCURSES_ATTR_SHIFT 8
53 #endif
55 #ifndef NCURSES_ACS
56 # ifdef PDCURSES
57 # define NCURSES_ACS(c) (acs_map[(unsigned char)(c)])
58 # else /* BSD curses */
59 # define NCURSES_ACS(c) (_acs_map[(unsigned char)(c)])
60 # endif
61 #endif
63 #ifdef NCURSES_VERSION
64 # ifndef NCURSES_EXT_COLORS
65 # define NCURSES_EXT_COLORS 0
66 # endif
67 # if !NCURSES_EXT_COLORS
68 # define MAX_COLOR_PAIRS 256
69 # endif
70 #endif
71 #ifndef MAX_COLOR_PAIRS
72 # define MAX_COLOR_PAIRS COLOR_PAIRS
73 #endif
75 #if defined _AIX && defined CTRL
76 # undef CTRL
77 #endif
78 #ifndef CTRL
79 # define CTRL(k) ((k) & 0x1F)
80 #endif
82 #define IS_CONTROL(ch) !((ch) & 0xffffff60UL)
83 #define MIN(x, y) ((x) < (y) ? (x) : (y))
84 #define sstrlen(str) (sizeof(str) - 1)
86 static bool is_utf8, has_default_colors;
87 static short color_pairs_reserved, color_pairs_max, color_pair_current;
88 static short *color2palette, default_fg, default_bg;
89 static char vt_term[32] = "dvtm";
91 typedef struct {
92 wchar_t text;
93 attr_t attr;
94 short fg;
95 short bg;
96 } Cell;
98 typedef struct {
99 Cell *cells;
100 unsigned dirty:1;
101 } Row;
103 typedef struct {
104 Row *lines;
105 Row *curs_row;
106 Row *scroll_buf;
107 Row *scroll_top;
108 Row *scroll_bot;
109 bool *tabs;
110 int scroll_buf_size;
111 int scroll_cur;
112 int scroll_above;
113 int scroll_below;
114 int rows, cols, maxcols;
115 attr_t curattrs, savattrs;
116 int curs_col, curs_srow, curs_scol;
117 short curfg, curbg, savfg, savbg;
118 } Buffer;
120 struct Vt {
121 Buffer buffer_normal;
122 Buffer buffer_alternate;
123 Buffer *buffer;
124 attr_t defattrs;
125 short deffg, defbg;
126 int pty;
127 pid_t pid;
129 /* flags */
130 unsigned seen_input:1;
131 unsigned insert:1;
132 unsigned escaped:1;
133 unsigned curshid:1;
134 unsigned curskeymode:1;
135 unsigned bell:1;
136 unsigned relposmode:1;
137 unsigned mousetrack:1;
138 unsigned graphmode:1;
139 unsigned savgraphmode:1;
140 bool charsets[2];
141 /* buffers and parsing state */
142 char rbuf[BUFSIZ];
143 char ebuf[BUFSIZ];
144 unsigned int rlen, elen;
145 /* last known start row, start column */
146 int srow, scol;
148 /* xterm style window title */
149 char title[256];
150 vt_title_handler_t title_handler;
151 void *data;
154 static const char *keytable[KEY_MAX+1] = {
155 ['\n'] = "\r",
156 /* for the arrow keys the CSI / SS3 sequences are not stored here
157 * because they depend on the current cursor terminal mode
159 [KEY_UP] = "A",
160 [KEY_DOWN] = "B",
161 [KEY_RIGHT] = "C",
162 [KEY_LEFT] = "D",
163 #ifdef KEY_SUP
164 [KEY_SUP] = "\e[1;2A",
165 #endif
166 #ifdef KEY_SDOWN
167 [KEY_SDOWN] = "\e[1;2B",
168 #endif
169 [KEY_SRIGHT] = "\e[1;2C",
170 [KEY_SLEFT] = "\e[1;2D",
171 [KEY_BACKSPACE] = "\177",
172 [KEY_IC] = "\e[2~",
173 [KEY_DC] = "\e[3~",
174 [KEY_PPAGE] = "\e[5~",
175 [KEY_NPAGE] = "\e[6~",
176 [KEY_HOME] = "\e[7~",
177 [KEY_END] = "\e[8~",
178 [KEY_BTAB] = "\e[Z",
179 [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */
180 [KEY_F(1)] = "\e[11~",
181 [KEY_F(2)] = "\e[12~",
182 [KEY_F(3)] = "\e[13~",
183 [KEY_F(4)] = "\e[14~",
184 [KEY_F(5)] = "\e[15~",
185 [KEY_F(6)] = "\e[17~",
186 [KEY_F(7)] = "\e[18~",
187 [KEY_F(8)] = "\e[19~",
188 [KEY_F(9)] = "\e[20~",
189 [KEY_F(10)] = "\e[21~",
190 [KEY_F(11)] = "\e[23~",
191 [KEY_F(12)] = "\e[24~",
192 [KEY_F(13)] = "\e[23~",
193 [KEY_F(14)] = "\e[24~",
194 [KEY_F(15)] = "\e[25~",
195 [KEY_F(16)] = "\e[26~",
196 [KEY_F(17)] = "\e[28~",
197 [KEY_F(18)] = "\e[29~",
198 [KEY_F(19)] = "\e[31~",
199 [KEY_F(20)] = "\e[32~",
200 [KEY_F(21)] = "\e[33~",
201 [KEY_F(22)] = "\e[34~",
202 [KEY_RESIZE] = "",
203 #ifdef KEY_EVENT
204 [KEY_EVENT] = "",
205 #endif
208 static void puttab(Vt *t, int count);
209 static void process_nonprinting(Vt *t, wchar_t wc);
210 static void send_curs(Vt *t);
211 static void fill_scroll_buf(Buffer *t, int s);
213 __attribute__ ((const))
214 static attr_t build_attrs(attr_t curattrs)
216 return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
217 >> NCURSES_ATTR_SHIFT;
220 static void row_set(Row *row, int start, int len, Buffer *t)
222 Cell cell = {
223 .text = L'\0',
224 .attr = t ? build_attrs(t->curattrs) : 0,
225 .fg = t ? t->curfg : -1,
226 .bg = t ? t->curbg : -1,
229 for (int i = start; i < len + start; i++)
230 row->cells[i] = cell;
231 row->dirty = true;
234 static void row_roll(Row *start, Row *end, int count)
236 int n = end - start;
238 count %= n;
239 if (count < 0)
240 count += n;
242 if (count) {
243 char buf[count * sizeof(Row)];
244 memcpy(buf, start, count * sizeof(Row));
245 memmove(start, start + count, (n - count) * sizeof(Row));
246 memcpy(end - count, buf, count * sizeof(Row));
247 for (Row *row = start; row < end; row++)
248 row->dirty = true;
253 static void buffer_free(Buffer *t)
255 for (int i = 0; i < t->rows; i++)
256 free(t->lines[i].cells);
257 free(t->lines);
258 for (int i = 0; i < t->scroll_buf_size; i++)
259 free(t->scroll_buf[i].cells);
260 free(t->scroll_buf);
261 free(t->tabs);
264 static void buffer_resize(Buffer *t, int rows, int cols)
266 Row *lines = t->lines;
268 if (t->rows != rows) {
269 if (t->curs_row >= lines + rows) {
270 /* scroll up instead of simply chopping off bottom */
271 fill_scroll_buf(t, (t->curs_row - t->lines) - rows + 1);
273 while (t->rows > rows) {
274 free(lines[t->rows - 1].cells);
275 t->rows--;
278 lines = realloc(lines, sizeof(Row) * rows);
281 if (t->maxcols < cols) {
282 for (int row = 0; row < t->rows; row++) {
283 lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols);
284 if (t->cols < cols)
285 row_set(lines + row, t->cols, cols - t->cols, NULL);
286 lines[row].dirty = true;
288 Row *sbuf = t->scroll_buf;
289 for (int row = 0; row < t->scroll_buf_size; row++) {
290 sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols);
291 if (t->cols < cols)
292 row_set(sbuf + row, t->cols, cols - t->cols, NULL);
294 t->tabs = realloc(t->tabs, sizeof(*t->tabs) * cols);
295 for (int col = t->cols; col < cols; col++)
296 t->tabs[col] = !(col & 7);
297 t->maxcols = cols;
298 t->cols = cols;
299 } else if (t->cols != cols) {
300 for (int row = 0; row < t->rows; row++)
301 lines[row].dirty = true;
302 t->cols = cols;
305 int deltarows = 0;
306 if (t->rows < rows) {
307 while (t->rows < rows) {
308 lines[t->rows].cells = calloc(t->maxcols, sizeof(Cell));
309 row_set(lines + t->rows, 0, t->maxcols, t);
310 t->rows++;
313 /* prepare for backfill */
314 if (t->curs_row >= t->scroll_bot - 1) {
315 deltarows = t->lines + rows - t->curs_row - 1;
316 if (deltarows > t->scroll_above)
317 deltarows = t->scroll_above;
321 t->curs_row += lines - t->lines;
322 t->scroll_top = lines;
323 t->scroll_bot = lines + rows;
324 t->lines = lines;
326 /* perform backfill */
327 if (deltarows > 0) {
328 fill_scroll_buf(t, -deltarows);
329 t->curs_row += deltarows;
333 static bool buffer_init(Buffer *t, int rows, int cols, int scroll_buf_size)
335 Row *lines, *scroll_buf;
336 t->lines = lines = calloc(rows, sizeof(Row));
337 if (!lines)
338 return false;
339 t->curattrs = A_NORMAL; /* white text over black background */
340 t->curfg = t->curbg = -1;
341 for (Row *row = lines, *end = lines + rows; row < end; row++) {
342 row->cells = malloc(cols * sizeof(Cell));
343 if (!row->cells) {
344 t->rows = row - lines;
345 goto fail;
347 row_set(row, 0, cols, NULL);
349 t->rows = rows;
350 if (scroll_buf_size < 0)
351 scroll_buf_size = 0;
352 t->scroll_buf = scroll_buf = calloc(scroll_buf_size, sizeof(Row));
353 if (!scroll_buf && scroll_buf_size)
354 goto fail;
355 for (Row *row = scroll_buf, *end = scroll_buf + scroll_buf_size; row < end; row++) {
356 row->cells = calloc(cols, sizeof(Cell));
357 if (!row->cells) {
358 t->scroll_buf_size = row - scroll_buf;
359 goto fail;
362 t->tabs = calloc(cols, sizeof(*t->tabs));
363 if (!t->tabs)
364 goto fail;
365 for (int col = 8; col < cols; col += 8)
366 t->tabs[col] = true;
367 t->curs_row = lines;
368 t->curs_col = 0;
369 /* initial scrolling area is the whole window */
370 t->scroll_top = lines;
371 t->scroll_bot = lines + rows;
372 t->scroll_buf_size = scroll_buf_size;
373 t->maxcols = t->cols = cols;
374 return true;
376 fail:
377 buffer_free(t);
378 return false;
381 static void buffer_boundry(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) {
382 if (bs)
383 *bs = NULL;
384 if (be)
385 *be = NULL;
386 if (as)
387 *as = NULL;
388 if (ae)
389 *ae = NULL;
390 if (!b->scroll_buf_size)
391 return;
393 if (b->scroll_above) {
394 if (bs)
395 *bs = &b->scroll_buf[(b->scroll_cur - b->scroll_above + b->scroll_buf_size) % b->scroll_buf_size];
396 if (be)
397 *be = &b->scroll_buf[(b->scroll_cur-1 + b->scroll_buf_size) % b->scroll_buf_size];
399 if (b->scroll_below) {
400 if (as)
401 *as = &b->scroll_buf[b->scroll_cur];
402 if (ae)
403 *ae = &b->scroll_buf[(b->scroll_cur + b->scroll_below-1) % b->scroll_buf_size];
407 static Row *buffer_row_first(Buffer *b) {
408 Row *bstart;
409 if (!b->scroll_buf_size || !b->scroll_above)
410 return b->lines;
411 buffer_boundry(b, &bstart, NULL, NULL, NULL);
412 return bstart;
415 static Row *buffer_row_last(Buffer *b) {
416 Row *aend;
417 if (!b->scroll_buf_size || !b->scroll_below)
418 return b->lines + b->rows - 1;
419 buffer_boundry(b, NULL, NULL, NULL, &aend);
420 return aend;
423 static Row *buffer_row_next(Buffer *b, Row *row)
425 Row *before_start, *before_end, *after_start, *after_end;
426 Row *first = b->lines, *last = b->lines + b->rows - 1;
428 if (!row)
429 return NULL;
431 buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
433 if (row >= first && row < last)
434 return ++row;
435 if (row == last)
436 return after_start;
437 if (row == before_end)
438 return first;
439 if (row == after_end)
440 return NULL;
441 if (row == &b->scroll_buf[b->scroll_buf_size - 1])
442 return b->scroll_buf;
443 return ++row;
446 static Row *buffer_row_prev(Buffer *b, Row *row)
448 Row *before_start, *before_end, *after_start, *after_end;
449 Row *first = b->lines, *last = b->lines + b->rows - 1;
451 if (!row)
452 return NULL;
454 buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
456 if (row > first && row <= last)
457 return --row;
458 if (row == first)
459 return before_end;
460 if (row == before_start)
461 return NULL;
462 if (row == after_start)
463 return last;
464 if (row == b->scroll_buf)
465 return &b->scroll_buf[b->scroll_buf_size - 1];
466 return --row;
470 static void clamp_cursor_to_bounds(Vt *t)
472 Buffer *b = t->buffer;
473 Row *lines = t->relposmode ? b->scroll_top : b->lines;
474 int rows = t->relposmode ? b->scroll_bot - b->scroll_top : b->rows;
476 if (b->curs_row < lines)
477 b->curs_row = lines;
478 if (b->curs_row >= lines + rows)
479 b->curs_row = lines + rows - 1;
480 if (b->curs_col < 0)
481 b->curs_col = 0;
482 if (b->curs_col >= b->cols)
483 b->curs_col = b->cols - 1;
486 static void save_curs(Vt *t)
488 Buffer *b = t->buffer;
489 b->curs_srow = b->curs_row - b->lines;
490 b->curs_scol = b->curs_col;
493 static void restore_curs(Vt *t)
495 Buffer *b = t->buffer;
496 b->curs_row = b->lines + b->curs_srow;
497 b->curs_col = b->curs_scol;
498 clamp_cursor_to_bounds(t);
501 static void save_attrs(Vt *t)
503 Buffer *b = t->buffer;
504 b->savattrs = b->curattrs;
505 b->savfg = b->curfg;
506 b->savbg = b->curbg;
507 t->savgraphmode = t->graphmode;
510 static void restore_attrs(Vt *t)
512 Buffer *b = t->buffer;
513 b->curattrs = b->savattrs;
514 b->curfg = b->savfg;
515 b->curbg = b->savbg;
516 t->graphmode = t->savgraphmode;
519 static void fill_scroll_buf(Buffer *t, int s)
521 /* work in screenfuls */
522 int ssz = t->scroll_bot - t->scroll_top;
523 if (s > ssz) {
524 fill_scroll_buf(t, ssz);
525 fill_scroll_buf(t, s - ssz);
526 return;
528 if (s < -ssz) {
529 fill_scroll_buf(t, -ssz);
530 fill_scroll_buf(t, s + ssz);
531 return;
534 t->scroll_above += s;
535 if (t->scroll_above >= t->scroll_buf_size)
536 t->scroll_above = t->scroll_buf_size;
538 if (s > 0 && t->scroll_buf_size) {
539 for (int i = 0; i < s; i++) {
540 Row tmp = t->scroll_top[i];
541 t->scroll_top[i] = t->scroll_buf[t->scroll_cur];
542 t->scroll_buf[t->scroll_cur] = tmp;
544 t->scroll_cur++;
545 if (t->scroll_cur == t->scroll_buf_size)
546 t->scroll_cur = 0;
549 row_roll(t->scroll_top, t->scroll_bot, s);
550 if (s < 0 && t->scroll_buf_size) {
551 for (int i = (-s) - 1; i >= 0; i--) {
552 t->scroll_cur--;
553 if (t->scroll_cur == -1)
554 t->scroll_cur = t->scroll_buf_size - 1;
556 Row tmp = t->scroll_top[i];
557 t->scroll_top[i] = t->scroll_buf[t->scroll_cur];
558 t->scroll_buf[t->scroll_cur] = tmp;
559 t->scroll_top[i].dirty = true;
564 static void cursor_line_down(Vt *t)
566 Buffer *b = t->buffer;
567 row_set(b->curs_row, b->cols, b->maxcols - b->cols, NULL);
568 b->curs_row++;
569 if (b->curs_row < b->scroll_bot)
570 return;
572 vt_noscroll(t);
574 b->curs_row = b->scroll_bot - 1;
575 fill_scroll_buf(b, 1);
576 row_set(b->curs_row, 0, b->cols, b);
579 static void new_escape_sequence(Vt *t)
581 t->escaped = true;
582 t->elen = 0;
583 t->ebuf[0] = '\0';
586 static void cancel_escape_sequence(Vt *t)
588 t->escaped = false;
589 t->elen = 0;
590 t->ebuf[0] = '\0';
593 static bool is_valid_csi_ender(int c)
595 return (c >= 'a' && c <= 'z')
596 || (c >= 'A' && c <= 'Z')
597 || (c == '@' || c == '`');
600 /* interprets a 'set attribute' (SGR) CSI escape sequence */
601 static void interpret_csi_sgr(Vt *t, int param[], int pcount)
603 Buffer *b = t->buffer;
604 if (pcount == 0) {
605 /* special case: reset attributes */
606 b->curattrs = A_NORMAL;
607 b->curfg = b->curbg = -1;
608 return;
611 for (int i = 0; i < pcount; i++) {
612 switch (param[i]) {
613 case 0:
614 b->curattrs = A_NORMAL;
615 b->curfg = b->curbg = -1;
616 break;
617 case 1:
618 b->curattrs |= A_BOLD;
619 break;
620 case 2:
621 b->curattrs |= A_DIM;
622 break;
623 #ifdef A_ITALIC
624 case 3:
625 b->curattrs |= A_ITALIC;
626 break;
627 #endif
628 case 4:
629 b->curattrs |= A_UNDERLINE;
630 break;
631 case 5:
632 b->curattrs |= A_BLINK;
633 break;
634 case 7:
635 b->curattrs |= A_REVERSE;
636 break;
637 case 8:
638 b->curattrs |= A_INVIS;
639 break;
640 case 22:
641 b->curattrs &= ~(A_BOLD | A_DIM);
642 break;
643 #ifdef A_ITALIC
644 case 23:
645 b->curattrs &= ~A_ITALIC;
646 break;
647 #endif
648 case 24:
649 b->curattrs &= ~A_UNDERLINE;
650 break;
651 case 25:
652 b->curattrs &= ~A_BLINK;
653 break;
654 case 27:
655 b->curattrs &= ~A_REVERSE;
656 break;
657 case 28:
658 b->curattrs &= ~A_INVIS;
659 break;
660 case 30 ... 37: /* fg */
661 b->curfg = param[i] - 30;
662 break;
663 case 38:
664 if ((i + 2) < pcount && param[i + 1] == 5) {
665 b->curfg = param[i + 2];
666 i += 2;
668 break;
669 case 39:
670 b->curfg = -1;
671 break;
672 case 40 ... 47: /* bg */
673 b->curbg = param[i] - 40;
674 break;
675 case 48:
676 if ((i + 2) < pcount && param[i + 1] == 5) {
677 b->curbg = param[i + 2];
678 i += 2;
680 break;
681 case 49:
682 b->curbg = -1;
683 break;
684 case 90 ... 97: /* hi fg */
685 b->curfg = param[i] - 82;
686 break;
687 case 100 ... 107: /* hi bg */
688 b->curbg = param[i] - 92;
689 break;
690 default:
691 break;
696 /* interprets an 'erase display' (ED) escape sequence */
697 static void interpret_csi_ed(Vt *t, int param[], int pcount)
699 Row *row, *start, *end;
700 Buffer *b = t->buffer;
702 save_attrs(t);
703 b->curattrs = A_NORMAL;
704 b->curfg = b->curbg = -1;
706 if (pcount && param[0] == 2) {
707 start = b->lines;
708 end = b->lines + b->rows;
709 } else if (pcount && param[0] == 1) {
710 start = b->lines;
711 end = b->curs_row;
712 row_set(b->curs_row, 0, b->curs_col + 1, b);
713 } else {
714 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
715 start = b->curs_row + 1;
716 end = b->lines + b->rows;
719 for (row = start; row < end; row++)
720 row_set(row, 0, b->cols, b);
722 restore_attrs(t);
725 /* interprets a 'move cursor' (CUP) escape sequence */
726 static void interpret_csi_cup(Vt *t, int param[], int pcount)
728 Buffer *b = t->buffer;
729 Row *lines = t->relposmode ? b->scroll_top : b->lines;
731 if (pcount == 0) {
732 b->curs_row = lines;
733 b->curs_col = 0;
734 } else if (pcount == 1) {
735 b->curs_row = lines + param[0] - 1;
736 b->curs_col = 0;
737 } else {
738 b->curs_row = lines + param[0] - 1;
739 b->curs_col = param[1] - 1;
742 clamp_cursor_to_bounds(t);
745 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
746 * CPL, CHA, HPR, VPA, VPR, HPA */
747 static void interpret_csi_c(Vt *t, char verb, int param[], int pcount)
749 Buffer *b = t->buffer;
750 int n = (pcount && param[0] > 0) ? param[0] : 1;
752 switch (verb) {
753 case 'A':
754 b->curs_row -= n;
755 break;
756 case 'B':
757 case 'e':
758 b->curs_row += n;
759 break;
760 case 'C':
761 case 'a':
762 b->curs_col += n;
763 break;
764 case 'D':
765 b->curs_col -= n;
766 break;
767 case 'E':
768 b->curs_row += n;
769 b->curs_col = 0;
770 break;
771 case 'F':
772 b->curs_row -= n;
773 b->curs_col = 0;
774 break;
775 case 'G':
776 case '`':
777 b->curs_col = param[0] - 1;
778 break;
779 case 'd':
780 b->curs_row = b->lines + param[0] - 1;
781 break;
784 clamp_cursor_to_bounds(t);
787 /* Interpret the 'erase line' escape sequence */
788 static void interpret_csi_el(Vt *t, int param[], int pcount)
790 Buffer *b = t->buffer;
791 switch (pcount ? param[0] : 0) {
792 case 1:
793 row_set(b->curs_row, 0, b->curs_col + 1, b);
794 break;
795 case 2:
796 row_set(b->curs_row, 0, b->cols, b);
797 break;
798 default:
799 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
800 break;
804 /* Interpret the 'insert blanks' sequence (ICH) */
805 static void interpret_csi_ich(Vt *t, int param[], int pcount)
807 Buffer *b = t->buffer;
808 Row *row = b->curs_row;
809 int n = (pcount && param[0] > 0) ? param[0] : 1;
811 if (b->curs_col + n > b->cols)
812 n = b->cols - b->curs_col;
814 for (int i = b->cols - 1; i >= b->curs_col + n; i--)
815 row->cells[i] = row->cells[i - n];
817 row_set(row, b->curs_col, n, b);
820 /* Interpret the 'delete chars' sequence (DCH) */
821 static void interpret_csi_dch(Vt *t, int param[], int pcount)
823 Buffer *b = t->buffer;
824 Row *row = b->curs_row;
825 int n = (pcount && param[0] > 0) ? param[0] : 1;
827 if (b->curs_col + n > b->cols)
828 n = b->cols - b->curs_col;
830 for (int i = b->curs_col; i < b->cols - n; i++)
831 row->cells[i] = row->cells[i + n];
833 row_set(row, b->cols - n, n, b);
836 /* Interpret an 'insert line' sequence (IL) */
837 static void interpret_csi_il(Vt *t, int param[], int pcount)
839 Buffer *b = t->buffer;
840 int n = (pcount && param[0] > 0) ? param[0] : 1;
842 if (b->curs_row + n >= b->scroll_bot) {
843 for (Row *row = b->curs_row; row < b->scroll_bot; row++)
844 row_set(row, 0, b->cols, b);
845 } else {
846 row_roll(b->curs_row, b->scroll_bot, -n);
847 for (Row *row = b->curs_row; row < b->curs_row + n; row++)
848 row_set(row, 0, b->cols, b);
852 /* Interpret a 'delete line' sequence (DL) */
853 static void interpret_csi_dl(Vt *t, int param[], int pcount)
855 Buffer *b = t->buffer;
856 int n = (pcount && param[0] > 0) ? param[0] : 1;
858 if (b->curs_row + n >= b->scroll_bot) {
859 for (Row *row = b->curs_row; row < b->scroll_bot; row++)
860 row_set(row, 0, b->cols, b);
861 } else {
862 row_roll(b->curs_row, b->scroll_bot, n);
863 for (Row *row = b->scroll_bot - n; row < b->scroll_bot; row++)
864 row_set(row, 0, b->cols, b);
868 /* Interpret an 'erase characters' (ECH) sequence */
869 static void interpret_csi_ech(Vt *t, int param[], int pcount)
871 Buffer *b = t->buffer;
872 int n = (pcount && param[0] > 0) ? param[0] : 1;
874 if (b->curs_col + n > b->cols)
875 n = b->cols - b->curs_col;
877 row_set(b->curs_row, b->curs_col, n, b);
880 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
881 static void interpret_csi_decstbm(Vt *t, int param[], int pcount)
883 Buffer *b = t->buffer;
884 int new_top, new_bot;
886 switch (pcount) {
887 case 0:
888 b->scroll_top = b->lines;
889 b->scroll_bot = b->lines + b->rows;
890 break;
891 case 2:
892 new_top = param[0] - 1;
893 new_bot = param[1];
895 /* clamp to bounds */
896 if (new_top < 0)
897 new_top = 0;
898 if (new_top >= b->rows)
899 new_top = b->rows - 1;
900 if (new_bot < 0)
901 new_bot = 0;
902 if (new_bot >= b->rows)
903 new_bot = b->rows;
905 /* check for range validity */
906 if (new_top < new_bot) {
907 b->scroll_top = b->lines + new_top;
908 b->scroll_bot = b->lines + new_bot;
910 break;
911 default:
912 return; /* malformed */
914 b->curs_row = b->scroll_top;
915 b->curs_col = 0;
918 static void interpret_csi_mode(Vt *t, int param[], int pcount, bool set)
920 for (int i = 0; i < pcount; i++) {
921 switch (param[i]) {
922 case 4: /* insert/replace mode */
923 t->insert = set;
924 break;
929 static void interpret_csi_priv_mode(Vt *t, int param[], int pcount, bool set)
931 for (int i = 0; i < pcount; i++) {
932 switch (param[i]) {
933 case 1: /* set application/normal cursor key mode (DECCKM) */
934 t->curskeymode = set;
935 break;
936 case 6: /* set origin to relative/absolute (DECOM) */
937 t->relposmode = set;
938 break;
939 case 25: /* make cursor visible/invisible (DECCM) */
940 t->curshid = !set;
941 break;
942 case 47: /* use alternate/normal screen buffer */
943 t->buffer = set ? &t->buffer_alternate : &t->buffer_normal;
944 vt_dirty(t);
945 break;
946 case 1000: /* enable/disable normal mouse tracking */
947 t->mousetrack = set;
948 break;
953 static void interpret_csi(Vt *t)
955 static int csiparam[BUFSIZ];
956 Buffer *b = t->buffer;
957 int param_count = 0;
958 const char *p = t->ebuf + 1;
959 char verb = t->ebuf[t->elen - 1];
961 /* parse numeric parameters */
962 for (p += (t->ebuf[1] == '?'); *p; p++) {
963 if (IS_CONTROL(*p)) {
964 process_nonprinting(t, *p);
965 } else if (*p == ';') {
966 if (param_count >= (int)sizeof(csiparam))
967 return; /* too long! */
968 csiparam[param_count++] = 0;
969 } else if (isdigit((unsigned char)*p)) {
970 if (param_count == 0)
971 csiparam[param_count++] = 0;
972 csiparam[param_count - 1] *= 10;
973 csiparam[param_count - 1] += *p - '0';
977 if (t->ebuf[1] == '?') {
978 switch (verb) {
979 case 'h':
980 case 'l': /* private set/reset mode */
981 interpret_csi_priv_mode(t, csiparam, param_count, verb == 'h');
982 break;
984 return;
987 /* delegate handling depending on command character (verb) */
988 switch (verb) {
989 case 'h':
990 case 'l': /* set/reset mode */
991 interpret_csi_mode(t, csiparam, param_count, verb == 'h');
992 break;
993 case 'm': /* set attribute */
994 interpret_csi_sgr(t, csiparam, param_count);
995 break;
996 case 'J': /* erase display */
997 interpret_csi_ed(t, csiparam, param_count);
998 break;
999 case 'H':
1000 case 'f': /* move cursor */
1001 interpret_csi_cup(t, csiparam, param_count);
1002 break;
1003 case 'A':
1004 case 'B':
1005 case 'C':
1006 case 'D':
1007 case 'E':
1008 case 'F':
1009 case 'G':
1010 case 'e':
1011 case 'a':
1012 case 'd':
1013 case '`': /* relative move */
1014 interpret_csi_c(t, verb, csiparam, param_count);
1015 break;
1016 case 'K': /* erase line */
1017 interpret_csi_el(t, csiparam, param_count);
1018 break;
1019 case '@': /* insert characters */
1020 interpret_csi_ich(t, csiparam, param_count);
1021 break;
1022 case 'P': /* delete characters */
1023 interpret_csi_dch(t, csiparam, param_count);
1024 break;
1025 case 'L': /* insert lines */
1026 interpret_csi_il(t, csiparam, param_count);
1027 break;
1028 case 'M': /* delete lines */
1029 interpret_csi_dl(t, csiparam, param_count);
1030 break;
1031 case 'X': /* erase chars */
1032 interpret_csi_ech(t, csiparam, param_count);
1033 break;
1034 case 'S': /* SU: scroll up */
1035 vt_scroll(t, param_count ? -csiparam[0] : -1);
1036 break;
1037 case 'T': /* SD: scroll down */
1038 vt_scroll(t, param_count ? csiparam[0] : 1);
1039 break;
1040 case 'Z': /* CBT: cursor backward tabulation */
1041 puttab(t, param_count ? -csiparam[0] : -1);
1042 break;
1043 case 'g': /* TBC: tabulation clear */
1044 switch (csiparam[0]) {
1045 case 0:
1046 b->tabs[b->curs_col] = false;
1047 break;
1048 case 3:
1049 memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols);
1050 break;
1052 case 'r': /* set scrolling region */
1053 interpret_csi_decstbm(t, csiparam, param_count);
1054 break;
1055 case 's': /* save cursor location */
1056 save_curs(t);
1057 break;
1058 case 'u': /* restore cursor location */
1059 restore_curs(t);
1060 break;
1061 case 'n': /* query cursor location */
1062 if (param_count == 1 && csiparam[0] == 6)
1063 send_curs(t);
1064 break;
1065 default:
1066 break;
1070 /* Interpret an 'index' (IND) sequence */
1071 static void interpret_csi_ind(Vt *t)
1073 Buffer *b = t->buffer;
1074 if (b->curs_row < b->lines + b->rows - 1)
1075 b->curs_row++;
1078 /* Interpret a 'reverse index' (RI) sequence */
1079 static void interpret_csi_ri(Vt *t)
1081 Buffer *b = t->buffer;
1082 if (b->curs_row > b->scroll_top)
1083 b->curs_row--;
1084 else {
1085 row_roll(b->scroll_top, b->scroll_bot, -1);
1086 row_set(b->scroll_top, 0, b->cols, b);
1090 /* Interpret a 'next line' (NEL) sequence */
1091 static void interpret_csi_nel(Vt *t)
1093 Buffer *b = t->buffer;
1094 if (b->curs_row < b->lines + b->rows - 1) {
1095 b->curs_row++;
1096 b->curs_col = 0;
1100 /* Interpret a 'select character set' (SCS) sequence */
1101 static void interpret_csi_scs(Vt *t)
1103 /* ESC ( sets G0, ESC ) sets G1 */
1104 t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0');
1105 t->graphmode = t->charsets[0];
1108 /* Interpret an 'operating system command' (OSC) sequence */
1109 static void interpret_osc(Vt *t)
1111 /* ESC ] command ; data BEL
1112 * ESC ] command ; data ESC \\
1113 * Note that BEL or ESC \\ have already been replaced with NUL.
1115 char *data = NULL;
1116 int command = strtoul(t->ebuf + 1, &data, 10);
1117 if (data && *data == ';') {
1118 switch (command) {
1119 case 0: /* icon name and window title */
1120 case 2: /* window title */
1121 if (t->title_handler)
1122 t->title_handler(t, data+1);
1123 break;
1124 case 1: /* icon name */
1125 break;
1126 default:
1127 #ifndef NDEBUG
1128 fprintf(stderr, "unknown OSC command: %d\n", command);
1129 #endif
1130 break;
1135 static void try_interpret_escape_seq(Vt *t)
1137 char lastchar = t->ebuf[t->elen - 1];
1139 if (!*t->ebuf)
1140 return;
1142 switch (*t->ebuf) {
1143 case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */
1144 if (t->elen == 2) {
1145 if (lastchar == '8') { /* DECALN */
1146 interpret_csi_ed(t, (int []){ 2 }, 1);
1147 goto handled;
1149 goto cancel;
1151 break;
1152 case '(':
1153 case ')':
1154 if (t->elen == 2) {
1155 interpret_csi_scs(t);
1156 goto handled;
1158 break;
1159 case ']': /* OSC - operating system command */
1160 if (lastchar == '\a' ||
1161 (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) {
1162 t->elen -= lastchar == '\a' ? 1 : 2;
1163 t->ebuf[t->elen] = '\0';
1164 interpret_osc(t);
1165 goto handled;
1167 break;
1168 case '[': /* CSI - control sequence introducer */
1169 if (is_valid_csi_ender(lastchar)) {
1170 interpret_csi(t);
1171 goto handled;
1173 break;
1174 case '7': /* DECSC: save cursor and attributes */
1175 save_attrs(t);
1176 save_curs(t);
1177 goto handled;
1178 case '8': /* DECRC: restore cursor and attributes */
1179 restore_attrs(t);
1180 restore_curs(t);
1181 goto handled;
1182 case 'D': /* IND: index */
1183 interpret_csi_ind(t);
1184 goto handled;
1185 case 'M': /* RI: reverse index */
1186 interpret_csi_ri(t);
1187 goto handled;
1188 case 'E': /* NEL: next line */
1189 interpret_csi_nel(t);
1190 goto handled;
1191 case 'H': /* HTS: horizontal tab set */
1192 t->buffer->tabs[t->buffer->curs_col] = true;
1193 goto handled;
1194 default:
1195 goto cancel;
1198 if (t->elen + 1 >= sizeof(t->ebuf)) {
1199 cancel:
1200 #ifndef NDEBUG
1201 fprintf(stderr, "cancelled: \\033");
1202 for (unsigned int i = 0; i < t->elen; i++) {
1203 if (isprint(t->ebuf[i])) {
1204 fputc(t->ebuf[i], stderr);
1205 } else {
1206 fprintf(stderr, "\\%03o", t->ebuf[i]);
1209 fputc('\n', stderr);
1210 #endif
1211 handled:
1212 cancel_escape_sequence(t);
1216 static void puttab(Vt *t, int count)
1218 Buffer *b = t->buffer;
1219 int direction = count >= 0 ? 1 : -1;
1220 int col = b->curs_col + direction;
1221 while (count) {
1222 if (col < 0) {
1223 b->curs_col = 0;
1224 break;
1226 if (col >= b->cols) {
1227 b->curs_col = b->cols - 1;
1228 break;
1230 if (b->tabs[col]) {
1231 b->curs_col = col;
1232 count -= direction;
1234 col += direction;
1238 static void process_nonprinting(Vt *t, wchar_t wc)
1240 Buffer *b = t->buffer;
1241 switch (wc) {
1242 case '\e': /* ESC */
1243 new_escape_sequence(t);
1244 break;
1245 case '\a': /* BEL */
1246 if (t->bell)
1247 beep();
1248 break;
1249 case '\b': /* BS */
1250 if (b->curs_col > 0)
1251 b->curs_col--;
1252 break;
1253 case '\t': /* HT */
1254 puttab(t, 1);
1255 break;
1256 case '\r': /* CR */
1257 b->curs_col = 0;
1258 break;
1259 case '\v': /* VT */
1260 case '\f': /* FF */
1261 case '\n': /* LF */
1262 cursor_line_down(t);
1263 break;
1264 case '\016': /* SO: shift out, invoke the G1 character set */
1265 t->graphmode = t->charsets[1];
1266 break;
1267 case '\017': /* SI: shift in, invoke the G0 character set */
1268 t->graphmode = t->charsets[0];
1269 break;
1273 static void is_utf8_locale(void)
1275 const char *cset = nl_langinfo(CODESET);
1276 if (!cset)
1277 cset = "ANSI_X3.4-1968";
1278 is_utf8 = !strcmp(cset, "UTF-8");
1281 static wchar_t get_vt100_graphic(char c)
1283 static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~";
1286 * 5f-7e standard vt100
1287 * 40-5e rxvt extension for extra curses acs chars
1289 static uint16_t const vt100_utf8[62] = {
1290 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47
1291 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f
1292 0, 0, 0, 0, 0, 0, 0, 0, // 50-57
1293 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f
1294 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
1295 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
1296 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
1297 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e
1300 if (is_utf8)
1301 return vt100_utf8[c - 0x41];
1302 else if (strchr(vt100_acs, c))
1303 return NCURSES_ACS(c);
1304 return '\0';
1307 static void put_wc(Vt *t, wchar_t wc)
1309 int width = 0;
1311 if (!t->seen_input) {
1312 t->seen_input = 1;
1313 kill(-t->pid, SIGWINCH);
1316 if (t->escaped) {
1317 if (t->elen + 1 < sizeof(t->ebuf)) {
1318 t->ebuf[t->elen] = wc;
1319 t->ebuf[++t->elen] = '\0';
1320 try_interpret_escape_seq(t);
1321 } else {
1322 cancel_escape_sequence(t);
1324 } else if (IS_CONTROL(wc)) {
1325 process_nonprinting(t, wc);
1326 } else {
1327 if (t->graphmode) {
1328 if (wc >= 0x41 && wc <= 0x7e) {
1329 wchar_t gc = get_vt100_graphic(wc);
1330 if (gc)
1331 wc = gc;
1333 width = 1;
1334 } else if ((width = wcwidth(wc)) < 1) {
1335 width = 1;
1337 Buffer *b = t->buffer;
1338 Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg };
1339 if (width == 2 && b->curs_col == b->cols - 1) {
1340 b->curs_row->cells[b->curs_col++] = blank_cell;
1341 b->curs_row->dirty = true;
1344 if (b->curs_col >= b->cols) {
1345 b->curs_col = 0;
1346 cursor_line_down(t);
1349 if (t->insert) {
1350 Cell *src = b->curs_row->cells + b->curs_col;
1351 Cell *dest = src + width;
1352 size_t len = b->cols - b->curs_col - width;
1353 memmove(dest, src, len * sizeof *dest);
1356 b->curs_row->cells[b->curs_col] = blank_cell;
1357 b->curs_row->cells[b->curs_col++].text = wc;
1358 b->curs_row->dirty = true;
1359 if (width == 2)
1360 b->curs_row->cells[b->curs_col++] = blank_cell;
1364 int vt_process(Vt *t)
1366 int res;
1367 unsigned int pos = 0;
1368 mbstate_t ps;
1369 memset(&ps, 0, sizeof(ps));
1371 if (t->pty < 0) {
1372 errno = EINVAL;
1373 return -1;
1376 res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen);
1377 if (res < 0)
1378 return -1;
1380 t->rlen += res;
1381 while (pos < t->rlen) {
1382 wchar_t wc;
1383 ssize_t len;
1385 len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &ps);
1386 if (len == -2) {
1387 t->rlen -= pos;
1388 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1389 return 0;
1392 if (len == -1) {
1393 len = 1;
1394 wc = t->rbuf[pos];
1397 pos += len ? len : 1;
1398 put_wc(t, wc);
1401 t->rlen -= pos;
1402 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1403 return 0;
1406 void vt_default_colors_set(Vt *t, attr_t attrs, short fg, short bg)
1408 t->defattrs = attrs;
1409 t->deffg = fg;
1410 t->defbg = bg;
1414 Vt *vt_create(int rows, int cols, int scroll_buf_size)
1416 Vt *t;
1418 if (rows <= 0 || cols <= 0)
1419 return NULL;
1421 t = calloc(1, sizeof(Vt));
1422 if (!t)
1423 return NULL;
1425 t->pty = -1;
1426 t->deffg = t->defbg = -1;
1427 if (!buffer_init(&t->buffer_normal, rows, cols, scroll_buf_size) ||
1428 !buffer_init(&t->buffer_alternate, rows, cols, 0)) {
1429 free(t);
1430 return NULL;
1432 t->buffer = &t->buffer_normal;
1433 return t;
1436 void vt_resize(Vt *t, int rows, int cols)
1438 struct winsize ws = { .ws_row = rows, .ws_col = cols };
1440 if (rows <= 0 || cols <= 0)
1441 return;
1443 vt_noscroll(t);
1444 buffer_resize(&t->buffer_normal, rows, cols);
1445 buffer_resize(&t->buffer_alternate, rows, cols);
1446 clamp_cursor_to_bounds(t);
1447 ioctl(t->pty, TIOCSWINSZ, &ws);
1448 kill(-t->pid, SIGWINCH);
1451 void vt_destroy(Vt *t)
1453 if (!t)
1454 return;
1455 buffer_free(&t->buffer_normal);
1456 buffer_free(&t->buffer_alternate);
1457 close(t->pty);
1458 free(t);
1461 void vt_dirty(Vt *t)
1463 Buffer *b = t->buffer;
1464 for (Row *row = b->lines, *end = row + b->rows; row < end; row++)
1465 row->dirty = true;
1468 void vt_draw(Vt *t, WINDOW * win, int srow, int scol)
1470 Buffer *b = t->buffer;
1472 if (srow != t->srow || scol != t->scol) {
1473 vt_dirty(t);
1474 t->srow = srow;
1475 t->scol = scol;
1478 for (int i = 0; i < b->rows; i++) {
1479 Row *row = b->lines + i;
1481 if (!row->dirty)
1482 continue;
1484 wmove(win, srow + i, scol);
1485 Cell *cell = NULL;
1486 for (int j = 0; j < b->cols; j++) {
1487 Cell *prev_cell = cell;
1488 cell = row->cells + j;
1489 if (!prev_cell || cell->attr != prev_cell->attr
1490 || cell->fg != prev_cell->fg
1491 || cell->bg != prev_cell->bg) {
1492 if (cell->attr == A_NORMAL)
1493 cell->attr = t->defattrs;
1494 if (cell->fg == -1)
1495 cell->fg = t->deffg;
1496 if (cell->bg == -1)
1497 cell->bg = t->defbg;
1498 wattrset(win, cell->attr << NCURSES_ATTR_SHIFT);
1499 wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL);
1502 if (is_utf8 && cell->text >= 128) {
1503 char buf[MB_CUR_MAX + 1];
1504 size_t len = wcrtomb(buf, cell->text, NULL);
1505 if (len > 0) {
1506 waddnstr(win, buf, len);
1507 if (wcwidth(cell->text) > 1)
1508 j++;
1510 } else {
1511 waddch(win, cell->text > ' ' ? cell->text : ' ');
1515 int x, y;
1516 getyx(win, y, x);
1517 (void)y;
1518 if (x && x < b->cols - 1)
1519 whline(win, ' ', b->cols - x);
1521 row->dirty = false;
1524 wmove(win, srow + b->curs_row - b->lines, scol + b->curs_col);
1527 void vt_scroll(Vt *t, int rows)
1529 Buffer *b = t->buffer;
1530 if (!b->scroll_buf_size)
1531 return;
1532 if (rows < 0) { /* scroll back */
1533 if (rows < -b->scroll_above)
1534 rows = -b->scroll_above;
1535 } else { /* scroll forward */
1536 if (rows > b->scroll_below)
1537 rows = b->scroll_below;
1539 fill_scroll_buf(b, rows);
1540 b->scroll_below -= rows;
1543 void vt_noscroll(Vt *t)
1545 int scroll_below = t->buffer->scroll_below;
1546 if (scroll_below)
1547 vt_scroll(t, scroll_below);
1550 void vt_bell(Vt *t, bool bell)
1552 t->bell = bell;
1555 void vt_togglebell(Vt *t)
1557 t->bell = !t->bell;
1560 pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from)
1562 int vt2ed[2], ed2vt[2];
1563 struct winsize ws;
1564 ws.ws_row = t->buffer->rows;
1565 ws.ws_col = t->buffer->cols;
1566 ws.ws_xpixel = ws.ws_ypixel = 0;
1568 if (to && pipe(vt2ed)) {
1569 *to = -1;
1570 to = NULL;
1572 if (from && pipe(ed2vt)) {
1573 *from = -1;
1574 from = NULL;
1577 pid_t pid = forkpty(&t->pty, NULL, NULL, &ws);
1578 if (pid < 0)
1579 return -1;
1581 if (pid == 0) {
1582 setsid();
1584 sigset_t emptyset;
1585 sigemptyset(&emptyset);
1586 sigprocmask(SIG_SETMASK, &emptyset, NULL);
1588 if (to) {
1589 close(vt2ed[1]);
1590 dup2(vt2ed[0], STDIN_FILENO);
1591 close(vt2ed[0]);
1594 if (from) {
1595 close(ed2vt[0]);
1596 dup2(ed2vt[1], STDOUT_FILENO);
1597 close(ed2vt[1]);
1600 int maxfd = sysconf(_SC_OPEN_MAX);
1601 for (int fd = 3; fd < maxfd; fd++)
1602 if (close(fd) == -1 && errno == EBADF)
1603 break;
1605 for (const char **envp = env; envp && envp[0]; envp += 2)
1606 setenv(envp[0], envp[1], 1);
1607 setenv("TERM", vt_term, 1);
1609 if (cwd)
1610 chdir(cwd);
1612 execvp(p, (char *const *)argv);
1613 fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
1614 exit(1);
1617 if (to) {
1618 close(vt2ed[0]);
1619 *to = vt2ed[1];
1622 if (from) {
1623 close(ed2vt[1]);
1624 *from = ed2vt[0];
1627 return t->pid = pid;
1630 int vt_pty_get(Vt *t)
1632 return t->pty;
1635 int vt_write(Vt *t, const char *buf, int len)
1637 int ret = len;
1639 while (len > 0) {
1640 int res = write(t->pty, buf, len);
1641 if (res < 0) {
1642 if (errno != EAGAIN && errno != EINTR)
1643 return -1;
1644 else
1645 continue;
1647 buf += res;
1648 len -= res;
1651 return ret;
1654 static void send_curs(Vt *t)
1656 Buffer *b = t->buffer;
1657 char keyseq[16];
1658 snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col);
1659 vt_write(t, keyseq, strlen(keyseq));
1662 void vt_keypress(Vt *t, int keycode)
1664 vt_noscroll(t);
1666 if (keycode >= 0 && keycode <= KEY_MAX && keytable[keycode]) {
1667 switch (keycode) {
1668 case KEY_UP:
1669 case KEY_DOWN:
1670 case KEY_RIGHT:
1671 case KEY_LEFT: {
1672 char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] };
1673 vt_write(t, keyseq, sizeof keyseq);
1674 break;
1676 default:
1677 vt_write(t, keytable[keycode], strlen(keytable[keycode]));
1679 } else if (keycode <= UCHAR_MAX) {
1680 char c = keycode;
1681 vt_write(t, &c, 1);
1682 } else {
1683 #ifndef NDEBUG
1684 fprintf(stderr, "unhandled key %#o\n", keycode);
1685 #endif
1689 void vt_mouse(Vt *t, int x, int y, mmask_t mask)
1691 #ifdef NCURSES_MOUSE_VERSION
1692 char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0;
1694 if (!t->mousetrack)
1695 return;
1697 if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED))
1698 button = 0;
1699 else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED))
1700 button = 1;
1701 else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED))
1702 button = 2;
1703 else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED))
1704 button = 3;
1706 if (mask & BUTTON_SHIFT)
1707 state |= 4;
1708 if (mask & BUTTON_ALT)
1709 state |= 8;
1710 if (mask & BUTTON_CTRL)
1711 state |= 16;
1713 seq[3] = 32 + button + state;
1714 seq[4] = 32 + x;
1715 seq[5] = 32 + y;
1717 vt_write(t, seq, sizeof seq);
1719 if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) {
1720 /* send a button release event */
1721 button = 3;
1722 seq[3] = 32 + button + state;
1723 vt_write(t, seq, sizeof seq);
1725 #endif /* NCURSES_MOUSE_VERSION */
1728 static unsigned int color_hash(short fg, short bg)
1730 if (fg == -1)
1731 fg = COLORS;
1732 if (bg == -1)
1733 bg = COLORS + 1;
1734 return fg * (COLORS + 2) + bg;
1737 short vt_color_get(Vt *t, short fg, short bg)
1739 if (fg >= COLORS)
1740 fg = (t ? t->deffg : default_fg);
1741 if (bg >= COLORS)
1742 bg = (t ? t->defbg : default_bg);
1744 if (!has_default_colors) {
1745 if (fg == -1)
1746 fg = (t && t->deffg != -1 ? t->deffg : default_fg);
1747 if (bg == -1)
1748 bg = (t && t->defbg != -1 ? t->defbg : default_bg);
1751 if (!color2palette || (fg == -1 && bg == -1))
1752 return 0;
1753 unsigned int index = color_hash(fg, bg);
1754 if (color2palette[index] == 0) {
1755 short oldfg, oldbg;
1756 for (;;) {
1757 if (++color_pair_current >= color_pairs_max)
1758 color_pair_current = color_pairs_reserved + 1;
1759 pair_content(color_pair_current, &oldfg, &oldbg);
1760 unsigned int old_index = color_hash(oldfg, oldbg);
1761 if (color2palette[old_index] >= 0) {
1762 if (init_pair(color_pair_current, fg, bg) == OK) {
1763 color2palette[old_index] = 0;
1764 color2palette[index] = color_pair_current;
1766 break;
1771 short color_pair = color2palette[index];
1772 return color_pair >= 0 ? color_pair : -color_pair;
1775 short vt_color_reserve(short fg, short bg)
1777 if (!color2palette || fg >= COLORS || bg >= COLORS)
1778 return 0;
1779 if (!has_default_colors && fg == -1)
1780 fg = default_fg;
1781 if (!has_default_colors && bg == -1)
1782 bg = default_bg;
1783 if (fg == -1 && bg == -1)
1784 return 0;
1785 unsigned int index = color_hash(fg, bg);
1786 if (color2palette[index] >= 0) {
1787 if (init_pair(color_pairs_reserved + 1, fg, bg) == OK)
1788 color2palette[index] = -(++color_pairs_reserved);
1790 short color_pair = color2palette[index];
1791 return color_pair >= 0 ? color_pair : -color_pair;
1794 static void init_colors(void)
1796 pair_content(0, &default_fg, &default_bg);
1797 if (default_fg == -1)
1798 default_fg = COLOR_WHITE;
1799 if (default_bg == -1)
1800 default_bg = COLOR_BLACK;
1801 has_default_colors = (use_default_colors() == OK);
1802 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
1803 if (COLORS)
1804 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
1805 vt_color_reserve(COLOR_WHITE, COLOR_BLACK);
1808 void vt_init(void)
1810 init_colors();
1811 is_utf8_locale();
1812 char color_suffix[] = "-256color";
1813 char *term = getenv("DVTM_TERM");
1814 if (term)
1815 strncpy(vt_term, term, sizeof(vt_term) - sizeof(color_suffix));
1816 if (COLORS >= 256)
1817 strncat(vt_term, color_suffix, sizeof(color_suffix) - 1);
1820 void vt_keytable_set(const char * const keytable_overlay[], int count)
1822 for (int k = 0; k < count && k < KEY_MAX; k++) {
1823 const char *keyseq = keytable_overlay[k];
1824 if (keyseq)
1825 keytable[k] = keyseq;
1829 void vt_shutdown(void)
1831 free(color2palette);
1834 void vt_title_handler_set(Vt *t, vt_title_handler_t handler)
1836 t->title_handler = handler;
1839 void vt_data_set(Vt *t, void *data)
1841 t->data = data;
1844 void *vt_data_get(Vt *t)
1846 return t->data;
1849 bool vt_cursor_visible(Vt *t)
1851 return t->buffer->scroll_below ? false : !t->curshid;
1854 pid_t vt_pid_get(Vt *t)
1856 return t->pid;
1859 size_t vt_content_get(Vt *t, char **buf) {
1860 Buffer *b = t->buffer;
1861 int lines = b->scroll_above + b->scroll_below + b->rows + 1;
1862 size_t size = lines * (b->cols * MB_CUR_MAX + 1);
1863 mbstate_t ps;
1864 memset(&ps, 0, sizeof(ps));
1866 if (!(*buf = malloc(size)))
1867 return 0;
1869 char *s = *buf;
1871 for (Row *row = buffer_row_first(b); row; row = buffer_row_next(b, row)) {
1872 size_t len = 0;
1873 char *last_non_space = s;
1874 for (int col = 0; col < b->cols; col++) {
1875 if (row->cells[col].text) {
1876 len = wcrtomb(s, row->cells[col].text, &ps);
1877 if (len > 0)
1878 s += len;
1879 last_non_space = s;
1880 } else if (len) {
1881 len = 0;
1882 } else {
1883 *s++ = ' ';
1887 s = last_non_space;
1888 *s++ = '\n';
1891 return s - *buf;
1894 int vt_content_start(Vt *t) {
1895 return t->buffer->scroll_above;