st

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

st.c (60347B)


      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 	if (term.row <= 0)
    979 		return;
    980 
    981 	LIMIT(top, 0, term.row-1);
    982 	LIMIT(bot, 0, term.row-1);
    983 
    984 	for (i = top; i <= bot; i++)
    985 		term.dirty[i] = 1;
    986 }
    987 
    988 void
    989 tsetdirtattr(int attr)
    990 {
    991 	int i, j;
    992 
    993 	for (i = 0; i < term.row-1; i++) {
    994 		for (j = 0; j < term.col-1; j++) {
    995 			if (term.line[i][j].mode & attr) {
    996 				tsetdirt(i, i);
    997 				break;
    998 			}
    999 		}
   1000 	}
   1001 }
   1002 
   1003 void
   1004 tfulldirt(void)
   1005 {
   1006 	tsetdirt(0, term.row-1);
   1007 }
   1008 
   1009 void
   1010 tcursor(int mode)
   1011 {
   1012 	static TCursor c[2];
   1013 	int alt = IS_SET(MODE_ALTSCREEN);
   1014 
   1015 	if (mode == CURSOR_SAVE) {
   1016 		c[alt] = term.c;
   1017 	} else if (mode == CURSOR_LOAD) {
   1018 		term.c = c[alt];
   1019 		tmoveto(c[alt].x, c[alt].y);
   1020 	}
   1021 }
   1022 
   1023 void
   1024 treset(void)
   1025 {
   1026 	uint i;
   1027 
   1028 	term.c = (TCursor){{
   1029 		.mode = ATTR_NULL,
   1030 		.fg = defaultfg,
   1031 		.bg = defaultbg
   1032 	}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
   1033 
   1034 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1035 	for (i = tabspaces; i < term.col; i += tabspaces)
   1036 		term.tabs[i] = 1;
   1037 	term.top = 0;
   1038 	term.bot = term.row - 1;
   1039 	term.mode = MODE_WRAP|MODE_UTF8;
   1040 	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
   1041 	term.charset = 0;
   1042 
   1043 	for (i = 0; i < 2; i++) {
   1044 		tmoveto(0, 0);
   1045 		tcursor(CURSOR_SAVE);
   1046 		tclearregion(0, 0, term.col-1, term.row-1);
   1047 		tswapscreen();
   1048 	}
   1049 }
   1050 
   1051 void
   1052 tnew(int col, int row)
   1053 {
   1054 	term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
   1055 	tresize(col, row);
   1056 	treset();
   1057 }
   1058 
   1059 void
   1060 tswapscreen(void)
   1061 {
   1062 	Line *tmp = term.line;
   1063 
   1064 	term.line = term.alt;
   1065 	term.alt = tmp;
   1066 	term.mode ^= MODE_ALTSCREEN;
   1067 	tfulldirt();
   1068 }
   1069 
   1070 void
   1071 kscrolldown(const Arg* a)
   1072 {
   1073 	int n = a->i;
   1074 
   1075 	if (n < 0)
   1076 		n = term.row + n;
   1077 
   1078 	if (n > term.scr)
   1079 		n = term.scr;
   1080 
   1081 	if (term.scr > 0) {
   1082 		term.scr -= n;
   1083 		selscroll(0, -n);
   1084 		tfulldirt();
   1085 	}
   1086 }
   1087 
   1088 void
   1089 kscrollup(const Arg* a)
   1090 {
   1091 	int n = a->i;
   1092 
   1093 	if (n < 0)
   1094 		n = term.row + n;
   1095 
   1096 	if (term.scr <= HISTSIZE-n) {
   1097 		term.scr += n;
   1098 		selscroll(0, n);
   1099 		tfulldirt();
   1100 	}
   1101 }
   1102 
   1103 void
   1104 tscrolldown(int orig, int n, int copyhist)
   1105 {
   1106 	int i;
   1107 	Line temp;
   1108 
   1109 	LIMIT(n, 0, term.bot-orig+1);
   1110 
   1111 	if (copyhist) {
   1112 		term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
   1113 		temp = term.hist[term.histi];
   1114 		term.hist[term.histi] = term.line[term.bot];
   1115 		term.line[term.bot] = temp;
   1116 	}
   1117 
   1118 	tsetdirt(orig, term.bot-n);
   1119 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
   1120 
   1121 	for (i = term.bot; i >= orig+n; i--) {
   1122 		temp = term.line[i];
   1123 		term.line[i] = term.line[i-n];
   1124 		term.line[i-n] = temp;
   1125 	}
   1126 
   1127 	if (term.scr == 0)
   1128 		selscroll(orig, n);
   1129 }
   1130 
   1131 void
   1132 tscrollup(int orig, int n, int copyhist)
   1133 {
   1134 	int i;
   1135 	Line temp;
   1136 
   1137 	LIMIT(n, 0, term.bot-orig+1);
   1138 
   1139 	if (copyhist) {
   1140 		term.histi = (term.histi + 1) % HISTSIZE;
   1141 		temp = term.hist[term.histi];
   1142 		term.hist[term.histi] = term.line[orig];
   1143 		term.line[orig] = temp;
   1144 	}
   1145 
   1146 	if (term.scr > 0 && term.scr < HISTSIZE)
   1147 		term.scr = MIN(term.scr + n, HISTSIZE-1);
   1148 
   1149 	tclearregion(0, orig, term.col-1, orig+n-1);
   1150 	tsetdirt(orig+n, term.bot);
   1151 
   1152 	for (i = orig; i <= term.bot-n; i++) {
   1153 		temp = term.line[i];
   1154 		term.line[i] = term.line[i+n];
   1155 		term.line[i+n] = temp;
   1156 	}
   1157 
   1158 	if (term.scr == 0)
   1159 		selscroll(orig, -n);
   1160 }
   1161 
   1162 void
   1163 selscroll(int orig, int n)
   1164 {
   1165 	if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
   1166 		return;
   1167 
   1168 	if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
   1169 		selclear();
   1170 	} else if (BETWEEN(sel.nb.y, orig, term.bot)) {
   1171 		sel.ob.y += n;
   1172 		sel.oe.y += n;
   1173 		if (sel.ob.y < term.top || sel.ob.y > term.bot ||
   1174 		    sel.oe.y < term.top || sel.oe.y > term.bot) {
   1175 			selclear();
   1176 		} else {
   1177 			selnormalize();
   1178 		}
   1179 	}
   1180 }
   1181 
   1182 void
   1183 tnewline(int first_col)
   1184 {
   1185 	int y = term.c.y;
   1186 
   1187 	if (y == term.bot) {
   1188 		tscrollup(term.top, 1, 1);
   1189 	} else {
   1190 		y++;
   1191 	}
   1192 	tmoveto(first_col ? 0 : term.c.x, y);
   1193 }
   1194 
   1195 void
   1196 csiparse(void)
   1197 {
   1198 	char *p = csiescseq.buf, *np;
   1199 	long int v;
   1200 	int sep = ';'; /* colon or semi-colon, but not both */
   1201 
   1202 	csiescseq.narg = 0;
   1203 	if (*p == '?') {
   1204 		csiescseq.priv = 1;
   1205 		p++;
   1206 	}
   1207 
   1208 	csiescseq.buf[csiescseq.len] = '\0';
   1209 	while (p < csiescseq.buf+csiescseq.len) {
   1210 		np = NULL;
   1211 		v = strtol(p, &np, 10);
   1212 		if (np == p)
   1213 			v = 0;
   1214 		if (v == LONG_MAX || v == LONG_MIN)
   1215 			v = -1;
   1216 		csiescseq.arg[csiescseq.narg++] = v;
   1217 		p = np;
   1218 		if (sep == ';' && *p == ':')
   1219 			sep = ':'; /* allow override to colon once */
   1220 		if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
   1221 			break;
   1222 		p++;
   1223 	}
   1224 	csiescseq.mode[0] = *p++;
   1225 	csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
   1226 }
   1227 
   1228 /* for absolute user moves, when decom is set */
   1229 void
   1230 tmoveato(int x, int y)
   1231 {
   1232 	tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
   1233 }
   1234 
   1235 void
   1236 tmoveto(int x, int y)
   1237 {
   1238 	int miny, maxy;
   1239 
   1240 	if (term.c.state & CURSOR_ORIGIN) {
   1241 		miny = term.top;
   1242 		maxy = term.bot;
   1243 	} else {
   1244 		miny = 0;
   1245 		maxy = term.row - 1;
   1246 	}
   1247 	term.c.state &= ~CURSOR_WRAPNEXT;
   1248 	term.c.x = LIMIT(x, 0, term.col-1);
   1249 	term.c.y = LIMIT(y, miny, maxy);
   1250 }
   1251 
   1252 void
   1253 tsetchar(Rune u, const Glyph *attr, int x, int y)
   1254 {
   1255 	static const char *vt100_0[62] = { /* 0x41 - 0x7e */
   1256 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
   1257 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
   1258 		0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
   1259 		0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
   1260 		"◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
   1261 		"␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
   1262 		"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
   1263 		"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
   1264 	};
   1265 
   1266 	/*
   1267 	 * The table is proudly stolen from rxvt.
   1268 	 */
   1269 	if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
   1270 	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
   1271 		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
   1272 
   1273 	if (term.line[y][x].mode & ATTR_WIDE) {
   1274 		if (x+1 < term.col) {
   1275 			term.line[y][x+1].u = ' ';
   1276 			term.line[y][x+1].mode &= ~ATTR_WDUMMY;
   1277 		}
   1278 	} else if (term.line[y][x].mode & ATTR_WDUMMY) {
   1279 		term.line[y][x-1].u = ' ';
   1280 		term.line[y][x-1].mode &= ~ATTR_WIDE;
   1281 	}
   1282 
   1283 	term.dirty[y] = 1;
   1284 	term.line[y][x] = *attr;
   1285 	term.line[y][x].u = u;
   1286 }
   1287 
   1288 void
   1289 tclearregion(int x1, int y1, int x2, int y2)
   1290 {
   1291 	int x, y, temp;
   1292 	Glyph *gp;
   1293 
   1294 	if (x1 > x2)
   1295 		temp = x1, x1 = x2, x2 = temp;
   1296 	if (y1 > y2)
   1297 		temp = y1, y1 = y2, y2 = temp;
   1298 
   1299 	LIMIT(x1, 0, term.col-1);
   1300 	LIMIT(x2, 0, term.col-1);
   1301 	LIMIT(y1, 0, term.row-1);
   1302 	LIMIT(y2, 0, term.row-1);
   1303 
   1304 	for (y = y1; y <= y2; y++) {
   1305 		term.dirty[y] = 1;
   1306 		for (x = x1; x <= x2; x++) {
   1307 			gp = &term.line[y][x];
   1308 			if (selected(x, y))
   1309 				selclear();
   1310 			gp->fg = term.c.attr.fg;
   1311 			gp->bg = term.c.attr.bg;
   1312 			gp->mode = 0;
   1313 			gp->u = ' ';
   1314 		}
   1315 	}
   1316 }
   1317 
   1318 void
   1319 tdeletechar(int n)
   1320 {
   1321 	int dst, src, size;
   1322 	Glyph *line;
   1323 
   1324 	LIMIT(n, 0, term.col - term.c.x);
   1325 
   1326 	dst = term.c.x;
   1327 	src = term.c.x + n;
   1328 	size = term.col - src;
   1329 	line = term.line[term.c.y];
   1330 
   1331 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1332 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1333 }
   1334 
   1335 void
   1336 tinsertblank(int n)
   1337 {
   1338 	int dst, src, size;
   1339 	Glyph *line;
   1340 
   1341 	LIMIT(n, 0, term.col - term.c.x);
   1342 
   1343 	dst = term.c.x + n;
   1344 	src = term.c.x;
   1345 	size = term.col - dst;
   1346 	line = term.line[term.c.y];
   1347 
   1348 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1349 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1350 }
   1351 
   1352 void
   1353 tinsertblankline(int n)
   1354 {
   1355 	if (BETWEEN(term.c.y, term.top, term.bot))
   1356 		tscrolldown(term.c.y, n, 0);
   1357 }
   1358 
   1359 void
   1360 tdeleteline(int n)
   1361 {
   1362 	if (BETWEEN(term.c.y, term.top, term.bot))
   1363 		tscrollup(term.c.y, n, 0);
   1364 }
   1365 
   1366 int32_t
   1367 tdefcolor(const int *attr, int *npar, int l)
   1368 {
   1369 	int32_t idx = -1;
   1370 	uint r, g, b;
   1371 
   1372 	switch (attr[*npar + 1]) {
   1373 	case 2: /* direct color in RGB space */
   1374 		if (*npar + 4 >= l) {
   1375 			fprintf(stderr,
   1376 				"erresc(38): Incorrect number of parameters (%d)\n",
   1377 				*npar);
   1378 			break;
   1379 		}
   1380 		r = attr[*npar + 2];
   1381 		g = attr[*npar + 3];
   1382 		b = attr[*npar + 4];
   1383 		*npar += 4;
   1384 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1385 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1386 				r, g, b);
   1387 		else
   1388 			idx = TRUECOLOR(r, g, b);
   1389 		break;
   1390 	case 5: /* indexed color */
   1391 		if (*npar + 2 >= l) {
   1392 			fprintf(stderr,
   1393 				"erresc(38): Incorrect number of parameters (%d)\n",
   1394 				*npar);
   1395 			break;
   1396 		}
   1397 		*npar += 2;
   1398 		if (!BETWEEN(attr[*npar], 0, 255))
   1399 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1400 		else
   1401 			idx = attr[*npar];
   1402 		break;
   1403 	case 0: /* implemented defined (only foreground) */
   1404 	case 1: /* transparent */
   1405 	case 3: /* direct color in CMY space */
   1406 	case 4: /* direct color in CMYK space */
   1407 	default:
   1408 		fprintf(stderr,
   1409 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1410 		break;
   1411 	}
   1412 
   1413 	return idx;
   1414 }
   1415 
   1416 void
   1417 tsetattr(const int *attr, int l)
   1418 {
   1419 	int i;
   1420 	int32_t idx;
   1421 
   1422 	for (i = 0; i < l; i++) {
   1423 		switch (attr[i]) {
   1424 		case 0:
   1425 			term.c.attr.mode &= ~(
   1426 				ATTR_BOLD       |
   1427 				ATTR_FAINT      |
   1428 				ATTR_ITALIC     |
   1429 				ATTR_UNDERLINE  |
   1430 				ATTR_BLINK      |
   1431 				ATTR_REVERSE    |
   1432 				ATTR_INVISIBLE  |
   1433 				ATTR_STRUCK     );
   1434 			term.c.attr.fg = defaultfg;
   1435 			term.c.attr.bg = defaultbg;
   1436 			break;
   1437 		case 1:
   1438 			term.c.attr.mode |= ATTR_BOLD;
   1439 			break;
   1440 		case 2:
   1441 			term.c.attr.mode |= ATTR_FAINT;
   1442 			break;
   1443 		case 3:
   1444 			term.c.attr.mode |= ATTR_ITALIC;
   1445 			break;
   1446 		case 4:
   1447 			term.c.attr.mode |= ATTR_UNDERLINE;
   1448 			break;
   1449 		case 5: /* slow blink */
   1450 			/* FALLTHROUGH */
   1451 		case 6: /* rapid blink */
   1452 			term.c.attr.mode |= ATTR_BLINK;
   1453 			break;
   1454 		case 7:
   1455 			term.c.attr.mode |= ATTR_REVERSE;
   1456 			break;
   1457 		case 8:
   1458 			term.c.attr.mode |= ATTR_INVISIBLE;
   1459 			break;
   1460 		case 9:
   1461 			term.c.attr.mode |= ATTR_STRUCK;
   1462 			break;
   1463 		case 22:
   1464 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1465 			break;
   1466 		case 23:
   1467 			term.c.attr.mode &= ~ATTR_ITALIC;
   1468 			break;
   1469 		case 24:
   1470 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1471 			break;
   1472 		case 25:
   1473 			term.c.attr.mode &= ~ATTR_BLINK;
   1474 			break;
   1475 		case 27:
   1476 			term.c.attr.mode &= ~ATTR_REVERSE;
   1477 			break;
   1478 		case 28:
   1479 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1480 			break;
   1481 		case 29:
   1482 			term.c.attr.mode &= ~ATTR_STRUCK;
   1483 			break;
   1484 		case 38:
   1485 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1486 				term.c.attr.fg = idx;
   1487 			break;
   1488 		case 39: /* set foreground color to default */
   1489 			term.c.attr.fg = defaultfg;
   1490 			break;
   1491 		case 48:
   1492 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1493 				term.c.attr.bg = idx;
   1494 			break;
   1495 		case 49: /* set background color to default */
   1496 			term.c.attr.bg = defaultbg;
   1497 			break;
   1498 		case 58:
   1499 			/* This starts a sequence to change the color of
   1500 			 * "underline" pixels. We don't support that and
   1501 			 * instead eat up a following "5;n" or "2;r;g;b". */
   1502 			tdefcolor(attr, &i, l);
   1503 			break;
   1504 		default:
   1505 			if (BETWEEN(attr[i], 30, 37)) {
   1506 				term.c.attr.fg = attr[i] - 30;
   1507 			} else if (BETWEEN(attr[i], 40, 47)) {
   1508 				term.c.attr.bg = attr[i] - 40;
   1509 			} else if (BETWEEN(attr[i], 90, 97)) {
   1510 				term.c.attr.fg = attr[i] - 90 + 8;
   1511 			} else if (BETWEEN(attr[i], 100, 107)) {
   1512 				term.c.attr.bg = attr[i] - 100 + 8;
   1513 			} else {
   1514 				fprintf(stderr,
   1515 					"erresc(default): gfx attr %d unknown\n",
   1516 					attr[i]);
   1517 				csidump();
   1518 			}
   1519 			break;
   1520 		}
   1521 	}
   1522 }
   1523 
   1524 void
   1525 tsetscroll(int t, int b)
   1526 {
   1527 	int temp;
   1528 
   1529 	LIMIT(t, 0, term.row-1);
   1530 	LIMIT(b, 0, term.row-1);
   1531 	if (t > b) {
   1532 		temp = t;
   1533 		t = b;
   1534 		b = temp;
   1535 	}
   1536 	term.top = t;
   1537 	term.bot = b;
   1538 }
   1539 
   1540 void
   1541 tsetmode(int priv, int set, const int *args, int narg)
   1542 {
   1543 	int alt; const int *lim;
   1544 
   1545 	for (lim = args + narg; args < lim; ++args) {
   1546 		if (priv) {
   1547 			switch (*args) {
   1548 			case 1: /* DECCKM -- Cursor key */
   1549 				xsetmode(set, MODE_APPCURSOR);
   1550 				break;
   1551 			case 5: /* DECSCNM -- Reverse video */
   1552 				xsetmode(set, MODE_REVERSE);
   1553 				break;
   1554 			case 6: /* DECOM -- Origin */
   1555 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1556 				tmoveato(0, 0);
   1557 				break;
   1558 			case 7: /* DECAWM -- Auto wrap */
   1559 				MODBIT(term.mode, set, MODE_WRAP);
   1560 				break;
   1561 			case 0:  /* Error (IGNORED) */
   1562 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1563 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1564 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1565 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1566 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1567 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1568 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1569 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1570 				break;
   1571 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1572 				xsetmode(!set, MODE_HIDE);
   1573 				break;
   1574 			case 9:    /* X10 mouse compatibility mode */
   1575 				xsetpointermotion(0);
   1576 				xsetmode(0, MODE_MOUSE);
   1577 				xsetmode(set, MODE_MOUSEX10);
   1578 				break;
   1579 			case 1000: /* 1000: report button press */
   1580 				xsetpointermotion(0);
   1581 				xsetmode(0, MODE_MOUSE);
   1582 				xsetmode(set, MODE_MOUSEBTN);
   1583 				break;
   1584 			case 1002: /* 1002: report motion on button press */
   1585 				xsetpointermotion(0);
   1586 				xsetmode(0, MODE_MOUSE);
   1587 				xsetmode(set, MODE_MOUSEMOTION);
   1588 				break;
   1589 			case 1003: /* 1003: enable all mouse motions */
   1590 				xsetpointermotion(set);
   1591 				xsetmode(0, MODE_MOUSE);
   1592 				xsetmode(set, MODE_MOUSEMANY);
   1593 				break;
   1594 			case 1004: /* 1004: send focus events to tty */
   1595 				xsetmode(set, MODE_FOCUS);
   1596 				break;
   1597 			case 1006: /* 1006: extended reporting mode */
   1598 				xsetmode(set, MODE_MOUSESGR);
   1599 				break;
   1600 			case 1034: /* 1034: enable 8-bit mode for keyboard input */
   1601 				xsetmode(set, MODE_8BIT);
   1602 				break;
   1603 			case 1049: /* swap screen & set/restore cursor as xterm */
   1604 				if (!allowaltscreen)
   1605 					break;
   1606 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1607 				/* FALLTHROUGH */
   1608 			case 47: /* swap screen buffer */
   1609 			case 1047: /* swap screen buffer */
   1610 				if (!allowaltscreen)
   1611 					break;
   1612 				alt = IS_SET(MODE_ALTSCREEN);
   1613 				if (alt) {
   1614 					tclearregion(0, 0, term.col-1,
   1615 							term.row-1);
   1616 				}
   1617 				if (set ^ alt) /* set is always 1 or 0 */
   1618 					tswapscreen();
   1619 				if (*args != 1049)
   1620 					break;
   1621 				/* FALLTHROUGH */
   1622 			case 1048: /* save/restore cursor (like DECSC/DECRC) */
   1623 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1624 				break;
   1625 			case 2004: /* 2004: bracketed paste mode */
   1626 				xsetmode(set, MODE_BRCKTPASTE);
   1627 				break;
   1628 			/* Not implemented mouse modes. See comments there. */
   1629 			case 1001: /* mouse highlight mode; can hang the
   1630 				      terminal by design when implemented. */
   1631 			case 1005: /* UTF-8 mouse mode; will confuse
   1632 				      applications not supporting UTF-8
   1633 				      and luit. */
   1634 			case 1015: /* urxvt mangled mouse mode; incompatible
   1635 				      and can be mistaken for other control
   1636 				      codes. */
   1637 				break;
   1638 			default:
   1639 				fprintf(stderr,
   1640 					"erresc: unknown private set/reset mode %d\n",
   1641 					*args);
   1642 				break;
   1643 			}
   1644 		} else {
   1645 			switch (*args) {
   1646 			case 0:  /* Error (IGNORED) */
   1647 				break;
   1648 			case 2:
   1649 				xsetmode(set, MODE_KBDLOCK);
   1650 				break;
   1651 			case 4:  /* IRM -- Insertion-replacement */
   1652 				MODBIT(term.mode, set, MODE_INSERT);
   1653 				break;
   1654 			case 12: /* SRM -- Send/Receive */
   1655 				MODBIT(term.mode, !set, MODE_ECHO);
   1656 				break;
   1657 			case 20: /* LNM -- Linefeed/new line */
   1658 				MODBIT(term.mode, set, MODE_CRLF);
   1659 				break;
   1660 			default:
   1661 				fprintf(stderr,
   1662 					"erresc: unknown set/reset mode %d\n",
   1663 					*args);
   1664 				break;
   1665 			}
   1666 		}
   1667 	}
   1668 }
   1669 
   1670 void
   1671 csihandle(void)
   1672 {
   1673 	char buf[40];
   1674 	int len;
   1675 
   1676 	switch (csiescseq.mode[0]) {
   1677 	default:
   1678 	unknown:
   1679 		fprintf(stderr, "erresc: unknown csi ");
   1680 		csidump();
   1681 		/* die(""); */
   1682 		break;
   1683 	case '@': /* ICH -- Insert <n> blank char */
   1684 		DEFAULT(csiescseq.arg[0], 1);
   1685 		tinsertblank(csiescseq.arg[0]);
   1686 		break;
   1687 	case 'A': /* CUU -- Cursor <n> Up */
   1688 		DEFAULT(csiescseq.arg[0], 1);
   1689 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   1690 		break;
   1691 	case 'B': /* CUD -- Cursor <n> Down */
   1692 	case 'e': /* VPR --Cursor <n> Down */
   1693 		DEFAULT(csiescseq.arg[0], 1);
   1694 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   1695 		break;
   1696 	case 'i': /* MC -- Media Copy */
   1697 		switch (csiescseq.arg[0]) {
   1698 		case 0:
   1699 			tdump();
   1700 			break;
   1701 		case 1:
   1702 			tdumpline(term.c.y);
   1703 			break;
   1704 		case 2:
   1705 			tdumpsel();
   1706 			break;
   1707 		case 4:
   1708 			term.mode &= ~MODE_PRINT;
   1709 			break;
   1710 		case 5:
   1711 			term.mode |= MODE_PRINT;
   1712 			break;
   1713 		}
   1714 		break;
   1715 	case 'c': /* DA -- Device Attributes */
   1716 		if (csiescseq.arg[0] == 0)
   1717 			ttywrite(vtiden, strlen(vtiden), 0);
   1718 		break;
   1719 	case 'b': /* REP -- if last char is printable print it <n> more times */
   1720 		LIMIT(csiescseq.arg[0], 1, 65535);
   1721 		if (term.lastc)
   1722 			while (csiescseq.arg[0]-- > 0)
   1723 				tputc(term.lastc);
   1724 		break;
   1725 	case 'C': /* CUF -- Cursor <n> Forward */
   1726 	case 'a': /* HPR -- Cursor <n> Forward */
   1727 		DEFAULT(csiescseq.arg[0], 1);
   1728 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   1729 		break;
   1730 	case 'D': /* CUB -- Cursor <n> Backward */
   1731 		DEFAULT(csiescseq.arg[0], 1);
   1732 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   1733 		break;
   1734 	case 'E': /* CNL -- Cursor <n> Down and first col */
   1735 		DEFAULT(csiescseq.arg[0], 1);
   1736 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   1737 		break;
   1738 	case 'F': /* CPL -- Cursor <n> Up and first col */
   1739 		DEFAULT(csiescseq.arg[0], 1);
   1740 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   1741 		break;
   1742 	case 'g': /* TBC -- Tabulation clear */
   1743 		switch (csiescseq.arg[0]) {
   1744 		case 0: /* clear current tab stop */
   1745 			term.tabs[term.c.x] = 0;
   1746 			break;
   1747 		case 3: /* clear all the tabs */
   1748 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1749 			break;
   1750 		default:
   1751 			goto unknown;
   1752 		}
   1753 		break;
   1754 	case 'G': /* CHA -- Move to <col> */
   1755 	case '`': /* HPA */
   1756 		DEFAULT(csiescseq.arg[0], 1);
   1757 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   1758 		break;
   1759 	case 'H': /* CUP -- Move to <row> <col> */
   1760 	case 'f': /* HVP */
   1761 		DEFAULT(csiescseq.arg[0], 1);
   1762 		DEFAULT(csiescseq.arg[1], 1);
   1763 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   1764 		break;
   1765 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   1766 		DEFAULT(csiescseq.arg[0], 1);
   1767 		tputtab(csiescseq.arg[0]);
   1768 		break;
   1769 	case 'J': /* ED -- Clear screen */
   1770 		switch (csiescseq.arg[0]) {
   1771 		case 0: /* below */
   1772 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
   1773 			if (term.c.y < term.row-1) {
   1774 				tclearregion(0, term.c.y+1, term.col-1,
   1775 						term.row-1);
   1776 			}
   1777 			break;
   1778 		case 1: /* above */
   1779 			if (term.c.y > 0)
   1780 				tclearregion(0, 0, term.col-1, term.c.y-1);
   1781 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1782 			break;
   1783 		case 2: /* all */
   1784 			tclearregion(0, 0, term.col-1, term.row-1);
   1785 			break;
   1786 		default:
   1787 			goto unknown;
   1788 		}
   1789 		break;
   1790 	case 'K': /* EL -- Clear line */
   1791 		switch (csiescseq.arg[0]) {
   1792 		case 0: /* right */
   1793 			tclearregion(term.c.x, term.c.y, term.col-1,
   1794 					term.c.y);
   1795 			break;
   1796 		case 1: /* left */
   1797 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1798 			break;
   1799 		case 2: /* all */
   1800 			tclearregion(0, term.c.y, term.col-1, term.c.y);
   1801 			break;
   1802 		}
   1803 		break;
   1804 	case 'S': /* SU -- Scroll <n> line up */
   1805 		if (csiescseq.priv) break;
   1806 		DEFAULT(csiescseq.arg[0], 1);
   1807 		tscrollup(term.top, csiescseq.arg[0], 0);
   1808 		break;
   1809 	case 'T': /* SD -- Scroll <n> line down */
   1810 		DEFAULT(csiescseq.arg[0], 1);
   1811 		tscrolldown(term.top, csiescseq.arg[0], 0);
   1812 		break;
   1813 	case 'L': /* IL -- Insert <n> blank lines */
   1814 		DEFAULT(csiescseq.arg[0], 1);
   1815 		tinsertblankline(csiescseq.arg[0]);
   1816 		break;
   1817 	case 'l': /* RM -- Reset Mode */
   1818 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1819 		break;
   1820 	case 'M': /* DL -- Delete <n> lines */
   1821 		DEFAULT(csiescseq.arg[0], 1);
   1822 		tdeleteline(csiescseq.arg[0]);
   1823 		break;
   1824 	case 'X': /* ECH -- Erase <n> char */
   1825 		DEFAULT(csiescseq.arg[0], 1);
   1826 		tclearregion(term.c.x, term.c.y,
   1827 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1828 		break;
   1829 	case 'P': /* DCH -- Delete <n> char */
   1830 		DEFAULT(csiescseq.arg[0], 1);
   1831 		tdeletechar(csiescseq.arg[0]);
   1832 		break;
   1833 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   1834 		DEFAULT(csiescseq.arg[0], 1);
   1835 		tputtab(-csiescseq.arg[0]);
   1836 		break;
   1837 	case 'd': /* VPA -- Move to <row> */
   1838 		DEFAULT(csiescseq.arg[0], 1);
   1839 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   1840 		break;
   1841 	case 'h': /* SM -- Set terminal mode */
   1842 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   1843 		break;
   1844 	case 'm': /* SGR -- Terminal attribute (color) */
   1845 		tsetattr(csiescseq.arg, csiescseq.narg);
   1846 		break;
   1847 	case 'n': /* DSR -- Device Status Report */
   1848 		switch (csiescseq.arg[0]) {
   1849 		case 5: /* Status Report "OK" `0n` */
   1850 			ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
   1851 			break;
   1852 		case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
   1853 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   1854 			               term.c.y+1, term.c.x+1);
   1855 			ttywrite(buf, len, 0);
   1856 			break;
   1857 		default:
   1858 			goto unknown;
   1859 		}
   1860 		break;
   1861 	case 'r': /* DECSTBM -- Set Scrolling Region */
   1862 		if (csiescseq.priv) {
   1863 			goto unknown;
   1864 		} else {
   1865 			DEFAULT(csiescseq.arg[0], 1);
   1866 			DEFAULT(csiescseq.arg[1], term.row);
   1867 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   1868 			tmoveato(0, 0);
   1869 		}
   1870 		break;
   1871 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   1872 		tcursor(CURSOR_SAVE);
   1873 		break;
   1874 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   1875 		if (csiescseq.priv) {
   1876 			goto unknown;
   1877 		} else {
   1878 			tcursor(CURSOR_LOAD);
   1879 		}
   1880 		break;
   1881 	case ' ':
   1882 		switch (csiescseq.mode[1]) {
   1883 		case 'q': /* DECSCUSR -- Set Cursor Style */
   1884 			if (xsetcursor(csiescseq.arg[0]))
   1885 				goto unknown;
   1886 			break;
   1887 		default:
   1888 			goto unknown;
   1889 		}
   1890 		break;
   1891 	}
   1892 }
   1893 
   1894 void
   1895 csidump(void)
   1896 {
   1897 	size_t i;
   1898 	uint c;
   1899 
   1900 	fprintf(stderr, "ESC[");
   1901 	for (i = 0; i < csiescseq.len; i++) {
   1902 		c = csiescseq.buf[i] & 0xff;
   1903 		if (isprint(c)) {
   1904 			putc(c, stderr);
   1905 		} else if (c == '\n') {
   1906 			fprintf(stderr, "(\\n)");
   1907 		} else if (c == '\r') {
   1908 			fprintf(stderr, "(\\r)");
   1909 		} else if (c == 0x1b) {
   1910 			fprintf(stderr, "(\\e)");
   1911 		} else {
   1912 			fprintf(stderr, "(%02x)", c);
   1913 		}
   1914 	}
   1915 	putc('\n', stderr);
   1916 }
   1917 
   1918 void
   1919 csireset(void)
   1920 {
   1921 	memset(&csiescseq, 0, sizeof(csiescseq));
   1922 }
   1923 
   1924 void
   1925 osc_color_response(int num, int index, int is_osc4)
   1926 {
   1927 	int n;
   1928 	char buf[32];
   1929 	unsigned char r, g, b;
   1930 
   1931 	if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
   1932 		fprintf(stderr, "erresc: failed to fetch %s color %d\n",
   1933 		        is_osc4 ? "osc4" : "osc",
   1934 		        is_osc4 ? num : index);
   1935 		return;
   1936 	}
   1937 
   1938 	n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
   1939 	             is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
   1940 	if (n < 0 || n >= sizeof(buf)) {
   1941 		fprintf(stderr, "error: %s while printing %s response\n",
   1942 		        n < 0 ? "snprintf failed" : "truncation occurred",
   1943 		        is_osc4 ? "osc4" : "osc");
   1944 	} else {
   1945 		ttywrite(buf, n, 1);
   1946 	}
   1947 }
   1948 
   1949 void
   1950 strhandle(void)
   1951 {
   1952 	char *p = NULL, *dec;
   1953 	int j, narg, par;
   1954 	const struct { int idx; char *str; } osc_table[] = {
   1955 		{ defaultfg, "foreground" },
   1956 		{ defaultbg, "background" },
   1957 		{ defaultcs, "cursor" }
   1958 	};
   1959 
   1960 	term.esc &= ~(ESC_STR_END|ESC_STR);
   1961 	strparse();
   1962 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   1963 
   1964 	switch (strescseq.type) {
   1965 	case ']': /* OSC -- Operating System Command */
   1966 		switch (par) {
   1967 		case 0:
   1968 			if (narg > 1) {
   1969 				xsettitle(strescseq.args[1]);
   1970 				xseticontitle(strescseq.args[1]);
   1971 			}
   1972 			return;
   1973 		case 1:
   1974 			if (narg > 1)
   1975 				xseticontitle(strescseq.args[1]);
   1976 			return;
   1977 		case 2:
   1978 			if (narg > 1)
   1979 				xsettitle(strescseq.args[1]);
   1980 			return;
   1981 		case 52: /* manipulate selection data */
   1982 			if (narg > 2 && allowwindowops) {
   1983 				dec = base64dec(strescseq.args[2]);
   1984 				if (dec) {
   1985 					xsetsel(dec);
   1986 					xclipcopy();
   1987 				} else {
   1988 					fprintf(stderr, "erresc: invalid base64\n");
   1989 				}
   1990 			}
   1991 			return;
   1992 		case 10: /* set dynamic VT100 text foreground color */
   1993 		case 11: /* set dynamic VT100 text background color */
   1994 		case 12: /* set dynamic text cursor color */
   1995 			if (narg < 2)
   1996 				break;
   1997 			p = strescseq.args[1];
   1998 			if ((j = par - 10) < 0 || j >= LEN(osc_table))
   1999 				break; /* shouldn't be possible */
   2000 
   2001 			if (!strcmp(p, "?")) {
   2002 				osc_color_response(par, osc_table[j].idx, 0);
   2003 			} else if (xsetcolorname(osc_table[j].idx, p)) {
   2004 				fprintf(stderr, "erresc: invalid %s color: %s\n",
   2005 				        osc_table[j].str, p);
   2006 			} else {
   2007 				tfulldirt();
   2008 			}
   2009 			return;
   2010 		case 4: /* color set */
   2011 			if (narg < 3)
   2012 				break;
   2013 			p = strescseq.args[2];
   2014 			/* FALLTHROUGH */
   2015 		case 104: /* color reset */
   2016 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   2017 
   2018 			if (p && !strcmp(p, "?")) {
   2019 				osc_color_response(j, 0, 1);
   2020 			} else if (xsetcolorname(j, p)) {
   2021 				if (par == 104 && narg <= 1) {
   2022 					xloadcols();
   2023 					return; /* color reset without parameter */
   2024 				}
   2025 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   2026 				        j, p ? p : "(null)");
   2027 			} else {
   2028 				/*
   2029 				 * TODO if defaultbg color is changed, borders
   2030 				 * are dirty
   2031 				 */
   2032 				tfulldirt();
   2033 			}
   2034 			return;
   2035 		case 110: /* reset dynamic VT100 text foreground color */
   2036 		case 111: /* reset dynamic VT100 text background color */
   2037 		case 112: /* reset dynamic text cursor color */
   2038 			if (narg != 1)
   2039 				break;
   2040 			if ((j = par - 110) < 0 || j >= LEN(osc_table))
   2041 				break; /* shouldn't be possible */
   2042 			if (xsetcolorname(osc_table[j].idx, NULL)) {
   2043 				fprintf(stderr, "erresc: %s color not found\n", osc_table[j].str);
   2044 			} else {
   2045 				tfulldirt();
   2046 			}
   2047 			return;
   2048 		}
   2049 		break;
   2050 	case 'k': /* old title set compatibility */
   2051 		xsettitle(strescseq.args[0]);
   2052 		return;
   2053 	case 'P': /* DCS -- Device Control String */
   2054 	case '_': /* APC -- Application Program Command */
   2055 	case '^': /* PM -- Privacy Message */
   2056 		return;
   2057 	}
   2058 
   2059 	fprintf(stderr, "erresc: unknown str ");
   2060 	strdump();
   2061 }
   2062 
   2063 void
   2064 strparse(void)
   2065 {
   2066 	int c;
   2067 	char *p = strescseq.buf;
   2068 
   2069 	strescseq.narg = 0;
   2070 	strescseq.buf[strescseq.len] = '\0';
   2071 
   2072 	if (*p == '\0')
   2073 		return;
   2074 
   2075 	while (strescseq.narg < STR_ARG_SIZ) {
   2076 		strescseq.args[strescseq.narg++] = p;
   2077 		while ((c = *p) != ';' && c != '\0')
   2078 			++p;
   2079 		if (c == '\0')
   2080 			return;
   2081 		*p++ = '\0';
   2082 	}
   2083 }
   2084 
   2085 void
   2086 strdump(void)
   2087 {
   2088 	size_t i;
   2089 	uint c;
   2090 
   2091 	fprintf(stderr, "ESC%c", strescseq.type);
   2092 	for (i = 0; i < strescseq.len; i++) {
   2093 		c = strescseq.buf[i] & 0xff;
   2094 		if (c == '\0') {
   2095 			putc('\n', stderr);
   2096 			return;
   2097 		} else if (isprint(c)) {
   2098 			putc(c, stderr);
   2099 		} else if (c == '\n') {
   2100 			fprintf(stderr, "(\\n)");
   2101 		} else if (c == '\r') {
   2102 			fprintf(stderr, "(\\r)");
   2103 		} else if (c == 0x1b) {
   2104 			fprintf(stderr, "(\\e)");
   2105 		} else {
   2106 			fprintf(stderr, "(%02x)", c);
   2107 		}
   2108 	}
   2109 	fprintf(stderr, "ESC\\\n");
   2110 }
   2111 
   2112 void
   2113 strreset(void)
   2114 {
   2115 	strescseq = (STREscape){
   2116 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2117 		.siz = STR_BUF_SIZ,
   2118 	};
   2119 }
   2120 
   2121 void
   2122 sendbreak(const Arg *arg)
   2123 {
   2124 	if (tcsendbreak(cmdfd, 0))
   2125 		perror("Error sending break");
   2126 }
   2127 
   2128 void
   2129 tprinter(char *s, size_t len)
   2130 {
   2131 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2132 		perror("Error writing to output file");
   2133 		close(iofd);
   2134 		iofd = -1;
   2135 	}
   2136 }
   2137 
   2138 void
   2139 toggleprinter(const Arg *arg)
   2140 {
   2141 	term.mode ^= MODE_PRINT;
   2142 }
   2143 
   2144 void
   2145 printscreen(const Arg *arg)
   2146 {
   2147 	tdump();
   2148 }
   2149 
   2150 void
   2151 printsel(const Arg *arg)
   2152 {
   2153 	tdumpsel();
   2154 }
   2155 
   2156 void
   2157 tdumpsel(void)
   2158 {
   2159 	char *ptr;
   2160 
   2161 	if ((ptr = getsel())) {
   2162 		tprinter(ptr, strlen(ptr));
   2163 		free(ptr);
   2164 	}
   2165 }
   2166 
   2167 void
   2168 tdumpline(int n)
   2169 {
   2170 	char buf[UTF_SIZ];
   2171 	const Glyph *bp, *end;
   2172 
   2173 	bp = &term.line[n][0];
   2174 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2175 	if (bp != end || bp->u != ' ') {
   2176 		for ( ; bp <= end; ++bp)
   2177 			tprinter(buf, utf8encode(bp->u, buf));
   2178 	}
   2179 	tprinter("\n", 1);
   2180 }
   2181 
   2182 void
   2183 tdump(void)
   2184 {
   2185 	int i;
   2186 
   2187 	for (i = 0; i < term.row; ++i)
   2188 		tdumpline(i);
   2189 }
   2190 
   2191 void
   2192 tputtab(int n)
   2193 {
   2194 	uint x = term.c.x;
   2195 
   2196 	if (n > 0) {
   2197 		while (x < term.col && n--)
   2198 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2199 				/* nothing */ ;
   2200 	} else if (n < 0) {
   2201 		while (x > 0 && n++)
   2202 			for (--x; x > 0 && !term.tabs[x]; --x)
   2203 				/* nothing */ ;
   2204 	}
   2205 	term.c.x = LIMIT(x, 0, term.col-1);
   2206 }
   2207 
   2208 void
   2209 tdefutf8(char ascii)
   2210 {
   2211 	if (ascii == 'G')
   2212 		term.mode |= MODE_UTF8;
   2213 	else if (ascii == '@')
   2214 		term.mode &= ~MODE_UTF8;
   2215 }
   2216 
   2217 void
   2218 tdeftran(char ascii)
   2219 {
   2220 	static char cs[] = "0B";
   2221 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2222 	char *p;
   2223 
   2224 	if ((p = strchr(cs, ascii)) == NULL) {
   2225 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2226 	} else {
   2227 		term.trantbl[term.icharset] = vcs[p - cs];
   2228 	}
   2229 }
   2230 
   2231 void
   2232 tdectest(char c)
   2233 {
   2234 	int x, y;
   2235 
   2236 	if (c == '8') { /* DEC screen alignment test. */
   2237 		for (x = 0; x < term.col; ++x) {
   2238 			for (y = 0; y < term.row; ++y)
   2239 				tsetchar('E', &term.c.attr, x, y);
   2240 		}
   2241 	}
   2242 }
   2243 
   2244 void
   2245 tstrsequence(uchar c)
   2246 {
   2247 	switch (c) {
   2248 	case 0x90:   /* DCS -- Device Control String */
   2249 		c = 'P';
   2250 		break;
   2251 	case 0x9f:   /* APC -- Application Program Command */
   2252 		c = '_';
   2253 		break;
   2254 	case 0x9e:   /* PM -- Privacy Message */
   2255 		c = '^';
   2256 		break;
   2257 	case 0x9d:   /* OSC -- Operating System Command */
   2258 		c = ']';
   2259 		break;
   2260 	}
   2261 	strreset();
   2262 	strescseq.type = c;
   2263 	term.esc |= ESC_STR;
   2264 }
   2265 
   2266 void
   2267 tcontrolcode(uchar ascii)
   2268 {
   2269 	switch (ascii) {
   2270 	case '\t':   /* HT */
   2271 		tputtab(1);
   2272 		return;
   2273 	case '\b':   /* BS */
   2274 		tmoveto(term.c.x-1, term.c.y);
   2275 		return;
   2276 	case '\r':   /* CR */
   2277 		tmoveto(0, term.c.y);
   2278 		return;
   2279 	case '\f':   /* LF */
   2280 	case '\v':   /* VT */
   2281 	case '\n':   /* LF */
   2282 		/* go to first col if the mode is set */
   2283 		tnewline(IS_SET(MODE_CRLF));
   2284 		return;
   2285 	case '\a':   /* BEL */
   2286 		if (term.esc & ESC_STR_END) {
   2287 			/* backwards compatibility to xterm */
   2288 			strhandle();
   2289 		} else {
   2290 			xbell();
   2291 		}
   2292 		break;
   2293 	case '\033': /* ESC */
   2294 		csireset();
   2295 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2296 		term.esc |= ESC_START;
   2297 		return;
   2298 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2299 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2300 		term.charset = 1 - (ascii - '\016');
   2301 		return;
   2302 	case '\032': /* SUB */
   2303 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2304 		/* FALLTHROUGH */
   2305 	case '\030': /* CAN */
   2306 		csireset();
   2307 		break;
   2308 	case '\005': /* ENQ (IGNORED) */
   2309 	case '\000': /* NUL (IGNORED) */
   2310 	case '\021': /* XON (IGNORED) */
   2311 	case '\023': /* XOFF (IGNORED) */
   2312 	case 0177:   /* DEL (IGNORED) */
   2313 		return;
   2314 	case 0x80:   /* TODO: PAD */
   2315 	case 0x81:   /* TODO: HOP */
   2316 	case 0x82:   /* TODO: BPH */
   2317 	case 0x83:   /* TODO: NBH */
   2318 	case 0x84:   /* TODO: IND */
   2319 		break;
   2320 	case 0x85:   /* NEL -- Next line */
   2321 		tnewline(1); /* always go to first col */
   2322 		break;
   2323 	case 0x86:   /* TODO: SSA */
   2324 	case 0x87:   /* TODO: ESA */
   2325 		break;
   2326 	case 0x88:   /* HTS -- Horizontal tab stop */
   2327 		term.tabs[term.c.x] = 1;
   2328 		break;
   2329 	case 0x89:   /* TODO: HTJ */
   2330 	case 0x8a:   /* TODO: VTS */
   2331 	case 0x8b:   /* TODO: PLD */
   2332 	case 0x8c:   /* TODO: PLU */
   2333 	case 0x8d:   /* TODO: RI */
   2334 	case 0x8e:   /* TODO: SS2 */
   2335 	case 0x8f:   /* TODO: SS3 */
   2336 	case 0x91:   /* TODO: PU1 */
   2337 	case 0x92:   /* TODO: PU2 */
   2338 	case 0x93:   /* TODO: STS */
   2339 	case 0x94:   /* TODO: CCH */
   2340 	case 0x95:   /* TODO: MW */
   2341 	case 0x96:   /* TODO: SPA */
   2342 	case 0x97:   /* TODO: EPA */
   2343 	case 0x98:   /* TODO: SOS */
   2344 	case 0x99:   /* TODO: SGCI */
   2345 		break;
   2346 	case 0x9a:   /* DECID -- Identify Terminal */
   2347 		ttywrite(vtiden, strlen(vtiden), 0);
   2348 		break;
   2349 	case 0x9b:   /* TODO: CSI */
   2350 	case 0x9c:   /* TODO: ST */
   2351 		break;
   2352 	case 0x90:   /* DCS -- Device Control String */
   2353 	case 0x9d:   /* OSC -- Operating System Command */
   2354 	case 0x9e:   /* PM -- Privacy Message */
   2355 	case 0x9f:   /* APC -- Application Program Command */
   2356 		tstrsequence(ascii);
   2357 		return;
   2358 	}
   2359 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2360 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2361 }
   2362 
   2363 /*
   2364  * returns 1 when the sequence is finished and it hasn't to read
   2365  * more characters for this sequence, otherwise 0
   2366  */
   2367 int
   2368 eschandle(uchar ascii)
   2369 {
   2370 	switch (ascii) {
   2371 	case '[':
   2372 		term.esc |= ESC_CSI;
   2373 		return 0;
   2374 	case '#':
   2375 		term.esc |= ESC_TEST;
   2376 		return 0;
   2377 	case '%':
   2378 		term.esc |= ESC_UTF8;
   2379 		return 0;
   2380 	case 'P': /* DCS -- Device Control String */
   2381 	case '_': /* APC -- Application Program Command */
   2382 	case '^': /* PM -- Privacy Message */
   2383 	case ']': /* OSC -- Operating System Command */
   2384 	case 'k': /* old title set compatibility */
   2385 		tstrsequence(ascii);
   2386 		return 0;
   2387 	case 'n': /* LS2 -- Locking shift 2 */
   2388 	case 'o': /* LS3 -- Locking shift 3 */
   2389 		term.charset = 2 + (ascii - 'n');
   2390 		break;
   2391 	case '(': /* GZD4 -- set primary charset G0 */
   2392 	case ')': /* G1D4 -- set secondary charset G1 */
   2393 	case '*': /* G2D4 -- set tertiary charset G2 */
   2394 	case '+': /* G3D4 -- set quaternary charset G3 */
   2395 		term.icharset = ascii - '(';
   2396 		term.esc |= ESC_ALTCHARSET;
   2397 		return 0;
   2398 	case 'D': /* IND -- Linefeed */
   2399 		if (term.c.y == term.bot) {
   2400 			tscrollup(term.top, 1, 1);
   2401 		} else {
   2402 			tmoveto(term.c.x, term.c.y+1);
   2403 		}
   2404 		break;
   2405 	case 'E': /* NEL -- Next line */
   2406 		tnewline(1); /* always go to first col */
   2407 		break;
   2408 	case 'H': /* HTS -- Horizontal tab stop */
   2409 		term.tabs[term.c.x] = 1;
   2410 		break;
   2411 	case 'M': /* RI -- Reverse index */
   2412 		if (term.c.y == term.top) {
   2413 			tscrolldown(term.top, 1, 1);
   2414 		} else {
   2415 			tmoveto(term.c.x, term.c.y-1);
   2416 		}
   2417 		break;
   2418 	case 'Z': /* DECID -- Identify Terminal */
   2419 		ttywrite(vtiden, strlen(vtiden), 0);
   2420 		break;
   2421 	case 'c': /* RIS -- Reset to initial state */
   2422 		treset();
   2423 		resettitle();
   2424 		xloadcols();
   2425 		xsetmode(0, MODE_HIDE);
   2426 		xsetmode(0, MODE_BRCKTPASTE);
   2427 		break;
   2428 	case '=': /* DECPAM -- Application keypad */
   2429 		xsetmode(1, MODE_APPKEYPAD);
   2430 		break;
   2431 	case '>': /* DECPNM -- Normal keypad */
   2432 		xsetmode(0, MODE_APPKEYPAD);
   2433 		break;
   2434 	case '7': /* DECSC -- Save Cursor */
   2435 		tcursor(CURSOR_SAVE);
   2436 		break;
   2437 	case '8': /* DECRC -- Restore Cursor */
   2438 		tcursor(CURSOR_LOAD);
   2439 		break;
   2440 	case '\\': /* ST -- String Terminator */
   2441 		if (term.esc & ESC_STR_END)
   2442 			strhandle();
   2443 		break;
   2444 	default:
   2445 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2446 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2447 		break;
   2448 	}
   2449 	return 1;
   2450 }
   2451 
   2452 void
   2453 tputc(Rune u)
   2454 {
   2455 	char c[UTF_SIZ];
   2456 	int control;
   2457 	int width, len;
   2458 	Glyph *gp;
   2459 
   2460 	control = ISCONTROL(u);
   2461 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2462 		c[0] = u;
   2463 		width = len = 1;
   2464 	} else {
   2465 		len = utf8encode(u, c);
   2466 		if (!control && (width = wcwidth(u)) == -1)
   2467 			width = 1;
   2468 	}
   2469 
   2470 	if (IS_SET(MODE_PRINT))
   2471 		tprinter(c, len);
   2472 
   2473 	/*
   2474 	 * STR sequence must be checked before anything else
   2475 	 * because it uses all following characters until it
   2476 	 * receives a ESC, a SUB, a ST or any other C1 control
   2477 	 * character.
   2478 	 */
   2479 	if (term.esc & ESC_STR) {
   2480 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2481 		   ISCONTROLC1(u)) {
   2482 			term.esc &= ~(ESC_START|ESC_STR);
   2483 			term.esc |= ESC_STR_END;
   2484 			goto check_control_code;
   2485 		}
   2486 
   2487 		if (strescseq.len+len >= strescseq.siz) {
   2488 			/*
   2489 			 * Here is a bug in terminals. If the user never sends
   2490 			 * some code to stop the str or esc command, then st
   2491 			 * will stop responding. But this is better than
   2492 			 * silently failing with unknown characters. At least
   2493 			 * then users will report back.
   2494 			 *
   2495 			 * In the case users ever get fixed, here is the code:
   2496 			 */
   2497 			/*
   2498 			 * term.esc = 0;
   2499 			 * strhandle();
   2500 			 */
   2501 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2502 				return;
   2503 			strescseq.siz *= 2;
   2504 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2505 		}
   2506 
   2507 		memmove(&strescseq.buf[strescseq.len], c, len);
   2508 		strescseq.len += len;
   2509 		return;
   2510 	}
   2511 
   2512 check_control_code:
   2513 	/*
   2514 	 * Actions of control codes must be performed as soon they arrive
   2515 	 * because they can be embedded inside a control sequence, and
   2516 	 * they must not cause conflicts with sequences.
   2517 	 */
   2518 	if (control) {
   2519 		/* in UTF-8 mode ignore handling C1 control characters */
   2520 		if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
   2521 			return;
   2522 		tcontrolcode(u);
   2523 		/*
   2524 		 * control codes are not shown ever
   2525 		 */
   2526 		if (!term.esc)
   2527 			term.lastc = 0;
   2528 		return;
   2529 	} else if (term.esc & ESC_START) {
   2530 		if (term.esc & ESC_CSI) {
   2531 			csiescseq.buf[csiescseq.len++] = u;
   2532 			if (BETWEEN(u, 0x40, 0x7E)
   2533 					|| csiescseq.len >= \
   2534 					sizeof(csiescseq.buf)-1) {
   2535 				term.esc = 0;
   2536 				csiparse();
   2537 				csihandle();
   2538 			}
   2539 			return;
   2540 		} else if (term.esc & ESC_UTF8) {
   2541 			tdefutf8(u);
   2542 		} else if (term.esc & ESC_ALTCHARSET) {
   2543 			tdeftran(u);
   2544 		} else if (term.esc & ESC_TEST) {
   2545 			tdectest(u);
   2546 		} else {
   2547 			if (!eschandle(u))
   2548 				return;
   2549 			/* sequence already finished */
   2550 		}
   2551 		term.esc = 0;
   2552 		/*
   2553 		 * All characters which form part of a sequence are not
   2554 		 * printed
   2555 		 */
   2556 		return;
   2557 	}
   2558 	if (selected(term.c.x, term.c.y))
   2559 		selclear();
   2560 
   2561 	gp = &term.line[term.c.y][term.c.x];
   2562 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2563 		gp->mode |= ATTR_WRAP;
   2564 		tnewline(1);
   2565 		gp = &term.line[term.c.y][term.c.x];
   2566 	}
   2567 
   2568 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
   2569 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2570 		gp->mode &= ~ATTR_WIDE;
   2571 	}
   2572 
   2573 	if (term.c.x+width > term.col) {
   2574 		if (IS_SET(MODE_WRAP))
   2575 			tnewline(1);
   2576 		else
   2577 			tmoveto(term.col - width, term.c.y);
   2578 		gp = &term.line[term.c.y][term.c.x];
   2579 	}
   2580 
   2581 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2582 	term.lastc = u;
   2583 
   2584 	if (width == 2) {
   2585 		gp->mode |= ATTR_WIDE;
   2586 		if (term.c.x+1 < term.col) {
   2587 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   2588 				gp[2].u = ' ';
   2589 				gp[2].mode &= ~ATTR_WDUMMY;
   2590 			}
   2591 			gp[1].u = '\0';
   2592 			gp[1].mode = ATTR_WDUMMY;
   2593 		}
   2594 	}
   2595 	if (term.c.x+width < term.col) {
   2596 		tmoveto(term.c.x+width, term.c.y);
   2597 	} else {
   2598 		term.c.state |= CURSOR_WRAPNEXT;
   2599 	}
   2600 }
   2601 
   2602 int
   2603 twrite(const char *buf, int buflen, int show_ctrl)
   2604 {
   2605 	int charsize;
   2606 	Rune u;
   2607 	int n;
   2608 
   2609 	for (n = 0; n < buflen; n += charsize) {
   2610 		if (IS_SET(MODE_UTF8)) {
   2611 			/* process a complete utf8 char */
   2612 			charsize = utf8decode(buf + n, &u, buflen - n);
   2613 			if (charsize == 0)
   2614 				break;
   2615 		} else {
   2616 			u = buf[n] & 0xFF;
   2617 			charsize = 1;
   2618 		}
   2619 		if (show_ctrl && ISCONTROL(u)) {
   2620 			if (u & 0x80) {
   2621 				u &= 0x7f;
   2622 				tputc('^');
   2623 				tputc('[');
   2624 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2625 				u ^= 0x40;
   2626 				tputc('^');
   2627 			}
   2628 		}
   2629 		tputc(u);
   2630 	}
   2631 	return n;
   2632 }
   2633 
   2634 void
   2635 tresize(int col, int row)
   2636 {
   2637 	int i, j;
   2638 	int minrow = MIN(row, term.row);
   2639 	int mincol = MIN(col, term.col);
   2640 	int *bp;
   2641 	TCursor c;
   2642 
   2643 	if (col < 1 || row < 1) {
   2644 		fprintf(stderr,
   2645 		        "tresize: error resizing to %dx%d\n", col, row);
   2646 		return;
   2647 	}
   2648 
   2649 	/*
   2650 	 * slide screen to keep cursor where we expect it -
   2651 	 * tscrollup would work here, but we can optimize to
   2652 	 * memmove because we're freeing the earlier lines
   2653 	 */
   2654 	for (i = 0; i <= term.c.y - row; i++) {
   2655 		free(term.line[i]);
   2656 		free(term.alt[i]);
   2657 	}
   2658 	/* ensure that both src and dst are not NULL */
   2659 	if (i > 0) {
   2660 		memmove(term.line, term.line + i, row * sizeof(Line));
   2661 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2662 	}
   2663 	for (i += row; i < term.row; i++) {
   2664 		free(term.line[i]);
   2665 		free(term.alt[i]);
   2666 	}
   2667 
   2668 	/* resize to new height */
   2669 	term.line = xrealloc(term.line, row * sizeof(Line));
   2670 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2671 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2672 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2673 
   2674 	for (i = 0; i < HISTSIZE; i++) {
   2675 		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
   2676 		for (j = mincol; j < col; j++) {
   2677 			term.hist[i][j] = term.c.attr;
   2678 			term.hist[i][j].u = ' ';
   2679 		}
   2680 	}
   2681 
   2682 	/* resize each row to new width, zero-pad if needed */
   2683 	for (i = 0; i < minrow; i++) {
   2684 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2685 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2686 	}
   2687 
   2688 	/* allocate any new rows */
   2689 	for (/* i = minrow */; i < row; i++) {
   2690 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2691 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2692 	}
   2693 	if (col > term.col) {
   2694 		bp = term.tabs + term.col;
   2695 
   2696 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2697 		while (--bp > term.tabs && !*bp)
   2698 			/* nothing */ ;
   2699 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2700 			*bp = 1;
   2701 	}
   2702 	/* update terminal size */
   2703 	term.col = col;
   2704 	term.row = row;
   2705 	/* reset scrolling region */
   2706 	tsetscroll(0, row-1);
   2707 	/* make use of the LIMIT in tmoveto */
   2708 	tmoveto(term.c.x, term.c.y);
   2709 	/* Clearing both screens (it makes dirty all lines) */
   2710 	c = term.c;
   2711 	for (i = 0; i < 2; i++) {
   2712 		if (mincol < col && 0 < minrow) {
   2713 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2714 		}
   2715 		if (0 < col && minrow < row) {
   2716 			tclearregion(0, minrow, col - 1, row - 1);
   2717 		}
   2718 		tswapscreen();
   2719 		tcursor(CURSOR_LOAD);
   2720 	}
   2721 	term.c = c;
   2722 }
   2723 
   2724 void
   2725 resettitle(void)
   2726 {
   2727 	xsettitle(NULL);
   2728 }
   2729 
   2730 void
   2731 drawregion(int x1, int y1, int x2, int y2)
   2732 {
   2733 	int y;
   2734 
   2735 	for (y = y1; y < y2; y++) {
   2736 		if (!term.dirty[y])
   2737 			continue;
   2738 
   2739 		term.dirty[y] = 0;
   2740 		xdrawline(TLINE(y), x1, y, x2);
   2741 	}
   2742 }
   2743 
   2744 void
   2745 draw(void)
   2746 {
   2747 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2748 
   2749 	if (!xstartdraw())
   2750 		return;
   2751 
   2752 	/* adjust cursor position */
   2753 	LIMIT(term.ocx, 0, term.col-1);
   2754 	LIMIT(term.ocy, 0, term.row-1);
   2755 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2756 		term.ocx--;
   2757 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2758 		cx--;
   2759 
   2760 	drawregion(0, 0, term.col, term.row);
   2761 	if (term.scr == 0)
   2762 		xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2763 				term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2764 	term.ocx = cx;
   2765 	term.ocy = term.c.y;
   2766 	xfinishdraw();
   2767 	if (ocx != term.ocx || ocy != term.ocy)
   2768 		xximspot(term.ocx, term.ocy);
   2769 }
   2770 
   2771 void
   2772 redraw(void)
   2773 {
   2774 	tfulldirt();
   2775 	draw();
   2776 }