dvtm

Fork of dvtm, a minimal terminal multiplexer
git clone git://git.laack.co/dvtm.git
Log | Files | Refs | README | LICENSE

vt.c (47410B)


      1 /*
      2  * Copyright © 2004 Bruno T. C. de Oliveira
      3  * Copyright © 2006 Pierre Habouzit
      4  * Copyright © 2008-2016 Marc André Tanner
      5  *
      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.
      9  *
     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.
     17  */
     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
     42 
     43 #include "vt.h"
     44 
     45 #ifdef _AIX
     46 # include "forkpty-aix.c"
     47 #elif defined __sun
     48 # include "forkpty-sunos.c"
     49 #endif
     50 
     51 #ifndef NCURSES_ATTR_SHIFT
     52 # define NCURSES_ATTR_SHIFT 8
     53 #endif
     54 
     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
     62 
     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 MIN(COLOR_PAIRS, 256)
     69 # endif
     70 #endif
     71 #ifndef MAX_COLOR_PAIRS
     72 # define MAX_COLOR_PAIRS COLOR_PAIRS
     73 #endif
     74 
     75 #if defined _AIX && defined CTRL
     76 # undef CTRL
     77 #endif
     78 #ifndef CTRL
     79 # define CTRL(k)   ((k) & 0x1F)
     80 #endif
     81 
     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]))
     85 
     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];
     90 
     91 typedef struct {
     92 	wchar_t text;
     93 	attr_t attr;
     94 	short fg;
     95 	short bg;
     96 } Cell;
     97 
     98 typedef struct {
     99 	Cell *cells;
    100 	unsigned dirty:1;
    101 } Row;
    102 
    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).
    105  *
    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.
    110  *
    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.
    115  *
    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.
    121  *
    122  *                                     scroll back buffer
    123  *
    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    ->
    147  */
    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;
    167 
    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 */
    197 };
    198 
    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
    204 	 */
    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
    252 };
    253 
    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);
    257 
    258 __attribute__ ((const))
    259 static attr_t build_attrs(attr_t curattrs)
    260 {
    261 	return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
    262 	    >> NCURSES_ATTR_SHIFT;
    263 }
    264 
    265 static void row_set(Row *row, int start, int len, Buffer *t)
    266 {
    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,
    272 	};
    273 
    274 	for (int i = start; i < len + start; i++)
    275 		row->cells[i] = cell;
    276 	row->dirty = true;
    277 }
    278 
    279 static void row_roll(Row *start, Row *end, int count)
    280 {
    281 	int n = end - start;
    282 
    283 	count %= n;
    284 	if (count < 0)
    285 		count += n;
    286 
    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;
    294 	}
    295 }
    296 
    297 static void buffer_clear(Buffer *b)
    298 {
    299 	Cell cell = {
    300 		.text = L'\0',
    301 		.attr = A_NORMAL,
    302 		.fg = -1,
    303 		.bg = -1,
    304 	};
    305 
    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;
    311 		}
    312 	}
    313 }
    314 
    315 static void buffer_free(Buffer *b)
    316 {
    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);
    324 }
    325 
    326 static void buffer_scroll(Buffer *b, int s)
    327 {
    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;
    334 	}
    335 	if (s < -ssz) {
    336 		buffer_scroll(b, -ssz);
    337 		buffer_scroll(b, s + ssz);
    338 		return;
    339 	}
    340 
    341 	b->scroll_above += s;
    342 	if (b->scroll_above >= b->scroll_size)
    343 		b->scroll_above = b->scroll_size;
    344 
    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;
    350 
    351 			b->scroll_index++;
    352 			if (b->scroll_index == b->scroll_size)
    353 				b->scroll_index = 0;
    354 		}
    355 	}
    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;
    362 
    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;
    367 		}
    368 	}
    369 }
    370 
    371 static void buffer_resize(Buffer *b, int rows, int cols)
    372 {
    373 	Row *lines = b->lines;
    374 
    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);
    379 		}
    380 		while (b->rows > rows) {
    381 			free(lines[b->rows - 1].cells);
    382 			b->rows--;
    383 		}
    384 
    385 		lines = realloc(lines, sizeof(Row) * rows);
    386 	}
    387 
    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;
    394 		}
    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);
    400 		}
    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;
    410 	}
    411 
    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++;
    418 		}
    419 
    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;
    425 		}
    426 	}
    427 
    428 	b->curs_row += lines - b->lines;
    429 	b->scroll_top = lines;
    430 	b->scroll_bot = lines + rows;
    431 	b->lines = lines;
    432 
    433 	/* perform backfill */
    434 	if (deltarows > 0) {
    435 		buffer_scroll(b, -deltarows);
    436 		b->curs_row += deltarows;
    437 	}
    438 }
    439 
    440 static bool buffer_init(Buffer *b, int rows, int cols, int scroll_size)
    441 {
    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;
    451 }
    452 
    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;
    464 
    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];
    470 	}
    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];
    476 	}
    477 }
    478 
    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;
    485 }
    486 
    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;
    493 }
    494 
    495 static Row *buffer_row_next(Buffer *b, Row *row)
    496 {
    497 	Row *before_start, *before_end, *after_start, *after_end;
    498 	Row *first = b->lines, *last = b->lines + b->rows - 1;
    499 
    500 	if (!row)
    501 		return NULL;
    502 
    503 	buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
    504 
    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;
    516 }
    517 
    518 static Row *buffer_row_prev(Buffer *b, Row *row)
    519 {
    520 	Row *before_start, *before_end, *after_start, *after_end;
    521 	Row *first = b->lines, *last = b->lines + b->rows - 1;
    522 
    523 	if (!row)
    524 		return NULL;
    525 
    526 	buffer_boundry(b, &before_start, &before_end, &after_start, &after_end);
    527 
    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;
    539 }
    540 
    541 static void cursor_clamp(Vt *t)
    542 {
    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;
    546 
    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;
    555 }
    556 
    557 static void cursor_line_down(Vt *t)
    558 {
    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;
    564 
    565 	vt_noscroll(t);
    566 
    567 	b->curs_row = b->scroll_bot - 1;
    568 	buffer_scroll(b, 1);
    569 	row_set(b->curs_row, 0, b->cols, b);
    570 }
    571 
    572 static void cursor_save(Vt *t)
    573 {
    574 	Buffer *b = t->buffer;
    575 	b->curs_srow = b->curs_row - b->lines;
    576 	b->curs_scol = b->curs_col;
    577 }
    578 
    579 static void cursor_restore(Vt *t)
    580 {
    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);
    585 }
    586 
    587 static void attributes_save(Vt *t)
    588 {
    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;
    594 }
    595 
    596 static void attributes_restore(Vt *t)
    597 {
    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;
    603 }
    604 
    605 static void new_escape_sequence(Vt *t)
    606 {
    607 	t->escaped = true;
    608 	t->elen = 0;
    609 	t->ebuf[0] = '\0';
    610 }
    611 
    612 static void cancel_escape_sequence(Vt *t)
    613 {
    614 	t->escaped = false;
    615 	t->elen = 0;
    616 	t->ebuf[0] = '\0';
    617 }
    618 
    619 static bool is_valid_csi_ender(int c)
    620 {
    621 	return (c >= 'a' && c <= 'z')
    622 	    || (c >= 'A' && c <= 'Z')
    623 	    || (c == '@' || c == '`');
    624 }
    625 
    626 /* interprets a 'set attribute' (SGR) CSI escape sequence */
    627 static void interpret_csi_sgr(Vt *t, int param[], int pcount)
    628 {
    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;
    635 	}
    636 
    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;
    693 			}
    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;
    705 			}
    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;
    718 		}
    719 	}
    720 }
    721 
    722 /* interprets an 'erase display' (ED) escape sequence */
    723 static void interpret_csi_ed(Vt *t, int param[], int pcount)
    724 {
    725 	Row *row, *start, *end;
    726 	Buffer *b = t->buffer;
    727 
    728 	attributes_save(t);
    729 	b->curattrs = A_NORMAL;
    730 	b->curfg = b->curbg = -1;
    731 
    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;
    743 	}
    744 
    745 	for (row = start; row < end; row++)
    746 		row_set(row, 0, b->cols, b);
    747 
    748 	attributes_restore(t);
    749 }
    750 
    751 /* interprets a 'move cursor' (CUP) escape sequence */
    752 static void interpret_csi_cup(Vt *t, int param[], int pcount)
    753 {
    754 	Buffer *b = t->buffer;
    755 	Row *lines = t->relposmode ? b->scroll_top : b->lines;
    756 
    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;
    766 	}
    767 
    768 	cursor_clamp(t);
    769 }
    770 
    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)
    774 {
    775 	Buffer *b = t->buffer;
    776 	int n = (pcount && param[0] > 0) ? param[0] : 1;
    777 
    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;
    808 	}
    809 
    810 	cursor_clamp(t);
    811 }
    812 
    813 /* Interpret the 'erase line' escape sequence */
    814 static void interpret_csi_el(Vt *t, int param[], int pcount)
    815 {
    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;
    827 	}
    828 }
    829 
    830 /* Interpret the 'insert blanks' sequence (ICH) */
    831 static void interpret_csi_ich(Vt *t, int param[], int pcount)
    832 {
    833 	Buffer *b = t->buffer;
    834 	Row *row = b->curs_row;
    835 	int n = (pcount && param[0] > 0) ? param[0] : 1;
    836 
    837 	if (b->curs_col + n > b->cols)
    838 		n = b->cols - b->curs_col;
    839 
    840 	for (int i = b->cols - 1; i >= b->curs_col + n; i--)
    841 		row->cells[i] = row->cells[i - n];
    842 
    843 	row_set(row, b->curs_col, n, b);
    844 }
    845 
    846 /* Interpret the 'delete chars' sequence (DCH) */
    847 static void interpret_csi_dch(Vt *t, int param[], int pcount)
    848 {
    849 	Buffer *b = t->buffer;
    850 	Row *row = b->curs_row;
    851 	int n = (pcount && param[0] > 0) ? param[0] : 1;
    852 
    853 	if (b->curs_col + n > b->cols)
    854 		n = b->cols - b->curs_col;
    855 
    856 	for (int i = b->curs_col; i < b->cols - n; i++)
    857 		row->cells[i] = row->cells[i + n];
    858 
    859 	row_set(row, b->cols - n, n, b);
    860 }
    861 
    862 /* Interpret an 'insert line' sequence (IL) */
    863 static void interpret_csi_il(Vt *t, int param[], int pcount)
    864 {
    865 	Buffer *b = t->buffer;
    866 	int n = (pcount && param[0] > 0) ? param[0] : 1;
    867 
    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);
    875 	}
    876 }
    877 
    878 /* Interpret a 'delete line' sequence (DL) */
    879 static void interpret_csi_dl(Vt *t, int param[], int pcount)
    880 {
    881 	Buffer *b = t->buffer;
    882 	int n = (pcount && param[0] > 0) ? param[0] : 1;
    883 
    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);
    891 	}
    892 }
    893 
    894 /* Interpret an 'erase characters' (ECH) sequence */
    895 static void interpret_csi_ech(Vt *t, int param[], int pcount)
    896 {
    897 	Buffer *b = t->buffer;
    898 	int n = (pcount && param[0] > 0) ? param[0] : 1;
    899 
    900 	if (b->curs_col + n > b->cols)
    901 		n = b->cols - b->curs_col;
    902 
    903 	row_set(b->curs_row, b->curs_col, n, b);
    904 }
    905 
    906 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
    907 static void interpret_csi_decstbm(Vt *t, int param[], int pcount)
    908 {
    909 	Buffer *b = t->buffer;
    910 	int new_top, new_bot;
    911 
    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];
    920 
    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;
    930 
    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;
    935 		}
    936 		break;
    937 	default:
    938 		return;	/* malformed */
    939 	}
    940 	b->curs_row = b->scroll_top;
    941 	b->curs_col = 0;
    942 }
    943 
    944 static void interpret_csi_mode(Vt *t, int param[], int pcount, bool set)
    945 {
    946 	for (int i = 0; i < pcount; i++) {
    947 		switch (param[i]) {
    948 		case 4: /* insert/replace mode */
    949 			t->insert = set;
    950 			break;
    951 		}
    952 	}
    953 }
    954 
    955 static void interpret_csi_priv_mode(Vt *t, int param[], int pcount, bool set)
    956 {
    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             cursor_clamp(t);
    975 			vt_dirty(t);
    976 			if (param[i] != 1049)
    977 				break;
    978 			/* fall through */
    979 		case 1048: /* save/restore cursor */
    980 			if (set)
    981 				cursor_save(t);
    982 			else
    983 				cursor_restore(t);
    984 			break;
    985 		case 1000: /* enable/disable normal mouse tracking */
    986 			t->mousetrack = set;
    987 			break;
    988 		}
    989 	}
    990 }
    991 
    992 static void interpret_csi(Vt *t)
    993 {
    994 	Buffer *b = t->buffer;
    995 	int csiparam[16];
    996 	unsigned int param_count = 0;
    997 	const char *p = t->ebuf + 1;
    998 	char verb = t->ebuf[t->elen - 1];
    999 
   1000 	/* parse numeric parameters */
   1001 	for (p += (t->ebuf[1] == '?'); *p; p++) {
   1002 		if (IS_CONTROL(*p)) {
   1003 			process_nonprinting(t, *p);
   1004 		} else if (*p == ';') {
   1005 			if (param_count >= LENGTH(csiparam))
   1006 				return;	/* too long! */
   1007 			csiparam[param_count++] = 0;
   1008 		} else if (isdigit((unsigned char)*p)) {
   1009 			if (param_count == 0)
   1010 				csiparam[param_count++] = 0;
   1011 			csiparam[param_count - 1] *= 10;
   1012 			csiparam[param_count - 1] += *p - '0';
   1013 		}
   1014 	}
   1015 
   1016 	if (t->ebuf[1] == '?') {
   1017 		switch (verb) {
   1018 		case 'h':
   1019 		case 'l': /* private set/reset mode */
   1020 			interpret_csi_priv_mode(t, csiparam, param_count, verb == 'h');
   1021 			break;
   1022 		}
   1023 		return;
   1024 	}
   1025 
   1026 	/* delegate handling depending on command character (verb) */
   1027 	switch (verb) {
   1028 	case 'h':
   1029 	case 'l': /* set/reset mode */
   1030 		interpret_csi_mode(t, csiparam, param_count, verb == 'h');
   1031 		break;
   1032 	case 'm': /* set attribute */
   1033 		interpret_csi_sgr(t, csiparam, param_count);
   1034 		break;
   1035 	case 'J': /* erase display */
   1036 		interpret_csi_ed(t, csiparam, param_count);
   1037 		break;
   1038 	case 'H':
   1039 	case 'f': /* move cursor */
   1040 		interpret_csi_cup(t, csiparam, param_count);
   1041 		break;
   1042 	case 'A':
   1043 	case 'B':
   1044 	case 'C':
   1045 	case 'D':
   1046 	case 'E':
   1047 	case 'F':
   1048 	case 'G':
   1049 	case 'e':
   1050 	case 'a':
   1051 	case 'd':
   1052 	case '`': /* relative move */
   1053 		interpret_csi_c(t, verb, csiparam, param_count);
   1054 		break;
   1055 	case 'K': /* erase line */
   1056 		interpret_csi_el(t, csiparam, param_count);
   1057 		break;
   1058 	case '@': /* insert characters */
   1059 		interpret_csi_ich(t, csiparam, param_count);
   1060 		break;
   1061 	case 'P': /* delete characters */
   1062 		interpret_csi_dch(t, csiparam, param_count);
   1063 		break;
   1064 	case 'L': /* insert lines */
   1065 		interpret_csi_il(t, csiparam, param_count);
   1066 		break;
   1067 	case 'M': /* delete lines */
   1068 		interpret_csi_dl(t, csiparam, param_count);
   1069 		break;
   1070 	case 'X': /* erase chars */
   1071 		interpret_csi_ech(t, csiparam, param_count);
   1072 		break;
   1073 	case 'S': /* SU: scroll up */
   1074 		vt_scroll(t, param_count ? -csiparam[0] : -1);
   1075 		break;
   1076 	case 'T': /* SD: scroll down */
   1077 		vt_scroll(t, param_count ? csiparam[0] : 1);
   1078 		break;
   1079 	case 'Z': /* CBT: cursor backward tabulation */
   1080 		puttab(t, param_count ? -csiparam[0] : -1);
   1081 		break;
   1082 	case 'g': /* TBC: tabulation clear */
   1083 		switch (param_count ? csiparam[0] : 0) {
   1084 		case 0:
   1085 			b->tabs[b->curs_col] = false;
   1086 			break;
   1087 		case 3:
   1088 			memset(b->tabs, 0, sizeof(*b->tabs) * b->maxcols);
   1089 			break;
   1090 		}
   1091 		break;
   1092 	case 'r': /* set scrolling region */
   1093 		interpret_csi_decstbm(t, csiparam, param_count);
   1094 		break;
   1095 	case 's': /* save cursor location */
   1096 		cursor_save(t);
   1097 		break;
   1098 	case 'u': /* restore cursor location */
   1099 		cursor_restore(t);
   1100 		break;
   1101 	case 'n': /* query cursor location */
   1102 		if (param_count == 1 && csiparam[0] == 6)
   1103 			send_curs(t);
   1104 		break;
   1105 	default:
   1106 		break;
   1107 	}
   1108 }
   1109 
   1110 /* Interpret an 'index' (IND) sequence */
   1111 static void interpret_csi_ind(Vt *t)
   1112 {
   1113 	Buffer *b = t->buffer;
   1114 	if (b->curs_row < b->lines + b->rows - 1)
   1115 		b->curs_row++;
   1116 }
   1117 
   1118 /* Interpret a 'reverse index' (RI) sequence */
   1119 static void interpret_csi_ri(Vt *t)
   1120 {
   1121 	Buffer *b = t->buffer;
   1122 	if (b->curs_row > b->scroll_top)
   1123 		b->curs_row--;
   1124 	else {
   1125 		row_roll(b->scroll_top, b->scroll_bot, -1);
   1126 		row_set(b->scroll_top, 0, b->cols, b);
   1127 	}
   1128 }
   1129 
   1130 /* Interpret a 'next line' (NEL) sequence */
   1131 static void interpret_csi_nel(Vt *t)
   1132 {
   1133 	Buffer *b = t->buffer;
   1134 	if (b->curs_row < b->lines + b->rows - 1) {
   1135 		b->curs_row++;
   1136 		b->curs_col = 0;
   1137 	}
   1138 }
   1139 
   1140 /* Interpret a 'select character set' (SCS) sequence */
   1141 static void interpret_csi_scs(Vt *t)
   1142 {
   1143 	/* ESC ( sets G0, ESC ) sets G1 */
   1144 	t->charsets[!!(t->ebuf[0] == ')')] = (t->ebuf[1] == '0');
   1145 	t->graphmode = t->charsets[0];
   1146 }
   1147 
   1148 /* Interpret an 'operating system command' (OSC) sequence */
   1149 static void interpret_osc(Vt *t)
   1150 {
   1151 	/* ESC ] command ; data BEL
   1152 	 * ESC ] command ; data ESC \\
   1153 	 * Note that BEL or ESC \\ have already been replaced with NUL.
   1154 	 */
   1155 	char *data = NULL;
   1156 	int command = strtoul(t->ebuf + 1, &data, 10);
   1157 	if (data && *data == ';') {
   1158 		switch (command) {
   1159 		case 0: /* icon name and window title */
   1160 		case 2: /* window title */
   1161 			if (t->title_handler)
   1162                 0;
   1163 			break;
   1164 		case 1: /* icon name */
   1165 			break;
   1166 		default:
   1167 #ifndef NDEBUG
   1168 			fprintf(stderr, "unknown OSC command: %d\n", command);
   1169 #endif
   1170 			break;
   1171 		}
   1172 	}
   1173 }
   1174 
   1175 static void try_interpret_escape_seq(Vt *t)
   1176 {
   1177 	char lastchar = t->ebuf[t->elen - 1];
   1178 
   1179 	if (!*t->ebuf)
   1180 		return;
   1181 
   1182 	switch (*t->ebuf) {
   1183 	case '#': /* ignore DECDHL, DECSWL, DECDWL, DECHCP, DECFPP */
   1184 		if (t->elen == 2) {
   1185 			if (lastchar == '8') { /* DECALN */
   1186 				interpret_csi_ed(t, (int []){ 2 }, 1);
   1187 				goto handled;
   1188 			}
   1189 			goto cancel;
   1190 		}
   1191 		break;
   1192 	case '(':
   1193 	case ')':
   1194 		if (t->elen == 2) {
   1195 			interpret_csi_scs(t);
   1196 			goto handled;
   1197 		}
   1198 		break;
   1199 	case ']': /* OSC - operating system command */
   1200 		if (lastchar == '\a' ||
   1201 		   (lastchar == '\\' && t->elen >= 2 && t->ebuf[t->elen - 2] == '\e')) {
   1202 			t->elen -= lastchar == '\a' ? 1 : 2;
   1203 			t->ebuf[t->elen] = '\0';
   1204 			interpret_osc(t);
   1205 			goto handled;
   1206 		}
   1207 		break;
   1208 	case '[': /* CSI - control sequence introducer */
   1209 		if (is_valid_csi_ender(lastchar)) {
   1210 			interpret_csi(t);
   1211 			goto handled;
   1212 		}
   1213 		break;
   1214 	case '7': /* DECSC: save cursor and attributes */
   1215 		attributes_save(t);
   1216 		cursor_save(t);
   1217 		goto handled;
   1218 	case '8': /* DECRC: restore cursor and attributes */
   1219 		attributes_restore(t);
   1220 		cursor_restore(t);
   1221 		goto handled;
   1222 	case 'D': /* IND: index */
   1223 		interpret_csi_ind(t);
   1224 		goto handled;
   1225 	case 'M': /* RI: reverse index */
   1226 		interpret_csi_ri(t);
   1227 		goto handled;
   1228 	case 'E': /* NEL: next line */
   1229 		interpret_csi_nel(t);
   1230 		goto handled;
   1231 	case 'H': /* HTS: horizontal tab set */
   1232 		t->buffer->tabs[t->buffer->curs_col] = true;
   1233 		goto handled;
   1234 	default:
   1235 		goto cancel;
   1236 	}
   1237 
   1238 	if (t->elen + 1 >= sizeof(t->ebuf)) {
   1239 cancel:
   1240 #ifndef NDEBUG
   1241 		fprintf(stderr, "cancelled: \\033");
   1242 		for (unsigned int i = 0; i < t->elen; i++) {
   1243 			if (isprint(t->ebuf[i])) {
   1244 				fputc(t->ebuf[i], stderr);
   1245 			} else {
   1246 				fprintf(stderr, "\\%03o", t->ebuf[i]);
   1247 			}
   1248 		}
   1249 		fputc('\n', stderr);
   1250 #endif
   1251 handled:
   1252 		cancel_escape_sequence(t);
   1253 	}
   1254 }
   1255 
   1256 static void puttab(Vt *t, int count)
   1257 {
   1258 	Buffer *b = t->buffer;
   1259 	int direction = count >= 0 ? 1 : -1;
   1260 	for (int col = b->curs_col + direction; count; col += direction) {
   1261 		if (col < 0) {
   1262 			b->curs_col = 0;
   1263 			break;
   1264 		}
   1265 		if (col >= b->cols) {
   1266 			b->curs_col = b->cols - 1;
   1267 			break;
   1268 		}
   1269 		if (b->tabs[col]) {
   1270 			b->curs_col = col;
   1271 			count -= direction;
   1272 		}
   1273 	}
   1274 }
   1275 
   1276 static void process_nonprinting(Vt *t, wchar_t wc)
   1277 {
   1278 	Buffer *b = t->buffer;
   1279 	switch (wc) {
   1280 	case '\e': /* ESC */
   1281 		new_escape_sequence(t);
   1282 		break;
   1283 	case '\a': /* BEL */
   1284 		if (t->urgent_handler)
   1285 			t->urgent_handler(t);
   1286 		break;
   1287 	case '\b': /* BS */
   1288 		if (b->curs_col > 0)
   1289 			b->curs_col--;
   1290 		break;
   1291 	case '\t': /* HT */
   1292 		puttab(t, 1);
   1293 		break;
   1294 	case '\r': /* CR */
   1295 		b->curs_col = 0;
   1296 		break;
   1297 	case '\v': /* VT */
   1298 	case '\f': /* FF */
   1299 	case '\n': /* LF */
   1300 		cursor_line_down(t);
   1301 		break;
   1302 	case '\016': /* SO: shift out, invoke the G1 character set */
   1303 		t->graphmode = t->charsets[1];
   1304 		break;
   1305 	case '\017': /* SI: shift in, invoke the G0 character set */
   1306 		t->graphmode = t->charsets[0];
   1307 		break;
   1308 	}
   1309 }
   1310 
   1311 static void is_utf8_locale(void)
   1312 {
   1313 	const char *cset = nl_langinfo(CODESET);
   1314 	if (!cset)
   1315 		cset = "ANSI_X3.4-1968";
   1316 	is_utf8 = !strcmp(cset, "UTF-8");
   1317 }
   1318 
   1319 static wchar_t get_vt100_graphic(char c)
   1320 {
   1321 	static char vt100_acs[] = "`afgjklmnopqrstuvwxyz{|}~";
   1322 
   1323 	/*
   1324 	 * 5f-7e standard vt100
   1325 	 * 40-5e rxvt extension for extra curses acs chars
   1326 	 */
   1327 	static uint16_t const vt100_utf8[62] = {
   1328 		        0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47
   1329 		     0,      0,      0,      0,      0,      0,      0,      0, // 48-4f
   1330 		     0,      0,      0,      0,      0,      0,      0,      0, // 50-57
   1331 		     0,      0,      0,      0,      0,      0,      0, 0x0020, // 58-5f
   1332 		0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67
   1333 		0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f
   1334 		0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77
   1335 		0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7,         // 78-7e
   1336 	};
   1337 
   1338 	if (is_utf8)
   1339 		return vt100_utf8[c - 0x41];
   1340 	else if (strchr(vt100_acs, c))
   1341 		return NCURSES_ACS(c);
   1342 	return '\0';
   1343 }
   1344 
   1345 static void put_wc(Vt *t, wchar_t wc)
   1346 {
   1347 	int width = 0;
   1348 
   1349 	if (!t->seen_input) {
   1350 		t->seen_input = 1;
   1351 		kill(-t->pid, SIGWINCH);
   1352 	}
   1353 
   1354 	if (t->escaped) {
   1355 		if (t->elen + 1 < sizeof(t->ebuf)) {
   1356 			t->ebuf[t->elen] = wc;
   1357 			t->ebuf[++t->elen] = '\0';
   1358 			try_interpret_escape_seq(t);
   1359 		} else {
   1360 			cancel_escape_sequence(t);
   1361 		}
   1362 	} else if (IS_CONTROL(wc)) {
   1363 		process_nonprinting(t, wc);
   1364 	} else {
   1365 		if (t->graphmode) {
   1366 			if (wc >= 0x41 && wc <= 0x7e) {
   1367 				wchar_t gc = get_vt100_graphic(wc);
   1368 				if (gc)
   1369 					wc = gc;
   1370 			}
   1371 			width = 1;
   1372 		} else if ((width = wcwidth(wc)) < 1) {
   1373 			width = 1;
   1374 		}
   1375 		Buffer *b = t->buffer;
   1376 		Cell blank_cell = { L'\0', build_attrs(b->curattrs), b->curfg, b->curbg };
   1377 		if (width == 2 && b->curs_col == b->cols - 1) {
   1378 			b->curs_row->cells[b->curs_col++] = blank_cell;
   1379 			b->curs_row->dirty = true;
   1380 		}
   1381 
   1382 		if (b->curs_col >= b->cols) {
   1383 			b->curs_col = 0;
   1384 			cursor_line_down(t);
   1385 		}
   1386 
   1387 		if (t->insert) {
   1388 			Cell *src = b->curs_row->cells + b->curs_col;
   1389 			Cell *dest = src + width;
   1390 			size_t len = b->cols - b->curs_col - width;
   1391 			memmove(dest, src, len * sizeof *dest);
   1392 		}
   1393 
   1394 		b->curs_row->cells[b->curs_col] = blank_cell;
   1395 		b->curs_row->cells[b->curs_col++].text = wc;
   1396 		b->curs_row->dirty = true;
   1397 		if (width == 2)
   1398 			b->curs_row->cells[b->curs_col++] = blank_cell;
   1399 	}
   1400 }
   1401 
   1402 int vt_process(Vt *t)
   1403 {
   1404 	int res;
   1405 	unsigned int pos = 0;
   1406 	mbstate_t ps;
   1407 	memset(&ps, 0, sizeof(ps));
   1408 
   1409 	if (t->pty < 0) {
   1410 		errno = EINVAL;
   1411 		return -1;
   1412 	}
   1413 
   1414 	res = read(t->pty, t->rbuf + t->rlen, sizeof(t->rbuf) - t->rlen);
   1415 	if (res < 0)
   1416 		return -1;
   1417 
   1418 	t->rlen += res;
   1419 	while (pos < t->rlen) {
   1420 		wchar_t wc;
   1421 		ssize_t len;
   1422 
   1423 		len = (ssize_t)mbrtowc(&wc, t->rbuf + pos, t->rlen - pos, &ps);
   1424 		if (len == -2) {
   1425 			t->rlen -= pos;
   1426 			memmove(t->rbuf, t->rbuf + pos, t->rlen);
   1427 			return 0;
   1428 		}
   1429 
   1430 		if (len == -1) {
   1431 			len = 1;
   1432 			wc = t->rbuf[pos];
   1433 		}
   1434 
   1435 		pos += len ? len : 1;
   1436 		put_wc(t, wc);
   1437 	}
   1438 
   1439 	t->rlen -= pos;
   1440 	memmove(t->rbuf, t->rbuf + pos, t->rlen);
   1441 	return 0;
   1442 }
   1443 
   1444 void vt_default_colors_set(Vt *t, attr_t attrs, short fg, short bg)
   1445 {
   1446 	t->defattrs = attrs;
   1447 	t->deffg = fg;
   1448 	t->defbg = bg;
   1449 }
   1450 
   1451 Vt *vt_create(int rows, int cols, int scroll_size)
   1452 {
   1453 	if (rows <= 0 || cols <= 0)
   1454 		return NULL;
   1455 
   1456 	Vt *t = calloc(1, sizeof(Vt));
   1457 	if (!t)
   1458 		return NULL;
   1459 
   1460 	t->pty = -1;
   1461 	t->deffg = t->defbg = -1;
   1462 	t->buffer = &t->buffer_normal;
   1463 
   1464 	if (!buffer_init(&t->buffer_normal, rows, cols, scroll_size) ||
   1465 	    !buffer_init(&t->buffer_alternate, rows, cols, 0)) {
   1466 		free(t);
   1467 		return NULL;
   1468 	}
   1469 
   1470 	return t;
   1471 }
   1472 
   1473 void vt_resize(Vt *t, int rows, int cols)
   1474 {
   1475 	struct winsize ws = { .ws_row = rows, .ws_col = cols };
   1476 
   1477 	if (rows <= 0 || cols <= 0)
   1478 		return;
   1479 
   1480 	vt_noscroll(t);
   1481 	buffer_resize(&t->buffer_normal, rows, cols);
   1482 	buffer_resize(&t->buffer_alternate, rows, cols);
   1483 	cursor_clamp(t);
   1484 	ioctl(t->pty, TIOCSWINSZ, &ws);
   1485 	kill(-t->pid, SIGWINCH);
   1486 }
   1487 
   1488 void vt_destroy(Vt *t)
   1489 {
   1490 	if (!t)
   1491 		return;
   1492 	buffer_free(&t->buffer_normal);
   1493 	buffer_free(&t->buffer_alternate);
   1494 	close(t->pty);
   1495 	free(t);
   1496 }
   1497 
   1498 void vt_dirty(Vt *t)
   1499 {
   1500 	Buffer *b = t->buffer;
   1501 	for (Row *row = b->lines, *end = row + b->rows; row < end; row++)
   1502 		row->dirty = true;
   1503 }
   1504 
   1505 void vt_draw(Vt *t, WINDOW *win, int srow, int scol)
   1506 {
   1507 	Buffer *b = t->buffer;
   1508 
   1509 	if (srow != t->srow || scol != t->scol) {
   1510 		vt_dirty(t);
   1511 		t->srow = srow;
   1512 		t->scol = scol;
   1513 	}
   1514 
   1515 	for (int i = 0; i < b->rows; i++) {
   1516 		Row *row = b->lines + i;
   1517 
   1518 		if (!row->dirty)
   1519 			continue;
   1520 
   1521 		wmove(win, srow + i, scol);
   1522 		Cell *cell = NULL;
   1523 		for (int j = 0; j < b->cols; j++) {
   1524 			Cell *prev_cell = cell;
   1525 			cell = row->cells + j;
   1526 			if (!prev_cell || cell->attr != prev_cell->attr
   1527 			    || cell->fg != prev_cell->fg
   1528 			    || cell->bg != prev_cell->bg) {
   1529 				if (cell->attr == A_NORMAL)
   1530 					cell->attr = t->defattrs;
   1531 				if (cell->fg == -1)
   1532 					cell->fg = t->deffg;
   1533 				if (cell->bg == -1)
   1534 					cell->bg = t->defbg;
   1535 				wattrset(win, cell->attr << NCURSES_ATTR_SHIFT);
   1536 				wcolor_set(win, vt_color_get(t, cell->fg, cell->bg), NULL);
   1537 			}
   1538 
   1539 			if (is_utf8 && cell->text >= 128) {
   1540 				char buf[MB_CUR_MAX + 1];
   1541 				size_t len = wcrtomb(buf, cell->text, NULL);
   1542 				if (len > 0) {
   1543 					waddnstr(win, buf, len);
   1544 					if (wcwidth(cell->text) > 1)
   1545 						j++;
   1546 				}
   1547 			} else {
   1548 				waddch(win, cell->text > ' ' ? cell->text : ' ');
   1549 			}
   1550 		}
   1551 
   1552 		int x, y;
   1553 		getyx(win, y, x);
   1554 		(void)y;
   1555 		if (x && x < b->cols - 1)
   1556 			whline(win, ' ', b->cols - x);
   1557 
   1558 		row->dirty = false;
   1559 	}
   1560 
   1561 	wmove(win, srow + b->curs_row - b->lines, scol + b->curs_col);
   1562 }
   1563 
   1564 void vt_scroll(Vt *t, int rows)
   1565 {
   1566 	Buffer *b = t->buffer;
   1567 	if (!b->scroll_size)
   1568 		return;
   1569 	if (rows < 0) { /* scroll back */
   1570 		if (rows < -b->scroll_above)
   1571 			rows = -b->scroll_above;
   1572 	} else { /* scroll forward */
   1573 		if (rows > b->scroll_below)
   1574 			rows = b->scroll_below;
   1575 	}
   1576 	buffer_scroll(b, rows);
   1577 	b->scroll_below -= rows;
   1578 }
   1579 
   1580 void vt_noscroll(Vt *t)
   1581 {
   1582 	int scroll_below = t->buffer->scroll_below;
   1583 	if (scroll_below)
   1584 		vt_scroll(t, scroll_below);
   1585 }
   1586 
   1587 pid_t vt_forkpty(Vt *t, const char *p, const char *argv[], const char *cwd, const char *env[], int *to, int *from)
   1588 {
   1589 	int vt2ed[2], ed2vt[2];
   1590 	struct winsize ws;
   1591 	ws.ws_row = t->buffer->rows;
   1592 	ws.ws_col = t->buffer->cols;
   1593 	ws.ws_xpixel = ws.ws_ypixel = 0;
   1594 
   1595 	if (to && pipe(vt2ed)) {
   1596 		*to = -1;
   1597 		to = NULL;
   1598 	}
   1599 	if (from && pipe(ed2vt)) {
   1600 		*from = -1;
   1601 		from = NULL;
   1602 	}
   1603 
   1604 	pid_t pid = forkpty(&t->pty, NULL, NULL, &ws);
   1605 	if (pid < 0)
   1606 		return -1;
   1607 
   1608 	if (pid == 0) {
   1609 		setsid();
   1610 
   1611 		sigset_t emptyset;
   1612 		sigemptyset(&emptyset);
   1613 		sigprocmask(SIG_SETMASK, &emptyset, NULL);
   1614 
   1615 		if (to) {
   1616 			close(vt2ed[1]);
   1617 			dup2(vt2ed[0], STDIN_FILENO);
   1618 			close(vt2ed[0]);
   1619 		}
   1620 
   1621 		if (from) {
   1622 			close(ed2vt[0]);
   1623 			dup2(ed2vt[1], STDOUT_FILENO);
   1624 			close(ed2vt[1]);
   1625 		}
   1626 
   1627 		int maxfd = sysconf(_SC_OPEN_MAX);
   1628 		for (int fd = 3; fd < maxfd; fd++)
   1629 			if (close(fd) == -1 && errno == EBADF)
   1630 				break;
   1631 
   1632 		for (const char **envp = env; envp && envp[0]; envp += 2)
   1633 			setenv(envp[0], envp[1], 1);
   1634 		setenv("TERM", vt_term, 1);
   1635 
   1636 		if (cwd) {
   1637 			int err = chdir(cwd);
   1638 			if (err) {
   1639 				fprintf(stderr, "\nchdir() failed. ");
   1640 				perror(cwd);
   1641 				exit(1);
   1642 			}
   1643 		}
   1644 
   1645 		struct sigaction sa;
   1646 		memset(&sa, 0, sizeof sa);
   1647 		sa.sa_flags = 0;
   1648 		sigemptyset(&sa.sa_mask);
   1649 		sa.sa_handler = SIG_DFL;
   1650 		sigaction(SIGPIPE, &sa, NULL);
   1651 
   1652 		execvp(p, (char *const *)argv);
   1653 		fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
   1654 		exit(1);
   1655 	}
   1656 
   1657 	if (to) {
   1658 		close(vt2ed[0]);
   1659 		*to = vt2ed[1];
   1660 	}
   1661 
   1662 	if (from) {
   1663 		close(ed2vt[1]);
   1664 		*from = ed2vt[0];
   1665 	}
   1666 
   1667 	return t->pid = pid;
   1668 }
   1669 
   1670 int vt_pty_get(Vt *t)
   1671 {
   1672 	return t->pty;
   1673 }
   1674 
   1675 ssize_t vt_write(Vt *t, const char *buf, size_t len)
   1676 {
   1677 	ssize_t ret = len;
   1678 
   1679 	while (len > 0) {
   1680 		ssize_t res = write(t->pty, buf, len);
   1681 		if (res < 0) {
   1682 			if (errno != EAGAIN && errno != EINTR)
   1683 				return -1;
   1684 			continue;
   1685 		}
   1686 		buf += res;
   1687 		len -= res;
   1688 	}
   1689 
   1690 	return ret;
   1691 }
   1692 
   1693 static void send_curs(Vt *t)
   1694 {
   1695 	Buffer *b = t->buffer;
   1696 	char keyseq[16];
   1697 	snprintf(keyseq, sizeof keyseq, "\e[%d;%dR", (int)(b->curs_row - b->lines), b->curs_col);
   1698 	vt_write(t, keyseq, strlen(keyseq));
   1699 }
   1700 
   1701 void vt_keypress(Vt *t, int keycode)
   1702 {
   1703 	vt_noscroll(t);
   1704 
   1705 	if (keycode >= 0 && keycode <= KEY_MAX && keytable[keycode]) {
   1706 		switch (keycode) {
   1707 		case KEY_UP:
   1708 		case KEY_DOWN:
   1709 		case KEY_RIGHT:
   1710 		case KEY_LEFT: {
   1711 			char keyseq[3] = { '\e', (t->curskeymode ? 'O' : '['), keytable[keycode][0] };
   1712 			vt_write(t, keyseq, sizeof keyseq);
   1713 			break;
   1714 		}
   1715 		default:
   1716 			vt_write(t, keytable[keycode], strlen(keytable[keycode]));
   1717 		}
   1718 	} else if (keycode <= UCHAR_MAX) {
   1719 		char c = keycode;
   1720 		vt_write(t, &c, 1);
   1721 	} else {
   1722 #ifndef NDEBUG
   1723 		fprintf(stderr, "unhandled key %#o\n", keycode);
   1724 #endif
   1725 	}
   1726 }
   1727 
   1728 void vt_mouse(Vt *t, int x, int y, mmask_t mask)
   1729 {
   1730 #ifdef NCURSES_MOUSE_VERSION
   1731 	char seq[6] = { '\e', '[', 'M' }, state = 0, button = 0;
   1732 
   1733 	if (!t->mousetrack)
   1734 		return;
   1735 
   1736 	if (mask & (BUTTON1_PRESSED | BUTTON1_CLICKED))
   1737 		button = 0;
   1738 	else if (mask & (BUTTON2_PRESSED | BUTTON2_CLICKED))
   1739 		button = 1;
   1740 	else if (mask & (BUTTON3_PRESSED | BUTTON3_CLICKED))
   1741 		button = 2;
   1742 	else if (mask & (BUTTON1_RELEASED | BUTTON2_RELEASED | BUTTON3_RELEASED))
   1743 		button = 3;
   1744 
   1745 	if (mask & BUTTON_SHIFT)
   1746 		state |= 4;
   1747 	if (mask & BUTTON_ALT)
   1748 		state |= 8;
   1749 	if (mask & BUTTON_CTRL)
   1750 		state |= 16;
   1751 
   1752 	seq[3] = 32 + button + state;
   1753 	seq[4] = 32 + x;
   1754 	seq[5] = 32 + y;
   1755 
   1756 	vt_write(t, seq, sizeof seq);
   1757 
   1758 	if (mask & (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)) {
   1759 		/* send a button release event */
   1760 		button = 3;
   1761 		seq[3] = 32 + button + state;
   1762 		vt_write(t, seq, sizeof seq);
   1763 	}
   1764 #endif /* NCURSES_MOUSE_VERSION */
   1765 }
   1766 
   1767 static unsigned int color_hash(short fg, short bg)
   1768 {
   1769 	if (fg == -1)
   1770 		fg = COLORS;
   1771 	if (bg == -1)
   1772 		bg = COLORS + 1;
   1773 	return fg * (COLORS + 2) + bg;
   1774 }
   1775 
   1776 short vt_color_get(Vt *t, short fg, short bg)
   1777 {
   1778 	if (fg >= COLORS)
   1779 		fg = (t ? t->deffg : default_fg);
   1780 	if (bg >= COLORS)
   1781 		bg = (t ? t->defbg : default_bg);
   1782 
   1783 	if (!has_default_colors) {
   1784 		if (fg == -1)
   1785 			fg = (t && t->deffg != -1 ? t->deffg : default_fg);
   1786 		if (bg == -1)
   1787 			bg = (t && t->defbg != -1 ? t->defbg : default_bg);
   1788 	}
   1789 
   1790 	if (!color2palette || (fg == -1 && bg == -1))
   1791 		return 0;
   1792 	unsigned int index = color_hash(fg, bg);
   1793 	if (color2palette[index] == 0) {
   1794 		short oldfg, oldbg;
   1795 		for (;;) {
   1796 			if (++color_pair_current >= color_pairs_max)
   1797 				color_pair_current = color_pairs_reserved + 1;
   1798 			pair_content(color_pair_current, &oldfg, &oldbg);
   1799 			unsigned int old_index = color_hash(oldfg, oldbg);
   1800 			if (color2palette[old_index] >= 0) {
   1801 				if (init_pair(color_pair_current, fg, bg) == OK) {
   1802 					color2palette[old_index] = 0;
   1803 					color2palette[index] = color_pair_current;
   1804 				}
   1805 				break;
   1806 			}
   1807 		}
   1808 	}
   1809 
   1810 	short color_pair = color2palette[index];
   1811 	return color_pair >= 0 ? color_pair : -color_pair;
   1812 }
   1813 
   1814 short vt_color_reserve(short fg, short bg)
   1815 {
   1816 	if (!color2palette || fg >= COLORS || bg >= COLORS)
   1817 		return 0;
   1818 	if (!has_default_colors && fg == -1)
   1819 		fg = default_fg;
   1820 	if (!has_default_colors && bg == -1)
   1821 		bg = default_bg;
   1822 	if (fg == -1 && bg == -1)
   1823 		return 0;
   1824 	unsigned int index = color_hash(fg, bg);
   1825 	if (color2palette[index] >= 0) {
   1826 		if (init_pair(color_pairs_reserved + 1, fg, bg) == OK)
   1827 			color2palette[index] = -(++color_pairs_reserved);
   1828 	}
   1829 	short color_pair = color2palette[index];
   1830 	return color_pair >= 0 ? color_pair : -color_pair;
   1831 }
   1832 
   1833 static void init_colors(void)
   1834 {
   1835 	pair_content(0, &default_fg, &default_bg);
   1836 	if (default_fg == -1)
   1837 		default_fg = COLOR_WHITE;
   1838 	if (default_bg == -1)
   1839 		default_bg = COLOR_BLACK;
   1840 	has_default_colors = (use_default_colors() == OK);
   1841 	color_pairs_max = MIN(MAX_COLOR_PAIRS, SHRT_MAX);
   1842 	if (COLORS)
   1843 		color2palette = calloc((COLORS + 2) * (COLORS + 2), sizeof(short));
   1844 	/*
   1845 	 * XXX: On undefined color-pairs NetBSD curses pair_content() set fg
   1846 	 *      and bg to default colors while ncurses set them respectively to
   1847 	 *      0 and 0. Initialize all color-pairs in order to have consistent
   1848 	 *      behaviour despite the implementation used.
   1849 	 */
   1850 	for (short i = 1; i < color_pairs_max; i++)
   1851 		init_pair(i, 0, 0);
   1852 	vt_color_reserve(COLOR_WHITE, COLOR_BLACK);
   1853 }
   1854 
   1855 void vt_init(void)
   1856 {
   1857 	init_colors();
   1858 	is_utf8_locale();
   1859 	char *term = getenv("DVTM_TERM");
   1860 	if (!term)
   1861 		term = "dvtm";
   1862 	snprintf(vt_term, sizeof vt_term, "%s%s", term, COLORS >= 256 ? "-256color" : "");
   1863 }
   1864 
   1865 void vt_keytable_set(const char * const keytable_overlay[], int count)
   1866 {
   1867 	for (int k = 0; k < count && k < KEY_MAX; k++) {
   1868 		const char *keyseq = keytable_overlay[k];
   1869 		if (keyseq)
   1870 			keytable[k] = keyseq;
   1871 	}
   1872 }
   1873 
   1874 void vt_shutdown(void)
   1875 {
   1876 	free(color2palette);
   1877 }
   1878 
   1879 void vt_title_handler_set(Vt *t, vt_title_handler_t handler)
   1880 {
   1881 	t->title_handler = handler;
   1882 }
   1883 
   1884 void vt_urgent_handler_set(Vt *t, vt_urgent_handler_t handler)
   1885 {
   1886 	t->urgent_handler = handler;
   1887 }
   1888 
   1889 void vt_data_set(Vt *t, void *data)
   1890 {
   1891 	t->data = data;
   1892 }
   1893 
   1894 void *vt_data_get(Vt *t)
   1895 {
   1896 	return t->data;
   1897 }
   1898 
   1899 bool vt_cursor_visible(Vt *t)
   1900 {
   1901 	return t->buffer->scroll_below ? false : !t->curshid;
   1902 }
   1903 
   1904 pid_t vt_pid_get(Vt *t)
   1905 {
   1906 	return t->pid;
   1907 }
   1908 
   1909 size_t vt_content_get(Vt *t, char **buf, bool colored)
   1910 {
   1911 	Buffer *b = t->buffer;
   1912 	int lines = b->scroll_above + b->scroll_below + b->rows + 1;
   1913 	size_t size = lines * ((b->cols + 1) * ((colored ? 64 : 0) + MB_CUR_MAX));
   1914 	mbstate_t ps;
   1915 	memset(&ps, 0, sizeof(ps));
   1916 
   1917 	if (!(*buf = malloc(size)))
   1918 		return 0;
   1919 
   1920 	char *s = *buf;
   1921 	Cell *prev_cell = NULL;
   1922 
   1923 	for (Row *row = buffer_row_first(b); row; row = buffer_row_next(b, row)) {
   1924 		size_t len = 0;
   1925 		char *last_non_space = s;
   1926 		for (int col = 0; col < b->cols; col++) {
   1927 			Cell *cell = row->cells + col;
   1928 			if (colored) {
   1929 				int esclen = 0;
   1930 				if (!prev_cell || cell->attr != prev_cell->attr) {
   1931 					attr_t attr = cell->attr << NCURSES_ATTR_SHIFT;
   1932 					esclen = sprintf(s, "\033[0%s%s%s%s%s%sm",
   1933 						attr & A_BOLD ? ";1" : "",
   1934 						attr & A_DIM ? ";2" : "",
   1935 						attr & A_UNDERLINE ? ";4" : "",
   1936 						attr & A_BLINK ? ";5" : "",
   1937 						attr & A_REVERSE ? ";7" : "",
   1938 						attr & A_INVIS ? ";8" : "");
   1939 					if (esclen > 0)
   1940 						s += esclen;
   1941 				}
   1942 				if (!prev_cell || cell->fg != prev_cell->fg || cell->attr != prev_cell->attr) {
   1943 					if (cell->fg == -1)
   1944 						esclen = sprintf(s, "\033[39m");
   1945 					else
   1946 						esclen = sprintf(s, "\033[38;5;%dm", cell->fg);
   1947 					if (esclen > 0)
   1948 						s += esclen;
   1949 				}
   1950 				if (!prev_cell || cell->bg != prev_cell->bg || cell->attr != prev_cell->attr) {
   1951 					if (cell->bg == -1)
   1952 						esclen = sprintf(s, "\033[49m");
   1953 					else
   1954 						esclen = sprintf(s, "\033[48;5;%dm", cell->bg);
   1955 					if (esclen > 0)
   1956 						s += esclen;
   1957 				}
   1958 				prev_cell = cell;
   1959 			}
   1960 			if (cell->text) {
   1961 				len = wcrtomb(s, cell->text, &ps);
   1962 				if (len > 0)
   1963 					s += len;
   1964 				last_non_space = s;
   1965 			} else if (len) {
   1966 				len = 0;
   1967 			} else {
   1968 				*s++ = ' ';
   1969 			}
   1970 		}
   1971 
   1972 		s = last_non_space;
   1973 		*s++ = '\n';
   1974 	}
   1975 
   1976 	return s - *buf;
   1977 }
   1978 
   1979 int vt_content_start(Vt *t)
   1980 {
   1981 	return t->buffer->scroll_above;
   1982 }
   1983 
   1984 int vt_content_end(Vt *t)
   1985 {
   1986 	int curs_row = t->buffer->curs_row - t->buffer->lines;
   1987 	return vt_content_start(t) + curs_row;
   1988 }