st

my customized st build
git clone git://git.hanetzok.net/st
Log | Files | Refs | README | LICENSE

st.c (60284B)


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