Fix Mod-C and eliminate redundant shell process
[dvtm.git] / vt.c
blobac40670df4cd190ca1b1faa76a3e2d96ad297e32
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__) || defined(__DragonFly__)
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 LENGTH(arr) (sizeof(arr) / sizeof((arr)[0]))
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];
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 /* Buffer holding the current terminal window content (as an array) as well
104 * as the scroll back buffer content (as a circular/ring buffer).
106 * If new content is added to terminal the view port slides down and the
107 * previously top most line is moved into the scroll back buffer at postion
108 * scroll_index. This index will eventually wrap around and thus overwrite
109 * the oldest lines.
111 * In the scenerio below a scroll up has been performed. That is 'scroll_above'
112 * lines still lie above the current view port. Further scrolling up will show
113 * them. Similarly 'scroll_below' is the amount of lines below the current
114 * viewport.
116 * The function buffer_boundary sets the row pointers to the start/end range
117 * of the section delimiting the region before/after the viewport. The functions
118 * buffer_row_{first,last} return the first/last logical row. And
119 * buffer_row_{next,prev} allows to iterate over the logical lines in either
120 * direction.
122 * scroll back buffer
124 * scroll_buf->+----------------+-----+
125 * | | | ^ \
126 * | before | | | |
127 * current terminal content | viewport | | | |
128 * | | | |
129 * +----------------+-----+\ | | | s > scroll_above
130 * ^ | | i | \ | | i | c |
131 * | | | n | \ | | n | r |
132 * | | v | \ | | v | o |
133 * r | | i | \ | | i | l /
134 * o | viewport | s | >|<- scroll_index | s | l \
135 * w | | i | / | | i | |
136 * s | | b | / | after | b | s > scroll_below
137 * | | l | / | viewport | l | i |
138 * v | | e | / | | e | z /
139 * +----------------+-----+/ | unused | | e
140 * <- maxcols -> | scroll back | |
141 * <- cols -> | buffer | | |
142 * | | | |
143 * | | | v
144 * roll_buf + scroll_size->+----------------+-----+
145 * <- maxcols ->
146 * <- cols ->
148 typedef struct {
149 Row *lines; /* array of Row pointers of size 'rows' */
150 Row *curs_row; /* row on which the cursor currently resides */
151 Row *scroll_buf; /* a ring buffer holding the scroll back content */
152 Row *scroll_top; /* row in lines where scrolling region starts */
153 Row *scroll_bot; /* row in lines where scrolling region ends */
154 bool *tabs; /* a boolean flag for each column whether it is a tab */
155 int scroll_size; /* maximal capacity of scroll back buffer (in lines) */
156 int scroll_index; /* current index into the ring buffer */
157 int scroll_above; /* number of lines above current viewport */
158 int scroll_below; /* number of lines below current viewport */
159 int rows, cols; /* current dimension of buffer */
160 int maxcols; /* allocated cells (maximal cols over time) */
161 attr_t curattrs, savattrs; /* current and saved attributes for cells */
162 int curs_col; /* current cursor column (zero based) */
163 int curs_srow, curs_scol; /* saved cursor row/colmn (zero based) */
164 short curfg, curbg; /* current fore and background colors */
165 short savfg, savbg; /* saved colors */
166 } Buffer;
168 struct Vt {
169 Buffer buffer_normal; /* normal screen buffer */
170 Buffer buffer_alternate; /* alternate screen buffer */
171 Buffer *buffer; /* currently active buffer (one of the above) */
172 attr_t defattrs; /* attributes to use for normal/empty cells */
173 short deffg, defbg; /* colors to use for back normal/empty cells (white/black) */
174 int pty; /* master side pty file descriptor */
175 pid_t pid; /* process id of the process running in this vt */
176 /* flags */
177 unsigned seen_input:1;
178 unsigned insert:1;
179 unsigned escaped:1;
180 unsigned curshid:1;
181 unsigned curskeymode:1;
182 unsigned bell:1;
183 unsigned relposmode:1;
184 unsigned mousetrack:1;
185 unsigned graphmode:1;
186 unsigned savgraphmode:1;
187 bool charsets[2];
188 /* buffers and parsing state */
189 char rbuf[BUFSIZ];
190 char ebuf[BUFSIZ];
191 unsigned int rlen, elen;
192 int srow, scol; /* last known offset to display start row, start column */
193 char title[256]; /* xterm style window title */
194 vt_title_handler_t title_handler; /* hook which is called when title changes */
195 vt_urgent_handler_t urgent_handler; /* hook which is called upon bell */
196 void *data; /* user supplied data */
199 static const char *keytable[KEY_MAX+1] = {
200 [KEY_ENTER] = "\r",
201 ['\n'] = "\n",
202 /* for the arrow keys the CSI / SS3 sequences are not stored here
203 * because they depend on the current cursor terminal mode
205 [KEY_UP] = "A",
206 [KEY_DOWN] = "B",
207 [KEY_RIGHT] = "C",
208 [KEY_LEFT] = "D",
209 #ifdef KEY_SUP
210 [KEY_SUP] = "\e[1;2A",
211 #endif
212 #ifdef KEY_SDOWN
213 [KEY_SDOWN] = "\e[1;2B",
214 #endif
215 [KEY_SRIGHT] = "\e[1;2C",
216 [KEY_SLEFT] = "\e[1;2D",
217 [KEY_BACKSPACE] = "\177",
218 [KEY_IC] = "\e[2~",
219 [KEY_DC] = "\e[3~",
220 [KEY_PPAGE] = "\e[5~",
221 [KEY_NPAGE] = "\e[6~",
222 [KEY_HOME] = "\e[7~",
223 [KEY_END] = "\e[8~",
224 [KEY_BTAB] = "\e[Z",
225 [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */
226 [KEY_F(1)] = "\e[11~",
227 [KEY_F(2)] = "\e[12~",
228 [KEY_F(3)] = "\e[13~",
229 [KEY_F(4)] = "\e[14~",
230 [KEY_F(5)] = "\e[15~",
231 [KEY_F(6)] = "\e[17~",
232 [KEY_F(7)] = "\e[18~",
233 [KEY_F(8)] = "\e[19~",
234 [KEY_F(9)] = "\e[20~",
235 [KEY_F(10)] = "\e[21~",
236 [KEY_F(11)] = "\e[23~",
237 [KEY_F(12)] = "\e[24~",
238 [KEY_F(13)] = "\e[23~",
239 [KEY_F(14)] = "\e[24~",
240 [KEY_F(15)] = "\e[25~",
241 [KEY_F(16)] = "\e[26~",
242 [KEY_F(17)] = "\e[28~",
243 [KEY_F(18)] = "\e[29~",
244 [KEY_F(19)] = "\e[31~",
245 [KEY_F(20)] = "\e[32~",
246 [KEY_F(21)] = "\e[33~",
247 [KEY_F(22)] = "\e[34~",
248 [KEY_RESIZE] = "",
249 #ifdef KEY_EVENT
250 [KEY_EVENT] = "",
251 #endif
254 static void puttab(Vt *t, int count);
255 static void process_nonprinting(Vt *t, wchar_t wc);
256 static void send_curs(Vt *t);
258 __attribute__ ((const))
259 static attr_t build_attrs(attr_t curattrs)
261 return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
262 >> NCURSES_ATTR_SHIFT;
265 static void row_set(Row *row, int start, int len, Buffer *t)
267 Cell cell = {
268 .text = L'\0',
269 .attr = t ? build_attrs(t->curattrs) : 0,
270 .fg = t ? t->curfg : -1,
271 .bg = t ? t->curbg : -1,
274 for (int i = start; i < len + start; i++)
275 row->cells[i] = cell;
276 row->dirty = true;
279 static void row_roll(Row *start, Row *end, int count)
281 int n = end - start;
283 count %= n;
284 if (count < 0)
285 count += n;
287 if (count) {
288 char buf[count * sizeof(Row)];
289 memcpy(buf, start, count * sizeof(Row));
290 memmove(start, start + count, (n - count) * sizeof(Row));
291 memcpy(end - count, buf, count * sizeof(Row));
292 for (Row *row = start; row < end; row++)
293 row->dirty = true;
297 static void buffer_clear(Buffer *b)
299 Cell cell = {
300 .text = L'\0',
301 .attr = A_NORMAL,
302 .fg = -1,
303 .bg = -1,
306 for (int i = 0; i < b->rows; i++) {
307 Row *row = b->lines + i;
308 for (int j = 0; j < b->cols; j++) {
309 row->cells[j] = cell;
310 row->dirty = true;
315 static void buffer_free(Buffer *b)
317 for (int i = 0; i < b->rows; i++)
318 free(b->lines[i].cells);
319 free(b->lines);
320 for (int i = 0; i < b->scroll_size; i++)
321 free(b->scroll_buf[i].cells);
322 free(b->scroll_buf);
323 free(b->tabs);
326 static void buffer_scroll(Buffer *b, int s)
328 /* work in screenfuls */
329 int ssz = b->scroll_bot - b->scroll_top;
330 if (s > ssz) {
331 buffer_scroll(b, ssz);
332 buffer_scroll(b, s - ssz);
333 return;
335 if (s < -ssz) {
336 buffer_scroll(b, -ssz);
337 buffer_scroll(b, s + ssz);
338 return;
341 b->scroll_above += s;
342 if (b->scroll_above >= b->scroll_size)
343 b->scroll_above = b->scroll_size;
345 if (s > 0 && b->scroll_size) {
346 for (int i = 0; i < s; i++) {
347 Row tmp = b->scroll_top[i];
348 b->scroll_top[i] = b->scroll_buf[b->scroll_index];
349 b->scroll_buf[b->scroll_index] = tmp;
351 b->scroll_index++;
352 if (b->scroll_index == b->scroll_size)
353 b->scroll_index = 0;
356 row_roll(b->scroll_top, b->scroll_bot, s);
357 if (s < 0 && b->scroll_size) {
358 for (int i = (-s) - 1; i >= 0; i--) {
359 b->scroll_index--;
360 if (b->scroll_index == -1)
361 b->scroll_index = b->scroll_size - 1;
363 Row tmp = b->scroll_top[i];
364 b->scroll_top[i] = b->scroll_buf[b->scroll_index];
365 b->scroll_buf[b->scroll_index] = tmp;
366 b->scroll_top[i].dirty = true;
371 static void buffer_resize(Buffer *b, int rows, int cols)
373 Row *lines = b->lines;
375 if (b->rows != rows) {
376 if (b->curs_row >= lines + rows) {
377 /* scroll up instead of simply chopping off bottom */
378 buffer_scroll(b, (b->curs_row - b->lines) - rows + 1);
380 while (b->rows > rows) {
381 free(lines[b->rows - 1].cells);
382 b->rows--;
385 lines = realloc(lines, sizeof(Row) * rows);
388 if (b->maxcols < cols) {
389 for (int row = 0; row < b->rows; row++) {
390 lines[row].cells = realloc(lines[row].cells, sizeof(Cell) * cols);
391 if (b->cols < cols)
392 row_set(lines + row, b->cols, cols - b->cols, NULL);
393 lines[row].dirty = true;
395 Row *sbuf = b->scroll_buf;
396 for (int row = 0; row < b->scroll_size; row++) {
397 sbuf[row].cells = realloc(sbuf[row].cells, sizeof(Cell) * cols);
398 if (b->cols < cols)
399 row_set(sbuf + row, b->cols, cols - b->cols, NULL);
401 b->tabs = realloc(b->tabs, sizeof(*b->tabs) * cols);
402 for (int col = b->cols; col < cols; col++)
403 b->tabs[col] = !(col & 7);
404 b->maxcols = cols;
405 b->cols = cols;
406 } else if (b->cols != cols) {
407 for (int row = 0; row < b->rows; row++)
408 lines[row].dirty = true;
409 b->cols = cols;
412 int deltarows = 0;
413 if (b->rows < rows) {
414 while (b->rows < rows) {
415 lines[b->rows].cells = calloc(b->maxcols, sizeof(Cell));
416 row_set(lines + b->rows, 0, b->maxcols, b);
417 b->rows++;
420 /* prepare for backfill */
421 if (b->curs_row >= b->scroll_bot - 1) {
422 deltarows = b->lines + rows - b->curs_row - 1;
423 if (deltarows > b->scroll_above)
424 deltarows = b->scroll_above;
428 b->curs_row += lines - b->lines;
429 b->scroll_top = lines;
430 b->scroll_bot = lines + rows;
431 b->lines = lines;
433 /* perform backfill */
434 if (deltarows > 0) {
435 buffer_scroll(b, -deltarows);
436 b->curs_row += deltarows;
440 static bool buffer_init(Buffer *b, int rows, int cols, int scroll_size)
442 b->curattrs = A_NORMAL; /* white text over black background */
443 b->curfg = b->curbg = -1;
444 if (scroll_size < 0)
445 scroll_size = 0;
446 if (scroll_size && !(b->scroll_buf = calloc(scroll_size, sizeof(Row))))
447 return false;
448 b->scroll_size = scroll_size;
449 buffer_resize(b, rows, cols);
450 return true;
453 static void buffer_boundry(Buffer *b, Row **bs, Row **be, Row **as, Row **ae) {
454 if (bs)
455 *bs = NULL;
456 if (be)
457 *be = NULL;
458 if (as)
459 *as = NULL;
460 if (ae)
461 *ae = NULL;
462 if (!b->scroll_size)
463 return;
465 if (b->scroll_above) {
466 if (bs)
467 *bs = &b->scroll_buf[(b->scroll_index - b->scroll_above + b->scroll_size) % b->scroll_size];
468 if (be)
469 *be = &b->scroll_buf[(b->scroll_index-1 + b->scroll_size) % b->scroll_size];
471 if (b->scroll_below) {
472 if (as)
473 *as = &b->scroll_buf[b->scroll_index];
474 if (ae)
475 *ae = &b->scroll_buf[(b->scroll_index + b->scroll_below-1) % b->scroll_size];
479 static Row *buffer_row_first(Buffer *b) {
480 Row *bstart;
481 if (!b->scroll_size || !b->scroll_above)
482 return b->lines;
483 buffer_boundry(b, &bstart, NULL, NULL, NULL);
484 return bstart;
487 static Row *buffer_row_last(Buffer *b) {
488 Row *aend;
489 if (!b->scroll_size || !b->scroll_below)
490 return b->lines + b->rows - 1;
491 buffer_boundry(b, NULL, NULL, NULL, &aend);
492 return aend;
495 static Row *buffer_row_next(Buffer *b, Row *row)
497 Row *before_start, *before_end, *after_start, *after_end;
498 Row *first = b->lines, *last = b->lines + b->rows - 1;
500 if (!row)
501 return NULL;
503 buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
505 if (row >= first && row < last)
506 return ++row;
507 if (row == last)
508 return after_start;
509 if (row == before_end)
510 return first;
511 if (row == after_end)
512 return NULL;
513 if (row == &b->scroll_buf[b->scroll_size - 1])
514 return b->scroll_buf;
515 return ++row;
518 static Row *buffer_row_prev(Buffer *b, Row *row)
520 Row *before_start, *before_end, *after_start, *after_end;
521 Row *first = b->lines, *last = b->lines + b->rows - 1;
523 if (!row)
524 return NULL;
526 buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
528 if (row > first && row <= last)
529 return --row;
530 if (row == first)
531 return before_end;
532 if (row == before_start)
533 return NULL;
534 if (row == after_start)
535 return last;
536 if (row == b->scroll_buf)
537 return &b->scroll_buf[b->scroll_size - 1];
538 return --row;
541 static void cursor_clamp(Vt *t)
543 Buffer *b = t->buffer;
544 Row *lines = t->relposmode ? b->scroll_top : b->lines;
545 int rows = t->relposmode ? b->scroll_bot - b->scroll_top : b->rows;
547 if (b->curs_row < lines)
548 b->curs_row = lines;
549 if (b->curs_row >= lines + rows)
550 b->curs_row = lines + rows - 1;
551 if (b->curs_col < 0)
552 b->curs_col = 0;
553 if (b->curs_col >= b->cols)
554 b->curs_col = b->cols - 1;
557 static void cursor_line_down(Vt *t)
559 Buffer *b = t->buffer;
560 row_set(b->curs_row, b->cols, b->maxcols - b->cols, NULL);
561 b->curs_row++;
562 if (b->curs_row < b->scroll_bot)
563 return;
565 vt_noscroll(t);
567 b->curs_row = b->scroll_bot - 1;
568 buffer_scroll(b, 1);
569 row_set(b->curs_row, 0, b->cols, b);
572 static void cursor_save(Vt *t)
574 Buffer *b = t->buffer;
575 b->curs_srow = b->curs_row - b->lines;
576 b->curs_scol = b->curs_col;
579 static void cursor_restore(Vt *t)
581 Buffer *b = t->buffer;
582 b->curs_row = b->lines + b->curs_srow;
583 b->curs_col = b->curs_scol;
584 cursor_clamp(t);
587 static void attributes_save(Vt *t)
589 Buffer *b = t->buffer;
590 b->savattrs = b->curattrs;
591 b->savfg = b->curfg;
592 b->savbg = b->curbg;
593 t->savgraphmode = t->graphmode;
596 static void attributes_restore(Vt *t)
598 Buffer *b = t->buffer;
599 b->curattrs = b->savattrs;
600 b->curfg = b->savfg;
601 b->curbg = b->savbg;
602 t->graphmode = t->savgraphmode;
605 static void new_escape_sequence(Vt *t)
607 t->escaped = true;
608 t->elen = 0;
609 t->ebuf[0] = '\0';
612 static void cancel_escape_sequence(Vt *t)
614 t->escaped = false;
615 t->elen = 0;
616 t->ebuf[0] = '\0';
619 static bool is_valid_csi_ender(int c)
621 return (c >= 'a' && c <= 'z')
622 || (c >= 'A' && c <= 'Z')
623 || (c == '@' || c == '`');
626 /* interprets a 'set attribute' (SGR) CSI escape sequence */
627 static void interpret_csi_sgr(Vt *t, int param[], int pcount)
629 Buffer *b = t->buffer;
630 if (pcount == 0) {
631 /* special case: reset attributes */
632 b->curattrs = A_NORMAL;
633 b->curfg = b->curbg = -1;
634 return;
637 for (int i = 0; i < pcount; i++) {
638 switch (param[i]) {
639 case 0:
640 b->curattrs = A_NORMAL;
641 b->curfg = b->curbg = -1;
642 break;
643 case 1:
644 b->curattrs |= A_BOLD;
645 break;
646 case 2:
647 b->curattrs |= A_DIM;
648 break;
649 #ifdef A_ITALIC
650 case 3:
651 b->curattrs |= A_ITALIC;
652 break;
653 #endif
654 case 4:
655 b->curattrs |= A_UNDERLINE;
656 break;
657 case 5:
658 b->curattrs |= A_BLINK;
659 break;
660 case 7:
661 b->curattrs |= A_REVERSE;
662 break;
663 case 8:
664 b->curattrs |= A_INVIS;
665 break;
666 case 22:
667 b->curattrs &= ~(A_BOLD | A_DIM);
668 break;
669 #ifdef A_ITALIC
670 case 23:
671 b->curattrs &= ~A_ITALIC;
672 break;
673 #endif
674 case 24:
675 b->curattrs &= ~A_UNDERLINE;
676 break;
677 case 25:
678 b->curattrs &= ~A_BLINK;
679 break;
680 case 27:
681 b->curattrs &= ~A_REVERSE;
682 break;
683 case 28:
684 b->curattrs &= ~A_INVIS;
685 break;
686 case 30 ... 37: /* fg */
687 b->curfg = param[i] - 30;
688 break;
689 case 38:
690 if ((i + 2) < pcount && param[i + 1] == 5) {
691 b->curfg = param[i + 2];
692 i += 2;
694 break;
695 case 39:
696 b->curfg = -1;
697 break;
698 case 40 ... 47: /* bg */
699 b->curbg = param[i] - 40;
700 break;
701 case 48:
702 if ((i + 2) < pcount && param[i + 1] == 5) {
703 b->curbg = param[i + 2];
704 i += 2;
706 break;
707 case 49:
708 b->curbg = -1;
709 break;
710 case 90 ... 97: /* hi fg */
711 b->curfg = param[i] - 82;
712 break;
713 case 100 ... 107: /* hi bg */
714 b->curbg = param[i] - 92;
715 break;
716 default:
717 break;
722 /* interprets an 'erase display' (ED) escape sequence */
723 static void interpret_csi_ed(Vt *t, int param[], int pcount)
725 Row *row, *start, *end;
726 Buffer *b = t->buffer;
728 attributes_save(t);
729 b->curattrs = A_NORMAL;
730 b->curfg = b->curbg = -1;
732 if (pcount && param[0] == 2) {
733 start = b->lines;
734 end = b->lines + b->rows;
735 } else if (pcount && param[0] == 1) {
736 start = b->lines;
737 end = b->curs_row;
738 row_set(b->curs_row, 0, b->curs_col + 1, b);
739 } else {
740 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
741 start = b->curs_row + 1;
742 end = b->lines + b->rows;
745 for (row = start; row < end; row++)
746 row_set(row, 0, b->cols, b);
748 attributes_restore(t);
751 /* interprets a 'move cursor' (CUP) escape sequence */
752 static void interpret_csi_cup(Vt *t, int param[], int pcount)
754 Buffer *b = t->buffer;
755 Row *lines = t->relposmode ? b->scroll_top : b->lines;
757 if (pcount == 0) {
758 b->curs_row = lines;
759 b->curs_col = 0;
760 } else if (pcount == 1) {
761 b->curs_row = lines + param[0] - 1;
762 b->curs_col = 0;
763 } else {
764 b->curs_row = lines + param[0] - 1;
765 b->curs_col = param[1] - 1;
768 cursor_clamp(t);
771 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
772 * CPL, CHA, HPR, VPA, VPR, HPA */
773 static void interpret_csi_c(Vt *t, char verb, int param[], int pcount)
775 Buffer *b = t->buffer;
776 int n = (pcount && param[0] > 0) ? param[0] : 1;
778 switch (verb) {
779 case 'A':
780 b->curs_row -= n;
781 break;
782 case 'B':
783 case 'e':
784 b->curs_row += n;
785 break;
786 case 'C':
787 case 'a':
788 b->curs_col += n;
789 break;
790 case 'D':
791 b->curs_col -= n;
792 break;
793 case 'E':
794 b->curs_row += n;
795 b->curs_col = 0;
796 break;
797 case 'F':
798 b->curs_row -= n;
799 b->curs_col = 0;
800 break;
801 case 'G':
802 case '`':
803 b->curs_col = n - 1;
804 break;
805 case 'd':
806 b->curs_row = b->lines + n - 1;
807 break;
810 cursor_clamp(t);
813 /* Interpret the 'erase line' escape sequence */
814 static void interpret_csi_el(Vt *t, int param[], int pcount)
816 Buffer *b = t->buffer;
817 switch (pcount ? param[0] : 0) {
818 case 1:
819 row_set(b->curs_row, 0, b->curs_col + 1, b);
820 break;
821 case 2:
822 row_set(b->curs_row, 0, b->cols, b);
823 break;
824 default:
825 row_set(b->curs_row, b->curs_col, b->cols - b->curs_col, b);
826 break;
830 /* Interpret the 'insert blanks' sequence (ICH) */
831 static void interpret_csi_ich(Vt *t, int param[], int pcount)
833 Buffer *b = t->buffer;
834 Row *row = b->curs_row;
835 int n = (pcount && param[0] > 0) ? param[0] : 1;
837 if (b->curs_col + n > b->cols)
838 n = b->cols - b->curs_col;
840 for (int i = b->cols - 1; i >= b->curs_col + n; i--)
841 row->cells[i] = row->cells[i - n];
843 row_set(row, b->curs_col, n, b);
846 /* Interpret the 'delete chars' sequence (DCH) */
847 static void interpret_csi_dch(Vt *t, int param[], int pcount)
849 Buffer *b = t->buffer;
850 Row *row = b->curs_row;
851 int n = (pcount && param[0] > 0) ? param[0] : 1;
853 if (b->curs_col + n > b->cols)
854 n = b->cols - b->curs_col;
856 for (int i = b->curs_col; i < b->cols - n; i++)
857 row->cells[i] = row->cells[i + n];
859 row_set(row, b->cols - n, n, b);
862 /* Interpret an 'insert line' sequence (IL) */
863 static void interpret_csi_il(Vt *t, int param[], int pcount)
865 Buffer *b = t->buffer;
866 int n = (pcount && param[0] > 0) ? param[0] : 1;
868 if (b->curs_row + n >= b->scroll_bot) {
869 for (Row *row = b->curs_row; row < b->scroll_bot; row++)
870 row_set(row, 0, b->cols, b);
871 } else {
872 row_roll(b->curs_row, b->scroll_bot, -n);
873 for (Row *row = b->curs_row; row < b->curs_row + n; row++)
874 row_set(row, 0, b->cols, b);
878 /* Interpret a 'delete line' sequence (DL) */
879 static void interpret_csi_dl(Vt *t, int param[], int pcount)
881 Buffer *b = t->buffer;
882 int n = (pcount && param[0] > 0) ? param[0] : 1;
884 if (b->curs_row + n >= b->scroll_bot) {
885 for (Row *row = b->curs_row; row < b->scroll_bot; row++)
886 row_set(row, 0, b->cols, b);
887 } else {
888 row_roll(b->curs_row, b->scroll_bot, n);
889 for (Row *row = b->scroll_bot - n; row < b->scroll_bot; row++)
890 row_set(row, 0, b->cols, b);
894 /* Interpret an 'erase characters' (ECH) sequence */
895 static void interpret_csi_ech(Vt *t, int param[], int pcount)
897 Buffer *b = t->buffer;
898 int n = (pcount && param[0] > 0) ? param[0] : 1;
900 if (b->curs_col + n > b->cols)
901 n = b->cols - b->curs_col;
903 row_set(b->curs_row, b->curs_col, n, b);
906 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
907 static void interpret_csi_decstbm(Vt *t, int param[], int pcount)
909 Buffer *b = t->buffer;
910 int new_top, new_bot;
912 switch (pcount) {
913 case 0:
914 b->scroll_top = b->lines;
915 b->scroll_bot = b->lines + b->rows;
916 break;
917 case 2:
918 new_top = param[0] - 1;
919 new_bot = param[1];
921 /* clamp to bounds */
922 if (new_top < 0)
923 new_top = 0;
924 if (new_top >= b->rows)
925 new_top = b->rows - 1;
926 if (new_bot < 0)
927 new_bot = 0;
928 if (new_bot >= b->rows)
929 new_bot = b->rows;
931 /* check for range validity */
932 if (new_top < new_bot) {
933 b->scroll_top = b->lines + new_top;
934 b->scroll_bot = b->lines + new_bot;
936 break;
937 default:
938 return; /* malformed */
940 b->curs_row = b->scroll_top;
941 b->curs_col = 0;
944 static void interpret_csi_mode(Vt *t, int param[], int pcount, bool set)
946 for (int i = 0; i < pcount; i++) {
947 switch (param[i]) {
948 case 4: /* insert/replace mode */
949 t->insert = set;
950 break;
955 static void interpret_csi_priv_mode(Vt *t, int param[], int pcount, bool set)
957 for (int i = 0; i < pcount; i++) {
958 switch (param[i]) {
959 case 1: /* set application/normal cursor key mode (DECCKM) */
960 t->curskeymode = set;
961 break;
962 case 6: /* set origin to relative/absolute (DECOM) */
963 t->relposmode = set;
964 break;
965 case 25: /* make cursor visible/invisible (DECCM) */
966 t->curshid = !set;
967 break;
968 case 1049: /* combine 1047 + 1048 */
969 case 47: /* use alternate/normal screen buffer */
970 case 1047:
971 if (!set)
972 buffer_clear(&t->buffer_alternate);
973 t->buffer = set ? &t->buffer_alternate : &t->buffer_normal;
974 vt_dirty(t);
975 if (param[i] != 1049)
976 break;
977 /* fall through */
978 case 1048: /* save/restore cursor */
979 if (set)
980 cursor_save(t);
981 else
982 cursor_restore(t);
983 break;
984 case 1000: /* enable/disable normal mouse tracking */
985 t->mousetrack = set;
986 break;
991 static void interpret_csi(Vt *t)
993 Buffer *b = t->buffer;
994 int csiparam[16];
995 unsigned int param_count = 0;
996 const char *p = t->ebuf + 1;
997 char verb = t->ebuf[t->elen - 1];
999 /* parse numeric parameters */
1000 for (p += (t->ebuf[1] == '?'); *p; p++) {
1001 if (IS_CONTROL(*p)) {
1002 process_nonprinting(t, *p);
1003 } else if (*p == ';') {
1004 if (param_count >= LENGTH(csiparam))
1005 return; /* too long! */
1006 csiparam[param_count++] = 0;
1007 } else if (isdigit((unsigned char)*p)) {
1008 if (param_count == 0)
1009 csiparam[param_count++] = 0;
1010 csiparam[param_count - 1] *= 10;
1011 csiparam[param_count - 1] += *p - '0';
1015 if (t->ebuf[1] == '?') {
1016 switch (verb) {
1017 case 'h':
1018 case 'l': /* private set/reset mode */
1019 interpret_csi_priv_mode(t, csiparam, param_count, verb == 'h');
1020 break;
1022 return;
1025 /* delegate handling depending on command character (verb) */
1026 switch (verb) {
1027 case 'h':
1028 case 'l': /* set/reset mode */
1029 interpret_csi_mode(t, csiparam, param_count, verb == 'h');
1030 break;
1031 case 'm': /* set attribute */
1032 interpret_csi_sgr(t, csiparam, param_count);
1033 break;
1034 case 'J': /* erase display */
1035 interpret_csi_ed(t, csiparam, param_count);
1036 break;
1037 case 'H':
1038 case 'f': /* move cursor */
1039 interpret_csi_cup(t, csiparam, param_count);
1040 break;
1041 case 'A':
1042 case 'B':
1043 case 'C':
1044 case 'D':
1045 case 'E':
1046 case 'F':
1047 case 'G':
1048 case 'e':
1049 case 'a':
1050 case 'd':
1051 case '`': /* relative move */
1052 interpret_csi_c(t, verb, csiparam, param_count);
1053 break;
1054 case 'K': /* erase line */
1055 interpret_csi_el(t, csiparam, param_count);
1056 break;
1057 case '@': /* insert characters */
1058 interpret_csi_ich(t, csiparam, param_count);
1059 break;
1060 case 'P': /* delete characters */
1061 interpret_csi_dch(t, csiparam, param_count);
1062 break;
1063 case 'L': /* insert lines */
1064 interpret_csi_il(t, csiparam, param_count);
1065 break;
1066 case 'M': /* delete lines */
1067 interpret_csi_dl(t, csiparam, param_count);
1068 break;
1069 case 'X': /* erase chars */
1070 interpret_csi_ech(t, csiparam, param_count);
1071 break;
1072 case 'S': /* SU: scroll up */
1073 vt_scroll(t, param_count ? -csiparam[0] : -1);
1074 break;
1075 case 'T': /* SD: scroll down */
1076 vt_scroll(t, param_count ? csiparam[0] : 1);
1077 break;
1078 case 'Z': /* CBT: cursor backward tabulation */
1079 puttab(t, param_count ? -csiparam[0] : -1);
1080 break;
1081 case 'g': /* TBC: tabulation clear */
1082 switch (param_count ? csiparam[0] : 0) {
1083 case 0:
1084 b->tabs[b->curs_col] = false;
1085 break;
1086 case 3:
1087 memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols);
1088 break;
1090 break;
1091 case 'r': /* set scrolling region */
1092 interpret_csi_decstbm(t, csiparam, param_count);
1093 break;
1094 case 's': /* save cursor location */
1095 cursor_save(t);
1096 break;
1097 case 'u': /* restore cursor location */
1098 cursor_restore(t);
1099 break;
1100 case 'n': /* query cursor location */
1101 if (param_count == 1 && csiparam[0] == 6)
1102 send_curs(t);
1103 break;
1104 default:
1105 break;
1109 /* Interpret an 'index' (IND) sequence */
1110 static void interpret_csi_ind(Vt *t)
1112 Buffer *b = t->buffer;
1113 if (b->curs_row < b->lines + b->rows - 1)
1114 b->curs_row++;
1117 /* Interpret a 'reverse index' (RI) sequence */
1118 static void interpret_csi_ri(Vt *t)
1120 Buffer *b = t->buffer;
1121 if (b->curs_row > b->scroll_top)
1122 b->curs_row--;
1123 else {
1124 row_roll(b->scroll_top, b->scroll_bot, -1);
1125 row_set(b->scroll_top, 0, b->cols, b);
1129 /* Interpret a 'next line' (NEL) sequence */
1130 static void interpret_csi_nel(Vt *t)
1132 Buffer *b = t->buffer;
1133 if (b->curs_row < b->lines + b->rows - 1) {
1134 b->curs_row++;
1135 b->curs_col = 0;
1139 /* Interpret a 'select character set' (SCS) sequence */
1140 static void interpret_csi_scs(Vt *t)
1142 /* ESC ( sets G0, ESC ) sets G1 */
1143 t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0');
1144 t->graphmode = t->charsets[0];
1147 /* Interpret an 'operating system command' (OSC) sequence */
1148 static void interpret_osc(Vt *t)
1150 /* ESC ] command ; data BEL
1151 * ESC ] command ; data ESC \\
1152 * Note that BEL or ESC \\ have already been replaced with NUL.
1154 char *data = NULL;
1155 int command = strtoul(t->ebuf + 1, &data, 10);
1156 if (data && *data == ';') {
1157 switch (command) {
1158 case 0: /* icon name and window title */
1159 case 2: /* window title */
1160 if (t->title_handler)
1161 t->title_handler(t, data+1);
1162 break;
1163 case 1: /* icon name */
1164 break;
1165 default:
1166 #ifndef NDEBUG
1167 fprintf(stderr, "unknown OSC command: %d\n", command);
1168 #endif
1169 break;
1174 static void try_interpret_escape_seq(Vt *t)
1176 char lastchar = t->ebuf[t->elen - 1];
1178 if (!*t->ebuf)
1179 return;
1181 switch (*t->ebuf) {
1182 case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */
1183 if (t->elen == 2) {
1184 if (lastchar == '8') { /* DECALN */
1185 interpret_csi_ed(t, (int []){ 2 }, 1);
1186 goto handled;
1188 goto cancel;
1190 break;
1191 case '(':
1192 case ')':
1193 if (t->elen == 2) {
1194 interpret_csi_scs(t);
1195 goto handled;
1197 break;
1198 case ']': /* OSC - operating system command */
1199 if (lastchar == '\a' ||
1200 (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) {
1201 t->elen -= lastchar == '\a' ? 1 : 2;
1202 t->ebuf[t->elen] = '\0';
1203 interpret_osc(t);
1204 goto handled;
1206 break;
1207 case '[': /* CSI - control sequence introducer */
1208 if (is_valid_csi_ender(lastchar)) {
1209 interpret_csi(t);
1210 goto handled;
1212 break;
1213 case '7': /* DECSC: save cursor and attributes */
1214 attributes_save(t);
1215 cursor_save(t);
1216 goto handled;
1217 case '8': /* DECRC: restore cursor and attributes */
1218 attributes_restore(t);
1219 cursor_restore(t);
1220 goto handled;
1221 case 'D': /* IND: index */
1222 interpret_csi_ind(t);
1223 goto handled;
1224 case 'M': /* RI: reverse index */
1225 interpret_csi_ri(t);
1226 goto handled;
1227 case 'E': /* NEL: next line */
1228 interpret_csi_nel(t);
1229 goto handled;
1230 case 'H': /* HTS: horizontal tab set */
1231 t->buffer->tabs[t->buffer->curs_col] = true;
1232 goto handled;
1233 default:
1234 goto cancel;
1237 if (t->elen + 1 >= sizeof(t->ebuf)) {
1238 cancel:
1239 #ifndef NDEBUG
1240 fprintf(stderr, "cancelled: \\033");
1241 for (unsigned int i = 0; i < t->elen; i++) {
1242 if (isprint(t->ebuf[i])) {
1243 fputc(t->ebuf[i], stderr);
1244 } else {
1245 fprintf(stderr, "\\%03o", t->ebuf[i]);
1248 fputc('\n', stderr);
1249 #endif
1250 handled:
1251 cancel_escape_sequence(t);
1255 static void puttab(Vt *t, int count)
1257 Buffer *b = t->buffer;
1258 int direction = count >= 0 ? 1 : -1;
1259 for (int col = b->curs_col + direction; count; col += direction) {
1260 if (col < 0) {
1261 b->curs_col = 0;
1262 break;
1264 if (col >= b->cols) {
1265 b->curs_col = b->cols - 1;
1266 break;
1268 if (b->tabs[col]) {
1269 b->curs_col = col;
1270 count -= direction;
1275 static void process_nonprinting(Vt *t, wchar_t wc)
1277 Buffer *b = t->buffer;
1278 switch (wc) {
1279 case '\e': /* ESC */
1280 new_escape_sequence(t);
1281 break;
1282 case '\a': /* BEL */
1283 if (t->urgent_handler)
1284 t->urgent_handler(t);
1285 break;
1286 case '\b': /* BS */
1287 if (b->curs_col > 0)
1288 b->curs_col--;
1289 break;
1290 case '\t': /* HT */
1291 puttab(t, 1);
1292 break;
1293 case '\r': /* CR */
1294 b->curs_col = 0;
1295 break;
1296 case '\v': /* VT */
1297 case '\f': /* FF */
1298 case '\n': /* LF */
1299 cursor_line_down(t);
1300 break;
1301 case '\016': /* SO: shift out, invoke the G1 character set */
1302 t->graphmode = t->charsets[1];
1303 break;
1304 case '\017': /* SI: shift in, invoke the G0 character set */
1305 t->graphmode = t->charsets[0];
1306 break;
1310 static void is_utf8_locale(void)
1312 const char *cset = nl_langinfo(CODESET);
1313 if (!cset)
1314 cset = "ANSI_X3.4-1968";
1315 is_utf8 = !strcmp(cset, "UTF-8");
1318 static wchar_t get_vt100_graphic(char c)
1320 static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~";
1323 * 5f-7e standard vt100
1324 * 40-5e rxvt extension for extra curses acs chars
1326 static uint16_t const vt100_utf8[62] = {
1327 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47
1328 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f
1329 0, 0, 0, 0, 0, 0, 0, 0, // 50-57
1330 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f
1331 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
1332 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
1333 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
1334 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e
1337 if (is_utf8)
1338 return vt100_utf8[c - 0x41];
1339 else if (strchr(vt100_acs, c))
1340 return NCURSES_ACS(c);
1341 return '\0';
1344 static void put_wc(Vt *t, wchar_t wc)
1346 int width = 0;
1348 if (!t->seen_input) {
1349 t->seen_input = 1;
1350 kill(-t->pid, SIGWINCH);
1353 if (t->escaped) {
1354 if (t->elen + 1 < sizeof(t->ebuf)) {
1355 t->ebuf[t->elen] = wc;
1356 t->ebuf[++t->elen] = '\0';
1357 try_interpret_escape_seq(t);
1358 } else {
1359 cancel_escape_sequence(t);
1361 } else if (IS_CONTROL(wc)) {
1362 process_nonprinting(t, wc);
1363 } else {
1364 if (t->graphmode) {
1365 if (wc >= 0x41 && wc <= 0x7e) {
1366 wchar_t gc = get_vt100_graphic(wc);
1367 if (gc)
1368 wc = gc;
1370 width = 1;
1371 } else if ((width = wcwidth(wc)) < 1) {
1372 width = 1;
1374 Buffer *b = t->buffer;
1375 Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg };
1376 if (width == 2 && b->curs_col == b->cols - 1) {
1377 b->curs_row->cells[b->curs_col++] = blank_cell;
1378 b->curs_row->dirty = true;
1381 if (b->curs_col >= b->cols) {
1382 b->curs_col = 0;
1383 cursor_line_down(t);
1386 if (t->insert) {
1387 Cell *src = b->curs_row->cells + b->curs_col;
1388 Cell *dest = src + width;
1389 size_t len = b->cols - b->curs_col - width;
1390 memmove(dest, src, len * sizeof *dest);
1393 b->curs_row->cells[b->curs_col] = blank_cell;
1394 b->curs_row->cells[b->curs_col++].text = wc;
1395 b->curs_row->dirty = true;
1396 if (width == 2)
1397 b->curs_row->cells[b->curs_col++] = blank_cell;
1401 int vt_process(Vt *t)
1403 int res;
1404 unsigned int pos = 0;
1405 mbstate_t ps;
1406 memset(&ps, 0, sizeof(ps));
1408 if (t->pty < 0) {
1409 errno = EINVAL;
1410 return -1;
1413 res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen);
1414 if (res < 0)
1415 return -1;
1417 t->rlen += res;
1418 while (pos < t->rlen) {
1419 wchar_t wc;
1420 ssize_t len;
1422 len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &ps);
1423 if (len == -2) {
1424 t->rlen -= pos;
1425 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1426 return 0;
1429 if (len == -1) {
1430 len = 1;
1431 wc = t->rbuf[pos];
1434 pos += len ? len : 1;
1435 put_wc(t, wc);
1438 t->rlen -= pos;
1439 memmove(t->rbuf, t->rbuf + pos, t->rlen);
1440 return 0;
1443 void vt_default_colors_set(Vt *t, attr_t attrs, short fg, short bg)
1445 t->defattrs = attrs;
1446 t->deffg = fg;
1447 t->defbg = bg;
1450 Vt *vt_create(int rows, int cols, int scroll_size)
1452 if (rows <= 0 || cols <= 0)
1453 return NULL;
1455 Vt *t = calloc(1, sizeof(Vt));
1456 if (!t)
1457 return NULL;
1459 t->pty = -1;
1460 t->deffg = t->defbg = -1;
1461 t->buffer = &t->buffer_normal;
1463 if (!buffer_init(&t->buffer_normal, rows, cols, scroll_size) ||
1464 !buffer_init(&t->buffer_alternate, rows, cols, 0)) {
1465 free(t);
1466 return NULL;
1469 return t;
1472 void vt_resize(Vt *t, int rows, int cols)
1474 struct winsize ws = { .ws_row = rows, .ws_col = cols };
1476 if (rows <= 0 || cols <= 0)
1477 return;
1479 vt_noscroll(t);
1480 buffer_resize(&t->buffer_normal, rows, cols);
1481 buffer_resize(&t->buffer_alternate, rows, cols);
1482 cursor_clamp(t);
1483 ioctl(t->pty, TIOCSWINSZ, &ws);
1484 kill(-t->pid, SIGWINCH);
1487 void vt_destroy(Vt *t)
1489 if (!t)
1490 return;
1491 buffer_free(&t->buffer_normal);
1492 buffer_free(&t->buffer_alternate);
1493 close(t->pty);
1494 free(t);
1497 void vt_dirty(Vt *t)
1499 Buffer *b = t->buffer;
1500 for (Row *row = b->lines, *end = row + b->rows; row < end; row++)
1501 row->dirty = true;
1504 void vt_draw(Vt *t, WINDOW *win, int srow, int scol)
1506 Buffer *b = t->buffer;
1508 if (srow != t->srow || scol != t->scol) {
1509 vt_dirty(t);
1510 t->srow = srow;
1511 t->scol = scol;
1514 for (int i = 0; i < b->rows; i++) {
1515 Row *row = b->lines + i;
1517 if (!row->dirty)
1518 continue;
1520 wmove(win, srow + i, scol);
1521 Cell *cell = NULL;
1522 for (int j = 0; j < b->cols; j++) {
1523 Cell *prev_cell = cell;
1524 cell = row->cells + j;
1525 if (!prev_cell || cell->attr != prev_cell->attr
1526 || cell->fg != prev_cell->fg
1527 || cell->bg != prev_cell->bg) {
1528 if (cell->attr == A_NORMAL)
1529 cell->attr = t->defattrs;
1530 if (cell->fg == -1)
1531 cell->fg = t->deffg;
1532 if (cell->bg == -1)
1533 cell->bg = t->defbg;
1534 wattrset(win, cell->attr << NCURSES_ATTR_SHIFT);
1535 wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL);
1538 if (is_utf8 && cell->text >= 128) {
1539 char buf[MB_CUR_MAX + 1];
1540 size_t len = wcrtomb(buf, cell->text, NULL);
1541 if (len > 0) {
1542 waddnstr(win, buf, len);
1543 if (wcwidth(cell->text) > 1)
1544 j++;
1546 } else {
1547 waddch(win, cell->text > ' ' ? cell->text : ' ');
1551 int x, y;
1552 getyx(win, y, x);
1553 (void)y;
1554 if (x && x < b->cols - 1)
1555 whline(win, ' ', b->cols - x);
1557 row->dirty = false;
1560 wmove(win, srow + b->curs_row - b->lines, scol + b->curs_col);
1563 void vt_scroll(Vt *t, int rows)
1565 Buffer *b = t->buffer;
1566 if (!b->scroll_size)
1567 return;
1568 if (rows < 0) { /* scroll back */
1569 if (rows < -b->scroll_above)
1570 rows = -b->scroll_above;
1571 } else { /* scroll forward */
1572 if (rows > b->scroll_below)
1573 rows = b->scroll_below;
1575 buffer_scroll(b, rows);
1576 b->scroll_below -= rows;
1579 void vt_noscroll(Vt *t)
1581 int scroll_below = t->buffer->scroll_below;
1582 if (scroll_below)
1583 vt_scroll(t, scroll_below);
1586 pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from)
1588 int vt2ed[2], ed2vt[2];
1589 struct winsize ws;
1590 ws.ws_row = t->buffer->rows;
1591 ws.ws_col = t->buffer->cols;
1592 ws.ws_xpixel = ws.ws_ypixel = 0;
1594 if (to && pipe(vt2ed)) {
1595 *to = -1;
1596 to = NULL;
1598 if (from && pipe(ed2vt)) {
1599 *from = -1;
1600 from = NULL;
1603 pid_t pid = forkpty(&t->pty, NULL, NULL, &ws);
1604 if (pid < 0)
1605 return -1;
1607 if (pid == 0) {
1608 setsid();
1610 sigset_t emptyset;
1611 sigemptyset(&emptyset);
1612 sigprocmask(SIG_SETMASK, &emptyset, NULL);
1614 if (to) {
1615 close(vt2ed[1]);
1616 dup2(vt2ed[0], STDIN_FILENO);
1617 close(vt2ed[0]);
1620 if (from) {
1621 close(ed2vt[0]);
1622 dup2(ed2vt[1], STDOUT_FILENO);
1623 close(ed2vt[1]);
1626 int maxfd = sysconf(_SC_OPEN_MAX);
1627 for (int fd = 3; fd < maxfd; fd++)
1628 if (close(fd) == -1 && errno == EBADF)
1629 break;
1631 for (const char **envp = env; envp && envp[0]; envp += 2)
1632 setenv(envp[0], envp[1], 1);
1633 setenv("TERM", vt_term, 1);
1635 if (cwd)
1636 chdir(cwd);
1638 execvp(p, (char *const *)argv);
1639 fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
1640 exit(1);
1643 if (to) {
1644 close(vt2ed[0]);
1645 *to = vt2ed[1];
1648 if (from) {
1649 close(ed2vt[1]);
1650 *from = ed2vt[0];
1653 return t->pid = pid;
1656 int vt_pty_get(Vt *t)
1658 return t->pty;
1661 ssize_t vt_write(Vt *t, const char *buf, size_t len)
1663 ssize_t ret = len;
1665 while (len > 0) {
1666 ssize_t res = write(t->pty, buf, len);
1667 if (res < 0) {
1668 if (errno != EAGAIN && errno != EINTR)
1669 return -1;
1670 continue;
1672 buf += res;
1673 len -= res;
1676 return ret;
1679 static void send_curs(Vt *t)
1681 Buffer *b = t->buffer;
1682 char keyseq[16];
1683 snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col);
1684 vt_write(t, keyseq, strlen(keyseq));
1687 void vt_keypress(Vt *t, int keycode)
1689 vt_noscroll(t);
1691 if (keycode >= 0 && keycode <= KEY_MAX && keytable[keycode]) {
1692 switch (keycode) {
1693 case KEY_UP:
1694 case KEY_DOWN:
1695 case KEY_RIGHT:
1696 case KEY_LEFT: {
1697 char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] };
1698 vt_write(t, keyseq, sizeof keyseq);
1699 break;
1701 default:
1702 vt_write(t, keytable[keycode], strlen(keytable[keycode]));
1704 } else if (keycode <= UCHAR_MAX) {
1705 char c = keycode;
1706 vt_write(t, &c, 1);
1707 } else {
1708 #ifndef NDEBUG
1709 fprintf(stderr, "unhandled key %#o\n", keycode);
1710 #endif
1714 void vt_mouse(Vt *t, int x, int y, mmask_t mask)
1716 #ifdef NCURSES_MOUSE_VERSION
1717 char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0;
1719 if (!t->mousetrack)
1720 return;
1722 if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED))
1723 button = 0;
1724 else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED))
1725 button = 1;
1726 else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED))
1727 button = 2;
1728 else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED))
1729 button = 3;
1731 if (mask & BUTTON_SHIFT)
1732 state |= 4;
1733 if (mask & BUTTON_ALT)
1734 state |= 8;
1735 if (mask & BUTTON_CTRL)
1736 state |= 16;
1738 seq[3] = 32 + button + state;
1739 seq[4] = 32 + x;
1740 seq[5] = 32 + y;
1742 vt_write(t, seq, sizeof seq);
1744 if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) {
1745 /* send a button release event */
1746 button = 3;
1747 seq[3] = 32 + button + state;
1748 vt_write(t, seq, sizeof seq);
1750 #endif /* NCURSES_MOUSE_VERSION */
1753 static unsigned int color_hash(short fg, short bg)
1755 if (fg == -1)
1756 fg = COLORS;
1757 if (bg == -1)
1758 bg = COLORS + 1;
1759 return fg * (COLORS + 2) + bg;
1762 short vt_color_get(Vt *t, short fg, short bg)
1764 if (fg >= COLORS)
1765 fg = (t ? t->deffg : default_fg);
1766 if (bg >= COLORS)
1767 bg = (t ? t->defbg : default_bg);
1769 if (!has_default_colors) {
1770 if (fg == -1)
1771 fg = (t && t->deffg != -1 ? t->deffg : default_fg);
1772 if (bg == -1)
1773 bg = (t && t->defbg != -1 ? t->defbg : default_bg);
1776 if (!color2palette || (fg == -1 && bg == -1))
1777 return 0;
1778 unsigned int index = color_hash(fg, bg);
1779 if (color2palette[index] == 0) {
1780 short oldfg, oldbg;
1781 for (;;) {
1782 if (++color_pair_current >= color_pairs_max)
1783 color_pair_current = color_pairs_reserved + 1;
1784 pair_content(color_pair_current, &oldfg, &oldbg);
1785 unsigned int old_index = color_hash(oldfg, oldbg);
1786 if (color2palette[old_index] >= 0) {
1787 if (init_pair(color_pair_current, fg, bg) == OK) {
1788 color2palette[old_index] = 0;
1789 color2palette[index] = color_pair_current;
1791 break;
1796 short color_pair = color2palette[index];
1797 return color_pair >= 0 ? color_pair : -color_pair;
1800 short vt_color_reserve(short fg, short bg)
1802 if (!color2palette || fg >= COLORS || bg >= COLORS)
1803 return 0;
1804 if (!has_default_colors && fg == -1)
1805 fg = default_fg;
1806 if (!has_default_colors && bg == -1)
1807 bg = default_bg;
1808 if (fg == -1 && bg == -1)
1809 return 0;
1810 unsigned int index = color_hash(fg, bg);
1811 if (color2palette[index] >= 0) {
1812 if (init_pair(color_pairs_reserved + 1, fg, bg) == OK)
1813 color2palette[index] = -(++color_pairs_reserved);
1815 short color_pair = color2palette[index];
1816 return color_pair >= 0 ? color_pair : -color_pair;
1819 static void init_colors(void)
1821 pair_content(0, &default_fg, &default_bg);
1822 if (default_fg == -1)
1823 default_fg = COLOR_WHITE;
1824 if (default_bg == -1)
1825 default_bg = COLOR_BLACK;
1826 has_default_colors = (use_default_colors() == OK);
1827 color_pairs_max = MIN(COLOR_PAIRS, MAX_COLOR_PAIRS);
1828 if (COLORS)
1829 color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
1830 vt_color_reserve(COLOR_WHITE, COLOR_BLACK);
1833 void vt_init(void)
1835 init_colors();
1836 is_utf8_locale();
1837 char *term = getenv("DVTM_TERM");
1838 if (!term)
1839 term = "dvtm";
1840 snprintf(vt_term, sizeof vt_term, "%s%s", term, COLORS >= 256 ? "-256color" : "");
1843 void vt_keytable_set(const char * const keytable_overlay[], int count)
1845 for (int k = 0; k < count && k < KEY_MAX; k++) {
1846 const char *keyseq = keytable_overlay[k];
1847 if (keyseq)
1848 keytable[k] = keyseq;
1852 void vt_shutdown(void)
1854 free(color2palette);
1857 void vt_title_handler_set(Vt *t, vt_title_handler_t handler)
1859 t->title_handler = handler;
1862 void vt_urgent_handler_set(Vt *t, vt_urgent_handler_t handler)
1864 t->urgent_handler = handler;
1867 void vt_data_set(Vt *t, void *data)
1869 t->data = data;
1872 void *vt_data_get(Vt *t)
1874 return t->data;
1877 bool vt_cursor_visible(Vt *t)
1879 return t->buffer->scroll_below ? false : !t->curshid;
1882 pid_t vt_pid_get(Vt *t)
1884 return t->pid;
1887 size_t vt_content_get(Vt *t, char **buf, bool colored)
1889 Buffer *b = t->buffer;
1890 int lines = b->scroll_above + b->scroll_below + b->rows + 1;
1891 size_t size = lines * ((b->cols + 1) * ((colored ? 64 : 0) + MB_CUR_MAX));
1892 mbstate_t ps;
1893 memset(&ps, 0, sizeof(ps));
1895 if (!(*buf = malloc(size)))
1896 return 0;
1898 char *s = *buf;
1899 Cell *prev_cell = NULL;
1901 for (Row *row = buffer_row_first(b); row; row = buffer_row_next(b, row)) {
1902 size_t len = 0;
1903 char *last_non_space = s;
1904 for (int col = 0; col < b->cols; col++) {
1905 Cell *cell = row->cells + col;
1906 if (colored) {
1907 int esclen = 0;
1908 if (!prev_cell || cell->attr != prev_cell->attr) {
1909 attr_t attr = cell->attr << NCURSES_ATTR_SHIFT;
1910 esclen = sprintf(s, "\033[0%s%s%s%s%s%sm",
1911 attr & A_BOLD ? ";1" : "",
1912 attr & A_DIM ? ";2" : "",
1913 attr & A_UNDERLINE ? ";4" : "",
1914 attr & A_BLINK ? ";5" : "",
1915 attr & A_REVERSE ? ";7" : "",
1916 attr & A_INVIS ? ";8" : "");
1917 if (esclen > 0)
1918 s += esclen;
1920 if (!prev_cell || cell->fg != prev_cell->fg || cell->attr != prev_cell->attr) {
1921 if (cell->fg == -1)
1922 esclen = sprintf(s, "\033[39m");
1923 else
1924 esclen = sprintf(s, "\033[38;5;%dm", cell->fg);
1925 if (esclen > 0)
1926 s += esclen;
1928 if (!prev_cell || cell->bg != prev_cell->bg || cell->attr != prev_cell->attr) {
1929 if (cell->bg == -1)
1930 esclen = sprintf(s, "\033[49m");
1931 else
1932 esclen = sprintf(s, "\033[48;5;%dm", cell->bg);
1933 if (esclen > 0)
1934 s += esclen;
1936 prev_cell = cell;
1938 if (cell->text) {
1939 len = wcrtomb(s, cell->text, &ps);
1940 if (len > 0)
1941 s += len;
1942 last_non_space = s;
1943 } else if (len) {
1944 len = 0;
1945 } else {
1946 *s++ = ' ';
1950 s = last_non_space;
1951 *s++ = '\n';
1954 return s - *buf;
1957 int vt_content_start(Vt *t)
1959 return t->buffer->scroll_above;