st-noxz

[fork] suckless st - personal fork
git clone git://git.noxz.tech/st-noxz
Log | Files | Refs | README | LICENSE

st.c (55849B)


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