st

thing1's st config
Log | Files | Refs | README | LICENSE

st.c (65313B)


      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 "autocomplete.h"
     21 #include "st.h"
     22 #include "win.h"
     23 
     24 #if   defined(__linux)
     25  #include <pty.h>
     26 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
     27  #include <util.h>
     28 #elif defined(__FreeBSD__) || defined(__DragonFly__)
     29  #include <libutil.h>
     30 #endif
     31 
     32 /* Arbitrary sizes */
     33 #define UTF_INVALID   0xFFFD
     34 #define UTF_SIZ       4
     35 #define ESC_BUF_SIZ   (128*UTF_SIZ)
     36 #define ESC_ARG_SIZ   16
     37 #define STR_BUF_SIZ   ESC_BUF_SIZ
     38 #define STR_ARG_SIZ   ESC_ARG_SIZ
     39 #define HISTSIZE      2000
     40 
     41 /* macros */
     42 #define IS_SET(flag)		((term.mode & (flag)) != 0)
     43 #define ISCONTROLC0(c)		(BETWEEN(c, 0, 0x1f) || (c) == 0x7f)
     44 #define ISCONTROLC1(c)		(BETWEEN(c, 0x80, 0x9f))
     45 #define ISCONTROL(c)		(ISCONTROLC0(c) || ISCONTROLC1(c))
     46 #define ISDELIM(u)		(u && wcschr(worddelimiters, u))
     47 #define TLINE(y)		((y) < term.scr ? term.hist[((y) + term.histi - \
     48             term.scr + HISTSIZE + 1) % HISTSIZE] : \
     49             term.line[(y) - term.scr])
     50 
     51 enum term_mode {
     52 	MODE_WRAP        = 1 << 0,
     53 	MODE_INSERT      = 1 << 1,
     54 	MODE_ALTSCREEN   = 1 << 2,
     55 	MODE_CRLF        = 1 << 3,
     56 	MODE_ECHO        = 1 << 4,
     57 	MODE_PRINT       = 1 << 5,
     58 	MODE_UTF8        = 1 << 6,
     59 };
     60 
     61 enum cursor_movement {
     62 	CURSOR_SAVE,
     63 	CURSOR_LOAD
     64 };
     65 
     66 enum cursor_state {
     67 	CURSOR_DEFAULT  = 0,
     68 	CURSOR_WRAPNEXT = 1,
     69 	CURSOR_ORIGIN   = 2
     70 };
     71 
     72 enum charset {
     73 	CS_GRAPHIC0,
     74 	CS_GRAPHIC1,
     75 	CS_UK,
     76 	CS_USA,
     77 	CS_MULTI,
     78 	CS_GER,
     79 	CS_FIN
     80 };
     81 
     82 enum escape_state {
     83 	ESC_START      = 1,
     84 	ESC_CSI        = 2,
     85 	ESC_STR        = 4,  /* DCS, OSC, PM, APC */
     86 	ESC_ALTCHARSET = 8,
     87 	ESC_STR_END    = 16, /* a final string was encountered */
     88 	ESC_TEST       = 32, /* Enter in test mode */
     89 	ESC_UTF8       = 64,
     90 };
     91 
     92 typedef struct {
     93 	Glyph attr; /* current char attributes */
     94 	int x;
     95 	int y;
     96 	char state;
     97 } TCursor;
     98 
     99 typedef struct {
    100 	int mode;
    101 	int type;
    102 	int snap;
    103 	/*
    104 	 * Selection variables:
    105 	 * nb – normalized coordinates of the beginning of the selection
    106 	 * ne – normalized coordinates of the end of the selection
    107 	 * ob – original coordinates of the beginning of the selection
    108 	 * oe – original coordinates of the end of the selection
    109 	 */
    110 	struct {
    111 		int x, y;
    112 	} nb, ne, ob, oe;
    113 
    114 	int alt;
    115 } Selection;
    116 
    117 /* Internal representation of the screen */
    118 typedef struct {
    119 	int row;      /* nb row */
    120 	int col;      /* nb col */
    121 	Line *line;   /* screen */
    122 	Line *alt;    /* alternate screen */
    123 	Line hist[HISTSIZE]; /* history buffer */
    124 	int histi;    /* history index */
    125 	int scr;      /* scroll back */
    126 	int *dirty;   /* dirtyness of lines */
    127 	TCursor c;    /* cursor */
    128 	int ocx;      /* old cursor col */
    129 	int ocy;      /* old cursor row */
    130 	int top;      /* top    scroll limit */
    131 	int bot;      /* bottom scroll limit */
    132 	int mode;     /* terminal mode flags */
    133 	int esc;      /* escape state flags */
    134 	char trantbl[4]; /* charset table translation */
    135 	int charset;  /* current charset */
    136 	int icharset; /* selected charset for sequence */
    137 	int *tabs;
    138 	Rune lastc;   /* last printed char outside of sequence, 0 if control */
    139 } Term;
    140 
    141 /* CSI Escape sequence structs */
    142 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
    143 typedef struct {
    144 	char buf[ESC_BUF_SIZ]; /* raw string */
    145 	size_t len;            /* raw string length */
    146 	char priv;
    147 	int arg[ESC_ARG_SIZ];
    148 	int narg;              /* nb of args */
    149 	char mode[2];
    150 } CSIEscape;
    151 
    152 /* STR Escape sequence structs */
    153 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
    154 typedef struct {
    155 	char type;             /* ESC type ... */
    156 	char *buf;             /* allocated raw string */
    157 	size_t siz;            /* allocation size */
    158 	size_t len;            /* raw string length */
    159 	char *args[STR_ARG_SIZ];
    160 	int narg;              /* nb of args */
    161 } STREscape;
    162 
    163 static void execsh(char *, char **);
    164 static void stty(char **);
    165 static void sigchld(int);
    166 static void ttywriteraw(const char *, size_t);
    167 
    168 static void csidump(void);
    169 static void csihandle(void);
    170 static void csiparse(void);
    171 static void csireset(void);
    172 static void osc_color_response(int, int, int);
    173 static int eschandle(uchar);
    174 static void strdump(void);
    175 static void strhandle(void);
    176 static void strparse(void);
    177 static void strreset(void);
    178 
    179 static void tprinter(char *, size_t);
    180 static void tdumpsel(void);
    181 static void tdumpline(int);
    182 static void tdump(void);
    183 static void tclearregion(int, int, int, int);
    184 static void tcursor(int);
    185 static void tdeletechar(int);
    186 static void tdeleteline(int);
    187 static void tinsertblank(int);
    188 static void tinsertblankline(int);
    189 static int tlinelen(int);
    190 static void tmoveto(int, int);
    191 static void tmoveato(int, int);
    192 static void tnewline(int);
    193 static void tputtab(int);
    194 static void tputc(Rune);
    195 static void treset(void);
    196 static void tscrollup(int, int, int);
    197 static void tscrolldown(int, int, int);
    198 static void tsetattr(const int *, int);
    199 static void tsetchar(Rune, const Glyph *, int, int);
    200 static void tsetdirt(int, int);
    201 static void tsetscroll(int, int);
    202 static void tswapscreen(void);
    203 static void tsetmode(int, int, const int *, int);
    204 static int twrite(const char *, int, int);
    205 static void tfulldirt(void);
    206 static void tcontrolcode(uchar );
    207 static void tdectest(char );
    208 static void tdefutf8(char);
    209 static int32_t tdefcolor(const int *, int *, int);
    210 static void tdeftran(char);
    211 static void tstrsequence(uchar);
    212 
    213 static void drawregion(int, int, int, int);
    214 
    215 static void selnormalize(void);
    216 static void selscroll(int, int);
    217 static void selsnap(int *, int *, int);
    218 
    219 static size_t utf8decode(const char *, Rune *, size_t);
    220 static Rune utf8decodebyte(char, size_t *);
    221 static char utf8encodebyte(Rune, size_t);
    222 static size_t utf8validate(Rune *, size_t);
    223 
    224 static char *base64dec(const char *);
    225 static char base64dec_getc(const char **);
    226 
    227 static ssize_t xwrite(int, const char *, size_t);
    228 
    229 /* Globals */
    230 static Term term;
    231 static Selection sel;
    232 static CSIEscape csiescseq;
    233 static STREscape strescseq;
    234 static int iofd = 1;
    235 static int cmdfd;
    236 static pid_t pid;
    237 
    238 static const uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
    239 static const uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
    240 static const Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
    241 static const Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
    242 
    243 ssize_t
    244 xwrite(int fd, const char *s, size_t len)
    245 {
    246 	size_t aux = len;
    247 	ssize_t r;
    248 
    249 	while (len > 0) {
    250 		r = write(fd, s, len);
    251 		if (r < 0)
    252 			return r;
    253 		len -= r;
    254 		s += r;
    255 	}
    256 
    257 	return aux;
    258 }
    259 
    260 void *
    261 xmalloc(size_t len)
    262 {
    263 	void *p;
    264 
    265 	if (!(p = malloc(len)))
    266 		die("malloc: %s\n", strerror(errno));
    267 
    268 	return p;
    269 }
    270 
    271 void *
    272 xrealloc(void *p, size_t len)
    273 {
    274 	if ((p = realloc(p, len)) == NULL)
    275 		die("realloc: %s\n", strerror(errno));
    276 
    277 	return p;
    278 }
    279 
    280 char *
    281 xstrdup(const char *s)
    282 {
    283 	char *p;
    284 
    285 	if ((p = strdup(s)) == NULL)
    286 		die("strdup: %s\n", strerror(errno));
    287 
    288 	return p;
    289 }
    290 
    291 size_t
    292 utf8decode(const char *c, Rune *u, size_t clen)
    293 {
    294 	size_t i, j, len, type;
    295 	Rune udecoded;
    296 
    297 	*u = UTF_INVALID;
    298 	if (!clen)
    299 		return 0;
    300 	udecoded = utf8decodebyte(c[0], &len);
    301 	if (!BETWEEN(len, 1, UTF_SIZ))
    302 		return 1;
    303 	for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
    304 		udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
    305 		if (type != 0)
    306 			return j;
    307 	}
    308 	if (j < len)
    309 		return 0;
    310 	*u = udecoded;
    311 	utf8validate(u, len);
    312 
    313 	return len;
    314 }
    315 
    316 Rune
    317 utf8decodebyte(char c, size_t *i)
    318 {
    319 	for (*i = 0; *i < LEN(utfmask); ++(*i))
    320 		if (((uchar)c & utfmask[*i]) == utfbyte[*i])
    321 			return (uchar)c & ~utfmask[*i];
    322 
    323 	return 0;
    324 }
    325 
    326 size_t
    327 utf8encode(Rune u, char *c)
    328 {
    329 	size_t len, i;
    330 
    331 	len = utf8validate(&u, 0);
    332 	if (len > UTF_SIZ)
    333 		return 0;
    334 
    335 	for (i = len - 1; i != 0; --i) {
    336 		c[i] = utf8encodebyte(u, 0);
    337 		u >>= 6;
    338 	}
    339 	c[0] = utf8encodebyte(u, len);
    340 
    341 	return len;
    342 }
    343 
    344 char
    345 utf8encodebyte(Rune u, size_t i)
    346 {
    347 	return utfbyte[i] | (u & ~utfmask[i]);
    348 }
    349 
    350 size_t
    351 utf8validate(Rune *u, size_t i)
    352 {
    353 	if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
    354 		*u = UTF_INVALID;
    355 	for (i = 1; *u > utfmax[i]; ++i)
    356 		;
    357 
    358 	return i;
    359 }
    360 
    361 char
    362 base64dec_getc(const char **src)
    363 {
    364 	while (**src && !isprint((unsigned char)**src))
    365 		(*src)++;
    366 	return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
    367 }
    368 
    369 char *
    370 base64dec(const char *src)
    371 {
    372 	size_t in_len = strlen(src);
    373 	char *result, *dst;
    374 	static const char base64_digits[256] = {
    375 		[43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
    376 		0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
    377 		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
    378 		0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    379 		40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    380 	};
    381 
    382 	if (in_len % 4)
    383 		in_len += 4 - (in_len % 4);
    384 	result = dst = xmalloc(in_len / 4 * 3 + 1);
    385 	while (*src) {
    386 		int a = base64_digits[(unsigned char) base64dec_getc(&src)];
    387 		int b = base64_digits[(unsigned char) base64dec_getc(&src)];
    388 		int c = base64_digits[(unsigned char) base64dec_getc(&src)];
    389 		int d = base64_digits[(unsigned char) base64dec_getc(&src)];
    390 
    391 		/* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
    392 		if (a == -1 || b == -1)
    393 			break;
    394 
    395 		*dst++ = (a << 2) | ((b & 0x30) >> 4);
    396 		if (c == -1)
    397 			break;
    398 		*dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
    399 		if (d == -1)
    400 			break;
    401 		*dst++ = ((c & 0x03) << 6) | d;
    402 	}
    403 	*dst = '\0';
    404 	return result;
    405 }
    406 
    407 void
    408 selinit(void)
    409 {
    410 	sel.mode = SEL_IDLE;
    411 	sel.snap = 0;
    412 	sel.ob.x = -1;
    413 }
    414 
    415 int
    416 tlinelen(int y)
    417 {
    418 	int i = term.col;
    419 
    420 	if (TLINE(y)[i - 1].mode & ATTR_WRAP)
    421 		return i;
    422 
    423 	while (i > 0 && TLINE(y)[i - 1].u == ' ')
    424 		--i;
    425 
    426 	return i;
    427 }
    428 
    429 void
    430 selstart(int col, int row, int snap)
    431 {
    432 	selclear();
    433 	sel.mode = SEL_EMPTY;
    434 	sel.type = SEL_REGULAR;
    435 	sel.alt = IS_SET(MODE_ALTSCREEN);
    436 	sel.snap = snap;
    437 	sel.oe.x = sel.ob.x = col;
    438 	sel.oe.y = sel.ob.y = row;
    439 	selnormalize();
    440 
    441 	if (sel.snap != 0)
    442 		sel.mode = SEL_READY;
    443 	tsetdirt(sel.nb.y, sel.ne.y);
    444 }
    445 
    446 void
    447 selextend(int col, int row, int type, int done)
    448 {
    449 	int oldey, oldex, oldsby, oldsey, oldtype;
    450 
    451 	if (sel.mode == SEL_IDLE)
    452 		return;
    453 	if (done && sel.mode == SEL_EMPTY) {
    454 		selclear();
    455 		return;
    456 	}
    457 
    458 	oldey = sel.oe.y;
    459 	oldex = sel.oe.x;
    460 	oldsby = sel.nb.y;
    461 	oldsey = sel.ne.y;
    462 	oldtype = sel.type;
    463 
    464 	sel.oe.x = col;
    465 	sel.oe.y = row;
    466 	selnormalize();
    467 	sel.type = type;
    468 
    469 	if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
    470 		tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
    471 
    472 	sel.mode = done ? SEL_IDLE : SEL_READY;
    473 }
    474 
    475 void
    476 selnormalize(void)
    477 {
    478 	int i;
    479 
    480 	if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
    481 		sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
    482 		sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
    483 	} else {
    484 		sel.nb.x = MIN(sel.ob.x, sel.oe.x);
    485 		sel.ne.x = MAX(sel.ob.x, sel.oe.x);
    486 	}
    487 	sel.nb.y = MIN(sel.ob.y, sel.oe.y);
    488 	sel.ne.y = MAX(sel.ob.y, sel.oe.y);
    489 
    490 	selsnap(&sel.nb.x, &sel.nb.y, -1);
    491 	selsnap(&sel.ne.x, &sel.ne.y, +1);
    492 
    493 	/* expand selection over line breaks */
    494 	if (sel.type == SEL_RECTANGULAR)
    495 		return;
    496 	i = tlinelen(sel.nb.y);
    497 	if (i < sel.nb.x)
    498 		sel.nb.x = i;
    499 	if (tlinelen(sel.ne.y) <= sel.ne.x)
    500 		sel.ne.x = term.col - 1;
    501 }
    502 
    503 int
    504 selected(int x, int y)
    505 {
    506 	if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
    507 			sel.alt != IS_SET(MODE_ALTSCREEN))
    508 		return 0;
    509 
    510 	if (sel.type == SEL_RECTANGULAR)
    511 		return BETWEEN(y, sel.nb.y, sel.ne.y)
    512 		    && BETWEEN(x, sel.nb.x, sel.ne.x);
    513 
    514 	return BETWEEN(y, sel.nb.y, sel.ne.y)
    515 	    && (y != sel.nb.y || x >= sel.nb.x)
    516 	    && (y != sel.ne.y || x <= sel.ne.x);
    517 }
    518 
    519 void
    520 selsnap(int *x, int *y, int direction)
    521 {
    522 	int newx, newy, xt, yt;
    523 	int delim, prevdelim;
    524 	const Glyph *gp, *prevgp;
    525 
    526 	switch (sel.snap) {
    527 	case SNAP_WORD:
    528 		/*
    529 		 * Snap around if the word wraps around at the end or
    530 		 * beginning of a line.
    531 		 */
    532 		prevgp = &TLINE(*y)[*x];
    533 		prevdelim = ISDELIM(prevgp->u);
    534 		for (;;) {
    535 			newx = *x + direction;
    536 			newy = *y;
    537 			if (!BETWEEN(newx, 0, term.col - 1)) {
    538 				newy += direction;
    539 				newx = (newx + term.col) % term.col;
    540 				if (!BETWEEN(newy, 0, term.row - 1))
    541 					break;
    542 
    543 				if (direction > 0)
    544 					yt = *y, xt = *x;
    545 				else
    546 					yt = newy, xt = newx;
    547 				if (!(TLINE(yt)[xt].mode & ATTR_WRAP))
    548 					break;
    549 			}
    550 
    551 			if (newx >= tlinelen(newy))
    552 				break;
    553 
    554 			gp = &TLINE(newy)[newx];
    555 			delim = ISDELIM(gp->u);
    556 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
    557 					|| (delim && gp->u != prevgp->u)))
    558 				break;
    559 
    560 			*x = newx;
    561 			*y = newy;
    562 			prevgp = gp;
    563 			prevdelim = delim;
    564 		}
    565 		break;
    566 	case SNAP_LINE:
    567 		/*
    568 		 * Snap around if the the previous line or the current one
    569 		 * has set ATTR_WRAP at its end. Then the whole next or
    570 		 * previous line will be selected.
    571 		 */
    572 		*x = (direction < 0) ? 0 : term.col - 1;
    573 		if (direction < 0) {
    574 			for (; *y > 0; *y += direction) {
    575 				if (!(TLINE(*y-1)[term.col-1].mode
    576 						& ATTR_WRAP)) {
    577 					break;
    578 				}
    579 			}
    580 		} else if (direction > 0) {
    581 			for (; *y < term.row-1; *y += direction) {
    582 				if (!(TLINE(*y)[term.col-1].mode
    583 						& ATTR_WRAP)) {
    584 					break;
    585 				}
    586 			}
    587 		}
    588 		break;
    589 	}
    590 }
    591 
    592 char *
    593 getsel(void)
    594 {
    595 	char *str, *ptr;
    596 	int y, bufsize, lastx, linelen;
    597 	const Glyph *gp, *last;
    598 
    599 	if (sel.ob.x == -1)
    600 		return NULL;
    601 
    602 	bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
    603 	ptr = str = xmalloc(bufsize);
    604 
    605 	/* append every set & selected glyph to the selection */
    606 	for (y = sel.nb.y; y <= sel.ne.y; y++) {
    607 		if ((linelen = tlinelen(y)) == 0) {
    608 			*ptr++ = '\n';
    609 			continue;
    610 		}
    611 
    612 		if (sel.type == SEL_RECTANGULAR) {
    613 			gp = &TLINE(y)[sel.nb.x];
    614 			lastx = sel.ne.x;
    615 		} else {
    616 			gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
    617 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
    618 		}
    619 		last = &TLINE(y)[MIN(lastx, linelen-1)];
    620 		while (last >= gp && last->u == ' ')
    621 			--last;
    622 
    623 		for ( ; gp <= last; ++gp) {
    624 			if (gp->mode & ATTR_WDUMMY)
    625 				continue;
    626 
    627 			ptr += utf8encode(gp->u, ptr);
    628 		}
    629 
    630 		/*
    631 		 * Copy and pasting of line endings is inconsistent
    632 		 * in the inconsistent terminal and GUI world.
    633 		 * The best solution seems like to produce '\n' when
    634 		 * something is copied from st and convert '\n' to
    635 		 * '\r', when something to be pasted is received by
    636 		 * st.
    637 		 * FIXME: Fix the computer world.
    638 		 */
    639 		if ((y < sel.ne.y || lastx >= linelen) &&
    640 		    (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
    641 			*ptr++ = '\n';
    642 	}
    643 	*ptr = 0;
    644 	return str;
    645 }
    646 
    647 void
    648 selclear(void)
    649 {
    650 	if (sel.ob.x == -1)
    651 		return;
    652 	sel.mode = SEL_IDLE;
    653 	sel.ob.x = -1;
    654 	tsetdirt(sel.nb.y, sel.ne.y);
    655 }
    656 
    657 void
    658 die(const char *errstr, ...)
    659 {
    660 	va_list ap;
    661 
    662 	va_start(ap, errstr);
    663 	vfprintf(stderr, errstr, ap);
    664 	va_end(ap);
    665 	exit(1);
    666 }
    667 
    668 void
    669 execsh(char *cmd, char **args)
    670 {
    671 	char *sh, *prog, *arg;
    672 	const struct passwd *pw;
    673 
    674 	errno = 0;
    675 	if ((pw = getpwuid(getuid())) == NULL) {
    676 		if (errno)
    677 			die("getpwuid: %s\n", strerror(errno));
    678 		else
    679 			die("who are you?\n");
    680 	}
    681 
    682 	if ((sh = getenv("SHELL")) == NULL)
    683 		sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
    684 
    685 	if (args) {
    686 		prog = args[0];
    687 		arg = NULL;
    688 	} else if (scroll) {
    689 		prog = scroll;
    690 		arg = utmp ? utmp : sh;
    691 	} else if (utmp) {
    692 		prog = utmp;
    693 		arg = NULL;
    694 	} else {
    695 		prog = sh;
    696 		arg = NULL;
    697 	}
    698 	DEFAULT(args, ((char *[]) {prog, arg, NULL}));
    699 
    700 	unsetenv("COLUMNS");
    701 	unsetenv("LINES");
    702 	unsetenv("TERMCAP");
    703 	setenv("LOGNAME", pw->pw_name, 1);
    704 	setenv("USER", pw->pw_name, 1);
    705 	setenv("SHELL", sh, 1);
    706 	setenv("HOME", pw->pw_dir, 1);
    707 	setenv("TERM", termname, 1);
    708 
    709 	signal(SIGCHLD, SIG_DFL);
    710 	signal(SIGHUP, SIG_DFL);
    711 	signal(SIGINT, SIG_DFL);
    712 	signal(SIGQUIT, SIG_DFL);
    713 	signal(SIGTERM, SIG_DFL);
    714 	signal(SIGALRM, SIG_DFL);
    715 
    716 	execvp(prog, args);
    717 	_exit(1);
    718 }
    719 
    720 void
    721 sigchld(int a)
    722 {
    723 	int stat;
    724 	pid_t p;
    725 
    726 	if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
    727 		die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
    728 
    729 	if (pid != p)
    730 		return;
    731 
    732 	if (WIFEXITED(stat) && WEXITSTATUS(stat))
    733 		die("child exited with status %d\n", WEXITSTATUS(stat));
    734 	else if (WIFSIGNALED(stat))
    735 		die("child terminated due to signal %d\n", WTERMSIG(stat));
    736 	_exit(0);
    737 }
    738 
    739 void
    740 stty(char **args)
    741 {
    742 	char cmd[_POSIX_ARG_MAX], **p, *q, *s;
    743 	size_t n, siz;
    744 
    745 	if ((n = strlen(stty_args)) > sizeof(cmd)-1)
    746 		die("incorrect stty parameters\n");
    747 	memcpy(cmd, stty_args, n);
    748 	q = cmd + n;
    749 	siz = sizeof(cmd) - n;
    750 	for (p = args; p && (s = *p); ++p) {
    751 		if ((n = strlen(s)) > siz-1)
    752 			die("stty parameter length too long\n");
    753 		*q++ = ' ';
    754 		memcpy(q, s, n);
    755 		q += n;
    756 		siz -= n + 1;
    757 	}
    758 	*q = '\0';
    759 	if (system(cmd) != 0)
    760 		perror("Couldn't call stty");
    761 }
    762 
    763 int
    764 ttynew(const char *line, char *cmd, const char *out, char **args)
    765 {
    766 	int m, s;
    767 
    768 	if (out) {
    769 		term.mode |= MODE_PRINT;
    770 		iofd = (!strcmp(out, "-")) ?
    771 			  1 : open(out, O_WRONLY | O_CREAT, 0666);
    772 		if (iofd < 0) {
    773 			fprintf(stderr, "Error opening %s:%s\n",
    774 				out, strerror(errno));
    775 		}
    776 	}
    777 
    778 	if (line) {
    779 		if ((cmdfd = open(line, O_RDWR)) < 0)
    780 			die("open line '%s' failed: %s\n",
    781 			    line, strerror(errno));
    782 		dup2(cmdfd, 0);
    783 		stty(args);
    784 		return cmdfd;
    785 	}
    786 
    787 	/* seems to work fine on linux, openbsd and freebsd */
    788 	if (openpty(&m, &s, NULL, NULL, NULL) < 0)
    789 		die("openpty failed: %s\n", strerror(errno));
    790 
    791 	switch (pid = fork()) {
    792 	case -1:
    793 		die("fork failed: %s\n", strerror(errno));
    794 		break;
    795 	case 0:
    796 		close(iofd);
    797 		close(m);
    798 		setsid(); /* create a new process group */
    799 		dup2(s, 0);
    800 		dup2(s, 1);
    801 		dup2(s, 2);
    802 		if (ioctl(s, TIOCSCTTY, NULL) < 0)
    803 			die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
    804 		if (s > 2)
    805 			close(s);
    806 #ifdef __OpenBSD__
    807 		if (pledge("stdio getpw proc exec", NULL) == -1)
    808 			die("pledge\n");
    809 #endif
    810 		execsh(cmd, args);
    811 		break;
    812 	default:
    813 #ifdef __OpenBSD__
    814 		if (pledge("stdio rpath tty proc", NULL) == -1)
    815 			die("pledge\n");
    816 #endif
    817 		close(s);
    818 		cmdfd = m;
    819 		signal(SIGCHLD, sigchld);
    820 		break;
    821 	}
    822 	return cmdfd;
    823 }
    824 
    825 size_t
    826 ttyread(void)
    827 {
    828 	static char buf[BUFSIZ];
    829 	static int buflen = 0;
    830 	int ret, written;
    831 
    832 	/* append read bytes to unprocessed bytes */
    833 	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
    834 
    835 	switch (ret) {
    836 	case 0:
    837 		exit(0);
    838 	case -1:
    839 		die("couldn't read from shell: %s\n", strerror(errno));
    840 	default:
    841 		buflen += ret;
    842 		written = twrite(buf, buflen, 0);
    843 		buflen -= written;
    844 		/* keep any incomplete UTF-8 byte sequence for the next call */
    845 		if (buflen > 0)
    846 			memmove(buf, buf + written, buflen);
    847 		return ret;
    848 	}
    849 }
    850 
    851 void
    852 ttywrite(const char *s, size_t n, int may_echo)
    853 {
    854 	const char *next;
    855 	Arg arg = (Arg) { .i = term.scr };
    856 
    857 	kscrolldown(&arg);
    858 
    859 	if (may_echo && IS_SET(MODE_ECHO))
    860 		twrite(s, n, 1);
    861 
    862 	if (!IS_SET(MODE_CRLF)) {
    863 		ttywriteraw(s, n);
    864 		return;
    865 	}
    866 
    867 	/* This is similar to how the kernel handles ONLCR for ttys */
    868 	while (n > 0) {
    869 		if (*s == '\r') {
    870 			next = s + 1;
    871 			ttywriteraw("\r\n", 2);
    872 		} else {
    873 			next = memchr(s, '\r', n);
    874 			DEFAULT(next, s + n);
    875 			ttywriteraw(s, next - s);
    876 		}
    877 		n -= next - s;
    878 		s = next;
    879 	}
    880 }
    881 
    882 void
    883 ttywriteraw(const char *s, size_t n)
    884 {
    885 	fd_set wfd, rfd;
    886 	ssize_t r;
    887 	size_t lim = 256;
    888 
    889 	/*
    890 	 * Remember that we are using a pty, which might be a modem line.
    891 	 * Writing too much will clog the line. That's why we are doing this
    892 	 * dance.
    893 	 * FIXME: Migrate the world to Plan 9.
    894 	 */
    895 	while (n > 0) {
    896 		FD_ZERO(&wfd);
    897 		FD_ZERO(&rfd);
    898 		FD_SET(cmdfd, &wfd);
    899 		FD_SET(cmdfd, &rfd);
    900 
    901 		/* Check if we can write. */
    902 		if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
    903 			if (errno == EINTR)
    904 				continue;
    905 			die("select failed: %s\n", strerror(errno));
    906 		}
    907 		if (FD_ISSET(cmdfd, &wfd)) {
    908 			/*
    909 			 * Only write the bytes written by ttywrite() or the
    910 			 * default of 256. This seems to be a reasonable value
    911 			 * for a serial line. Bigger values might clog the I/O.
    912 			 */
    913 			if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
    914 				goto write_error;
    915 			if (r < n) {
    916 				/*
    917 				 * We weren't able to write out everything.
    918 				 * This means the buffer is getting full
    919 				 * again. Empty it.
    920 				 */
    921 				if (n < lim)
    922 					lim = ttyread();
    923 				n -= r;
    924 				s += r;
    925 			} else {
    926 				/* All bytes have been written. */
    927 				break;
    928 			}
    929 		}
    930 		if (FD_ISSET(cmdfd, &rfd))
    931 			lim = ttyread();
    932 	}
    933 	return;
    934 
    935 write_error:
    936 	die("write error on tty: %s\n", strerror(errno));
    937 }
    938 
    939 void
    940 ttyresize(int tw, int th)
    941 {
    942 	struct winsize w;
    943 
    944 	w.ws_row = term.row;
    945 	w.ws_col = term.col;
    946 	w.ws_xpixel = tw;
    947 	w.ws_ypixel = th;
    948 	if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
    949 		fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
    950 }
    951 
    952 void
    953 ttyhangup(void)
    954 {
    955 	/* Send SIGHUP to shell */
    956 	kill(pid, SIGHUP);
    957 }
    958 
    959 int
    960 tattrset(int attr)
    961 {
    962 	int i, j;
    963 
    964 	for (i = 0; i < term.row-1; i++) {
    965 		for (j = 0; j < term.col-1; j++) {
    966 			if (term.line[i][j].mode & attr)
    967 				return 1;
    968 		}
    969 	}
    970 
    971 	return 0;
    972 }
    973 
    974 void
    975 tsetdirt(int top, int bot)
    976 {
    977 	int i;
    978 
    979 	LIMIT(top, 0, term.row-1);
    980 	LIMIT(bot, 0, term.row-1);
    981 
    982 	for (i = top; i <= bot; i++)
    983 		term.dirty[i] = 1;
    984 }
    985 
    986 void
    987 tsetdirtattr(int attr)
    988 {
    989 	int i, j;
    990 
    991 	for (i = 0; i < term.row-1; i++) {
    992 		for (j = 0; j < term.col-1; j++) {
    993 			if (term.line[i][j].mode & attr) {
    994 				tsetdirt(i, i);
    995 				break;
    996 			}
    997 		}
    998 	}
    999 }
   1000 
   1001 void
   1002 tfulldirt(void)
   1003 {
   1004 	tsetdirt(0, term.row-1);
   1005 }
   1006 
   1007 void
   1008 tcursor(int mode)
   1009 {
   1010 	static TCursor c[2];
   1011 	int alt = IS_SET(MODE_ALTSCREEN);
   1012 
   1013 	if (mode == CURSOR_SAVE) {
   1014 		c[alt] = term.c;
   1015 	} else if (mode == CURSOR_LOAD) {
   1016 		term.c = c[alt];
   1017 		tmoveto(c[alt].x, c[alt].y);
   1018 	}
   1019 }
   1020 
   1021 void
   1022 treset(void)
   1023 {
   1024 	uint i;
   1025 
   1026 	term.c = (TCursor){{
   1027 		.mode = ATTR_NULL,
   1028 		.fg = defaultfg,
   1029 		.bg = defaultbg
   1030 	}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
   1031 
   1032 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1033 	for (i = tabspaces; i < term.col; i += tabspaces)
   1034 		term.tabs[i] = 1;
   1035 	term.top = 0;
   1036 	term.bot = term.row - 1;
   1037 	term.mode = MODE_WRAP|MODE_UTF8;
   1038 	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
   1039 	term.charset = 0;
   1040 
   1041 	for (i = 0; i < 2; i++) {
   1042 		tmoveto(0, 0);
   1043 		tcursor(CURSOR_SAVE);
   1044 		tclearregion(0, 0, term.col-1, term.row-1);
   1045 		tswapscreen();
   1046 	}
   1047 }
   1048 
   1049 void
   1050 tnew(int col, int row)
   1051 {
   1052 	term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
   1053 	tresize(col, row);
   1054 	treset();
   1055 }
   1056 
   1057 int tisaltscr(void)
   1058 {
   1059 	return IS_SET(MODE_ALTSCREEN);
   1060 }
   1061 
   1062 void
   1063 tswapscreen(void)
   1064 {
   1065 	Line *tmp = term.line;
   1066 
   1067 	term.line = term.alt;
   1068 	term.alt = tmp;
   1069 	term.mode ^= MODE_ALTSCREEN;
   1070 	tfulldirt();
   1071 }
   1072 
   1073 void
   1074 kscrolldown(const Arg* a)
   1075 {
   1076 	int n = a->i;
   1077 
   1078 	if (n < 0)
   1079 		n = term.row + n;
   1080 
   1081 	if (n > term.scr)
   1082 		n = term.scr;
   1083 
   1084 	if (term.scr > 0) {
   1085 		term.scr -= n;
   1086 		selscroll(0, -n);
   1087 		tfulldirt();
   1088 	}
   1089 }
   1090 
   1091 void
   1092 kscrollup(const Arg* a)
   1093 {
   1094 	int n = a->i;
   1095 
   1096 	if (n < 0)
   1097 		n = term.row + n;
   1098 
   1099 	if (term.scr <= HISTSIZE-n) {
   1100 		term.scr += n;
   1101 		selscroll(0, n);
   1102 		tfulldirt();
   1103 	}
   1104 }
   1105 
   1106 void
   1107 tscrolldown(int orig, int n, int copyhist)
   1108 {
   1109 	int i;
   1110 	Line temp;
   1111 
   1112 	LIMIT(n, 0, term.bot-orig+1);
   1113 
   1114 	if (copyhist) {
   1115 		term.histi = (term.histi - 1 + HISTSIZE) % HISTSIZE;
   1116 		temp = term.hist[term.histi];
   1117 		term.hist[term.histi] = term.line[term.bot];
   1118 		term.line[term.bot] = temp;
   1119 	}
   1120 
   1121 	tsetdirt(orig, term.bot-n);
   1122 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
   1123 
   1124 	for (i = term.bot; i >= orig+n; i--) {
   1125 		temp = term.line[i];
   1126 		term.line[i] = term.line[i-n];
   1127 		term.line[i-n] = temp;
   1128 	}
   1129 
   1130 	if (term.scr == 0)
   1131 		selscroll(orig, n);
   1132 }
   1133 
   1134 void
   1135 tscrollup(int orig, int n, int copyhist)
   1136 {
   1137 	int i;
   1138 	Line temp;
   1139 
   1140 	LIMIT(n, 0, term.bot-orig+1);
   1141 
   1142 	if (copyhist) {
   1143 		term.histi = (term.histi + 1) % HISTSIZE;
   1144 		temp = term.hist[term.histi];
   1145 		term.hist[term.histi] = term.line[orig];
   1146 		term.line[orig] = temp;
   1147 	}
   1148 
   1149 	if (term.scr > 0 && term.scr < HISTSIZE)
   1150 		term.scr = MIN(term.scr + n, HISTSIZE-1);
   1151 
   1152 	tclearregion(0, orig, term.col-1, orig+n-1);
   1153 	tsetdirt(orig+n, term.bot);
   1154 
   1155 	for (i = orig; i <= term.bot-n; i++) {
   1156 		temp = term.line[i];
   1157 		term.line[i] = term.line[i+n];
   1158 		term.line[i+n] = temp;
   1159 	}
   1160 
   1161 	if (term.scr == 0)
   1162 		selscroll(orig, -n);
   1163 }
   1164 
   1165 void
   1166 selscroll(int orig, int n)
   1167 {
   1168 	if (sel.ob.x == -1 || sel.alt != IS_SET(MODE_ALTSCREEN))
   1169 		return;
   1170 
   1171 	if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
   1172 		selclear();
   1173 	} else if (BETWEEN(sel.nb.y, orig, term.bot)) {
   1174 		sel.ob.y += n;
   1175 		sel.oe.y += n;
   1176 		if (sel.ob.y < term.top || sel.ob.y > term.bot ||
   1177 		    sel.oe.y < term.top || sel.oe.y > term.bot) {
   1178 			selclear();
   1179 		} else {
   1180 			selnormalize();
   1181 		}
   1182 	}
   1183 }
   1184 
   1185 void
   1186 tnewline(int first_col)
   1187 {
   1188 	int y = term.c.y;
   1189 
   1190 	if (y == term.bot) {
   1191 		tscrollup(term.top, 1, 1);
   1192 	} else {
   1193 		y++;
   1194 	}
   1195 	tmoveto(first_col ? 0 : term.c.x, y);
   1196 }
   1197 
   1198 void
   1199 csiparse(void)
   1200 {
   1201 	char *p = csiescseq.buf, *np;
   1202 	long int v;
   1203 	int sep = ';'; /* colon or semi-colon, but not both */
   1204 
   1205 	csiescseq.narg = 0;
   1206 	if (*p == '?') {
   1207 		csiescseq.priv = 1;
   1208 		p++;
   1209 	}
   1210 
   1211 	csiescseq.buf[csiescseq.len] = '\0';
   1212 	while (p < csiescseq.buf+csiescseq.len) {
   1213 		np = NULL;
   1214 		v = strtol(p, &np, 10);
   1215 		if (np == p)
   1216 			v = 0;
   1217 		if (v == LONG_MAX || v == LONG_MIN)
   1218 			v = -1;
   1219 		csiescseq.arg[csiescseq.narg++] = v;
   1220 		p = np;
   1221 		if (sep == ';' && *p == ':')
   1222 			sep = ':'; /* allow override to colon once */
   1223 		if (*p != sep || csiescseq.narg == ESC_ARG_SIZ)
   1224 			break;
   1225 		p++;
   1226 	}
   1227 	csiescseq.mode[0] = *p++;
   1228 	csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
   1229 }
   1230 
   1231 /* for absolute user moves, when decom is set */
   1232 void
   1233 tmoveato(int x, int y)
   1234 {
   1235 	tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
   1236 }
   1237 
   1238 void
   1239 tmoveto(int x, int y)
   1240 {
   1241 	int miny, maxy;
   1242 
   1243 	if (term.c.state & CURSOR_ORIGIN) {
   1244 		miny = term.top;
   1245 		maxy = term.bot;
   1246 	} else {
   1247 		miny = 0;
   1248 		maxy = term.row - 1;
   1249 	}
   1250 	term.c.state &= ~CURSOR_WRAPNEXT;
   1251 	term.c.x = LIMIT(x, 0, term.col-1);
   1252 	term.c.y = LIMIT(y, miny, maxy);
   1253 }
   1254 
   1255 void
   1256 tsetchar(Rune u, const Glyph *attr, int x, int y)
   1257 {
   1258 	static const char *vt100_0[62] = { /* 0x41 - 0x7e */
   1259 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
   1260 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
   1261 		0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
   1262 		0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
   1263 		"◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
   1264 		"␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
   1265 		"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
   1266 		"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
   1267 	};
   1268 
   1269 	/*
   1270 	 * The table is proudly stolen from rxvt.
   1271 	 */
   1272 	if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
   1273 	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
   1274 		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
   1275 
   1276 	if (term.line[y][x].mode & ATTR_WIDE) {
   1277 		if (x+1 < term.col) {
   1278 			term.line[y][x+1].u = ' ';
   1279 			term.line[y][x+1].mode &= ~ATTR_WDUMMY;
   1280 		}
   1281 	} else if (term.line[y][x].mode & ATTR_WDUMMY) {
   1282 		term.line[y][x-1].u = ' ';
   1283 		term.line[y][x-1].mode &= ~ATTR_WIDE;
   1284 	}
   1285 
   1286 	term.dirty[y] = 1;
   1287 	term.line[y][x] = *attr;
   1288 	term.line[y][x].u = u;
   1289 }
   1290 
   1291 void
   1292 tclearregion(int x1, int y1, int x2, int y2)
   1293 {
   1294 	int x, y, temp;
   1295 	Glyph *gp;
   1296 
   1297 	if (x1 > x2)
   1298 		temp = x1, x1 = x2, x2 = temp;
   1299 	if (y1 > y2)
   1300 		temp = y1, y1 = y2, y2 = temp;
   1301 
   1302 	LIMIT(x1, 0, term.col-1);
   1303 	LIMIT(x2, 0, term.col-1);
   1304 	LIMIT(y1, 0, term.row-1);
   1305 	LIMIT(y2, 0, term.row-1);
   1306 
   1307 	for (y = y1; y <= y2; y++) {
   1308 		term.dirty[y] = 1;
   1309 		for (x = x1; x <= x2; x++) {
   1310 			gp = &term.line[y][x];
   1311 			if (selected(x, y))
   1312 				selclear();
   1313 			gp->fg = term.c.attr.fg;
   1314 			gp->bg = term.c.attr.bg;
   1315 			gp->mode = 0;
   1316 			gp->u = ' ';
   1317 		}
   1318 	}
   1319 }
   1320 
   1321 void
   1322 tdeletechar(int n)
   1323 {
   1324 	int dst, src, size;
   1325 	Glyph *line;
   1326 
   1327 	LIMIT(n, 0, term.col - term.c.x);
   1328 
   1329 	dst = term.c.x;
   1330 	src = term.c.x + n;
   1331 	size = term.col - src;
   1332 	line = term.line[term.c.y];
   1333 
   1334 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1335 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1336 }
   1337 
   1338 void
   1339 tinsertblank(int n)
   1340 {
   1341 	int dst, src, size;
   1342 	Glyph *line;
   1343 
   1344 	LIMIT(n, 0, term.col - term.c.x);
   1345 
   1346 	dst = term.c.x + n;
   1347 	src = term.c.x;
   1348 	size = term.col - dst;
   1349 	line = term.line[term.c.y];
   1350 
   1351 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1352 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1353 }
   1354 
   1355 void
   1356 tinsertblankline(int n)
   1357 {
   1358 	if (BETWEEN(term.c.y, term.top, term.bot))
   1359 		tscrolldown(term.c.y, n, 0);
   1360 }
   1361 
   1362 void
   1363 tdeleteline(int n)
   1364 {
   1365 	if (BETWEEN(term.c.y, term.top, term.bot))
   1366 		tscrollup(term.c.y, n, 0);
   1367 }
   1368 
   1369 int32_t
   1370 tdefcolor(const int *attr, int *npar, int l)
   1371 {
   1372 	int32_t idx = -1;
   1373 	uint r, g, b;
   1374 
   1375 	switch (attr[*npar + 1]) {
   1376 	case 2: /* direct color in RGB space */
   1377 		if (*npar + 4 >= l) {
   1378 			fprintf(stderr,
   1379 				"erresc(38): Incorrect number of parameters (%d)\n",
   1380 				*npar);
   1381 			break;
   1382 		}
   1383 		r = attr[*npar + 2];
   1384 		g = attr[*npar + 3];
   1385 		b = attr[*npar + 4];
   1386 		*npar += 4;
   1387 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1388 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1389 				r, g, b);
   1390 		else
   1391 			idx = TRUECOLOR(r, g, b);
   1392 		break;
   1393 	case 5: /* indexed color */
   1394 		if (*npar + 2 >= l) {
   1395 			fprintf(stderr,
   1396 				"erresc(38): Incorrect number of parameters (%d)\n",
   1397 				*npar);
   1398 			break;
   1399 		}
   1400 		*npar += 2;
   1401 		if (!BETWEEN(attr[*npar], 0, 255))
   1402 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1403 		else
   1404 			idx = attr[*npar];
   1405 		break;
   1406 	case 0: /* implemented defined (only foreground) */
   1407 	case 1: /* transparent */
   1408 	case 3: /* direct color in CMY space */
   1409 	case 4: /* direct color in CMYK space */
   1410 	default:
   1411 		fprintf(stderr,
   1412 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1413 		break;
   1414 	}
   1415 
   1416 	return idx;
   1417 }
   1418 
   1419 void
   1420 tsetattr(const int *attr, int l)
   1421 {
   1422 	int i;
   1423 	int32_t idx;
   1424 
   1425 	for (i = 0; i < l; i++) {
   1426 		switch (attr[i]) {
   1427 		case 0:
   1428 			term.c.attr.mode &= ~(
   1429 				ATTR_BOLD       |
   1430 				ATTR_FAINT      |
   1431 				ATTR_ITALIC     |
   1432 				ATTR_UNDERLINE  |
   1433 				ATTR_BLINK      |
   1434 				ATTR_REVERSE    |
   1435 				ATTR_INVISIBLE  |
   1436 				ATTR_STRUCK     );
   1437 			term.c.attr.fg = defaultfg;
   1438 			term.c.attr.bg = defaultbg;
   1439 			break;
   1440 		case 1:
   1441 			term.c.attr.mode |= ATTR_BOLD;
   1442 			break;
   1443 		case 2:
   1444 			term.c.attr.mode |= ATTR_FAINT;
   1445 			break;
   1446 		case 3:
   1447 			term.c.attr.mode |= ATTR_ITALIC;
   1448 			break;
   1449 		case 4:
   1450 			term.c.attr.mode |= ATTR_UNDERLINE;
   1451 			break;
   1452 		case 5: /* slow blink */
   1453 			/* FALLTHROUGH */
   1454 		case 6: /* rapid blink */
   1455 			term.c.attr.mode |= ATTR_BLINK;
   1456 			break;
   1457 		case 7:
   1458 			term.c.attr.mode |= ATTR_REVERSE;
   1459 			break;
   1460 		case 8:
   1461 			term.c.attr.mode |= ATTR_INVISIBLE;
   1462 			break;
   1463 		case 9:
   1464 			term.c.attr.mode |= ATTR_STRUCK;
   1465 			break;
   1466 		case 22:
   1467 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1468 			break;
   1469 		case 23:
   1470 			term.c.attr.mode &= ~ATTR_ITALIC;
   1471 			break;
   1472 		case 24:
   1473 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1474 			break;
   1475 		case 25:
   1476 			term.c.attr.mode &= ~ATTR_BLINK;
   1477 			break;
   1478 		case 27:
   1479 			term.c.attr.mode &= ~ATTR_REVERSE;
   1480 			break;
   1481 		case 28:
   1482 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1483 			break;
   1484 		case 29:
   1485 			term.c.attr.mode &= ~ATTR_STRUCK;
   1486 			break;
   1487 		case 38:
   1488 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1489 				term.c.attr.fg = idx;
   1490 			break;
   1491 		case 39:
   1492 			term.c.attr.fg = defaultfg;
   1493 			break;
   1494 		case 48:
   1495 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1496 				term.c.attr.bg = idx;
   1497 			break;
   1498 		case 49:
   1499 			term.c.attr.bg = defaultbg;
   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:
   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 */
   1606 			case 1047:
   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:
   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:
   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:
   1990 		case 11:
   1991 		case 12:
   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 		}
   2033 		break;
   2034 	case 'k': /* old title set compatibility */
   2035 		xsettitle(strescseq.args[0]);
   2036 		return;
   2037 	case 'P': /* DCS -- Device Control String */
   2038 	case '_': /* APC -- Application Program Command */
   2039 	case '^': /* PM -- Privacy Message */
   2040 		return;
   2041 	}
   2042 
   2043 	fprintf(stderr, "erresc: unknown str ");
   2044 	strdump();
   2045 }
   2046 
   2047 void
   2048 strparse(void)
   2049 {
   2050 	int c;
   2051 	char *p = strescseq.buf;
   2052 
   2053 	strescseq.narg = 0;
   2054 	strescseq.buf[strescseq.len] = '\0';
   2055 
   2056 	if (*p == '\0')
   2057 		return;
   2058 
   2059 	while (strescseq.narg < STR_ARG_SIZ) {
   2060 		strescseq.args[strescseq.narg++] = p;
   2061 		while ((c = *p) != ';' && c != '\0')
   2062 			++p;
   2063 		if (c == '\0')
   2064 			return;
   2065 		*p++ = '\0';
   2066 	}
   2067 }
   2068 
   2069 void
   2070 strdump(void)
   2071 {
   2072 	size_t i;
   2073 	uint c;
   2074 
   2075 	fprintf(stderr, "ESC%c", strescseq.type);
   2076 	for (i = 0; i < strescseq.len; i++) {
   2077 		c = strescseq.buf[i] & 0xff;
   2078 		if (c == '\0') {
   2079 			putc('\n', stderr);
   2080 			return;
   2081 		} else if (isprint(c)) {
   2082 			putc(c, stderr);
   2083 		} else if (c == '\n') {
   2084 			fprintf(stderr, "(\\n)");
   2085 		} else if (c == '\r') {
   2086 			fprintf(stderr, "(\\r)");
   2087 		} else if (c == 0x1b) {
   2088 			fprintf(stderr, "(\\e)");
   2089 		} else {
   2090 			fprintf(stderr, "(%02x)", c);
   2091 		}
   2092 	}
   2093 	fprintf(stderr, "ESC\\\n");
   2094 }
   2095 
   2096 void
   2097 strreset(void)
   2098 {
   2099 	strescseq = (STREscape){
   2100 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2101 		.siz = STR_BUF_SIZ,
   2102 	};
   2103 }
   2104 
   2105 void
   2106 sendbreak(const Arg *arg)
   2107 {
   2108 	if (tcsendbreak(cmdfd, 0))
   2109 		perror("Error sending break");
   2110 }
   2111 
   2112 void
   2113 tprinter(char *s, size_t len)
   2114 {
   2115 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2116 		perror("Error writing to output file");
   2117 		close(iofd);
   2118 		iofd = -1;
   2119 	}
   2120 }
   2121 
   2122 void
   2123 toggleprinter(const Arg *arg)
   2124 {
   2125 	term.mode ^= MODE_PRINT;
   2126 }
   2127 
   2128 void
   2129 printscreen(const Arg *arg)
   2130 {
   2131 	tdump();
   2132 }
   2133 
   2134 void
   2135 printsel(const Arg *arg)
   2136 {
   2137 	tdumpsel();
   2138 }
   2139 
   2140 void
   2141 tdumpsel(void)
   2142 {
   2143 	char *ptr;
   2144 
   2145 	if ((ptr = getsel())) {
   2146 		tprinter(ptr, strlen(ptr));
   2147 		free(ptr);
   2148 	}
   2149 }
   2150 
   2151 void
   2152 tdumpline(int n)
   2153 {
   2154 	char buf[UTF_SIZ];
   2155 	const Glyph *bp, *end;
   2156 
   2157 	bp = &term.line[n][0];
   2158 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2159 	if (bp != end || bp->u != ' ') {
   2160 		for ( ; bp <= end; ++bp)
   2161 			tprinter(buf, utf8encode(bp->u, buf));
   2162 	}
   2163 	tprinter("\n", 1);
   2164 }
   2165 
   2166 void
   2167 tdump(void)
   2168 {
   2169 	int i;
   2170 
   2171 	for (i = 0; i < term.row; ++i)
   2172 		tdumpline(i);
   2173 }
   2174 
   2175 void
   2176 tputtab(int n)
   2177 {
   2178 	uint x = term.c.x;
   2179 
   2180 	if (n > 0) {
   2181 		while (x < term.col && n--)
   2182 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2183 				/* nothing */ ;
   2184 	} else if (n < 0) {
   2185 		while (x > 0 && n++)
   2186 			for (--x; x > 0 && !term.tabs[x]; --x)
   2187 				/* nothing */ ;
   2188 	}
   2189 	term.c.x = LIMIT(x, 0, term.col-1);
   2190 }
   2191 
   2192 void
   2193 tdefutf8(char ascii)
   2194 {
   2195 	if (ascii == 'G')
   2196 		term.mode |= MODE_UTF8;
   2197 	else if (ascii == '@')
   2198 		term.mode &= ~MODE_UTF8;
   2199 }
   2200 
   2201 void
   2202 tdeftran(char ascii)
   2203 {
   2204 	static char cs[] = "0B";
   2205 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2206 	char *p;
   2207 
   2208 	if ((p = strchr(cs, ascii)) == NULL) {
   2209 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2210 	} else {
   2211 		term.trantbl[term.icharset] = vcs[p - cs];
   2212 	}
   2213 }
   2214 
   2215 void
   2216 tdectest(char c)
   2217 {
   2218 	int x, y;
   2219 
   2220 	if (c == '8') { /* DEC screen alignment test. */
   2221 		for (x = 0; x < term.col; ++x) {
   2222 			for (y = 0; y < term.row; ++y)
   2223 				tsetchar('E', &term.c.attr, x, y);
   2224 		}
   2225 	}
   2226 }
   2227 
   2228 void
   2229 tstrsequence(uchar c)
   2230 {
   2231 	switch (c) {
   2232 	case 0x90:   /* DCS -- Device Control String */
   2233 		c = 'P';
   2234 		break;
   2235 	case 0x9f:   /* APC -- Application Program Command */
   2236 		c = '_';
   2237 		break;
   2238 	case 0x9e:   /* PM -- Privacy Message */
   2239 		c = '^';
   2240 		break;
   2241 	case 0x9d:   /* OSC -- Operating System Command */
   2242 		c = ']';
   2243 		break;
   2244 	}
   2245 	strreset();
   2246 	strescseq.type = c;
   2247 	term.esc |= ESC_STR;
   2248 }
   2249 
   2250 void
   2251 tcontrolcode(uchar ascii)
   2252 {
   2253 	switch (ascii) {
   2254 	case '\t':   /* HT */
   2255 		tputtab(1);
   2256 		return;
   2257 	case '\b':   /* BS */
   2258 		tmoveto(term.c.x-1, term.c.y);
   2259 		return;
   2260 	case '\r':   /* CR */
   2261 		tmoveto(0, term.c.y);
   2262 		return;
   2263 	case '\f':   /* LF */
   2264 	case '\v':   /* VT */
   2265 	case '\n':   /* LF */
   2266 		/* go to first col if the mode is set */
   2267 		tnewline(IS_SET(MODE_CRLF));
   2268 		return;
   2269 	case '\a':   /* BEL */
   2270 		if (term.esc & ESC_STR_END) {
   2271 			/* backwards compatibility to xterm */
   2272 			strhandle();
   2273 		} else {
   2274 			xbell();
   2275 		}
   2276 		break;
   2277 	case '\033': /* ESC */
   2278 		csireset();
   2279 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2280 		term.esc |= ESC_START;
   2281 		return;
   2282 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2283 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2284 		term.charset = 1 - (ascii - '\016');
   2285 		return;
   2286 	case '\032': /* SUB */
   2287 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2288 		/* FALLTHROUGH */
   2289 	case '\030': /* CAN */
   2290 		csireset();
   2291 		break;
   2292 	case '\005': /* ENQ (IGNORED) */
   2293 	case '\000': /* NUL (IGNORED) */
   2294 	case '\021': /* XON (IGNORED) */
   2295 	case '\023': /* XOFF (IGNORED) */
   2296 	case 0177:   /* DEL (IGNORED) */
   2297 		return;
   2298 	case 0x80:   /* TODO: PAD */
   2299 	case 0x81:   /* TODO: HOP */
   2300 	case 0x82:   /* TODO: BPH */
   2301 	case 0x83:   /* TODO: NBH */
   2302 	case 0x84:   /* TODO: IND */
   2303 		break;
   2304 	case 0x85:   /* NEL -- Next line */
   2305 		tnewline(1); /* always go to first col */
   2306 		break;
   2307 	case 0x86:   /* TODO: SSA */
   2308 	case 0x87:   /* TODO: ESA */
   2309 		break;
   2310 	case 0x88:   /* HTS -- Horizontal tab stop */
   2311 		term.tabs[term.c.x] = 1;
   2312 		break;
   2313 	case 0x89:   /* TODO: HTJ */
   2314 	case 0x8a:   /* TODO: VTS */
   2315 	case 0x8b:   /* TODO: PLD */
   2316 	case 0x8c:   /* TODO: PLU */
   2317 	case 0x8d:   /* TODO: RI */
   2318 	case 0x8e:   /* TODO: SS2 */
   2319 	case 0x8f:   /* TODO: SS3 */
   2320 	case 0x91:   /* TODO: PU1 */
   2321 	case 0x92:   /* TODO: PU2 */
   2322 	case 0x93:   /* TODO: STS */
   2323 	case 0x94:   /* TODO: CCH */
   2324 	case 0x95:   /* TODO: MW */
   2325 	case 0x96:   /* TODO: SPA */
   2326 	case 0x97:   /* TODO: EPA */
   2327 	case 0x98:   /* TODO: SOS */
   2328 	case 0x99:   /* TODO: SGCI */
   2329 		break;
   2330 	case 0x9a:   /* DECID -- Identify Terminal */
   2331 		ttywrite(vtiden, strlen(vtiden), 0);
   2332 		break;
   2333 	case 0x9b:   /* TODO: CSI */
   2334 	case 0x9c:   /* TODO: ST */
   2335 		break;
   2336 	case 0x90:   /* DCS -- Device Control String */
   2337 	case 0x9d:   /* OSC -- Operating System Command */
   2338 	case 0x9e:   /* PM -- Privacy Message */
   2339 	case 0x9f:   /* APC -- Application Program Command */
   2340 		tstrsequence(ascii);
   2341 		return;
   2342 	}
   2343 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2344 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2345 }
   2346 
   2347 /*
   2348  * returns 1 when the sequence is finished and it hasn't to read
   2349  * more characters for this sequence, otherwise 0
   2350  */
   2351 int
   2352 eschandle(uchar ascii)
   2353 {
   2354 	switch (ascii) {
   2355 	case '[':
   2356 		term.esc |= ESC_CSI;
   2357 		return 0;
   2358 	case '#':
   2359 		term.esc |= ESC_TEST;
   2360 		return 0;
   2361 	case '%':
   2362 		term.esc |= ESC_UTF8;
   2363 		return 0;
   2364 	case 'P': /* DCS -- Device Control String */
   2365 	case '_': /* APC -- Application Program Command */
   2366 	case '^': /* PM -- Privacy Message */
   2367 	case ']': /* OSC -- Operating System Command */
   2368 	case 'k': /* old title set compatibility */
   2369 		tstrsequence(ascii);
   2370 		return 0;
   2371 	case 'n': /* LS2 -- Locking shift 2 */
   2372 	case 'o': /* LS3 -- Locking shift 3 */
   2373 		term.charset = 2 + (ascii - 'n');
   2374 		break;
   2375 	case '(': /* GZD4 -- set primary charset G0 */
   2376 	case ')': /* G1D4 -- set secondary charset G1 */
   2377 	case '*': /* G2D4 -- set tertiary charset G2 */
   2378 	case '+': /* G3D4 -- set quaternary charset G3 */
   2379 		term.icharset = ascii - '(';
   2380 		term.esc |= ESC_ALTCHARSET;
   2381 		return 0;
   2382 	case 'D': /* IND -- Linefeed */
   2383 		if (term.c.y == term.bot) {
   2384 			tscrollup(term.top, 1, 1);
   2385 		} else {
   2386 			tmoveto(term.c.x, term.c.y+1);
   2387 		}
   2388 		break;
   2389 	case 'E': /* NEL -- Next line */
   2390 		tnewline(1); /* always go to first col */
   2391 		break;
   2392 	case 'H': /* HTS -- Horizontal tab stop */
   2393 		term.tabs[term.c.x] = 1;
   2394 		break;
   2395 	case 'M': /* RI -- Reverse index */
   2396 		if (term.c.y == term.top) {
   2397 			tscrolldown(term.top, 1, 1);
   2398 		} else {
   2399 			tmoveto(term.c.x, term.c.y-1);
   2400 		}
   2401 		break;
   2402 	case 'Z': /* DECID -- Identify Terminal */
   2403 		ttywrite(vtiden, strlen(vtiden), 0);
   2404 		break;
   2405 	case 'c': /* RIS -- Reset to initial state */
   2406 		treset();
   2407 		resettitle();
   2408 		xloadcols();
   2409 		xsetmode(0, MODE_HIDE);
   2410 		break;
   2411 	case '=': /* DECPAM -- Application keypad */
   2412 		xsetmode(1, MODE_APPKEYPAD);
   2413 		break;
   2414 	case '>': /* DECPNM -- Normal keypad */
   2415 		xsetmode(0, MODE_APPKEYPAD);
   2416 		break;
   2417 	case '7': /* DECSC -- Save Cursor */
   2418 		tcursor(CURSOR_SAVE);
   2419 		break;
   2420 	case '8': /* DECRC -- Restore Cursor */
   2421 		tcursor(CURSOR_LOAD);
   2422 		break;
   2423 	case '\\': /* ST -- String Terminator */
   2424 		if (term.esc & ESC_STR_END)
   2425 			strhandle();
   2426 		break;
   2427 	default:
   2428 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2429 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2430 		break;
   2431 	}
   2432 	return 1;
   2433 }
   2434 
   2435 void
   2436 tputc(Rune u)
   2437 {
   2438 	char c[UTF_SIZ];
   2439 	int control;
   2440 	int width, len;
   2441 	Glyph *gp;
   2442 
   2443 	control = ISCONTROL(u);
   2444 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2445 		c[0] = u;
   2446 		width = len = 1;
   2447 	} else {
   2448 		len = utf8encode(u, c);
   2449 		if (!control && (width = wcwidth(u)) == -1)
   2450 			width = 1;
   2451 	}
   2452 
   2453 	if (IS_SET(MODE_PRINT))
   2454 		tprinter(c, len);
   2455 
   2456 	/*
   2457 	 * STR sequence must be checked before anything else
   2458 	 * because it uses all following characters until it
   2459 	 * receives a ESC, a SUB, a ST or any other C1 control
   2460 	 * character.
   2461 	 */
   2462 	if (term.esc & ESC_STR) {
   2463 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2464 		   ISCONTROLC1(u)) {
   2465 			term.esc &= ~(ESC_START|ESC_STR);
   2466 			term.esc |= ESC_STR_END;
   2467 			goto check_control_code;
   2468 		}
   2469 
   2470 		if (strescseq.len+len >= strescseq.siz) {
   2471 			/*
   2472 			 * Here is a bug in terminals. If the user never sends
   2473 			 * some code to stop the str or esc command, then st
   2474 			 * will stop responding. But this is better than
   2475 			 * silently failing with unknown characters. At least
   2476 			 * then users will report back.
   2477 			 *
   2478 			 * In the case users ever get fixed, here is the code:
   2479 			 */
   2480 			/*
   2481 			 * term.esc = 0;
   2482 			 * strhandle();
   2483 			 */
   2484 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2485 				return;
   2486 			strescseq.siz *= 2;
   2487 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2488 		}
   2489 
   2490 		memmove(&strescseq.buf[strescseq.len], c, len);
   2491 		strescseq.len += len;
   2492 		return;
   2493 	}
   2494 
   2495 check_control_code:
   2496 	/*
   2497 	 * Actions of control codes must be performed as soon they arrive
   2498 	 * because they can be embedded inside a control sequence, and
   2499 	 * they must not cause conflicts with sequences.
   2500 	 */
   2501 	if (control) {
   2502 		/* in UTF-8 mode ignore handling C1 control characters */
   2503 		if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
   2504 			return;
   2505 		tcontrolcode(u);
   2506 		/*
   2507 		 * control codes are not shown ever
   2508 		 */
   2509 		if (!term.esc)
   2510 			term.lastc = 0;
   2511 		return;
   2512 	} else if (term.esc & ESC_START) {
   2513 		if (term.esc & ESC_CSI) {
   2514 			csiescseq.buf[csiescseq.len++] = u;
   2515 			if (BETWEEN(u, 0x40, 0x7E)
   2516 					|| csiescseq.len >= \
   2517 					sizeof(csiescseq.buf)-1) {
   2518 				term.esc = 0;
   2519 				csiparse();
   2520 				csihandle();
   2521 			}
   2522 			return;
   2523 		} else if (term.esc & ESC_UTF8) {
   2524 			tdefutf8(u);
   2525 		} else if (term.esc & ESC_ALTCHARSET) {
   2526 			tdeftran(u);
   2527 		} else if (term.esc & ESC_TEST) {
   2528 			tdectest(u);
   2529 		} else {
   2530 			if (!eschandle(u))
   2531 				return;
   2532 			/* sequence already finished */
   2533 		}
   2534 		term.esc = 0;
   2535 		/*
   2536 		 * All characters which form part of a sequence are not
   2537 		 * printed
   2538 		 */
   2539 		return;
   2540 	}
   2541 	if (selected(term.c.x, term.c.y))
   2542 		selclear();
   2543 
   2544 	gp = &term.line[term.c.y][term.c.x];
   2545 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2546 		gp->mode |= ATTR_WRAP;
   2547 		tnewline(1);
   2548 		gp = &term.line[term.c.y][term.c.x];
   2549 	}
   2550 
   2551 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
   2552 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2553 		gp->mode &= ~ATTR_WIDE;
   2554 	}
   2555 
   2556 	if (term.c.x+width > term.col) {
   2557 		if (IS_SET(MODE_WRAP))
   2558 			tnewline(1);
   2559 		else
   2560 			tmoveto(term.col - width, term.c.y);
   2561 		gp = &term.line[term.c.y][term.c.x];
   2562 	}
   2563 
   2564 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2565 	term.lastc = u;
   2566 
   2567 	if (width == 2) {
   2568 		gp->mode |= ATTR_WIDE;
   2569 		if (term.c.x+1 < term.col) {
   2570 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   2571 				gp[2].u = ' ';
   2572 				gp[2].mode &= ~ATTR_WDUMMY;
   2573 			}
   2574 			gp[1].u = '\0';
   2575 			gp[1].mode = ATTR_WDUMMY;
   2576 		}
   2577 	}
   2578 	if (term.c.x+width < term.col) {
   2579 		tmoveto(term.c.x+width, term.c.y);
   2580 	} else {
   2581 		term.c.state |= CURSOR_WRAPNEXT;
   2582 	}
   2583 }
   2584 
   2585 int
   2586 twrite(const char *buf, int buflen, int show_ctrl)
   2587 {
   2588 	int charsize;
   2589 	Rune u;
   2590 	int n;
   2591 
   2592 	for (n = 0; n < buflen; n += charsize) {
   2593 		if (IS_SET(MODE_UTF8)) {
   2594 			/* process a complete utf8 char */
   2595 			charsize = utf8decode(buf + n, &u, buflen - n);
   2596 			if (charsize == 0)
   2597 				break;
   2598 		} else {
   2599 			u = buf[n] & 0xFF;
   2600 			charsize = 1;
   2601 		}
   2602 		if (show_ctrl && ISCONTROL(u)) {
   2603 			if (u & 0x80) {
   2604 				u &= 0x7f;
   2605 				tputc('^');
   2606 				tputc('[');
   2607 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2608 				u ^= 0x40;
   2609 				tputc('^');
   2610 			}
   2611 		}
   2612 		tputc(u);
   2613 	}
   2614 	return n;
   2615 }
   2616 
   2617 void
   2618 tresize(int col, int row)
   2619 {
   2620 	int i, j;
   2621 	int minrow = MIN(row, term.row);
   2622 	int mincol = MIN(col, term.col);
   2623 	int *bp;
   2624 	TCursor c;
   2625 
   2626 	if (col < 1 || row < 1) {
   2627 		fprintf(stderr,
   2628 		        "tresize: error resizing to %dx%d\n", col, row);
   2629 		return;
   2630 	}
   2631 
   2632 	autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
   2633 
   2634 	/*
   2635 	 * slide screen to keep cursor where we expect it -
   2636 	 * tscrollup would work here, but we can optimize to
   2637 	 * memmove because we're freeing the earlier lines
   2638 	 */
   2639 	for (i = 0; i <= term.c.y - row; i++) {
   2640 		free(term.line[i]);
   2641 		free(term.alt[i]);
   2642 	}
   2643 	/* ensure that both src and dst are not NULL */
   2644 	if (i > 0) {
   2645 		memmove(term.line, term.line + i, row * sizeof(Line));
   2646 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2647 	}
   2648 	for (i += row; i < term.row; i++) {
   2649 		free(term.line[i]);
   2650 		free(term.alt[i]);
   2651 	}
   2652 
   2653 	/* resize to new height */
   2654 	term.line = xrealloc(term.line, row * sizeof(Line));
   2655 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2656 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2657 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2658 
   2659 	for (i = 0; i < HISTSIZE; i++) {
   2660 		term.hist[i] = xrealloc(term.hist[i], col * sizeof(Glyph));
   2661 		for (j = mincol; j < col; j++) {
   2662 			term.hist[i][j] = term.c.attr;
   2663 			term.hist[i][j].u = ' ';
   2664 		}
   2665 	}
   2666 
   2667 	/* resize each row to new width, zero-pad if needed */
   2668 	for (i = 0; i < minrow; i++) {
   2669 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2670 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2671 	}
   2672 
   2673 	/* allocate any new rows */
   2674 	for (/* i = minrow */; i < row; i++) {
   2675 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2676 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2677 	}
   2678 	if (col > term.col) {
   2679 		bp = term.tabs + term.col;
   2680 
   2681 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2682 		while (--bp > term.tabs && !*bp)
   2683 			/* nothing */ ;
   2684 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2685 			*bp = 1;
   2686 	}
   2687 	/* update terminal size */
   2688 	term.col = col;
   2689 	term.row = row;
   2690 	/* reset scrolling region */
   2691 	tsetscroll(0, row-1);
   2692 	/* make use of the LIMIT in tmoveto */
   2693 	tmoveto(term.c.x, term.c.y);
   2694 	/* Clearing both screens (it makes dirty all lines) */
   2695 	c = term.c;
   2696 	for (i = 0; i < 2; i++) {
   2697 		if (mincol < col && 0 < minrow) {
   2698 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2699 		}
   2700 		if (0 < col && minrow < row) {
   2701 			tclearregion(0, minrow, col - 1, row - 1);
   2702 		}
   2703 		tswapscreen();
   2704 		tcursor(CURSOR_LOAD);
   2705 	}
   2706 	term.c = c;
   2707 }
   2708 
   2709 void
   2710 resettitle(void)
   2711 {
   2712 	xsettitle(NULL);
   2713 }
   2714 
   2715 void
   2716 drawregion(int x1, int y1, int x2, int y2)
   2717 {
   2718 	int y;
   2719 
   2720 	for (y = y1; y < y2; y++) {
   2721 		if (!term.dirty[y])
   2722 			continue;
   2723 
   2724 		term.dirty[y] = 0;
   2725 		xdrawline(TLINE(y), x1, y, x2);
   2726 	}
   2727 }
   2728 
   2729 void
   2730 draw(void)
   2731 {
   2732 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2733 
   2734 	if (!xstartdraw())
   2735 		return;
   2736 
   2737 	/* adjust cursor position */
   2738 	LIMIT(term.ocx, 0, term.col-1);
   2739 	LIMIT(term.ocy, 0, term.row-1);
   2740 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2741 		term.ocx--;
   2742 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2743 		cx--;
   2744 
   2745 	drawregion(0, 0, term.col, term.row);
   2746 	if (term.scr == 0)
   2747 		xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2748 				term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2749 	term.ocx = cx;
   2750 	term.ocy = term.c.y;
   2751 	xfinishdraw();
   2752 	if (ocx != term.ocx || ocy != term.ocy)
   2753 		xximspot(term.ocx, term.ocy);
   2754 }
   2755 
   2756 void
   2757 redraw(void)
   2758 {
   2759 	tfulldirt();
   2760 	draw();
   2761 }
   2762 
   2763 void autocomplete (const Arg *arg) {
   2764     static _Bool active = 0;
   2765     int acmpl_cmdindex = arg->i;
   2766     static int acmpl_cmdindex_prev;
   2767 
   2768     if (active == 0)
   2769         acmpl_cmdindex_prev = acmpl_cmdindex;
   2770 
   2771     static const char * const acmpl_cmd[] = {
   2772         [ACMPL_DEACTIVATE]   = "__DEACTIVATE__",
   2773         [ACMPL_WORD]         = "word-complete",
   2774         [ACMPL_WWORD]        = "WORD-complete",
   2775         [ACMPL_FUZZY_WORD]   = "fuzzy-word-complete",
   2776         [ACMPL_FUZZY_WWORD]  = "fuzzy-WORD-complete",
   2777         [ACMPL_FUZZY]        = "fuzzy-complete",
   2778         [ACMPL_SUFFIX]       = "suffix-complete",
   2779         [ACMPL_SURROUND]     = "surround-complete",
   2780         [ACMPL_UNDO]         = "__UNDO__",
   2781     };
   2782 
   2783     static FILE *acmpl_exec = NULL;
   2784     static int acmpl_status;
   2785     static char *stbuffile;
   2786     static char *target = NULL;
   2787     static size_t targetlen;
   2788     static char *completion = NULL;
   2789     static size_t complen_prev = 0;
   2790     static int cx, cy;
   2791 
   2792     if (acmpl_cmdindex == ACMPL_DEACTIVATE) {
   2793         if (active) {
   2794             active = 0;
   2795             pclose(acmpl_exec);
   2796             unlink(stbuffile);
   2797             free(stbuffile);
   2798             stbuffile = NULL;
   2799 
   2800             if (complen_prev) {
   2801                 selclear();
   2802                 complen_prev = 0;
   2803             }
   2804         }
   2805         return;
   2806     }
   2807 
   2808     if (acmpl_cmdindex == ACMPL_UNDO) {
   2809         if (active) {
   2810             active = 0;
   2811             pclose(acmpl_exec);
   2812             unlink(stbuffile);
   2813             free(stbuffile);
   2814             stbuffile = NULL;
   2815 
   2816             if (complen_prev) {
   2817                 selclear();
   2818                 for (size_t i = 0; i < complen_prev; i++)
   2819                     ttywrite((char[]) {'\b'}, 1, 1);
   2820                 complen_prev = 0;
   2821                 ttywrite(target, targetlen, 0);
   2822             }
   2823         }
   2824         return;
   2825     }
   2826 
   2827     if (acmpl_cmdindex != acmpl_cmdindex_prev) {
   2828         if (active) {
   2829             acmpl_cmdindex_prev = acmpl_cmdindex;
   2830             goto acmpl_begin;
   2831         }
   2832     }
   2833 
   2834     if (active == 0) {
   2835         acmpl_cmdindex_prev = acmpl_cmdindex;
   2836         cx = term.c.x;
   2837         cy = term.c.y;
   2838 
   2839         char filename[] = "/tmp/st-autocomplete-XXXXXX";
   2840         int fd = mkstemp(filename);
   2841 
   2842         if (fd == -1) {
   2843             perror("mkstemp");
   2844             return;
   2845         }
   2846 
   2847         stbuffile = strdup(filename);
   2848 
   2849         FILE *stbuf = fdopen(fd, "w");
   2850         if (!stbuf) {
   2851             perror("fdopen");
   2852             close(fd);
   2853             unlink(stbuffile);
   2854             free(stbuffile);
   2855             stbuffile = NULL;
   2856             return;
   2857         }
   2858 
   2859         char *stbufline = malloc(term.col + 2);
   2860         if (!stbufline) {
   2861             perror("malloc");
   2862             fclose(stbuf);
   2863             unlink(stbuffile);
   2864             free(stbuffile);
   2865             stbuffile = NULL;
   2866             return;
   2867         }
   2868 
   2869         int cxp = 0;
   2870         for (size_t y = 0; y < term.row; y++) {
   2871             if (y == term.c.y) cx += cxp * term.col;
   2872 
   2873             size_t x = 0;
   2874             for (; x < term.col; x++)
   2875                 utf8encode(term.line[y][x].u, stbufline + x);
   2876             if (term.line[y][x - 1].mode & ATTR_WRAP) {
   2877                 x--;
   2878                 if (y <= term.c.y) cy--;
   2879                 cxp++;
   2880             } else {
   2881                 stbufline[x] = '\n';
   2882                 cxp = 0;
   2883             }
   2884             stbufline[x + 1] = 0;
   2885             fputs(stbufline, stbuf);
   2886         }
   2887 
   2888         free(stbufline);
   2889         fclose(stbuf);
   2890 
   2891 acmpl_begin:
   2892         target = malloc(term.col + 1);
   2893         completion = malloc(term.col + 1);
   2894         if (!target || !completion) {
   2895             perror("malloc");
   2896             free(target);
   2897             free(completion);
   2898             unlink(stbuffile);
   2899             free(stbuffile);
   2900             stbuffile = NULL;
   2901             return;
   2902         }
   2903 
   2904         char acmpl[1500];
   2905         snprintf(acmpl, sizeof(acmpl),
   2906                  "cat %s | st-autocomplete %s %d %d",
   2907                  stbuffile, acmpl_cmd[acmpl_cmdindex], cy, cx);
   2908 
   2909         acmpl_exec = popen(acmpl, "r");
   2910         if (!acmpl_exec) {
   2911             perror("popen");
   2912             free(target);
   2913             free(completion);
   2914             unlink(stbuffile);
   2915             free(stbuffile);
   2916             stbuffile = NULL;
   2917             return;
   2918         }
   2919 
   2920         if (fscanf(acmpl_exec, "%s\n", target) != 1) {
   2921             perror("fscanf");
   2922             pclose(acmpl_exec);
   2923             free(target);
   2924             free(completion);
   2925             unlink(stbuffile);
   2926             free(stbuffile);
   2927             stbuffile = NULL;
   2928             return;
   2929         }
   2930         targetlen = strlen(target);
   2931     }
   2932 
   2933     unsigned line, beg, end;
   2934 
   2935     acmpl_status = fscanf(acmpl_exec, "%[^\n]\n%u\n%u\n%u\n", completion, &line, &beg, &end);
   2936     if (acmpl_status == EOF) {
   2937         if (active == 0) {
   2938             pclose(acmpl_exec);
   2939             free(target);
   2940             free(completion);
   2941             unlink(stbuffile);
   2942             free(stbuffile);
   2943             stbuffile = NULL;
   2944             return;
   2945         }
   2946         active = 0;
   2947         pclose(acmpl_exec);
   2948         ttywrite(target, targetlen, 0);
   2949         goto acmpl_begin;
   2950     }
   2951 
   2952     active = 1;
   2953 
   2954     if (complen_prev == 0) {
   2955         for (size_t i = 0; i < targetlen; i++)
   2956             ttywrite((char[]) {'\b'}, 1, 1);
   2957     } else {
   2958         selclear();
   2959         for (size_t i = 0; i < complen_prev; i++)
   2960             ttywrite((char[]) {'\b'}, 1, 1);
   2961         complen_prev = 0;
   2962     }
   2963 
   2964     complen_prev = strlen(completion);
   2965     ttywrite(completion, complen_prev, 0);
   2966 
   2967     if (line == cy && beg > cx) {
   2968         beg += complen_prev - targetlen;
   2969         end += complen_prev - targetlen;
   2970     }
   2971 
   2972     end--;
   2973 
   2974     int wl = 0;
   2975     int tl = line;
   2976     for (int l = 0; l < tl; l++)
   2977         if (term.line[l][term.col - 1].mode & ATTR_WRAP) {
   2978             wl++;
   2979             tl++;
   2980         }
   2981 
   2982     selstart(beg % term.col, line + wl + beg / term.col, 0);
   2983     selextend(end % term.col, line + wl + end / term.col, 1, 0);
   2984     xsetsel(getsel());
   2985 }