st

simple terminal

git clone https://9o.is/git/st.git

st.c

(62044B)


      1 /* See LICENSE for license details. */
      2 #include <ctype.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <limits.h>
      6 #include <pwd.h>
      7 #include <stdarg.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <signal.h>
     12 #include <sys/ioctl.h>
     13 #include <sys/select.h>
     14 #include <sys/types.h>
     15 #include <sys/wait.h>
     16 #include <termios.h>
     17 #include <unistd.h>
     18 #include <wchar.h>
     19 
     20 #include "st.h"
     21 #include "win.h"
     22 
     23 #if   defined(__linux)
     24  #include <pty.h>
     25 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
     26  #include <util.h>
     27 #elif defined(__FreeBSD__) || defined(__DragonFly__)
     28  #include <libutil.h>
     29 #endif
     30 
     31 /* Arbitrary sizes */
     32 #define UTF_INVALID   0xFFFD
     33 #define UTF_SIZ       4
     34 #define ESC_BUF_SIZ   (128*UTF_SIZ)
     35 #define ESC_ARG_SIZ   16
     36 #define STR_BUF_SIZ   ESC_BUF_SIZ
     37 #define STR_ARG_SIZ   ESC_ARG_SIZ
     38 
     39 /* macros */
     40 #define IS_SET(flag)		((term.mode & (flag)) != 0)
     41 #define ISCONTROLC0(c)		(BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
     42 #define ISCONTROLC1(c)		(BETWEEN(c, 0x80, 0x9f))
     43 #define ISCONTROL(c)		(ISCONTROLC0(c) || ISCONTROLC1(c))
     44 #define ISDELIM(u)		(u && wcschr(worddelimiters, u))
     45 
     46 #define TSCREEN term.screen[IS_SET(MODE_ALTSCREEN)]
     47 #define TLINEOFFSET(y) (((y) + TSCREEN.cur - TSCREEN.off + TSCREEN.size) % TSCREEN.size)
     48 #define TLINE(y) (TSCREEN.buffer[TLINEOFFSET(y)])
     49 
     50 enum term_mode {
     51 	MODE_WRAP        = 1 << 0,
     52 	MODE_INSERT      = 1 << 1,
     53 	MODE_ALTSCREEN   = 1 << 2,
     54 	MODE_CRLF        = 1 << 3,
     55 	MODE_ECHO        = 1 << 4,
     56 	MODE_PRINT       = 1 << 5,
     57 	MODE_UTF8        = 1 << 6,
     58 };
     59 
     60 enum cursor_movement {
     61 	CURSOR_SAVE,
     62 	CURSOR_LOAD
     63 };
     64 
     65 enum cursor_state {
     66 	CURSOR_DEFAULT  = 0,
     67 	CURSOR_WRAPNEXT = 1,
     68 	CURSOR_ORIGIN   = 2
     69 };
     70 
     71 enum charset {
     72 	CS_GRAPHIC0,
     73 	CS_GRAPHIC1,
     74 	CS_UK,
     75 	CS_USA,
     76 	CS_MULTI,
     77 	CS_GER,
     78 	CS_FIN
     79 };
     80 
     81 enum escape_state {
     82 	ESC_START      = 1,
     83 	ESC_CSI        = 2,
     84 	ESC_STR        = 4,  /* DCS, OSC, PM, APC */
     85 	ESC_ALTCHARSET = 8,
     86 	ESC_STR_END    = 16, /* a final string was encountered */
     87 	ESC_TEST       = 32, /* Enter in test mode */
     88 	ESC_UTF8       = 64,
     89 };
     90 
     91 typedef struct {
     92 	Glyph attr; /* current char attributes */
     93 	int x;
     94 	int y;
     95 	char state;
     96 } TCursor;
     97 
     98 typedef struct {
     99 	int mode;
    100 	int type;
    101 	int snap;
    102 	/*
    103 	 * Selection variables:
    104 	 * nb – normalized coordinates of the beginning of the selection
    105 	 * ne – normalized coordinates of the end of the selection
    106 	 * ob – original coordinates of the beginning of the selection
    107 	 * oe – original coordinates of the end of the selection
    108 	 */
    109 	struct {
    110 		int x, y;
    111 	} nb, ne, ob, oe;
    112 
    113 	int alt;
    114 } Selection;
    115 
    116 /* Screen lines */
    117 typedef struct {
    118 	Line* buffer;  /* ring buffer */
    119 	int size;      /* size of buffer */
    120 	int cur;       /* start of active screen */
    121 	int off;       /* scrollback line offset */
    122 	TCursor sc;    /* saved cursor */
    123 } LineBuffer;
    124 
    125 /* Internal representation of the screen */
    126 typedef struct {
    127 	int row;      /* nb row */
    128 	int col;      /* nb col */
    129 	LineBuffer screen[2]; /* screen and alternate screen */
    130 	int linelen;  /* allocated line length */
    131 	int *dirty;   /* dirtyness of lines */
    132 	TCursor c;    /* cursor */
    133 	int ocx;      /* old cursor col */
    134 	int ocy;      /* old cursor row */
    135 	int top;      /* top    scroll limit */
    136 	int bot;      /* bottom scroll limit */
    137 	int mode;     /* terminal mode flags */
    138 	int esc;      /* escape state flags */
    139 	char trantbl[4]; /* charset table translation */
    140 	int charset;  /* current charset */
    141 	int icharset; /* selected charset for sequence */
    142 	int *tabs;
    143 	Rune lastc;   /* last printed char outside of sequence, 0 if control */
    144 } Term;
    145 
    146 /* CSI Escape sequence structs */
    147 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
    148 typedef struct {
    149 	char buf[ESC_BUF_SIZ]; /* raw string */
    150 	size_t len;            /* raw string length */
    151 	char priv;
    152 	int arg[ESC_ARG_SIZ];
    153 	int narg;              /* nb of args */
    154 	char mode[2];
    155 } CSIEscape;
    156 
    157 /* STR Escape sequence structs */
    158 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
    159 typedef struct {
    160 	char type;             /* ESC type ... */
    161 	char *buf;             /* allocated raw string */
    162 	size_t siz;            /* allocation size */
    163 	size_t len;            /* raw string length */
    164 	char *args[STR_ARG_SIZ];
    165 	int narg;              /* nb of args */
    166 } STREscape;
    167 
    168 static void execsh(char *, char **);
    169 static void stty(char **);
    170 static void sigchld(int);
    171 static void ttywriteraw(const char *, size_t);
    172 
    173 static void csidump(void);
    174 static void csihandle(void);
    175 static void csiparse(void);
    176 static void csireset(void);
    177 static void osc_color_response(int, int, int);
    178 static int eschandle(uchar);
    179 static void strdump(void);
    180 static void strhandle(void);
    181 static void strparse(void);
    182 static void strreset(void);
    183 
    184 static void tprinter(char *, size_t);
    185 static void tdumpsel(void);
    186 static void tdumpline(int);
    187 static void tdump(void);
    188 static void tclearregion(int, int, int, int);
    189 static void tcursor(int);
    190 static void tdeletechar(int);
    191 static void tdeleteline(int);
    192 static void tinsertblank(int);
    193 static void tinsertblankline(int);
    194 static int tlinelen(int);
    195 static void tmoveto(int, int);
    196 static void tmoveato(int, int);
    197 static void tnewline(int);
    198 static void tputtab(int);
    199 static void tputc(Rune);
    200 static void treset(void);
    201 static void tscrollup(int, int);
    202 static void tscrolldown(int, int);
    203 static void tsetattr(const int *, int);
    204 static void tsetchar(Rune, const Glyph *, int, int);
    205 static void tsetdirt(int, int);
    206 static void tsetscroll(int, int);
    207 static void tswapscreen(void);
    208 static void tsetmode(int, int, const int *, int);
    209 static int twrite(const char *, int, int);
    210 static void tfulldirt(void);
    211 static void tcontrolcode(uchar );
    212 static void tdectest(char );
    213 static void tdefutf8(char);
    214 static int32_t tdefcolor(const int *, int *, int);
    215 static void tdeftran(char);
    216 static void tstrsequence(uchar);
    217 
    218 static void drawregion(int, int, int, int);
    219 static void clearline(Line, Glyph, int, int);
    220 static Line ensureline(Line);
    221 
    222 static void selnormalize(void);
    223 static void selscroll(int, int);
    224 static void selsnap(int *, int *, int);
    225 
    226 static size_t utf8decode(const char *, Rune *, size_t);
    227 static Rune utf8decodebyte(char, size_t *);
    228 static char utf8encodebyte(Rune, size_t);
    229 static size_t utf8validate(Rune *, size_t);
    230 
    231 static char *base64dec(const char *);
    232 static char base64dec_getc(const char **);
    233 
    234 static ssize_t xwrite(int, const char *, size_t);
    235 
    236 /* Globals */
    237 static Term term;
    238 static Selection sel;
    239 static CSIEscape csiescseq;
    240 static STREscape strescseq;
    241 static int iofd = 1;
    242 static int cmdfd;
    243 static pid_t pid;
    244 
    245 static const uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
    246 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
    247 static const Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
    248 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
    249 
    250 ssize_t
    251 xwrite(int fd, const char *s, size_t len)
    252 {
    253 	size_t aux = len;
    254 	ssize_t r;
    255 
    256 	while (len > 0) {
    257 		r = write(fd, s, len);
    258 		if (r < 0)
    259 			return r;
    260 		len -= r;
    261 		s += r;
    262 	}
    263 
    264 	return aux;
    265 }
    266 
    267 void *
    268 xmalloc(size_t len)
    269 {
    270 	void *p;
    271 
    272 	if (!(p = malloc(len)))
    273 		die("malloc: %s\n", strerror(errno));
    274 
    275 	return p;
    276 }
    277 
    278 void *
    279 xrealloc(void *p, size_t len)
    280 {
    281 	if ((p = realloc(p, len)) == NULL)
    282 		die("realloc: %s\n", strerror(errno));
    283 
    284 	return p;
    285 }
    286 
    287 char *
    288 xstrdup(const char *s)
    289 {
    290 	char *p;
    291 
    292 	if ((p = strdup(s)) == NULL)
    293 		die("strdup: %s\n", strerror(errno));
    294 
    295 	return p;
    296 }
    297 
    298 size_t
    299 utf8decode(const char *c, Rune *u, size_t clen)
    300 {
    301 	size_t i, j, len, type;
    302 	Rune udecoded;
    303 
    304 	*u = UTF_INVALID;
    305 	if (!clen)
    306 		return 0;
    307 	udecoded = utf8decodebyte(c[0], &len);
    308 	if (!BETWEEN(len, 1, UTF_SIZ))
    309 		return 1;
    310 	for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
    311 		udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
    312 		if (type != 0)
    313 			return j;
    314 	}
    315 	if (j < len)
    316 		return 0;
    317 	*u = udecoded;
    318 	utf8validate(u, len);
    319 
    320 	return len;
    321 }
    322 
    323 Rune
    324 utf8decodebyte(char c, size_t *i)
    325 {
    326 	for (*i = 0; *i < LEN(utfmask); ++(*i))
    327 		if (((uchar)c & utfmask[*i]) == utfbyte[*i])
    328 			return (uchar)c & ~utfmask[*i];
    329 
    330 	return 0;
    331 }
    332 
    333 size_t
    334 utf8encode(Rune u, char *c)
    335 {
    336 	size_t len, i;
    337 
    338 	len = utf8validate(&u, 0);
    339 	if (len > UTF_SIZ)
    340 		return 0;
    341 
    342 	for (i = len - 1; i != 0; --i) {
    343 		c[i] = utf8encodebyte(u, 0);
    344 		u >>= 6;
    345 	}
    346 	c[0] = utf8encodebyte(u, len);
    347 
    348 	return len;
    349 }
    350 
    351 char
    352 utf8encodebyte(Rune u, size_t i)
    353 {
    354 	return utfbyte[i] | (u & ~utfmask[i]);
    355 }
    356 
    357 size_t
    358 utf8validate(Rune *u, size_t i)
    359 {
    360 	if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
    361 		*u = UTF_INVALID;
    362 	for (i = 1; *u > utfmax[i]; ++i)
    363 		;
    364 
    365 	return i;
    366 }
    367 
    368 char
    369 base64dec_getc(const char **src)
    370 {
    371 	while (**src && !isprint((unsigned char)**src))
    372 		(*src)++;
    373 	return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
    374 }
    375 
    376 char *
    377 base64dec(const char *src)
    378 {
    379 	size_t in_len = strlen(src);
    380 	char *result, *dst;
    381 	static const char base64_digits[256] = {
    382 		[43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
    383 		0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
    384 		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
    385 		0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    386 		40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    387 	};
    388 
    389 	if (in_len % 4)
    390 		in_len += 4 - (in_len % 4);
    391 	result = dst = xmalloc(in_len / 4 * 3 + 1);
    392 	while (*src) {
    393 		int a = base64_digits[(unsigned char) base64dec_getc(&src)];
    394 		int b = base64_digits[(unsigned char) base64dec_getc(&src)];
    395 		int c = base64_digits[(unsigned char) base64dec_getc(&src)];
    396 		int d = base64_digits[(unsigned char) base64dec_getc(&src)];
    397 
    398 		/* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
    399 		if (a == -1 || b == -1)
    400 			break;
    401 
    402 		*dst++ = (a << 2) | ((b & 0x30) >> 4);
    403 		if (c == -1)
    404 			break;
    405 		*dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
    406 		if (d == -1)
    407 			break;
    408 		*dst++ = ((c & 0x03) << 6) | d;
    409 	}
    410 	*dst = '\0';
    411 	return result;
    412 }
    413 
    414 void
    415 selinit(void)
    416 {
    417 	sel.mode = SEL_IDLE;
    418 	sel.snap = 0;
    419 	sel.ob.x = -1;
    420 }
    421 
    422 int
    423 tlinelen(int y)
    424 {
    425 	int i = term.col;
    426 	Line line = TLINE(y);
    427 
    428 	if (line[i - 1].mode & ATTR_WRAP)
    429 		return i;
    430 
    431 	while (i > 0 && line[i - 1].u == ' ')
    432 		--i;
    433 
    434 	return i;
    435 }
    436 
    437 void
    438 selstart(int col, int row, int snap)
    439 {
    440 	selclear();
    441 	sel.mode = SEL_EMPTY;
    442 	sel.type = SEL_REGULAR;
    443 	sel.alt = IS_SET(MODE_ALTSCREEN);
    444 	sel.snap = snap;
    445 	sel.oe.x = sel.ob.x = col;
    446 	sel.oe.y = sel.ob.y = row;
    447 	selnormalize();
    448 
    449 	if (sel.snap != 0)
    450 		sel.mode = SEL_READY;
    451 	tsetdirt(sel.nb.y, sel.ne.y);
    452 }
    453 
    454 void
    455 selextend(int col, int row, int type, int done)
    456 {
    457 	int oldey, oldex, oldsby, oldsey, oldtype;
    458 
    459 	if (sel.mode == SEL_IDLE)
    460 		return;
    461 	if (done && sel.mode == SEL_EMPTY) {
    462 		selclear();
    463 		return;
    464 	}
    465 
    466 	oldey = sel.oe.y;
    467 	oldex = sel.oe.x;
    468 	oldsby = sel.nb.y;
    469 	oldsey = sel.ne.y;
    470 	oldtype = sel.type;
    471 
    472 	sel.oe.x = col;
    473 	sel.oe.y = row;
    474 	selnormalize();
    475 	sel.type = type;
    476 
    477 	if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
    478 		tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
    479 
    480 	sel.mode = done ? SEL_IDLE : SEL_READY;
    481 }
    482 
    483 void
    484 selnormalize(void)
    485 {
    486 	int i;
    487 
    488 	if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
    489 		sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
    490 		sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
    491 	} else {
    492 		sel.nb.x = MIN(sel.ob.x, sel.oe.x);
    493 		sel.ne.x = MAX(sel.ob.x, sel.oe.x);
    494 	}
    495 	sel.nb.y = MIN(sel.ob.y, sel.oe.y);
    496 	sel.ne.y = MAX(sel.ob.y, sel.oe.y);
    497 
    498 	selsnap(&sel.nb.x, &sel.nb.y, -1);
    499 	selsnap(&sel.ne.x, &sel.ne.y, +1);
    500 
    501 	/* expand selection over line breaks */
    502 	if (sel.type == SEL_RECTANGULAR)
    503 		return;
    504 	i = tlinelen(sel.nb.y);
    505 	if (i < sel.nb.x)
    506 		sel.nb.x = i;
    507 	if (tlinelen(sel.ne.y) <= sel.ne.x)
    508 		sel.ne.x = term.col - 1;
    509 }
    510 
    511 int
    512 selected(int x, int y)
    513 {
    514 	if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
    515 			sel.alt != IS_SET(MODE_ALTSCREEN))
    516 		return 0;
    517 
    518 	if (sel.type == SEL_RECTANGULAR)
    519 		return BETWEEN(y, sel.nb.y, sel.ne.y)
    520 		    && BETWEEN(x, sel.nb.x, sel.ne.x);
    521 
    522 	return BETWEEN(y, sel.nb.y, sel.ne.y)
    523 	    && (y != sel.nb.y || x >= sel.nb.x)
    524 	    && (y != sel.ne.y || x <= sel.ne.x);
    525 }
    526 
    527 void
    528 selsnap(int *x, int *y, int direction)
    529 {
    530 	int newx, newy, xt, yt;
    531 	int delim, prevdelim;
    532 	const Glyph *gp, *prevgp;
    533 
    534 	switch (sel.snap) {
    535 	case SNAP_WORD:
    536 		/*
    537 		 * Snap around if the word wraps around at the end or
    538 		 * beginning of a line.
    539 		 */
    540 		prevgp = &TLINE(*y)[*x];
    541 		prevdelim = ISDELIM(prevgp->u);
    542 		for (;;) {
    543 			newx = *x + direction;
    544 			newy = *y;
    545 			if (!BETWEEN(newx, 0, term.col - 1)) {
    546 				newy += direction;
    547 				newx = (newx + term.col) % term.col;
    548 				if (!BETWEEN(newy, 0, term.row - 1))
    549 					break;
    550 
    551 				if (direction > 0)
    552 					yt = *y, xt = *x;
    553 				else
    554 					yt = newy, xt = newx;
    555 				if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
    556 					break;
    557 			}
    558 
    559 			if (newx >= tlinelen(newy))
    560 				break;
    561 
    562 			gp = &TLINE(newy)[newx];
    563 			delim = ISDELIM(gp->u);
    564 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
    565 					|| (delim && gp->u != prevgp->u)))
    566 				break;
    567 
    568 			*x = newx;
    569 			*y = newy;
    570 			prevgp = gp;
    571 			prevdelim = delim;
    572 		}
    573 		break;
    574 	case SNAP_LINE:
    575 		/*
    576 		 * Snap around if the the previous line or the current one
    577 		 * has set ATTR_WRAP at its end. Then the whole next or
    578 		 * previous line will be selected.
    579 		 */
    580 		*x = (direction < 0) ? 0 : term.col - 1;
    581 		if (direction < 0) {
    582 			for (; *y > 0; *y += direction) {
    583 				if (!(TLINE(*y-1)[term.col-1].mode
    584 						& ATTR_WRAP)) {
    585 					break;
    586 				}
    587 			}
    588 		} else if (direction > 0) {
    589 			for (; *y < term.row-1; *y += direction) {
    590 				if (!(TLINE(*y)[term.col-1].mode
    591 						& ATTR_WRAP)) {
    592 					break;
    593 				}
    594 			}
    595 		}
    596 		break;
    597 	}
    598 }
    599 
    600 char *
    601 getsel(void)
    602 {
    603 	char *str, *ptr;
    604 	int y, bufsize, lastx, linelen;
    605 	const Glyph *gp, *last;
    606 
    607 	if (sel.ob.x == -1)
    608 		return NULL;
    609 
    610 	bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
    611 	ptr = str = xmalloc(bufsize);
    612 
    613 	/* append every set & selected glyph to the selection */
    614 	for (y = sel.nb.y; y <= sel.ne.y; y++) {
    615 		if ((linelen = tlinelen(y)) == 0) {
    616 			*ptr++ = '\n';
    617 			continue;
    618 		}
    619 
    620 		if (sel.type == SEL_RECTANGULAR) {
    621 			gp = &TLINE(y)[sel.nb.x];
    622 			lastx = sel.ne.x;
    623 		} else {
    624 			gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
    625 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
    626 		}
    627 		last = &TLINE(y)[MIN(lastx, linelen-1)];
    628 		while (last >= gp && last->u == ' ')
    629 			--last;
    630 
    631 		for ( ; gp <= last; ++gp) {
    632 			if (gp->mode & ATTR_WDUMMY)
    633 				continue;
    634 
    635 			ptr += utf8encode(gp->u, ptr);
    636 		}
    637 
    638 		/*
    639 		 * Copy and pasting of line endings is inconsistent
    640 		 * in the inconsistent terminal and GUI world.
    641 		 * The best solution seems like to produce '\n' when
    642 		 * something is copied from st and convert '\n' to
    643 		 * '\r', when something to be pasted is received by
    644 		 * st.
    645 		 * FIXME: Fix the computer world.
    646 		 */
    647 		if ((y < sel.ne.y || lastx >= linelen) &&
    648 		    (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
    649 			*ptr++ = '\n';
    650 	}
    651 	*ptr = 0;
    652 	return str;
    653 }
    654 
    655 void
    656 selclear(void)
    657 {
    658 	if (sel.ob.x == -1)
    659 		return;
    660 	sel.mode = SEL_IDLE;
    661 	sel.ob.x = -1;
    662 	tsetdirt(sel.nb.y, sel.ne.y);
    663 }
    664 
    665 void
    666 die(const char *errstr, ...)
    667 {
    668 	va_list ap;
    669 
    670 	va_start(ap, errstr);
    671 	vfprintf(stderr, errstr, ap);
    672 	va_end(ap);
    673 	exit(1);
    674 }
    675 
    676 void
    677 execsh(char *cmd, char **args)
    678 {
    679 	char *sh, *prog, *arg;
    680 	const struct passwd *pw;
    681 
    682 	errno = 0;
    683 	if ((pw = getpwuid(getuid())) == NULL) {
    684 		if (errno)
    685 			die("getpwuid: %s\n", strerror(errno));
    686 		else
    687 			die("who are you?\n");
    688 	}
    689 
    690 	if ((sh = getenv("SHELL")) == NULL)
    691 		sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
    692 
    693 	if (args) {
    694 		prog = args[0];
    695 		arg = NULL;
    696 	} else if (scroll) {
    697 		prog = scroll;
    698 		arg = utmp ? utmp : sh;
    699 	} else if (utmp) {
    700 		prog = utmp;
    701 		arg = NULL;
    702 	} else {
    703 		prog = sh;
    704 		arg = NULL;
    705 	}
    706 	DEFAULT(args, ((char *[]) {prog, arg, NULL}));
    707 
    708 	unsetenv("COLUMNS");
    709 	unsetenv("LINES");
    710 	unsetenv("TERMCAP");
    711 	setenv("LOGNAME", pw->pw_name, 1);
    712 	setenv("USER", pw->pw_name, 1);
    713 	setenv("SHELL", sh, 1);
    714 	setenv("HOME", pw->pw_dir, 1);
    715 	setenv("TERM", termname, 1);
    716 
    717 	signal(SIGCHLD, SIG_DFL);
    718 	signal(SIGHUP, SIG_DFL);
    719 	signal(SIGINT, SIG_DFL);
    720 	signal(SIGQUIT, SIG_DFL);
    721 	signal(SIGTERM, SIG_DFL);
    722 	signal(SIGALRM, SIG_DFL);
    723 
    724 	execvp(prog, args);
    725 	_exit(1);
    726 }
    727 
    728 void
    729 sigchld(int a)
    730 {
    731 	int stat;
    732 	pid_t p;
    733 
    734 	if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
    735 		die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
    736 
    737 	if (pid != p)
    738 		return;
    739 
    740 	if (WIFEXITED(stat) && WEXITSTATUS(stat))
    741 		die("child exited with status %d\n", WEXITSTATUS(stat));
    742 	else if (WIFSIGNALED(stat))
    743 		die("child terminated due to signal %d\n", WTERMSIG(stat));
    744 	_exit(0);
    745 }
    746 
    747 void
    748 stty(char **args)
    749 {
    750 	char cmd[_POSIX_ARG_MAX], **p, *q, *s;
    751 	size_t n, siz;
    752 
    753 	if ((n = strlen(stty_args)) > sizeof(cmd)-1)
    754 		die("incorrect stty parameters\n");
    755 	memcpy(cmd, stty_args, n);
    756 	q = cmd + n;
    757 	siz = sizeof(cmd) - n;
    758 	for (p = args; p && (s = *p); ++p) {
    759 		if ((n = strlen(s)) > siz-1)
    760 			die("stty parameter length too long\n");
    761 		*q++ = ' ';
    762 		memcpy(q, s, n);
    763 		q += n;
    764 		siz -= n + 1;
    765 	}
    766 	*q = '\0';
    767 	if (system(cmd) != 0)
    768 		perror("Couldn't call stty");
    769 }
    770 
    771 int
    772 ttynew(const char *line, char *cmd, const char *out, char **args)
    773 {
    774 	int m, s;
    775 
    776 	if (out) {
    777 		term.mode |= MODE_PRINT;
    778 		iofd = (!strcmp(out, "-")) ?
    779 			  1 : open(out, O_WRONLY | O_CREAT, 0666);
    780 		if (iofd < 0) {
    781 			fprintf(stderr, "Error opening %s:%s\n",
    782 				out, strerror(errno));
    783 		}
    784 	}
    785 
    786 	if (line) {
    787 		if ((cmdfd = open(line, O_RDWR)) < 0)
    788 			die("open line '%s' failed: %s\n",
    789 			    line, strerror(errno));
    790 		dup2(cmdfd, 0);
    791 		stty(args);
    792 		return cmdfd;
    793 	}
    794 
    795 	/* seems to work fine on linux, openbsd and freebsd */
    796 	if (openpty(&m, &s, NULL, NULL, NULL) < 0)
    797 		die("openpty failed: %s\n", strerror(errno));
    798 
    799 	switch (pid = fork()) {
    800 	case -1:
    801 		die("fork failed: %s\n", strerror(errno));
    802 		break;
    803 	case 0:
    804 		close(iofd);
    805 		close(m);
    806 		setsid(); /* create a new process group */
    807 		dup2(s, 0);
    808 		dup2(s, 1);
    809 		dup2(s, 2);
    810 		if (ioctl(s, TIOCSCTTY, NULL) < 0)
    811 			die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
    812 		if (s > 2)
    813 			close(s);
    814 #ifdef __OpenBSD__
    815 		if (pledge("stdio getpw proc exec", NULL) == -1)
    816 			die("pledge\n");
    817 #endif
    818 		execsh(cmd, args);
    819 		break;
    820 	default:
    821 #ifdef __OpenBSD__
    822 		if (pledge("stdio rpath tty proc", NULL) == -1)
    823 			die("pledge\n");
    824 #endif
    825 		close(s);
    826 		cmdfd = m;
    827 		signal(SIGCHLD, sigchld);
    828 		break;
    829 	}
    830 	return cmdfd;
    831 }
    832 
    833 size_t
    834 ttyread(void)
    835 {
    836 	static char buf[BUFSIZ];
    837 	static int buflen = 0;
    838 	int ret, written;
    839 
    840 	/* append read bytes to unprocessed bytes */
    841 	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
    842 
    843 	switch (ret) {
    844 	case 0:
    845 		exit(0);
    846 	case -1:
    847 		die("couldn't read from shell: %s\n", strerror(errno));
    848 	default:
    849 		buflen += ret;
    850 		written = twrite(buf, buflen, 0);
    851 		buflen -= written;
    852 		/* keep any incomplete UTF-8 byte sequence for the next call */
    853 		if (buflen > 0)
    854 			memmove(buf, buf + written, buflen);
    855 		return ret;
    856 	}
    857 }
    858 
    859 void
    860 ttywrite(const char *s, size_t n, int may_echo)
    861 {
    862 	const char *next;
    863 
    864 	if (may_echo && IS_SET(MODE_ECHO))
    865 		twrite(s, n, 1);
    866 
    867 	if (!IS_SET(MODE_CRLF)) {
    868 		ttywriteraw(s, n);
    869 		return;
    870 	}
    871 
    872 	/* This is similar to how the kernel handles ONLCR for ttys */
    873 	while (n > 0) {
    874 		if (*s == '\r') {
    875 			next = s + 1;
    876 			ttywriteraw("\r\n", 2);
    877 		} else {
    878 			next = memchr(s, '\r', n);
    879 			DEFAULT(next, s + n);
    880 			ttywriteraw(s, next - s);
    881 		}
    882 		n -= next - s;
    883 		s = next;
    884 	}
    885 }
    886 
    887 void
    888 ttywriteraw(const char *s, size_t n)
    889 {
    890 	fd_set wfd, rfd;
    891 	ssize_t r;
    892 	size_t lim = 256;
    893 
    894 	/*
    895 	 * Remember that we are using a pty, which might be a modem line.
    896 	 * Writing too much will clog the line. That's why we are doing this
    897 	 * dance.
    898 	 * FIXME: Migrate the world to Plan 9.
    899 	 */
    900 	while (n > 0) {
    901 		FD_ZERO(&wfd);
    902 		FD_ZERO(&rfd);
    903 		FD_SET(cmdfd, &wfd);
    904 		FD_SET(cmdfd, &rfd);
    905 
    906 		/* Check if we can write. */
    907 		if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
    908 			if (errno == EINTR)
    909 				continue;
    910 			die("select failed: %s\n", strerror(errno));
    911 		}
    912 		if (FD_ISSET(cmdfd, &wfd)) {
    913 			/*
    914 			 * Only write the bytes written by ttywrite() or the
    915 			 * default of 256. This seems to be a reasonable value
    916 			 * for a serial line. Bigger values might clog the I/O.
    917 			 */
    918 			if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
    919 				goto write_error;
    920 			if (r < n) {
    921 				/*
    922 				 * We weren't able to write out everything.
    923 				 * This means the buffer is getting full
    924 				 * again. Empty it.
    925 				 */
    926 				if (n < lim)
    927 					lim = ttyread();
    928 				n -= r;
    929 				s += r;
    930 			} else {
    931 				/* All bytes have been written. */
    932 				break;
    933 			}
    934 		}
    935 		if (FD_ISSET(cmdfd, &rfd))
    936 			lim = ttyread();
    937 	}
    938 	return;
    939 
    940 write_error:
    941 	die("write error on tty: %s\n", strerror(errno));
    942 }
    943 
    944 void
    945 ttyresize(int tw, int th)
    946 {
    947 	struct winsize w;
    948 
    949 	w.ws_row = term.row;
    950 	w.ws_col = term.col;
    951 	w.ws_xpixel = tw;
    952 	w.ws_ypixel = th;
    953 	if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
    954 		fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
    955 }
    956 
    957 void
    958 ttyhangup(void)
    959 {
    960 	/* Send SIGHUP to shell */
    961 	kill(pid, SIGHUP);
    962 }
    963 
    964 int
    965 tattrset(int attr)
    966 {
    967 	int i, j;
    968 	int y = TLINEOFFSET(0);
    969 
    970 	for (i = 0; i < term.row-1; i++) {
    971 		Line line = TSCREEN.buffer[y];
    972 		for (j = 0; j < term.col-1; j++) {
    973 			if (line[j].mode & attr)
    974 				return 1;
    975 		}
    976 		y = (y+1) % TSCREEN.size;
    977 	}
    978 
    979 	return 0;
    980 }
    981 
    982 void
    983 tsetdirt(int top, int bot)
    984 {
    985 	int i;
    986 
    987 	if (term.row <= 0)
    988 		return;
    989 
    990 	LIMIT(top, 0, term.row-1);
    991 	LIMIT(bot, 0, term.row-1);
    992 
    993 	for (i = top; i <= bot; i++)
    994 		term.dirty[i] = 1;
    995 }
    996 
    997 void
    998 tsetdirtattr(int attr)
    999 {
   1000 	int i, j;
   1001 	int y = TLINEOFFSET(0);
   1002 
   1003 	for (i = 0; i < term.row-1; i++) {
   1004 		Line line = TSCREEN.buffer[y];
   1005 		for (j = 0; j < term.col-1; j++) {
   1006 			if (line[j].mode & attr) {
   1007 				tsetdirt(i, i);
   1008 				break;
   1009 			}
   1010 		}
   1011 		y = (y+1) % TSCREEN.size;
   1012 	}
   1013 }
   1014 
   1015 void
   1016 tfulldirt(void)
   1017 {
   1018 	tsetdirt(0, term.row-1);
   1019 }
   1020 
   1021 void
   1022 tcursor(int mode)
   1023 {
   1024 	if (mode == CURSOR_SAVE) {
   1025 		TSCREEN.sc = term.c;
   1026 	} else if (mode == CURSOR_LOAD) {
   1027 		term.c = TSCREEN.sc;
   1028 		tmoveto(term.c.x, term.c.y);
   1029 	}
   1030 }
   1031 
   1032 void
   1033 treset(void)
   1034 {
   1035 	int i, j;
   1036 	Glyph g = (Glyph){ .fg = defaultfg, .bg = defaultbg};
   1037 
   1038 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1039 	for (i = tabspaces; i < term.col; i += tabspaces)
   1040 		term.tabs[i] = 1;
   1041 	term.top = 0;
   1042 	term.bot = term.row - 1;
   1043 	term.mode = MODE_WRAP|MODE_UTF8;
   1044 	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
   1045 	term.charset = 0;
   1046 
   1047 	for (i = 0; i < 2; i++) {
   1048 		term.screen[i].sc = (TCursor){{
   1049 			.fg = defaultfg,
   1050 			.bg = defaultbg
   1051 		}};
   1052 		term.screen[i].cur = 0;
   1053 		term.screen[i].off = 0;
   1054 		for (j = 0; j < term.row; ++j) {
   1055 			if (term.col != term.linelen)
   1056 				term.screen[i].buffer[j] = xrealloc(term.screen[i].buffer[j], term.col * sizeof(Glyph));
   1057 			clearline(term.screen[i].buffer[j], g, 0, term.col);
   1058 		}
   1059 		for (j = term.row; j < term.screen[i].size; ++j) {
   1060 			free(term.screen[i].buffer[j]);
   1061 			term.screen[i].buffer[j] = NULL;
   1062 		}
   1063 	}
   1064 	tcursor(CURSOR_LOAD);
   1065 	term.linelen = term.col;
   1066 	tfulldirt();
   1067 }
   1068 
   1069 void
   1070 tnew(int col, int row)
   1071 {
   1072 	int i;
   1073 	term = (Term){};
   1074 	term.screen[0].buffer = xmalloc(HISTSIZE * sizeof(Line));
   1075 	term.screen[0].size = HISTSIZE;
   1076 	term.screen[1].buffer = NULL;
   1077 	for (i = 0; i < HISTSIZE; ++i) term.screen[0].buffer[i] = NULL;
   1078 
   1079 	tresize(col, row);
   1080 	treset();
   1081 }
   1082 
   1083 int tisaltscr(void)
   1084 {
   1085 	return IS_SET(MODE_ALTSCREEN);
   1086 }
   1087 
   1088 void
   1089 tswapscreen(void)
   1090 {
   1091 	term.mode ^= MODE_ALTSCREEN;
   1092 	tfulldirt();
   1093 }
   1094 
   1095 void
   1096 kscrollup(const Arg *a)
   1097 {
   1098 	float n = a->f;
   1099 
   1100 	if (IS_SET(MODE_ALTSCREEN))
   1101 		return;
   1102 
   1103 	if (n < 0) n = MAX((-n) * term.row, 1);
   1104 	if (n > TSCREEN.size - term.row - TSCREEN.off) n = TSCREEN.size - term.row - TSCREEN.off;
   1105 	while (!TLINE((int)-n)) --n;
   1106 	TSCREEN.off += n;
   1107 	selscroll(0, n);
   1108 	tfulldirt();
   1109 }
   1110 
   1111 void
   1112 kscrolldown(const Arg *a)
   1113 {
   1114 
   1115 	float n = a->f;
   1116 
   1117 	if (IS_SET(MODE_ALTSCREEN))
   1118 		return;
   1119 
   1120 	if (n < 0) n = MAX((-n) * term.row, 1);
   1121 	if (n > TSCREEN.off) n = TSCREEN.off;
   1122 	TSCREEN.off -= n;
   1123 	selscroll(0, -n);
   1124 	tfulldirt();
   1125 }
   1126 
   1127 void
   1128 tscrolldown(int orig, int n)
   1129 {
   1130 	int i;
   1131 	Line temp;
   1132 
   1133 	LIMIT(n, 0, term.bot-orig+1);
   1134 
   1135 	/* Ensure that lines are allocated */
   1136 	for (i = -n; i < 0; i++) {
   1137 		TLINE(i) = ensureline(TLINE(i));
   1138 	}
   1139 
   1140 	/* Shift non-scrolling areas in ring buffer */
   1141 	for (i = term.bot+1; i < term.row; i++) {
   1142 		temp = TLINE(i);
   1143 		TLINE(i) = TLINE(i-n);
   1144 		TLINE(i-n) = temp;
   1145 	}
   1146 	for (i = 0; i < orig; i++) {
   1147 		temp = TLINE(i);
   1148 		TLINE(i) = TLINE(i-n);
   1149 		TLINE(i-n) = temp;
   1150 	}
   1151 
   1152 	/* Scroll buffer */
   1153 	TSCREEN.cur = (TSCREEN.cur + TSCREEN.size - n) % TSCREEN.size;
   1154 	/* Clear lines that have entered the view */
   1155 	tclearregion(0, orig, term.linelen-1, orig+n-1);
   1156 	/* Redraw portion of the screen that has scrolled */
   1157 	tsetdirt(orig+n-1, term.bot);
   1158 	selscroll(orig, n);
   1159 }
   1160 
   1161 void
   1162 tscrollup(int orig, int n)
   1163 {
   1164 	int i;
   1165 	Line temp;
   1166 
   1167 	LIMIT(n, 0, term.bot-orig+1);
   1168 
   1169 	/* Ensure that lines are allocated */
   1170 	for (i = term.row; i < term.row + n; i++) {
   1171 		TLINE(i) = ensureline(TLINE(i));
   1172 	}
   1173 
   1174 	/* Shift non-scrolling areas in ring buffer */
   1175 	for (i = orig-1; i >= 0; i--) {
   1176 		temp = TLINE(i);
   1177 		TLINE(i) = TLINE(i+n);
   1178 		TLINE(i+n) = temp;
   1179 	}
   1180 	for (i = term.row-1; i >term.bot; i--) {
   1181 		temp = TLINE(i);
   1182 		TLINE(i) = TLINE(i+n);
   1183 		TLINE(i+n) = temp;
   1184 	}
   1185 
   1186 	/* Scroll buffer */
   1187 	TSCREEN.cur = (TSCREEN.cur + n) % TSCREEN.size;
   1188 	/* Clear lines that have entered the view */
   1189 	tclearregion(0, term.bot-n+1, term.linelen-1, term.bot);
   1190 	/* Redraw portion of the screen that has scrolled */
   1191 	tsetdirt(orig, term.bot-n+1);
   1192 	selscroll(orig, -n);
   1193 }
   1194 
   1195 void
   1196 selscroll(int orig, int n)
   1197 {
   1198 	if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
   1199 		return;
   1200 
   1201 	if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
   1202 		selclear();
   1203 	} else if (BETWEEN(sel.nb.y, orig, term.bot)) {
   1204 		sel.ob.y += n;
   1205 		sel.oe.y += n;
   1206 		if (sel.ob.y < term.top || sel.ob.y > term.bot ||
   1207 		    sel.oe.y < term.top || sel.oe.y > term.bot) {
   1208 			selclear();
   1209 		} else {
   1210 			selnormalize();
   1211 		}
   1212 	}
   1213 }
   1214 
   1215 void
   1216 tnewline(int first_col)
   1217 {
   1218 	int y = term.c.y;
   1219 
   1220 	if (y == term.bot) {
   1221 		tscrollup(term.top, 1);
   1222 	} else {
   1223 		y++;
   1224 	}
   1225 	tmoveto(first_col ? 0 : term.c.x, y);
   1226 }
   1227 
   1228 void
   1229 csiparse(void)
   1230 {
   1231 	char *p = csiescseq.buf, *np;
   1232 	long int v;
   1233 	int sep = ';'; /* colon or semi-colon, but not both */
   1234 
   1235 	csiescseq.narg = 0;
   1236 	if (*p == '?') {
   1237 		csiescseq.priv = 1;
   1238 		p++;
   1239 	}
   1240 
   1241 	csiescseq.buf[csiescseq.len] = '\0';
   1242 	while (p < csiescseq.buf+csiescseq.len) {
   1243 		np = NULL;
   1244 		v = strtol(p, &np, 10);
   1245 		if (np == p)
   1246 			v = 0;
   1247 		if (v == LONG_MAX || v == LONG_MIN)
   1248 			v = -1;
   1249 		csiescseq.arg[csiescseq.narg++] = v;
   1250 		p = np;
   1251 		if (sep == ';' && *p == ':')
   1252 			sep = ':'; /* allow override to colon once */
   1253 		if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
   1254 			break;
   1255 		p++;
   1256 	}
   1257 	csiescseq.mode[0] = *p++;
   1258 	csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
   1259 }
   1260 
   1261 /* for absolute user moves, when decom is set */
   1262 void
   1263 tmoveato(int x, int y)
   1264 {
   1265 	tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
   1266 }
   1267 
   1268 void
   1269 tmoveto(int x, int y)
   1270 {
   1271 	int miny, maxy;
   1272 
   1273 	if (term.c.state & CURSOR_ORIGIN) {
   1274 		miny = term.top;
   1275 		maxy = term.bot;
   1276 	} else {
   1277 		miny = 0;
   1278 		maxy = term.row - 1;
   1279 	}
   1280 	term.c.state &= ~CURSOR_WRAPNEXT;
   1281 	term.c.x = LIMIT(x, 0, term.col-1);
   1282 	term.c.y = LIMIT(y, miny, maxy);
   1283 }
   1284 
   1285 void
   1286 tsetchar(Rune u, const Glyph *attr, int x, int y)
   1287 {
   1288 	static const char *vt100_0[62] = { /* 0x41 - 0x7e */
   1289 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
   1290 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
   1291 		0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
   1292 		0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
   1293 		"◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
   1294 		"␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
   1295 		"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
   1296 		"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
   1297 	};
   1298 	Line line = TLINE(y);
   1299 
   1300 	/*
   1301 	 * The table is proudly stolen from rxvt.
   1302 	 */
   1303 	if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
   1304 	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
   1305 		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
   1306 
   1307 	if (line[x].mode & ATTR_WIDE) {
   1308 		if (x+1 < term.col) {
   1309 			line[x+1].u = ' ';
   1310 			line[x+1].mode &= ~ATTR_WDUMMY;
   1311 		}
   1312 	} else if (line[x].mode & ATTR_WDUMMY) {
   1313 		line[x-1].u = ' ';
   1314 		line[x-1].mode &= ~ATTR_WIDE;
   1315 	}
   1316 
   1317 	term.dirty[y] = 1;
   1318 	line[x] = *attr;
   1319 	line[x].u = u;
   1320 }
   1321 
   1322 void
   1323 tclearregion(int x1, int y1, int x2, int y2)
   1324 {
   1325 	int x, y, L, S, temp;
   1326 	Glyph *gp;
   1327 
   1328 	if (x1 > x2)
   1329 		temp = x1, x1 = x2, x2 = temp;
   1330 	if (y1 > y2)
   1331 		temp = y1, y1 = y2, y2 = temp;
   1332 
   1333 	LIMIT(x1, 0, term.linelen-1);
   1334 	LIMIT(x2, 0, term.linelen-1);
   1335 	LIMIT(y1, 0, term.row-1);
   1336 	LIMIT(y2, 0, term.row-1);
   1337 
   1338 	L = TLINEOFFSET(y1);
   1339 	for (y = y1; y <= y2; y++) {
   1340 		term.dirty[y] = 1;
   1341 		for (x = x1; x <= x2; x++) {
   1342 			gp = &TSCREEN.buffer[L][x];
   1343 			if (selected(x, y))
   1344 				selclear();
   1345 			gp->fg = term.c.attr.fg;
   1346 			gp->bg = term.c.attr.bg;
   1347 			gp->mode = 0;
   1348 			gp->u = ' ';
   1349 		}
   1350 		L = (L + 1) % TSCREEN.size;
   1351 	}
   1352 }
   1353 
   1354 void
   1355 tdeletechar(int n)
   1356 {
   1357 	int dst, src, size;
   1358 	Glyph *line;
   1359 
   1360 	LIMIT(n, 0, term.col - term.c.x);
   1361 
   1362 	dst = term.c.x;
   1363 	src = term.c.x + n;
   1364 	size = term.col - src;
   1365 	line = TLINE(term.c.y);
   1366 
   1367 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1368 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1369 }
   1370 
   1371 void
   1372 tinsertblank(int n)
   1373 {
   1374 	int dst, src, size;
   1375 	Glyph *line;
   1376 
   1377 	LIMIT(n, 0, term.col - term.c.x);
   1378 
   1379 	dst = term.c.x + n;
   1380 	src = term.c.x;
   1381 	size = term.col - dst;
   1382 	line = TLINE(term.c.y);
   1383 
   1384 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1385 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1386 }
   1387 
   1388 void
   1389 tinsertblankline(int n)
   1390 {
   1391 	if (BETWEEN(term.c.y, term.top, term.bot))
   1392 		tscrolldown(term.c.y, n);
   1393 }
   1394 
   1395 void
   1396 tdeleteline(int n)
   1397 {
   1398 	if (BETWEEN(term.c.y, term.top, term.bot))
   1399 		tscrollup(term.c.y, n);
   1400 }
   1401 
   1402 int32_t
   1403 tdefcolor(const int *attr, int *npar, int l)
   1404 {
   1405 	int32_t idx = -1;
   1406 	uint r, g, b;
   1407 
   1408 	switch (attr[*npar + 1]) {
   1409 	case 2: /* direct color in RGB space */
   1410 		if (*npar + 4 >= l) {
   1411 			fprintf(stderr,
   1412 				"erresc(38): Incorrect number of parameters (%d)\n",
   1413 				*npar);
   1414 			break;
   1415 		}
   1416 		r = attr[*npar + 2];
   1417 		g = attr[*npar + 3];
   1418 		b = attr[*npar + 4];
   1419 		*npar += 4;
   1420 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1421 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1422 				r, g, b);
   1423 		else
   1424 			idx = TRUECOLOR(r, g, b);
   1425 		break;
   1426 	case 5: /* indexed color */
   1427 		if (*npar + 2 >= l) {
   1428 			fprintf(stderr,
   1429 				"erresc(38): Incorrect number of parameters (%d)\n",
   1430 				*npar);
   1431 			break;
   1432 		}
   1433 		*npar += 2;
   1434 		if (!BETWEEN(attr[*npar], 0, 255))
   1435 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1436 		else
   1437 			idx = attr[*npar];
   1438 		break;
   1439 	case 0: /* implemented defined (only foreground) */
   1440 	case 1: /* transparent */
   1441 	case 3: /* direct color in CMY space */
   1442 	case 4: /* direct color in CMYK space */
   1443 	default:
   1444 		fprintf(stderr,
   1445 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1446 		break;
   1447 	}
   1448 
   1449 	return idx;
   1450 }
   1451 
   1452 void
   1453 tsetattr(const int *attr, int l)
   1454 {
   1455 	int i;
   1456 	int32_t idx;
   1457 
   1458 	for (i = 0; i < l; i++) {
   1459 		switch (attr[i]) {
   1460 		case 0:
   1461 			term.c.attr.mode &= ~(
   1462 				ATTR_BOLD       |
   1463 				ATTR_FAINT      |
   1464 				ATTR_ITALIC     |
   1465 				ATTR_UNDERLINE  |
   1466 				ATTR_BLINK      |
   1467 				ATTR_REVERSE    |
   1468 				ATTR_INVISIBLE  |
   1469 				ATTR_STRUCK     );
   1470 			term.c.attr.fg = defaultfg;
   1471 			term.c.attr.bg = defaultbg;
   1472 			break;
   1473 		case 1:
   1474 			term.c.attr.mode |= ATTR_BOLD;
   1475 			break;
   1476 		case 2:
   1477 			term.c.attr.mode |= ATTR_FAINT;
   1478 			break;
   1479 		case 3:
   1480 			term.c.attr.mode |= ATTR_ITALIC;
   1481 			break;
   1482 		case 4:
   1483 			term.c.attr.mode |= ATTR_UNDERLINE;
   1484 			break;
   1485 		case 5: /* slow blink */
   1486 			/* FALLTHROUGH */
   1487 		case 6: /* rapid blink */
   1488 			term.c.attr.mode |= ATTR_BLINK;
   1489 			break;
   1490 		case 7:
   1491 			term.c.attr.mode |= ATTR_REVERSE;
   1492 			break;
   1493 		case 8:
   1494 			term.c.attr.mode |= ATTR_INVISIBLE;
   1495 			break;
   1496 		case 9:
   1497 			term.c.attr.mode |= ATTR_STRUCK;
   1498 			break;
   1499 		case 22:
   1500 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1501 			break;
   1502 		case 23:
   1503 			term.c.attr.mode &= ~ATTR_ITALIC;
   1504 			break;
   1505 		case 24:
   1506 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1507 			break;
   1508 		case 25:
   1509 			term.c.attr.mode &= ~ATTR_BLINK;
   1510 			break;
   1511 		case 27:
   1512 			term.c.attr.mode &= ~ATTR_REVERSE;
   1513 			break;
   1514 		case 28:
   1515 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1516 			break;
   1517 		case 29:
   1518 			term.c.attr.mode &= ~ATTR_STRUCK;
   1519 			break;
   1520 		case 38:
   1521 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1522 				term.c.attr.fg = idx;
   1523 			break;
   1524 		case 39: /* set foreground color to default */
   1525 			term.c.attr.fg = defaultfg;
   1526 			break;
   1527 		case 48:
   1528 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1529 				term.c.attr.bg = idx;
   1530 			break;
   1531 		case 49: /* set background color to default */
   1532 			term.c.attr.bg = defaultbg;
   1533 			break;
   1534 		case 58:
   1535 			/* This starts a sequence to change the color of
   1536 			 * "underline" pixels. We don't support that and
   1537 			 * instead eat up a following "5;n" or "2;r;g;b". */
   1538 			tdefcolor(attr, &i, l);
   1539 			break;
   1540 		default:
   1541 			if (BETWEEN(attr[i], 30, 37)) {
   1542 				term.c.attr.fg = attr[i] - 30;
   1543 			} else if (BETWEEN(attr[i], 40, 47)) {
   1544 				term.c.attr.bg = attr[i] - 40;
   1545 			} else if (BETWEEN(attr[i], 90, 97)) {
   1546 				term.c.attr.fg = attr[i] - 90 + 8;
   1547 			} else if (BETWEEN(attr[i], 100, 107)) {
   1548 				term.c.attr.bg = attr[i] - 100 + 8;
   1549 			} else {
   1550 				fprintf(stderr,
   1551 					"erresc(default): gfx attr %d unknown\n",
   1552 					attr[i]);
   1553 				csidump();
   1554 			}
   1555 			break;
   1556 		}
   1557 	}
   1558 }
   1559 
   1560 void
   1561 tsetscroll(int t, int b)
   1562 {
   1563 	int temp;
   1564 
   1565 	LIMIT(t, 0, term.row-1);
   1566 	LIMIT(b, 0, term.row-1);
   1567 	if (t > b) {
   1568 		temp = t;
   1569 		t = b;
   1570 		b = temp;
   1571 	}
   1572 	term.top = t;
   1573 	term.bot = b;
   1574 }
   1575 
   1576 void
   1577 tsetmode(int priv, int set, const int *args, int narg)
   1578 {
   1579 	int alt; const int *lim;
   1580 
   1581 	for (lim = args + narg; args < lim; ++args) {
   1582 		if (priv) {
   1583 			switch (*args) {
   1584 			case 1: /* DECCKM -- Cursor key */
   1585 				xsetmode(set, MODE_APPCURSOR);
   1586 				break;
   1587 			case 5: /* DECSCNM -- Reverse video */
   1588 				xsetmode(set, MODE_REVERSE);
   1589 				break;
   1590 			case 6: /* DECOM -- Origin */
   1591 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1592 				tmoveato(0, 0);
   1593 				break;
   1594 			case 7: /* DECAWM -- Auto wrap */
   1595 				MODBIT(term.mode, set, MODE_WRAP);
   1596 				break;
   1597 			case 0:  /* Error (IGNORED) */
   1598 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1599 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1600 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1601 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1602 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1603 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1604 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1605 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1606 				break;
   1607 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1608 				xsetmode(!set, MODE_HIDE);
   1609 				break;
   1610 			case 9:    /* X10 mouse compatibility mode */
   1611 				xsetpointermotion(0);
   1612 				xsetmode(0, MODE_MOUSE);
   1613 				xsetmode(set, MODE_MOUSEX10);
   1614 				break;
   1615 			case 1000: /* 1000: report button press */
   1616 				xsetpointermotion(0);
   1617 				xsetmode(0, MODE_MOUSE);
   1618 				xsetmode(set, MODE_MOUSEBTN);
   1619 				break;
   1620 			case 1002: /* 1002: report motion on button press */
   1621 				xsetpointermotion(0);
   1622 				xsetmode(0, MODE_MOUSE);
   1623 				xsetmode(set, MODE_MOUSEMOTION);
   1624 				break;
   1625 			case 1003: /* 1003: enable all mouse motions */
   1626 				xsetpointermotion(set);
   1627 				xsetmode(0, MODE_MOUSE);
   1628 				xsetmode(set, MODE_MOUSEMANY);
   1629 				break;
   1630 			case 1004: /* 1004: send focus events to tty */
   1631 				xsetmode(set, MODE_FOCUS);
   1632 				break;
   1633 			case 1006: /* 1006: extended reporting mode */
   1634 				xsetmode(set, MODE_MOUSESGR);
   1635 				break;
   1636 			case 1034: /* 1034: enable 8-bit mode for keyboard input */
   1637 				xsetmode(set, MODE_8BIT);
   1638 				break;
   1639 			case 1049: /* swap screen & set/restore cursor as xterm */
   1640 				if (!allowaltscreen)
   1641 					break;
   1642 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1643 				/* FALLTHROUGH */
   1644 			case 47: /* swap screen buffer */
   1645 			case 1047: /* swap screen buffer */
   1646 				if (!allowaltscreen)
   1647 					break;
   1648 				alt = IS_SET(MODE_ALTSCREEN);
   1649 				if (alt) {
   1650 					tclearregion(0, 0, term.col-1,
   1651 							term.row-1);
   1652 				}
   1653 				if (set ^ alt) /* set is always 1 or 0 */
   1654 					tswapscreen();
   1655 				if (*args != 1049)
   1656 					break;
   1657 				/* FALLTHROUGH */
   1658 			case 1048: /* save/restore cursor (like DECSC/DECRC) */
   1659 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1660 				break;
   1661 			case 2004: /* 2004: bracketed paste mode */
   1662 				xsetmode(set, MODE_BRCKTPASTE);
   1663 				break;
   1664 			/* Not implemented mouse modes. See comments there. */
   1665 			case 1001: /* mouse highlight mode; can hang the
   1666 				      terminal by design when implemented. */
   1667 			case 1005: /* UTF-8 mouse mode; will confuse
   1668 				      applications not supporting UTF-8
   1669 				      and luit. */
   1670 			case 1015: /* urxvt mangled mouse mode; incompatible
   1671 				      and can be mistaken for other control
   1672 				      codes. */
   1673 				break;
   1674 			default:
   1675 				fprintf(stderr,
   1676 					"erresc: unknown private set/reset mode %d\n",
   1677 					*args);
   1678 				break;
   1679 			}
   1680 		} else {
   1681 			switch (*args) {
   1682 			case 0:  /* Error (IGNORED) */
   1683 				break;
   1684 			case 2:
   1685 				xsetmode(set, MODE_KBDLOCK);
   1686 				break;
   1687 			case 4:  /* IRM -- Insertion-replacement */
   1688 				MODBIT(term.mode, set, MODE_INSERT);
   1689 				break;
   1690 			case 12: /* SRM -- Send/Receive */
   1691 				MODBIT(term.mode, !set, MODE_ECHO);
   1692 				break;
   1693 			case 20: /* LNM -- Linefeed/new line */
   1694 				MODBIT(term.mode, set, MODE_CRLF);
   1695 				break;
   1696 			default:
   1697 				fprintf(stderr,
   1698 					"erresc: unknown set/reset mode %d\n",
   1699 					*args);
   1700 				break;
   1701 			}
   1702 		}
   1703 	}
   1704 }
   1705 
   1706 void
   1707 csihandle(void)
   1708 {
   1709 	char buf[40];
   1710 	int len;
   1711 
   1712 	switch (csiescseq.mode[0]) {
   1713 	default:
   1714 	unknown:
   1715 		fprintf(stderr, "erresc: unknown csi ");
   1716 		csidump();
   1717 		/* die(""); */
   1718 		break;
   1719 	case '@': /* ICH -- Insert <n> blank char */
   1720 		DEFAULT(csiescseq.arg[0], 1);
   1721 		tinsertblank(csiescseq.arg[0]);
   1722 		break;
   1723 	case 'A': /* CUU -- Cursor <n> Up */
   1724 		DEFAULT(csiescseq.arg[0], 1);
   1725 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   1726 		break;
   1727 	case 'B': /* CUD -- Cursor <n> Down */
   1728 	case 'e': /* VPR --Cursor <n> Down */
   1729 		DEFAULT(csiescseq.arg[0], 1);
   1730 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   1731 		break;
   1732 	case 'i': /* MC -- Media Copy */
   1733 		switch (csiescseq.arg[0]) {
   1734 		case 0:
   1735 			tdump();
   1736 			break;
   1737 		case 1:
   1738 			tdumpline(term.c.y);
   1739 			break;
   1740 		case 2:
   1741 			tdumpsel();
   1742 			break;
   1743 		case 4:
   1744 			term.mode &= ~MODE_PRINT;
   1745 			break;
   1746 		case 5:
   1747 			term.mode |= MODE_PRINT;
   1748 			break;
   1749 		}
   1750 		break;
   1751 	case 'c': /* DA -- Device Attributes */
   1752 		if (csiescseq.arg[0] == 0)
   1753 			ttywrite(vtiden, strlen(vtiden), 0);
   1754 		break;
   1755 	case 'b': /* REP -- if last char is printable print it <n> more times */
   1756 		LIMIT(csiescseq.arg[0], 1, 65535);
   1757 		if (term.lastc)
   1758 			while (csiescseq.arg[0]-- > 0)
   1759 				tputc(term.lastc);
   1760 		break;
   1761 	case 'C': /* CUF -- Cursor <n> Forward */
   1762 	case 'a': /* HPR -- Cursor <n> Forward */
   1763 		DEFAULT(csiescseq.arg[0], 1);
   1764 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   1765 		break;
   1766 	case 'D': /* CUB -- Cursor <n> Backward */
   1767 		DEFAULT(csiescseq.arg[0], 1);
   1768 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   1769 		break;
   1770 	case 'E': /* CNL -- Cursor <n> Down and first col */
   1771 		DEFAULT(csiescseq.arg[0], 1);
   1772 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   1773 		break;
   1774 	case 'F': /* CPL -- Cursor <n> Up and first col */
   1775 		DEFAULT(csiescseq.arg[0], 1);
   1776 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   1777 		break;
   1778 	case 'g': /* TBC -- Tabulation clear */
   1779 		switch (csiescseq.arg[0]) {
   1780 		case 0: /* clear current tab stop */
   1781 			term.tabs[term.c.x] = 0;
   1782 			break;
   1783 		case 3: /* clear all the tabs */
   1784 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1785 			break;
   1786 		default:
   1787 			goto unknown;
   1788 		}
   1789 		break;
   1790 	case 'G': /* CHA -- Move to <col> */
   1791 	case '`': /* HPA */
   1792 		DEFAULT(csiescseq.arg[0], 1);
   1793 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   1794 		break;
   1795 	case 'H': /* CUP -- Move to <row> <col> */
   1796 	case 'f': /* HVP */
   1797 		DEFAULT(csiescseq.arg[0], 1);
   1798 		DEFAULT(csiescseq.arg[1], 1);
   1799 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   1800 		break;
   1801 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   1802 		DEFAULT(csiescseq.arg[0], 1);
   1803 		tputtab(csiescseq.arg[0]);
   1804 		break;
   1805 	case 'J': /* ED -- Clear screen */
   1806 		switch (csiescseq.arg[0]) {
   1807 		case 0: /* below */
   1808 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
   1809 			if (term.c.y < term.row-1) {
   1810 				tclearregion(0, term.c.y+1, term.col-1,
   1811 						term.row-1);
   1812 			}
   1813 			break;
   1814 		case 1: /* above */
   1815 			if (term.c.y > 0)
   1816 				tclearregion(0, 0, term.col-1, term.c.y-1);
   1817 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1818 			break;
   1819 		case 2: /* all */
   1820 			tclearregion(0, 0, term.col-1, term.row-1);
   1821 			break;
   1822 		default:
   1823 			goto unknown;
   1824 		}
   1825 		break;
   1826 	case 'K': /* EL -- Clear line */
   1827 		switch (csiescseq.arg[0]) {
   1828 		case 0: /* right */
   1829 			tclearregion(term.c.x, term.c.y, term.col-1,
   1830 					term.c.y);
   1831 			break;
   1832 		case 1: /* left */
   1833 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1834 			break;
   1835 		case 2: /* all */
   1836 			tclearregion(0, term.c.y, term.col-1, term.c.y);
   1837 			break;
   1838 		}
   1839 		break;
   1840 	case 'S': /* SU -- Scroll <n> line up */
   1841 		if (csiescseq.priv) break;
   1842 		DEFAULT(csiescseq.arg[0], 1);
   1843 		tscrollup(term.top, csiescseq.arg[0]);
   1844 		break;
   1845 	case 'T': /* SD -- Scroll <n> line down */
   1846 		DEFAULT(csiescseq.arg[0], 1);
   1847 		tscrolldown(term.top, csiescseq.arg[0]);
   1848 		break;
   1849 	case 'L': /* IL -- Insert <n> blank lines */
   1850 		DEFAULT(csiescseq.arg[0], 1);
   1851 		tinsertblankline(csiescseq.arg[0]);
   1852 		break;
   1853 	case 'l': /* RM -- Reset Mode */
   1854 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1855 		break;
   1856 	case 'M': /* DL -- Delete <n> lines */
   1857 		DEFAULT(csiescseq.arg[0], 1);
   1858 		tdeleteline(csiescseq.arg[0]);
   1859 		break;
   1860 	case 'X': /* ECH -- Erase <n> char */
   1861 		DEFAULT(csiescseq.arg[0], 1);
   1862 		tclearregion(term.c.x, term.c.y,
   1863 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1864 		break;
   1865 	case 'P': /* DCH -- Delete <n> char */
   1866 		DEFAULT(csiescseq.arg[0], 1);
   1867 		tdeletechar(csiescseq.arg[0]);
   1868 		break;
   1869 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   1870 		DEFAULT(csiescseq.arg[0], 1);
   1871 		tputtab(-csiescseq.arg[0]);
   1872 		break;
   1873 	case 'd': /* VPA -- Move to <row> */
   1874 		DEFAULT(csiescseq.arg[0], 1);
   1875 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   1876 		break;
   1877 	case 'h': /* SM -- Set terminal mode */
   1878 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   1879 		break;
   1880 	case 'm': /* SGR -- Terminal attribute (color) */
   1881 		tsetattr(csiescseq.arg, csiescseq.narg);
   1882 		break;
   1883 	case 'n': /* DSR -- Device Status Report */
   1884 		switch (csiescseq.arg[0]) {
   1885 		case 5: /* Status Report "OK" `0n` */
   1886 			ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
   1887 			break;
   1888 		case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
   1889 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   1890 			               term.c.y+1, term.c.x+1);
   1891 			ttywrite(buf, len, 0);
   1892 			break;
   1893 		default:
   1894 			goto unknown;
   1895 		}
   1896 		break;
   1897 	case 'r': /* DECSTBM -- Set Scrolling Region */
   1898 		if (csiescseq.priv) {
   1899 			goto unknown;
   1900 		} else {
   1901 			DEFAULT(csiescseq.arg[0], 1);
   1902 			DEFAULT(csiescseq.arg[1], term.row);
   1903 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   1904 			tmoveato(0, 0);
   1905 		}
   1906 		break;
   1907 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   1908 		tcursor(CURSOR_SAVE);
   1909 		break;
   1910 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   1911 		if (csiescseq.priv) {
   1912 			goto unknown;
   1913 		} else {
   1914 			tcursor(CURSOR_LOAD);
   1915 		}
   1916 		break;
   1917 	case ' ':
   1918 		switch (csiescseq.mode[1]) {
   1919 		case 'q': /* DECSCUSR -- Set Cursor Style */
   1920 			if (xsetcursor(csiescseq.arg[0]))
   1921 				goto unknown;
   1922 			break;
   1923 		default:
   1924 			goto unknown;
   1925 		}
   1926 		break;
   1927 	}
   1928 }
   1929 
   1930 void
   1931 csidump(void)
   1932 {
   1933 	size_t i;
   1934 	uint c;
   1935 
   1936 	fprintf(stderr, "ESC[");
   1937 	for (i = 0; i < csiescseq.len; i++) {
   1938 		c = csiescseq.buf[i] & 0xff;
   1939 		if (isprint(c)) {
   1940 			putc(c, stderr);
   1941 		} else if (c == '\n') {
   1942 			fprintf(stderr, "(\\n)");
   1943 		} else if (c == '\r') {
   1944 			fprintf(stderr, "(\\r)");
   1945 		} else if (c == 0x1b) {
   1946 			fprintf(stderr, "(\\e)");
   1947 		} else {
   1948 			fprintf(stderr, "(%02x)", c);
   1949 		}
   1950 	}
   1951 	putc('\n', stderr);
   1952 }
   1953 
   1954 void
   1955 csireset(void)
   1956 {
   1957 	memset(&csiescseq, 0, sizeof(csiescseq));
   1958 }
   1959 
   1960 void
   1961 osc_color_response(int num, int index, int is_osc4)
   1962 {
   1963 	int n;
   1964 	char buf[32];
   1965 	unsigned char r, g, b;
   1966 
   1967 	if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
   1968 		fprintf(stderr, "erresc: failed to fetch %s color %d\n",
   1969 		        is_osc4 ? "osc4" : "osc",
   1970 		        is_osc4 ? num : index);
   1971 		return;
   1972 	}
   1973 
   1974 	n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
   1975 	             is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
   1976 	if (n < 0 || n >= sizeof(buf)) {
   1977 		fprintf(stderr, "error: %s while printing %s response\n",
   1978 		        n < 0 ? "snprintf failed" : "truncation occurred",
   1979 		        is_osc4 ? "osc4" : "osc");
   1980 	} else {
   1981 		ttywrite(buf, n, 1);
   1982 	}
   1983 }
   1984 
   1985 void
   1986 strhandle(void)
   1987 {
   1988 	char *p = NULL, *dec;
   1989 	int j, narg, par;
   1990 	const struct { int idx; char *str; } osc_table[] = {
   1991 		{ defaultfg, "foreground" },
   1992 		{ defaultbg, "background" },
   1993 		{ defaultcs, "cursor" }
   1994 	};
   1995 
   1996 	term.esc &= ~(ESC_STR_END|ESC_STR);
   1997 	strparse();
   1998 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   1999 
   2000 	switch (strescseq.type) {
   2001 	case ']': /* OSC -- Operating System Command */
   2002 		switch (par) {
   2003 		case 0:
   2004 			if (narg > 1) {
   2005 				xsettitle(strescseq.args[1]);
   2006 				xseticontitle(strescseq.args[1]);
   2007 			}
   2008 			return;
   2009 		case 1:
   2010 			if (narg > 1)
   2011 				xseticontitle(strescseq.args[1]);
   2012 			return;
   2013 		case 2:
   2014 			if (narg > 1)
   2015 				xsettitle(strescseq.args[1]);
   2016 			return;
   2017 		case 52: /* manipulate selection data */
   2018 			if (narg > 2 && allowwindowops) {
   2019 				dec = base64dec(strescseq.args[2]);
   2020 				if (dec) {
   2021 					xsetsel(dec);
   2022 					xclipcopy();
   2023 				} else {
   2024 					fprintf(stderr, "erresc: invalid base64\n");
   2025 				}
   2026 			}
   2027 			return;
   2028 		case 10: /* set dynamic VT100 text foreground color */
   2029 		case 11: /* set dynamic VT100 text background color */
   2030 		case 12: /* set dynamic text cursor color */
   2031 			if (narg < 2)
   2032 				break;
   2033 			p = strescseq.args[1];
   2034 			if ((j = par - 10) < 0 || j >= LEN(osc_table))
   2035 				break; /* shouldn't be possible */
   2036 
   2037 			if (!strcmp(p, "?")) {
   2038 				osc_color_response(par, osc_table[j].idx, 0);
   2039 			} else if (xsetcolorname(osc_table[j].idx, p)) {
   2040 				fprintf(stderr, "erresc: invalid %s color: %s\n",
   2041 				        osc_table[j].str, p);
   2042 			} else {
   2043 				tfulldirt();
   2044 			}
   2045 			return;
   2046 		case 4: /* color set */
   2047 			if (narg < 3)
   2048 				break;
   2049 			p = strescseq.args[2];
   2050 			/* FALLTHROUGH */
   2051 		case 104: /* color reset */
   2052 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   2053 
   2054 			if (p && !strcmp(p, "?")) {
   2055 				osc_color_response(j, 0, 1);
   2056 			} else if (xsetcolorname(j, p)) {
   2057 				if (par == 104 && narg <= 1) {
   2058 					xloadcols();
   2059 					return; /* color reset without parameter */
   2060 				}
   2061 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   2062 				        j, p ? p : "(null)");
   2063 			} else {
   2064 				/*
   2065 				 * TODO if defaultbg color is changed, borders
   2066 				 * are dirty
   2067 				 */
   2068 				tfulldirt();
   2069 			}
   2070 			return;
   2071 		case 110: /* reset dynamic VT100 text foreground color */
   2072 		case 111: /* reset dynamic VT100 text background color */
   2073 		case 112: /* reset dynamic text cursor color */
   2074 			if (narg != 1)
   2075 				break;
   2076 			if ((j = par - 110) < 0 || j >= LEN(osc_table))
   2077 				break; /* shouldn't be possible */
   2078 			if (xsetcolorname(osc_table[j].idx, NULL)) {
   2079 				fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str);
   2080 			} else {
   2081 				tfulldirt();
   2082 			}
   2083 			return;
   2084 		}
   2085 		break;
   2086 	case 'k': /* old title set compatibility */
   2087 		xsettitle(strescseq.args[0]);
   2088 		return;
   2089 	case 'P': /* DCS -- Device Control String */
   2090 	case '_': /* APC -- Application Program Command */
   2091 	case '^': /* PM -- Privacy Message */
   2092 		return;
   2093 	}
   2094 
   2095 	fprintf(stderr, "erresc: unknown str ");
   2096 	strdump();
   2097 }
   2098 
   2099 void
   2100 strparse(void)
   2101 {
   2102 	int c;
   2103 	char *p = strescseq.buf;
   2104 
   2105 	strescseq.narg = 0;
   2106 	strescseq.buf[strescseq.len] = '\0';
   2107 
   2108 	if (*p == '\0')
   2109 		return;
   2110 
   2111 	while (strescseq.narg < STR_ARG_SIZ) {
   2112 		strescseq.args[strescseq.narg++] = p;
   2113 		while ((c = *p) != ';' && c != '\0')
   2114 			++p;
   2115 		if (c == '\0')
   2116 			return;
   2117 		*p++ = '\0';
   2118 	}
   2119 }
   2120 
   2121 void
   2122 strdump(void)
   2123 {
   2124 	size_t i;
   2125 	uint c;
   2126 
   2127 	fprintf(stderr, "ESC%c", strescseq.type);
   2128 	for (i = 0; i < strescseq.len; i++) {
   2129 		c = strescseq.buf[i] & 0xff;
   2130 		if (c == '\0') {
   2131 			putc('\n', stderr);
   2132 			return;
   2133 		} else if (isprint(c)) {
   2134 			putc(c, stderr);
   2135 		} else if (c == '\n') {
   2136 			fprintf(stderr, "(\\n)");
   2137 		} else if (c == '\r') {
   2138 			fprintf(stderr, "(\\r)");
   2139 		} else if (c == 0x1b) {
   2140 			fprintf(stderr, "(\\e)");
   2141 		} else {
   2142 			fprintf(stderr, "(%02x)", c);
   2143 		}
   2144 	}
   2145 	fprintf(stderr, "ESC\\\n");
   2146 }
   2147 
   2148 void
   2149 strreset(void)
   2150 {
   2151 	strescseq = (STREscape){
   2152 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2153 		.siz = STR_BUF_SIZ,
   2154 	};
   2155 }
   2156 
   2157 void
   2158 sendbreak(const Arg *arg)
   2159 {
   2160 	if (tcsendbreak(cmdfd, 0))
   2161 		perror("Error sending break");
   2162 }
   2163 
   2164 void
   2165 tprinter(char *s, size_t len)
   2166 {
   2167 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2168 		perror("Error writing to output file");
   2169 		close(iofd);
   2170 		iofd = -1;
   2171 	}
   2172 }
   2173 
   2174 void
   2175 toggleprinter(const Arg *arg)
   2176 {
   2177 	term.mode ^= MODE_PRINT;
   2178 }
   2179 
   2180 void
   2181 printscreen(const Arg *arg)
   2182 {
   2183 	tdump();
   2184 }
   2185 
   2186 void
   2187 printsel(const Arg *arg)
   2188 {
   2189 	tdumpsel();
   2190 }
   2191 
   2192 void
   2193 tdumpsel(void)
   2194 {
   2195 	char *ptr;
   2196 
   2197 	if ((ptr = getsel())) {
   2198 		tprinter(ptr, strlen(ptr));
   2199 		free(ptr);
   2200 	}
   2201 }
   2202 
   2203 void
   2204 tdumpline(int n)
   2205 {
   2206 	char buf[UTF_SIZ];
   2207 	const Glyph *bp, *end;
   2208 
   2209 	bp = &TLINE(n)[0];
   2210 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2211 	if (bp != end || bp->u != ' ') {
   2212 		for ( ; bp <= end; ++bp)
   2213 			tprinter(buf, utf8encode(bp->u, buf));
   2214 	}
   2215 	tprinter("\n", 1);
   2216 }
   2217 
   2218 void
   2219 tdump(void)
   2220 {
   2221 	int i;
   2222 
   2223 	for (i = 0; i < term.row; ++i)
   2224 		tdumpline(i);
   2225 }
   2226 
   2227 void
   2228 tputtab(int n)
   2229 {
   2230 	uint x = term.c.x;
   2231 
   2232 	if (n > 0) {
   2233 		while (x < term.col && n--)
   2234 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2235 				/* nothing */ ;
   2236 	} else if (n < 0) {
   2237 		while (x > 0 && n++)
   2238 			for (--x; x > 0 && !term.tabs[x]; --x)
   2239 				/* nothing */ ;
   2240 	}
   2241 	term.c.x = LIMIT(x, 0, term.col-1);
   2242 }
   2243 
   2244 void
   2245 tdefutf8(char ascii)
   2246 {
   2247 	if (ascii == 'G')
   2248 		term.mode |= MODE_UTF8;
   2249 	else if (ascii == '@')
   2250 		term.mode &= ~MODE_UTF8;
   2251 }
   2252 
   2253 void
   2254 tdeftran(char ascii)
   2255 {
   2256 	static char cs[] = "0B";
   2257 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2258 	char *p;
   2259 
   2260 	if ((p = strchr(cs, ascii)) == NULL) {
   2261 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2262 	} else {
   2263 		term.trantbl[term.icharset] = vcs[p - cs];
   2264 	}
   2265 }
   2266 
   2267 void
   2268 tdectest(char c)
   2269 {
   2270 	int x, y;
   2271 
   2272 	if (c == '8') { /* DEC screen alignment test. */
   2273 		for (x = 0; x < term.col; ++x) {
   2274 			for (y = 0; y < term.row; ++y)
   2275 				tsetchar('E', &term.c.attr, x, y);
   2276 		}
   2277 	}
   2278 }
   2279 
   2280 void
   2281 tstrsequence(uchar c)
   2282 {
   2283 	switch (c) {
   2284 	case 0x90:   /* DCS -- Device Control String */
   2285 		c = 'P';
   2286 		break;
   2287 	case 0x9f:   /* APC -- Application Program Command */
   2288 		c = '_';
   2289 		break;
   2290 	case 0x9e:   /* PM -- Privacy Message */
   2291 		c = '^';
   2292 		break;
   2293 	case 0x9d:   /* OSC -- Operating System Command */
   2294 		c = ']';
   2295 		break;
   2296 	}
   2297 	strreset();
   2298 	strescseq.type = c;
   2299 	term.esc |= ESC_STR;
   2300 }
   2301 
   2302 void
   2303 tcontrolcode(uchar ascii)
   2304 {
   2305 	switch (ascii) {
   2306 	case '\t':   /* HT */
   2307 		tputtab(1);
   2308 		return;
   2309 	case '\b':   /* BS */
   2310 		tmoveto(term.c.x-1, term.c.y);
   2311 		return;
   2312 	case '\r':   /* CR */
   2313 		tmoveto(0, term.c.y);
   2314 		return;
   2315 	case '\f':   /* LF */
   2316 	case '\v':   /* VT */
   2317 	case '\n':   /* LF */
   2318 		/* go to first col if the mode is set */
   2319 		tnewline(IS_SET(MODE_CRLF));
   2320 		return;
   2321 	case '\a':   /* BEL */
   2322 		if (term.esc & ESC_STR_END) {
   2323 			/* backwards compatibility to xterm */
   2324 			strhandle();
   2325 		} else {
   2326 			xbell();
   2327 		}
   2328 		break;
   2329 	case '\033': /* ESC */
   2330 		csireset();
   2331 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2332 		term.esc |= ESC_START;
   2333 		return;
   2334 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2335 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2336 		term.charset = 1 - (ascii - '\016');
   2337 		return;
   2338 	case '\032': /* SUB */
   2339 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2340 		/* FALLTHROUGH */
   2341 	case '\030': /* CAN */
   2342 		csireset();
   2343 		break;
   2344 	case '\005': /* ENQ (IGNORED) */
   2345 	case '\000': /* NUL (IGNORED) */
   2346 	case '\021': /* XON (IGNORED) */
   2347 	case '\023': /* XOFF (IGNORED) */
   2348 	case 0177:   /* DEL (IGNORED) */
   2349 		return;
   2350 	case 0x80:   /* TODO: PAD */
   2351 	case 0x81:   /* TODO: HOP */
   2352 	case 0x82:   /* TODO: BPH */
   2353 	case 0x83:   /* TODO: NBH */
   2354 	case 0x84:   /* TODO: IND */
   2355 		break;
   2356 	case 0x85:   /* NEL -- Next line */
   2357 		tnewline(1); /* always go to first col */
   2358 		break;
   2359 	case 0x86:   /* TODO: SSA */
   2360 	case 0x87:   /* TODO: ESA */
   2361 		break;
   2362 	case 0x88:   /* HTS -- Horizontal tab stop */
   2363 		term.tabs[term.c.x] = 1;
   2364 		break;
   2365 	case 0x89:   /* TODO: HTJ */
   2366 	case 0x8a:   /* TODO: VTS */
   2367 	case 0x8b:   /* TODO: PLD */
   2368 	case 0x8c:   /* TODO: PLU */
   2369 	case 0x8d:   /* TODO: RI */
   2370 	case 0x8e:   /* TODO: SS2 */
   2371 	case 0x8f:   /* TODO: SS3 */
   2372 	case 0x91:   /* TODO: PU1 */
   2373 	case 0x92:   /* TODO: PU2 */
   2374 	case 0x93:   /* TODO: STS */
   2375 	case 0x94:   /* TODO: CCH */
   2376 	case 0x95:   /* TODO: MW */
   2377 	case 0x96:   /* TODO: SPA */
   2378 	case 0x97:   /* TODO: EPA */
   2379 	case 0x98:   /* TODO: SOS */
   2380 	case 0x99:   /* TODO: SGCI */
   2381 		break;
   2382 	case 0x9a:   /* DECID -- Identify Terminal */
   2383 		ttywrite(vtiden, strlen(vtiden), 0);
   2384 		break;
   2385 	case 0x9b:   /* TODO: CSI */
   2386 	case 0x9c:   /* TODO: ST */
   2387 		break;
   2388 	case 0x90:   /* DCS -- Device Control String */
   2389 	case 0x9d:   /* OSC -- Operating System Command */
   2390 	case 0x9e:   /* PM -- Privacy Message */
   2391 	case 0x9f:   /* APC -- Application Program Command */
   2392 		tstrsequence(ascii);
   2393 		return;
   2394 	}
   2395 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2396 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2397 }
   2398 
   2399 /*
   2400  * returns 1 when the sequence is finished and it hasn't to read
   2401  * more characters for this sequence, otherwise 0
   2402  */
   2403 int
   2404 eschandle(uchar ascii)
   2405 {
   2406 	switch (ascii) {
   2407 	case '[':
   2408 		term.esc |= ESC_CSI;
   2409 		return 0;
   2410 	case '#':
   2411 		term.esc |= ESC_TEST;
   2412 		return 0;
   2413 	case '%':
   2414 		term.esc |= ESC_UTF8;
   2415 		return 0;
   2416 	case 'P': /* DCS -- Device Control String */
   2417 	case '_': /* APC -- Application Program Command */
   2418 	case '^': /* PM -- Privacy Message */
   2419 	case ']': /* OSC -- Operating System Command */
   2420 	case 'k': /* old title set compatibility */
   2421 		tstrsequence(ascii);
   2422 		return 0;
   2423 	case 'n': /* LS2 -- Locking shift 2 */
   2424 	case 'o': /* LS3 -- Locking shift 3 */
   2425 		term.charset = 2 + (ascii - 'n');
   2426 		break;
   2427 	case '(': /* GZD4 -- set primary charset G0 */
   2428 	case ')': /* G1D4 -- set secondary charset G1 */
   2429 	case '*': /* G2D4 -- set tertiary charset G2 */
   2430 	case '+': /* G3D4 -- set quaternary charset G3 */
   2431 		term.icharset = ascii - '(';
   2432 		term.esc |= ESC_ALTCHARSET;
   2433 		return 0;
   2434 	case 'D': /* IND -- Linefeed */
   2435 		if (term.c.y == term.bot) {
   2436 			tscrollup(term.top, 1);
   2437 		} else {
   2438 			tmoveto(term.c.x, term.c.y+1);
   2439 		}
   2440 		break;
   2441 	case 'E': /* NEL -- Next line */
   2442 		tnewline(1); /* always go to first col */
   2443 		break;
   2444 	case 'H': /* HTS -- Horizontal tab stop */
   2445 		term.tabs[term.c.x] = 1;
   2446 		break;
   2447 	case 'M': /* RI -- Reverse index */
   2448 		if (term.c.y == term.top) {
   2449 			tscrolldown(term.top, 1);
   2450 		} else {
   2451 			tmoveto(term.c.x, term.c.y-1);
   2452 		}
   2453 		break;
   2454 	case 'Z': /* DECID -- Identify Terminal */
   2455 		ttywrite(vtiden, strlen(vtiden), 0);
   2456 		break;
   2457 	case 'c': /* RIS -- Reset to initial state */
   2458 		treset();
   2459 		resettitle();
   2460 		xloadcols();
   2461 		xsetmode(0, MODE_HIDE);
   2462 		xsetmode(0, MODE_BRCKTPASTE);
   2463 		break;
   2464 	case '=': /* DECPAM -- Application keypad */
   2465 		xsetmode(1, MODE_APPKEYPAD);
   2466 		break;
   2467 	case '>': /* DECPNM -- Normal keypad */
   2468 		xsetmode(0, MODE_APPKEYPAD);
   2469 		break;
   2470 	case '7': /* DECSC -- Save Cursor */
   2471 		tcursor(CURSOR_SAVE);
   2472 		break;
   2473 	case '8': /* DECRC -- Restore Cursor */
   2474 		tcursor(CURSOR_LOAD);
   2475 		break;
   2476 	case '\\': /* ST -- String Terminator */
   2477 		if (term.esc & ESC_STR_END)
   2478 			strhandle();
   2479 		break;
   2480 	default:
   2481 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2482 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2483 		break;
   2484 	}
   2485 	return 1;
   2486 }
   2487 
   2488 void
   2489 tputc(Rune u)
   2490 {
   2491 	char c[UTF_SIZ];
   2492 	int control;
   2493 	int width, len;
   2494 	Glyph *gp;
   2495 
   2496 	control = ISCONTROL(u);
   2497 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2498 		c[0] = u;
   2499 		width = len = 1;
   2500 	} else {
   2501 		len = utf8encode(u, c);
   2502 		if (!control && (width = wcwidth(u)) == -1)
   2503 			width = 1;
   2504 	}
   2505 
   2506 	if (IS_SET(MODE_PRINT))
   2507 		tprinter(c, len);
   2508 
   2509 	/*
   2510 	 * STR sequence must be checked before anything else
   2511 	 * because it uses all following characters until it
   2512 	 * receives a ESC, a SUB, a ST or any other C1 control
   2513 	 * character.
   2514 	 */
   2515 	if (term.esc & ESC_STR) {
   2516 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2517 		   ISCONTROLC1(u)) {
   2518 			term.esc &= ~(ESC_START|ESC_STR);
   2519 			term.esc |= ESC_STR_END;
   2520 			goto check_control_code;
   2521 		}
   2522 
   2523 		if (strescseq.len+len >= strescseq.siz) {
   2524 			/*
   2525 			 * Here is a bug in terminals. If the user never sends
   2526 			 * some code to stop the str or esc command, then st
   2527 			 * will stop responding. But this is better than
   2528 			 * silently failing with unknown characters. At least
   2529 			 * then users will report back.
   2530 			 *
   2531 			 * In the case users ever get fixed, here is the code:
   2532 			 */
   2533 			/*
   2534 			 * term.esc = 0;
   2535 			 * strhandle();
   2536 			 */
   2537 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2538 				return;
   2539 			strescseq.siz *= 2;
   2540 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2541 		}
   2542 
   2543 		memmove(&strescseq.buf[strescseq.len], c, len);
   2544 		strescseq.len += len;
   2545 		return;
   2546 	}
   2547 
   2548 check_control_code:
   2549 	/*
   2550 	 * Actions of control codes must be performed as soon they arrive
   2551 	 * because they can be embedded inside a control sequence, and
   2552 	 * they must not cause conflicts with sequences.
   2553 	 */
   2554 	if (control) {
   2555 		/* in UTF-8 mode ignore handling C1 control characters */
   2556 		if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
   2557 			return;
   2558 		tcontrolcode(u);
   2559 		/*
   2560 		 * control codes are not shown ever
   2561 		 */
   2562 		if (!term.esc)
   2563 			term.lastc = 0;
   2564 		return;
   2565 	} else if (term.esc & ESC_START) {
   2566 		if (term.esc & ESC_CSI) {
   2567 			csiescseq.buf[csiescseq.len++] = u;
   2568 			if (BETWEEN(u, 0x40, 0x7E)
   2569 					|| csiescseq.len >= \
   2570 					sizeof(csiescseq.buf)-1) {
   2571 				term.esc = 0;
   2572 				csiparse();
   2573 				csihandle();
   2574 			}
   2575 			return;
   2576 		} else if (term.esc & ESC_UTF8) {
   2577 			tdefutf8(u);
   2578 		} else if (term.esc & ESC_ALTCHARSET) {
   2579 			tdeftran(u);
   2580 		} else if (term.esc & ESC_TEST) {
   2581 			tdectest(u);
   2582 		} else {
   2583 			if (!eschandle(u))
   2584 				return;
   2585 			/* sequence already finished */
   2586 		}
   2587 		term.esc = 0;
   2588 		/*
   2589 		 * All characters which form part of a sequence are not
   2590 		 * printed
   2591 		 */
   2592 		return;
   2593 	}
   2594 	if (selected(term.c.x, term.c.y))
   2595 		selclear();
   2596 
   2597 	gp = &TLINE(term.c.y)[term.c.x];
   2598 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2599 		gp->mode |= ATTR_WRAP;
   2600 		tnewline(1);
   2601 		gp = &TLINE(term.c.y)[term.c.x];
   2602 	}
   2603 
   2604 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
   2605 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2606 		gp->mode &= ~ATTR_WIDE;
   2607 	}
   2608 
   2609 	if (term.c.x+width > term.col) {
   2610 		if (IS_SET(MODE_WRAP))
   2611 			tnewline(1);
   2612 		else
   2613 			tmoveto(term.col - width, term.c.y);
   2614 		gp = &TLINE(term.c.y)[term.c.x];
   2615 	}
   2616 
   2617 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2618 	term.lastc = u;
   2619 
   2620 	if (width == 2) {
   2621 		gp->mode |= ATTR_WIDE;
   2622 		if (term.c.x+1 < term.col) {
   2623 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   2624 				gp[2].u = ' ';
   2625 				gp[2].mode &= ~ATTR_WDUMMY;
   2626 			}
   2627 			gp[1].u = '\0';
   2628 			gp[1].mode = ATTR_WDUMMY;
   2629 		}
   2630 	}
   2631 	if (term.c.x+width < term.col) {
   2632 		tmoveto(term.c.x+width, term.c.y);
   2633 	} else {
   2634 		term.c.state |= CURSOR_WRAPNEXT;
   2635 	}
   2636 }
   2637 
   2638 int
   2639 twrite(const char *buf, int buflen, int show_ctrl)
   2640 {
   2641 	int charsize;
   2642 	Rune u;
   2643 	int n;
   2644 
   2645 	if (TSCREEN.off) {
   2646 		TSCREEN.off = 0;
   2647 		tfulldirt();
   2648 	}
   2649 
   2650 	for (n = 0; n < buflen; n += charsize) {
   2651 		if (IS_SET(MODE_UTF8)) {
   2652 			/* process a complete utf8 char */
   2653 			charsize = utf8decode(buf + n, &u, buflen - n);
   2654 			if (charsize == 0)
   2655 				break;
   2656 		} else {
   2657 			u = buf[n] & 0xFF;
   2658 			charsize = 1;
   2659 		}
   2660 		if (show_ctrl && ISCONTROL(u)) {
   2661 			if (u & 0x80) {
   2662 				u &= 0x7f;
   2663 				tputc('^');
   2664 				tputc('[');
   2665 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2666 				u ^= 0x40;
   2667 				tputc('^');
   2668 			}
   2669 		}
   2670 		tputc(u);
   2671 	}
   2672 	return n;
   2673 }
   2674 
   2675 void
   2676 clearline(Line line, Glyph g, int x, int xend)
   2677 {
   2678 	int i;
   2679 	g.mode = 0;
   2680 	g.u = ' ';
   2681 	for (i = x; i < xend; ++i) {
   2682 		line[i] = g;
   2683 	}
   2684 }
   2685 
   2686 Line
   2687 ensureline(Line line)
   2688 {
   2689 	if (!line) {
   2690 		line = xmalloc(term.linelen * sizeof(Glyph));
   2691 	}
   2692 	return line;
   2693 }
   2694 
   2695 void
   2696 tresize(int col, int row)
   2697 {
   2698 	int i, j;
   2699 	int minrow = MIN(row, term.row);
   2700 	int mincol = MIN(col, term.col);
   2701 	int linelen = MAX(col, term.linelen);
   2702 	int *bp;
   2703 
   2704 	if (col < 1 || row < 1 || row > HISTSIZE) {
   2705 		fprintf(stderr,
   2706 		        "tresize: error resizing to %dx%d\n", col, row);
   2707 		return;
   2708 	}
   2709 
   2710 	/* Shift buffer to keep the cursor where we expect it */
   2711 	if (row <= term.c.y) {
   2712 		term.screen[0].cur = (term.screen[0].cur - row + term.c.y + 1) % term.screen[0].size;
   2713 	}
   2714 
   2715 	/* Resize and clear line buffers as needed */
   2716 	if (linelen > term.linelen) {
   2717 		for (i = 0; i < term.screen[0].size; ++i) {
   2718 			if (term.screen[0].buffer[i]) {
   2719 				term.screen[0].buffer[i] = xrealloc(term.screen[0].buffer[i], linelen * sizeof(Glyph));
   2720 				clearline(term.screen[0].buffer[i], term.c.attr, term.linelen, linelen);
   2721 			}
   2722 		}
   2723 		for (i = 0; i < minrow; ++i) {
   2724 			term.screen[1].buffer[i] = xrealloc(term.screen[1].buffer[i], linelen * sizeof(Glyph));
   2725 			clearline(term.screen[1].buffer[i], term.c.attr, term.linelen, linelen);
   2726 		}
   2727 	}
   2728 	/* Allocate all visible lines for regular line buffer */
   2729 	for (j = term.screen[0].cur, i = 0; i < row; ++i, j = (j + 1) % term.screen[0].size)
   2730 	{
   2731 		if (!term.screen[0].buffer[j]) {
   2732 			term.screen[0].buffer[j] = xmalloc(linelen * sizeof(Glyph));
   2733 		}
   2734 		if (i >= term.row) {
   2735 			clearline(term.screen[0].buffer[j], term.c.attr, 0, linelen);
   2736 		}
   2737 	}
   2738 	/* Resize alt screen */
   2739 	term.screen[1].cur = 0;
   2740 	term.screen[1].size = row;
   2741 	for (i = row; i < term.row; ++i) {
   2742 		free(term.screen[1].buffer[i]);
   2743 	}
   2744 	term.screen[1].buffer = xrealloc(term.screen[1].buffer, row * sizeof(Line));
   2745 	for (i = term.row; i < row; ++i) {
   2746 		term.screen[1].buffer[i] = xmalloc(linelen * sizeof(Glyph));
   2747 		clearline(term.screen[1].buffer[i], term.c.attr, 0, linelen);
   2748 	}
   2749 
   2750 	/* resize to new height */
   2751 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2752 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2753 
   2754 	/* fix tabstops */
   2755 	if (col > term.col) {
   2756 		bp = term.tabs + term.col;
   2757 
   2758 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2759 		while (--bp > term.tabs && !*bp)
   2760 			/* nothing */ ;
   2761 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2762 			*bp = 1;
   2763 	}
   2764 
   2765 	/* update terminal size */
   2766 	term.col = col;
   2767 	term.row = row;
   2768 	term.linelen = linelen;
   2769 	/* reset scrolling region */
   2770 	tsetscroll(0, row-1);
   2771 	/* make use of the LIMIT in tmoveto */
   2772 	tmoveto(term.c.x, term.c.y);
   2773 	tfulldirt();
   2774 }
   2775 
   2776 void
   2777 resettitle(void)
   2778 {
   2779 	xsettitle(NULL);
   2780 }
   2781 
   2782 void
   2783 drawregion(int x1, int y1, int x2, int y2)
   2784 {
   2785 	int y, L;
   2786 
   2787 	L = TLINEOFFSET(y1);
   2788 	for (y = y1; y < y2; y++) {
   2789 		if (term.dirty[y]) {
   2790 			term.dirty[y] = 0;
   2791 			xdrawline(TSCREEN.buffer[L], x1, y, x2);
   2792 		}
   2793 		L = (L + 1) % TSCREEN.size;
   2794 	}
   2795 }
   2796 
   2797 void
   2798 draw(void)
   2799 {
   2800 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2801 
   2802 	if (!xstartdraw())
   2803 		return;
   2804 
   2805 	/* adjust cursor position */
   2806 	LIMIT(term.ocx, 0, term.col-1);
   2807 	LIMIT(term.ocy, 0, term.row-1);
   2808 	if (TLINE(term.ocy)[term.ocx].mode & ATTR_WDUMMY)
   2809 		term.ocx--;
   2810 	if (TLINE(term.c.y)[cx].mode & ATTR_WDUMMY)
   2811 		cx--;
   2812 
   2813 	drawregion(0, 0, term.col, term.row);
   2814 	if (TSCREEN.off == 0)
   2815 		xdrawcursor(cx, term.c.y, TLINE(term.c.y)[cx],
   2816 				term.ocx, term.ocy, TLINE(term.ocy)[term.ocx]);
   2817 	term.ocx = cx;
   2818 	term.ocy = term.c.y;
   2819 	xfinishdraw();
   2820 	if (ocx != term.ocx || ocy != term.ocy)
   2821 		xximspot(term.ocx, term.ocy);
   2822 }
   2823 
   2824 void
   2825 redraw(void)
   2826 {
   2827 	tfulldirt();
   2828 	draw();
   2829 }