st

simple terminal
git clone git://git.suckless.org/st
Log | Files | Refs | README | LICENSE

st.c (55789B)


      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 
   1224 void
   1225 tclearregion(int x1, int y1, int x2, int y2)
   1226 {
   1227 	int x, y, temp;
   1228 	Glyph *gp;
   1229 
   1230 	if (x1 > x2)
   1231 		temp = x1, x1 = x2, x2 = temp;
   1232 	if (y1 > y2)
   1233 		temp = y1, y1 = y2, y2 = temp;
   1234 
   1235 	LIMIT(x1, 0, term.col-1);
   1236 	LIMIT(x2, 0, term.col-1);
   1237 	LIMIT(y1, 0, term.row-1);
   1238 	LIMIT(y2, 0, term.row-1);
   1239 
   1240 	for (y = y1; y <= y2; y++) {
   1241 		term.dirty[y] = 1;
   1242 		for (x = x1; x <= x2; x++) {
   1243 			gp = &term.line[y][x];
   1244 			if (selected(x, y))
   1245 				selclear();
   1246 			gp->fg = term.c.attr.fg;
   1247 			gp->bg = term.c.attr.bg;
   1248 			gp->mode = 0;
   1249 			gp->u = ' ';
   1250 		}
   1251 	}
   1252 }
   1253 
   1254 void
   1255 tdeletechar(int n)
   1256 {
   1257 	int dst, src, size;
   1258 	Glyph *line;
   1259 
   1260 	LIMIT(n, 0, term.col - term.c.x);
   1261 
   1262 	dst = term.c.x;
   1263 	src = term.c.x + n;
   1264 	size = term.col - src;
   1265 	line = term.line[term.c.y];
   1266 
   1267 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1268 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1269 }
   1270 
   1271 void
   1272 tinsertblank(int n)
   1273 {
   1274 	int dst, src, size;
   1275 	Glyph *line;
   1276 
   1277 	LIMIT(n, 0, term.col - term.c.x);
   1278 
   1279 	dst = term.c.x + n;
   1280 	src = term.c.x;
   1281 	size = term.col - dst;
   1282 	line = term.line[term.c.y];
   1283 
   1284 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1285 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1286 }
   1287 
   1288 void
   1289 tinsertblankline(int n)
   1290 {
   1291 	if (BETWEEN(term.c.y, term.top, term.bot))
   1292 		tscrolldown(term.c.y, n);
   1293 }
   1294 
   1295 void
   1296 tdeleteline(int n)
   1297 {
   1298 	if (BETWEEN(term.c.y, term.top, term.bot))
   1299 		tscrollup(term.c.y, n);
   1300 }
   1301 
   1302 int32_t
   1303 tdefcolor(int *attr, int *npar, int l)
   1304 {
   1305 	int32_t idx = -1;
   1306 	uint r, g, b;
   1307 
   1308 	switch (attr[*npar + 1]) {
   1309 	case 2: /* direct color in RGB space */
   1310 		if (*npar + 4 >= l) {
   1311 			fprintf(stderr,
   1312 				"erresc(38): Incorrect number of parameters (%d)\n",
   1313 				*npar);
   1314 			break;
   1315 		}
   1316 		r = attr[*npar + 2];
   1317 		g = attr[*npar + 3];
   1318 		b = attr[*npar + 4];
   1319 		*npar += 4;
   1320 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1321 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1322 				r, g, b);
   1323 		else
   1324 			idx = TRUECOLOR(r, g, b);
   1325 		break;
   1326 	case 5: /* indexed color */
   1327 		if (*npar + 2 >= l) {
   1328 			fprintf(stderr,
   1329 				"erresc(38): Incorrect number of parameters (%d)\n",
   1330 				*npar);
   1331 			break;
   1332 		}
   1333 		*npar += 2;
   1334 		if (!BETWEEN(attr[*npar], 0, 255))
   1335 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1336 		else
   1337 			idx = attr[*npar];
   1338 		break;
   1339 	case 0: /* implemented defined (only foreground) */
   1340 	case 1: /* transparent */
   1341 	case 3: /* direct color in CMY space */
   1342 	case 4: /* direct color in CMYK space */
   1343 	default:
   1344 		fprintf(stderr,
   1345 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1346 		break;
   1347 	}
   1348 
   1349 	return idx;
   1350 }
   1351 
   1352 void
   1353 tsetattr(int *attr, int l)
   1354 {
   1355 	int i;
   1356 	int32_t idx;
   1357 
   1358 	for (i = 0; i < l; i++) {
   1359 		switch (attr[i]) {
   1360 		case 0:
   1361 			term.c.attr.mode &= ~(
   1362 				ATTR_BOLD       |
   1363 				ATTR_FAINT      |
   1364 				ATTR_ITALIC     |
   1365 				ATTR_UNDERLINE  |
   1366 				ATTR_BLINK      |
   1367 				ATTR_REVERSE    |
   1368 				ATTR_INVISIBLE  |
   1369 				ATTR_STRUCK     );
   1370 			term.c.attr.fg = defaultfg;
   1371 			term.c.attr.bg = defaultbg;
   1372 			break;
   1373 		case 1:
   1374 			term.c.attr.mode |= ATTR_BOLD;
   1375 			break;
   1376 		case 2:
   1377 			term.c.attr.mode |= ATTR_FAINT;
   1378 			break;
   1379 		case 3:
   1380 			term.c.attr.mode |= ATTR_ITALIC;
   1381 			break;
   1382 		case 4:
   1383 			term.c.attr.mode |= ATTR_UNDERLINE;
   1384 			break;
   1385 		case 5: /* slow blink */
   1386 			/* FALLTHROUGH */
   1387 		case 6: /* rapid blink */
   1388 			term.c.attr.mode |= ATTR_BLINK;
   1389 			break;
   1390 		case 7:
   1391 			term.c.attr.mode |= ATTR_REVERSE;
   1392 			break;
   1393 		case 8:
   1394 			term.c.attr.mode |= ATTR_INVISIBLE;
   1395 			break;
   1396 		case 9:
   1397 			term.c.attr.mode |= ATTR_STRUCK;
   1398 			break;
   1399 		case 22:
   1400 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1401 			break;
   1402 		case 23:
   1403 			term.c.attr.mode &= ~ATTR_ITALIC;
   1404 			break;
   1405 		case 24:
   1406 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1407 			break;
   1408 		case 25:
   1409 			term.c.attr.mode &= ~ATTR_BLINK;
   1410 			break;
   1411 		case 27:
   1412 			term.c.attr.mode &= ~ATTR_REVERSE;
   1413 			break;
   1414 		case 28:
   1415 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1416 			break;
   1417 		case 29:
   1418 			term.c.attr.mode &= ~ATTR_STRUCK;
   1419 			break;
   1420 		case 38:
   1421 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1422 				term.c.attr.fg = idx;
   1423 			break;
   1424 		case 39:
   1425 			term.c.attr.fg = defaultfg;
   1426 			break;
   1427 		case 48:
   1428 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1429 				term.c.attr.bg = idx;
   1430 			break;
   1431 		case 49:
   1432 			term.c.attr.bg = defaultbg;
   1433 			break;
   1434 		default:
   1435 			if (BETWEEN(attr[i], 30, 37)) {
   1436 				term.c.attr.fg = attr[i] - 30;
   1437 			} else if (BETWEEN(attr[i], 40, 47)) {
   1438 				term.c.attr.bg = attr[i] - 40;
   1439 			} else if (BETWEEN(attr[i], 90, 97)) {
   1440 				term.c.attr.fg = attr[i] - 90 + 8;
   1441 			} else if (BETWEEN(attr[i], 100, 107)) {
   1442 				term.c.attr.bg = attr[i] - 100 + 8;
   1443 			} else {
   1444 				fprintf(stderr,
   1445 					"erresc(default): gfx attr %d unknown\n",
   1446 					attr[i]);
   1447 				csidump();
   1448 			}
   1449 			break;
   1450 		}
   1451 	}
   1452 }
   1453 
   1454 void
   1455 tsetscroll(int t, int b)
   1456 {
   1457 	int temp;
   1458 
   1459 	LIMIT(t, 0, term.row-1);
   1460 	LIMIT(b, 0, term.row-1);
   1461 	if (t > b) {
   1462 		temp = t;
   1463 		t = b;
   1464 		b = temp;
   1465 	}
   1466 	term.top = t;
   1467 	term.bot = b;
   1468 }
   1469 
   1470 void
   1471 tsetmode(int priv, int set, int *args, int narg)
   1472 {
   1473 	int alt, *lim;
   1474 
   1475 	for (lim = args + narg; args < lim; ++args) {
   1476 		if (priv) {
   1477 			switch (*args) {
   1478 			case 1: /* DECCKM -- Cursor key */
   1479 				xsetmode(set, MODE_APPCURSOR);
   1480 				break;
   1481 			case 5: /* DECSCNM -- Reverse video */
   1482 				xsetmode(set, MODE_REVERSE);
   1483 				break;
   1484 			case 6: /* DECOM -- Origin */
   1485 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1486 				tmoveato(0, 0);
   1487 				break;
   1488 			case 7: /* DECAWM -- Auto wrap */
   1489 				MODBIT(term.mode, set, MODE_WRAP);
   1490 				break;
   1491 			case 0:  /* Error (IGNORED) */
   1492 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1493 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1494 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1495 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1496 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1497 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1498 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1499 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1500 				break;
   1501 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1502 				xsetmode(!set, MODE_HIDE);
   1503 				break;
   1504 			case 9:    /* X10 mouse compatibility mode */
   1505 				xsetpointermotion(0);
   1506 				xsetmode(0, MODE_MOUSE);
   1507 				xsetmode(set, MODE_MOUSEX10);
   1508 				break;
   1509 			case 1000: /* 1000: report button press */
   1510 				xsetpointermotion(0);
   1511 				xsetmode(0, MODE_MOUSE);
   1512 				xsetmode(set, MODE_MOUSEBTN);
   1513 				break;
   1514 			case 1002: /* 1002: report motion on button press */
   1515 				xsetpointermotion(0);
   1516 				xsetmode(0, MODE_MOUSE);
   1517 				xsetmode(set, MODE_MOUSEMOTION);
   1518 				break;
   1519 			case 1003: /* 1003: enable all mouse motions */
   1520 				xsetpointermotion(set);
   1521 				xsetmode(0, MODE_MOUSE);
   1522 				xsetmode(set, MODE_MOUSEMANY);
   1523 				break;
   1524 			case 1004: /* 1004: send focus events to tty */
   1525 				xsetmode(set, MODE_FOCUS);
   1526 				break;
   1527 			case 1006: /* 1006: extended reporting mode */
   1528 				xsetmode(set, MODE_MOUSESGR);
   1529 				break;
   1530 			case 1034:
   1531 				xsetmode(set, MODE_8BIT);
   1532 				break;
   1533 			case 1049: /* swap screen & set/restore cursor as xterm */
   1534 				if (!allowaltscreen)
   1535 					break;
   1536 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1537 				/* FALLTHROUGH */
   1538 			case 47: /* swap screen */
   1539 			case 1047:
   1540 				if (!allowaltscreen)
   1541 					break;
   1542 				alt = IS_SET(MODE_ALTSCREEN);
   1543 				if (alt) {
   1544 					tclearregion(0, 0, term.col-1,
   1545 							term.row-1);
   1546 				}
   1547 				if (set ^ alt) /* set is always 1 or 0 */
   1548 					tswapscreen();
   1549 				if (*args != 1049)
   1550 					break;
   1551 				/* FALLTHROUGH */
   1552 			case 1048:
   1553 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1554 				break;
   1555 			case 2004: /* 2004: bracketed paste mode */
   1556 				xsetmode(set, MODE_BRCKTPASTE);
   1557 				break;
   1558 			/* Not implemented mouse modes. See comments there. */
   1559 			case 1001: /* mouse highlight mode; can hang the
   1560 				      terminal by design when implemented. */
   1561 			case 1005: /* UTF-8 mouse mode; will confuse
   1562 				      applications not supporting UTF-8
   1563 				      and luit. */
   1564 			case 1015: /* urxvt mangled mouse mode; incompatible
   1565 				      and can be mistaken for other control
   1566 				      codes. */
   1567 				break;
   1568 			default:
   1569 				fprintf(stderr,
   1570 					"erresc: unknown private set/reset mode %d\n",
   1571 					*args);
   1572 				break;
   1573 			}
   1574 		} else {
   1575 			switch (*args) {
   1576 			case 0:  /* Error (IGNORED) */
   1577 				break;
   1578 			case 2:
   1579 				xsetmode(set, MODE_KBDLOCK);
   1580 				break;
   1581 			case 4:  /* IRM -- Insertion-replacement */
   1582 				MODBIT(term.mode, set, MODE_INSERT);
   1583 				break;
   1584 			case 12: /* SRM -- Send/Receive */
   1585 				MODBIT(term.mode, !set, MODE_ECHO);
   1586 				break;
   1587 			case 20: /* LNM -- Linefeed/new line */
   1588 				MODBIT(term.mode, set, MODE_CRLF);
   1589 				break;
   1590 			default:
   1591 				fprintf(stderr,
   1592 					"erresc: unknown set/reset mode %d\n",
   1593 					*args);
   1594 				break;
   1595 			}
   1596 		}
   1597 	}
   1598 }
   1599 
   1600 void
   1601 csihandle(void)
   1602 {
   1603 	char buf[40];
   1604 	int len;
   1605 
   1606 	switch (csiescseq.mode[0]) {
   1607 	default:
   1608 	unknown:
   1609 		fprintf(stderr, "erresc: unknown csi ");
   1610 		csidump();
   1611 		/* die(""); */
   1612 		break;
   1613 	case '@': /* ICH -- Insert <n> blank char */
   1614 		DEFAULT(csiescseq.arg[0], 1);
   1615 		tinsertblank(csiescseq.arg[0]);
   1616 		break;
   1617 	case 'A': /* CUU -- Cursor <n> Up */
   1618 		DEFAULT(csiescseq.arg[0], 1);
   1619 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   1620 		break;
   1621 	case 'B': /* CUD -- Cursor <n> Down */
   1622 	case 'e': /* VPR --Cursor <n> Down */
   1623 		DEFAULT(csiescseq.arg[0], 1);
   1624 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   1625 		break;
   1626 	case 'i': /* MC -- Media Copy */
   1627 		switch (csiescseq.arg[0]) {
   1628 		case 0:
   1629 			tdump();
   1630 			break;
   1631 		case 1:
   1632 			tdumpline(term.c.y);
   1633 			break;
   1634 		case 2:
   1635 			tdumpsel();
   1636 			break;
   1637 		case 4:
   1638 			term.mode &= ~MODE_PRINT;
   1639 			break;
   1640 		case 5:
   1641 			term.mode |= MODE_PRINT;
   1642 			break;
   1643 		}
   1644 		break;
   1645 	case 'c': /* DA -- Device Attributes */
   1646 		if (csiescseq.arg[0] == 0)
   1647 			ttywrite(vtiden, strlen(vtiden), 0);
   1648 		break;
   1649 	case 'b': /* REP -- if last char is printable print it <n> more times */
   1650 		DEFAULT(csiescseq.arg[0], 1);
   1651 		if (term.lastc)
   1652 			while (csiescseq.arg[0]-- > 0)
   1653 				tputc(term.lastc);
   1654 		break;
   1655 	case 'C': /* CUF -- Cursor <n> Forward */
   1656 	case 'a': /* HPR -- Cursor <n> Forward */
   1657 		DEFAULT(csiescseq.arg[0], 1);
   1658 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   1659 		break;
   1660 	case 'D': /* CUB -- Cursor <n> Backward */
   1661 		DEFAULT(csiescseq.arg[0], 1);
   1662 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   1663 		break;
   1664 	case 'E': /* CNL -- Cursor <n> Down and first col */
   1665 		DEFAULT(csiescseq.arg[0], 1);
   1666 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   1667 		break;
   1668 	case 'F': /* CPL -- Cursor <n> Up and first col */
   1669 		DEFAULT(csiescseq.arg[0], 1);
   1670 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   1671 		break;
   1672 	case 'g': /* TBC -- Tabulation clear */
   1673 		switch (csiescseq.arg[0]) {
   1674 		case 0: /* clear current tab stop */
   1675 			term.tabs[term.c.x] = 0;
   1676 			break;
   1677 		case 3: /* clear all the tabs */
   1678 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1679 			break;
   1680 		default:
   1681 			goto unknown;
   1682 		}
   1683 		break;
   1684 	case 'G': /* CHA -- Move to <col> */
   1685 	case '`': /* HPA */
   1686 		DEFAULT(csiescseq.arg[0], 1);
   1687 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   1688 		break;
   1689 	case 'H': /* CUP -- Move to <row> <col> */
   1690 	case 'f': /* HVP */
   1691 		DEFAULT(csiescseq.arg[0], 1);
   1692 		DEFAULT(csiescseq.arg[1], 1);
   1693 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   1694 		break;
   1695 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   1696 		DEFAULT(csiescseq.arg[0], 1);
   1697 		tputtab(csiescseq.arg[0]);
   1698 		break;
   1699 	case 'J': /* ED -- Clear screen */
   1700 		switch (csiescseq.arg[0]) {
   1701 		case 0: /* below */
   1702 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
   1703 			if (term.c.y < term.row-1) {
   1704 				tclearregion(0, term.c.y+1, term.col-1,
   1705 						term.row-1);
   1706 			}
   1707 			break;
   1708 		case 1: /* above */
   1709 			if (term.c.y > 1)
   1710 				tclearregion(0, 0, term.col-1, term.c.y-1);
   1711 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1712 			break;
   1713 		case 2: /* all */
   1714 			tclearregion(0, 0, term.col-1, term.row-1);
   1715 			break;
   1716 		default:
   1717 			goto unknown;
   1718 		}
   1719 		break;
   1720 	case 'K': /* EL -- Clear line */
   1721 		switch (csiescseq.arg[0]) {
   1722 		case 0: /* right */
   1723 			tclearregion(term.c.x, term.c.y, term.col-1,
   1724 					term.c.y);
   1725 			break;
   1726 		case 1: /* left */
   1727 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1728 			break;
   1729 		case 2: /* all */
   1730 			tclearregion(0, term.c.y, term.col-1, term.c.y);
   1731 			break;
   1732 		}
   1733 		break;
   1734 	case 'S': /* SU -- Scroll <n> line up */
   1735 		DEFAULT(csiescseq.arg[0], 1);
   1736 		tscrollup(term.top, csiescseq.arg[0]);
   1737 		break;
   1738 	case 'T': /* SD -- Scroll <n> line down */
   1739 		DEFAULT(csiescseq.arg[0], 1);
   1740 		tscrolldown(term.top, csiescseq.arg[0]);
   1741 		break;
   1742 	case 'L': /* IL -- Insert <n> blank lines */
   1743 		DEFAULT(csiescseq.arg[0], 1);
   1744 		tinsertblankline(csiescseq.arg[0]);
   1745 		break;
   1746 	case 'l': /* RM -- Reset Mode */
   1747 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1748 		break;
   1749 	case 'M': /* DL -- Delete <n> lines */
   1750 		DEFAULT(csiescseq.arg[0], 1);
   1751 		tdeleteline(csiescseq.arg[0]);
   1752 		break;
   1753 	case 'X': /* ECH -- Erase <n> char */
   1754 		DEFAULT(csiescseq.arg[0], 1);
   1755 		tclearregion(term.c.x, term.c.y,
   1756 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1757 		break;
   1758 	case 'P': /* DCH -- Delete <n> char */
   1759 		DEFAULT(csiescseq.arg[0], 1);
   1760 		tdeletechar(csiescseq.arg[0]);
   1761 		break;
   1762 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   1763 		DEFAULT(csiescseq.arg[0], 1);
   1764 		tputtab(-csiescseq.arg[0]);
   1765 		break;
   1766 	case 'd': /* VPA -- Move to <row> */
   1767 		DEFAULT(csiescseq.arg[0], 1);
   1768 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   1769 		break;
   1770 	case 'h': /* SM -- Set terminal mode */
   1771 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   1772 		break;
   1773 	case 'm': /* SGR -- Terminal attribute (color) */
   1774 		tsetattr(csiescseq.arg, csiescseq.narg);
   1775 		break;
   1776 	case 'n': /* DSR – Device Status Report (cursor position) */
   1777 		if (csiescseq.arg[0] == 6) {
   1778 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   1779 					term.c.y+1, term.c.x+1);
   1780 			ttywrite(buf, len, 0);
   1781 		}
   1782 		break;
   1783 	case 'r': /* DECSTBM -- Set Scrolling Region */
   1784 		if (csiescseq.priv) {
   1785 			goto unknown;
   1786 		} else {
   1787 			DEFAULT(csiescseq.arg[0], 1);
   1788 			DEFAULT(csiescseq.arg[1], term.row);
   1789 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   1790 			tmoveato(0, 0);
   1791 		}
   1792 		break;
   1793 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   1794 		tcursor(CURSOR_SAVE);
   1795 		break;
   1796 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   1797 		tcursor(CURSOR_LOAD);
   1798 		break;
   1799 	case ' ':
   1800 		switch (csiescseq.mode[1]) {
   1801 		case 'q': /* DECSCUSR -- Set Cursor Style */
   1802 			if (xsetcursor(csiescseq.arg[0]))
   1803 				goto unknown;
   1804 			break;
   1805 		default:
   1806 			goto unknown;
   1807 		}
   1808 		break;
   1809 	}
   1810 }
   1811 
   1812 void
   1813 csidump(void)
   1814 {
   1815 	size_t i;
   1816 	uint c;
   1817 
   1818 	fprintf(stderr, "ESC[");
   1819 	for (i = 0; i < csiescseq.len; i++) {
   1820 		c = csiescseq.buf[i] & 0xff;
   1821 		if (isprint(c)) {
   1822 			putc(c, stderr);
   1823 		} else if (c == '\n') {
   1824 			fprintf(stderr, "(\\n)");
   1825 		} else if (c == '\r') {
   1826 			fprintf(stderr, "(\\r)");
   1827 		} else if (c == 0x1b) {
   1828 			fprintf(stderr, "(\\e)");
   1829 		} else {
   1830 			fprintf(stderr, "(%02x)", c);
   1831 		}
   1832 	}
   1833 	putc('\n', stderr);
   1834 }
   1835 
   1836 void
   1837 csireset(void)
   1838 {
   1839 	memset(&csiescseq, 0, sizeof(csiescseq));
   1840 }
   1841 
   1842 void
   1843 strhandle(void)
   1844 {
   1845 	char *p = NULL, *dec;
   1846 	int j, narg, par;
   1847 
   1848 	term.esc &= ~(ESC_STR_END|ESC_STR);
   1849 	strparse();
   1850 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   1851 
   1852 	switch (strescseq.type) {
   1853 	case ']': /* OSC -- Operating System Command */
   1854 		switch (par) {
   1855 		case 0:
   1856 		case 1:
   1857 		case 2:
   1858 			if (narg > 1)
   1859 				xsettitle(strescseq.args[1]);
   1860 			return;
   1861 		case 52:
   1862 			if (narg > 2 && allowwindowops) {
   1863 				dec = base64dec(strescseq.args[2]);
   1864 				if (dec) {
   1865 					xsetsel(dec);
   1866 					xclipcopy();
   1867 				} else {
   1868 					fprintf(stderr, "erresc: invalid base64\n");
   1869 				}
   1870 			}
   1871 			return;
   1872 		case 4: /* color set */
   1873 			if (narg < 3)
   1874 				break;
   1875 			p = strescseq.args[2];
   1876 			/* FALLTHROUGH */
   1877 		case 104: /* color reset, here p = NULL */
   1878 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   1879 			if (xsetcolorname(j, p)) {
   1880 				if (par == 104 && narg <= 1)
   1881 					return; /* color reset without parameter */
   1882 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   1883 				        j, p ? p : "(null)");
   1884 			} else {
   1885 				/*
   1886 				 * TODO if defaultbg color is changed, borders
   1887 				 * are dirty
   1888 				 */
   1889 				redraw();
   1890 			}
   1891 			return;
   1892 		}
   1893 		break;
   1894 	case 'k': /* old title set compatibility */
   1895 		xsettitle(strescseq.args[0]);
   1896 		return;
   1897 	case 'P': /* DCS -- Device Control String */
   1898 	case '_': /* APC -- Application Program Command */
   1899 	case '^': /* PM -- Privacy Message */
   1900 		return;
   1901 	}
   1902 
   1903 	fprintf(stderr, "erresc: unknown str ");
   1904 	strdump();
   1905 }
   1906 
   1907 void
   1908 strparse(void)
   1909 {
   1910 	int c;
   1911 	char *p = strescseq.buf;
   1912 
   1913 	strescseq.narg = 0;
   1914 	strescseq.buf[strescseq.len] = '\0';
   1915 
   1916 	if (*p == '\0')
   1917 		return;
   1918 
   1919 	while (strescseq.narg < STR_ARG_SIZ) {
   1920 		strescseq.args[strescseq.narg++] = p;
   1921 		while ((c = *p) != ';' && c != '\0')
   1922 			++p;
   1923 		if (c == '\0')
   1924 			return;
   1925 		*p++ = '\0';
   1926 	}
   1927 }
   1928 
   1929 void
   1930 strdump(void)
   1931 {
   1932 	size_t i;
   1933 	uint c;
   1934 
   1935 	fprintf(stderr, "ESC%c", strescseq.type);
   1936 	for (i = 0; i < strescseq.len; i++) {
   1937 		c = strescseq.buf[i] & 0xff;
   1938 		if (c == '\0') {
   1939 			putc('\n', stderr);
   1940 			return;
   1941 		} else if (isprint(c)) {
   1942 			putc(c, stderr);
   1943 		} else if (c == '\n') {
   1944 			fprintf(stderr, "(\\n)");
   1945 		} else if (c == '\r') {
   1946 			fprintf(stderr, "(\\r)");
   1947 		} else if (c == 0x1b) {
   1948 			fprintf(stderr, "(\\e)");
   1949 		} else {
   1950 			fprintf(stderr, "(%02x)", c);
   1951 		}
   1952 	}
   1953 	fprintf(stderr, "ESC\\\n");
   1954 }
   1955 
   1956 void
   1957 strreset(void)
   1958 {
   1959 	strescseq = (STREscape){
   1960 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   1961 		.siz = STR_BUF_SIZ,
   1962 	};
   1963 }
   1964 
   1965 void
   1966 sendbreak(const Arg *arg)
   1967 {
   1968 	if (tcsendbreak(cmdfd, 0))
   1969 		perror("Error sending break");
   1970 }
   1971 
   1972 void
   1973 tprinter(char *s, size_t len)
   1974 {
   1975 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   1976 		perror("Error writing to output file");
   1977 		close(iofd);
   1978 		iofd = -1;
   1979 	}
   1980 }
   1981 
   1982 void
   1983 toggleprinter(const Arg *arg)
   1984 {
   1985 	term.mode ^= MODE_PRINT;
   1986 }
   1987 
   1988 void
   1989 printscreen(const Arg *arg)
   1990 {
   1991 	tdump();
   1992 }
   1993 
   1994 void
   1995 printsel(const Arg *arg)
   1996 {
   1997 	tdumpsel();
   1998 }
   1999 
   2000 void
   2001 tdumpsel(void)
   2002 {
   2003 	char *ptr;
   2004 
   2005 	if ((ptr = getsel())) {
   2006 		tprinter(ptr, strlen(ptr));
   2007 		free(ptr);
   2008 	}
   2009 }
   2010 
   2011 void
   2012 tdumpline(int n)
   2013 {
   2014 	char buf[UTF_SIZ];
   2015 	Glyph *bp, *end;
   2016 
   2017 	bp = &term.line[n][0];
   2018 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2019 	if (bp != end || bp->u != ' ') {
   2020 		for ( ; bp <= end; ++bp)
   2021 			tprinter(buf, utf8encode(bp->u, buf));
   2022 	}
   2023 	tprinter("\n", 1);
   2024 }
   2025 
   2026 void
   2027 tdump(void)
   2028 {
   2029 	int i;
   2030 
   2031 	for (i = 0; i < term.row; ++i)
   2032 		tdumpline(i);
   2033 }
   2034 
   2035 void
   2036 tputtab(int n)
   2037 {
   2038 	uint x = term.c.x;
   2039 
   2040 	if (n > 0) {
   2041 		while (x < term.col && n--)
   2042 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2043 				/* nothing */ ;
   2044 	} else if (n < 0) {
   2045 		while (x > 0 && n++)
   2046 			for (--x; x > 0 && !term.tabs[x]; --x)
   2047 				/* nothing */ ;
   2048 	}
   2049 	term.c.x = LIMIT(x, 0, term.col-1);
   2050 }
   2051 
   2052 void
   2053 tdefutf8(char ascii)
   2054 {
   2055 	if (ascii == 'G')
   2056 		term.mode |= MODE_UTF8;
   2057 	else if (ascii == '@')
   2058 		term.mode &= ~MODE_UTF8;
   2059 }
   2060 
   2061 void
   2062 tdeftran(char ascii)
   2063 {
   2064 	static char cs[] = "0B";
   2065 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2066 	char *p;
   2067 
   2068 	if ((p = strchr(cs, ascii)) == NULL) {
   2069 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2070 	} else {
   2071 		term.trantbl[term.icharset] = vcs[p - cs];
   2072 	}
   2073 }
   2074 
   2075 void
   2076 tdectest(char c)
   2077 {
   2078 	int x, y;
   2079 
   2080 	if (c == '8') { /* DEC screen alignment test. */
   2081 		for (x = 0; x < term.col; ++x) {
   2082 			for (y = 0; y < term.row; ++y)
   2083 				tsetchar('E', &term.c.attr, x, y);
   2084 		}
   2085 	}
   2086 }
   2087 
   2088 void
   2089 tstrsequence(uchar c)
   2090 {
   2091 	switch (c) {
   2092 	case 0x90:   /* DCS -- Device Control String */
   2093 		c = 'P';
   2094 		break;
   2095 	case 0x9f:   /* APC -- Application Program Command */
   2096 		c = '_';
   2097 		break;
   2098 	case 0x9e:   /* PM -- Privacy Message */
   2099 		c = '^';
   2100 		break;
   2101 	case 0x9d:   /* OSC -- Operating System Command */
   2102 		c = ']';
   2103 		break;
   2104 	}
   2105 	strreset();
   2106 	strescseq.type = c;
   2107 	term.esc |= ESC_STR;
   2108 }
   2109 
   2110 void
   2111 tcontrolcode(uchar ascii)
   2112 {
   2113 	switch (ascii) {
   2114 	case '\t':   /* HT */
   2115 		tputtab(1);
   2116 		return;
   2117 	case '\b':   /* BS */
   2118 		tmoveto(term.c.x-1, term.c.y);
   2119 		return;
   2120 	case '\r':   /* CR */
   2121 		tmoveto(0, term.c.y);
   2122 		return;
   2123 	case '\f':   /* LF */
   2124 	case '\v':   /* VT */
   2125 	case '\n':   /* LF */
   2126 		/* go to first col if the mode is set */
   2127 		tnewline(IS_SET(MODE_CRLF));
   2128 		return;
   2129 	case '\a':   /* BEL */
   2130 		if (term.esc & ESC_STR_END) {
   2131 			/* backwards compatibility to xterm */
   2132 			strhandle();
   2133 		} else {
   2134 			xbell();
   2135 		}
   2136 		break;
   2137 	case '\033': /* ESC */
   2138 		csireset();
   2139 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2140 		term.esc |= ESC_START;
   2141 		return;
   2142 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2143 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2144 		term.charset = 1 - (ascii - '\016');
   2145 		return;
   2146 	case '\032': /* SUB */
   2147 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2148 		/* FALLTHROUGH */
   2149 	case '\030': /* CAN */
   2150 		csireset();
   2151 		break;
   2152 	case '\005': /* ENQ (IGNORED) */
   2153 	case '\000': /* NUL (IGNORED) */
   2154 	case '\021': /* XON (IGNORED) */
   2155 	case '\023': /* XOFF (IGNORED) */
   2156 	case 0177:   /* DEL (IGNORED) */
   2157 		return;
   2158 	case 0x80:   /* TODO: PAD */
   2159 	case 0x81:   /* TODO: HOP */
   2160 	case 0x82:   /* TODO: BPH */
   2161 	case 0x83:   /* TODO: NBH */
   2162 	case 0x84:   /* TODO: IND */
   2163 		break;
   2164 	case 0x85:   /* NEL -- Next line */
   2165 		tnewline(1); /* always go to first col */
   2166 		break;
   2167 	case 0x86:   /* TODO: SSA */
   2168 	case 0x87:   /* TODO: ESA */
   2169 		break;
   2170 	case 0x88:   /* HTS -- Horizontal tab stop */
   2171 		term.tabs[term.c.x] = 1;
   2172 		break;
   2173 	case 0x89:   /* TODO: HTJ */
   2174 	case 0x8a:   /* TODO: VTS */
   2175 	case 0x8b:   /* TODO: PLD */
   2176 	case 0x8c:   /* TODO: PLU */
   2177 	case 0x8d:   /* TODO: RI */
   2178 	case 0x8e:   /* TODO: SS2 */
   2179 	case 0x8f:   /* TODO: SS3 */
   2180 	case 0x91:   /* TODO: PU1 */
   2181 	case 0x92:   /* TODO: PU2 */
   2182 	case 0x93:   /* TODO: STS */
   2183 	case 0x94:   /* TODO: CCH */
   2184 	case 0x95:   /* TODO: MW */
   2185 	case 0x96:   /* TODO: SPA */
   2186 	case 0x97:   /* TODO: EPA */
   2187 	case 0x98:   /* TODO: SOS */
   2188 	case 0x99:   /* TODO: SGCI */
   2189 		break;
   2190 	case 0x9a:   /* DECID -- Identify Terminal */
   2191 		ttywrite(vtiden, strlen(vtiden), 0);
   2192 		break;
   2193 	case 0x9b:   /* TODO: CSI */
   2194 	case 0x9c:   /* TODO: ST */
   2195 		break;
   2196 	case 0x90:   /* DCS -- Device Control String */
   2197 	case 0x9d:   /* OSC -- Operating System Command */
   2198 	case 0x9e:   /* PM -- Privacy Message */
   2199 	case 0x9f:   /* APC -- Application Program Command */
   2200 		tstrsequence(ascii);
   2201 		return;
   2202 	}
   2203 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2204 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2205 }
   2206 
   2207 /*
   2208  * returns 1 when the sequence is finished and it hasn't to read
   2209  * more characters for this sequence, otherwise 0
   2210  */
   2211 int
   2212 eschandle(uchar ascii)
   2213 {
   2214 	switch (ascii) {
   2215 	case '[':
   2216 		term.esc |= ESC_CSI;
   2217 		return 0;
   2218 	case '#':
   2219 		term.esc |= ESC_TEST;
   2220 		return 0;
   2221 	case '%':
   2222 		term.esc |= ESC_UTF8;
   2223 		return 0;
   2224 	case 'P': /* DCS -- Device Control String */
   2225 	case '_': /* APC -- Application Program Command */
   2226 	case '^': /* PM -- Privacy Message */
   2227 	case ']': /* OSC -- Operating System Command */
   2228 	case 'k': /* old title set compatibility */
   2229 		tstrsequence(ascii);
   2230 		return 0;
   2231 	case 'n': /* LS2 -- Locking shift 2 */
   2232 	case 'o': /* LS3 -- Locking shift 3 */
   2233 		term.charset = 2 + (ascii - 'n');
   2234 		break;
   2235 	case '(': /* GZD4 -- set primary charset G0 */
   2236 	case ')': /* G1D4 -- set secondary charset G1 */
   2237 	case '*': /* G2D4 -- set tertiary charset G2 */
   2238 	case '+': /* G3D4 -- set quaternary charset G3 */
   2239 		term.icharset = ascii - '(';
   2240 		term.esc |= ESC_ALTCHARSET;
   2241 		return 0;
   2242 	case 'D': /* IND -- Linefeed */
   2243 		if (term.c.y == term.bot) {
   2244 			tscrollup(term.top, 1);
   2245 		} else {
   2246 			tmoveto(term.c.x, term.c.y+1);
   2247 		}
   2248 		break;
   2249 	case 'E': /* NEL -- Next line */
   2250 		tnewline(1); /* always go to first col */
   2251 		break;
   2252 	case 'H': /* HTS -- Horizontal tab stop */
   2253 		term.tabs[term.c.x] = 1;
   2254 		break;
   2255 	case 'M': /* RI -- Reverse index */
   2256 		if (term.c.y == term.top) {
   2257 			tscrolldown(term.top, 1);
   2258 		} else {
   2259 			tmoveto(term.c.x, term.c.y-1);
   2260 		}
   2261 		break;
   2262 	case 'Z': /* DECID -- Identify Terminal */
   2263 		ttywrite(vtiden, strlen(vtiden), 0);
   2264 		break;
   2265 	case 'c': /* RIS -- Reset to initial state */
   2266 		treset();
   2267 		resettitle();
   2268 		xloadcols();
   2269 		break;
   2270 	case '=': /* DECPAM -- Application keypad */
   2271 		xsetmode(1, MODE_APPKEYPAD);
   2272 		break;
   2273 	case '>': /* DECPNM -- Normal keypad */
   2274 		xsetmode(0, MODE_APPKEYPAD);
   2275 		break;
   2276 	case '7': /* DECSC -- Save Cursor */
   2277 		tcursor(CURSOR_SAVE);
   2278 		break;
   2279 	case '8': /* DECRC -- Restore Cursor */
   2280 		tcursor(CURSOR_LOAD);
   2281 		break;
   2282 	case '\\': /* ST -- String Terminator */
   2283 		if (term.esc & ESC_STR_END)
   2284 			strhandle();
   2285 		break;
   2286 	default:
   2287 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2288 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2289 		break;
   2290 	}
   2291 	return 1;
   2292 }
   2293 
   2294 void
   2295 tputc(Rune u)
   2296 {
   2297 	char c[UTF_SIZ];
   2298 	int control;
   2299 	int width, len;
   2300 	Glyph *gp;
   2301 
   2302 	control = ISCONTROL(u);
   2303 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2304 		c[0] = u;
   2305 		width = len = 1;
   2306 	} else {
   2307 		len = utf8encode(u, c);
   2308 		if (!control && (width = wcwidth(u)) == -1)
   2309 			width = 1;
   2310 	}
   2311 
   2312 	if (IS_SET(MODE_PRINT))
   2313 		tprinter(c, len);
   2314 
   2315 	/*
   2316 	 * STR sequence must be checked before anything else
   2317 	 * because it uses all following characters until it
   2318 	 * receives a ESC, a SUB, a ST or any other C1 control
   2319 	 * character.
   2320 	 */
   2321 	if (term.esc & ESC_STR) {
   2322 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2323 		   ISCONTROLC1(u)) {
   2324 			term.esc &= ~(ESC_START|ESC_STR);
   2325 			term.esc |= ESC_STR_END;
   2326 			goto check_control_code;
   2327 		}
   2328 
   2329 		if (strescseq.len+len >= strescseq.siz) {
   2330 			/*
   2331 			 * Here is a bug in terminals. If the user never sends
   2332 			 * some code to stop the str or esc command, then st
   2333 			 * will stop responding. But this is better than
   2334 			 * silently failing with unknown characters. At least
   2335 			 * then users will report back.
   2336 			 *
   2337 			 * In the case users ever get fixed, here is the code:
   2338 			 */
   2339 			/*
   2340 			 * term.esc = 0;
   2341 			 * strhandle();
   2342 			 */
   2343 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2344 				return;
   2345 			strescseq.siz *= 2;
   2346 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2347 		}
   2348 
   2349 		memmove(&strescseq.buf[strescseq.len], c, len);
   2350 		strescseq.len += len;
   2351 		return;
   2352 	}
   2353 
   2354 check_control_code:
   2355 	/*
   2356 	 * Actions of control codes must be performed as soon they arrive
   2357 	 * because they can be embedded inside a control sequence, and
   2358 	 * they must not cause conflicts with sequences.
   2359 	 */
   2360 	if (control) {
   2361 		tcontrolcode(u);
   2362 		/*
   2363 		 * control codes are not shown ever
   2364 		 */
   2365 		if (!term.esc)
   2366 			term.lastc = 0;
   2367 		return;
   2368 	} else if (term.esc & ESC_START) {
   2369 		if (term.esc & ESC_CSI) {
   2370 			csiescseq.buf[csiescseq.len++] = u;
   2371 			if (BETWEEN(u, 0x40, 0x7E)
   2372 					|| csiescseq.len >= \
   2373 					sizeof(csiescseq.buf)-1) {
   2374 				term.esc = 0;
   2375 				csiparse();
   2376 				csihandle();
   2377 			}
   2378 			return;
   2379 		} else if (term.esc & ESC_UTF8) {
   2380 			tdefutf8(u);
   2381 		} else if (term.esc & ESC_ALTCHARSET) {
   2382 			tdeftran(u);
   2383 		} else if (term.esc & ESC_TEST) {
   2384 			tdectest(u);
   2385 		} else {
   2386 			if (!eschandle(u))
   2387 				return;
   2388 			/* sequence already finished */
   2389 		}
   2390 		term.esc = 0;
   2391 		/*
   2392 		 * All characters which form part of a sequence are not
   2393 		 * printed
   2394 		 */
   2395 		return;
   2396 	}
   2397 	if (selected(term.c.x, term.c.y))
   2398 		selclear();
   2399 
   2400 	gp = &term.line[term.c.y][term.c.x];
   2401 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2402 		gp->mode |= ATTR_WRAP;
   2403 		tnewline(1);
   2404 		gp = &term.line[term.c.y][term.c.x];
   2405 	}
   2406 
   2407 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
   2408 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2409 
   2410 	if (term.c.x+width > term.col) {
   2411 		tnewline(1);
   2412 		gp = &term.line[term.c.y][term.c.x];
   2413 	}
   2414 
   2415 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2416 	term.lastc = u;
   2417 
   2418 	if (width == 2) {
   2419 		gp->mode |= ATTR_WIDE;
   2420 		if (term.c.x+1 < term.col) {
   2421 			gp[1].u = '\0';
   2422 			gp[1].mode = ATTR_WDUMMY;
   2423 		}
   2424 	}
   2425 	if (term.c.x+width < term.col) {
   2426 		tmoveto(term.c.x+width, term.c.y);
   2427 	} else {
   2428 		term.c.state |= CURSOR_WRAPNEXT;
   2429 	}
   2430 }
   2431 
   2432 int
   2433 twrite(const char *buf, int buflen, int show_ctrl)
   2434 {
   2435 	int charsize;
   2436 	Rune u;
   2437 	int n;
   2438 
   2439 	for (n = 0; n < buflen; n += charsize) {
   2440 		if (IS_SET(MODE_UTF8)) {
   2441 			/* process a complete utf8 char */
   2442 			charsize = utf8decode(buf + n, &u, buflen - n);
   2443 			if (charsize == 0)
   2444 				break;
   2445 		} else {
   2446 			u = buf[n] & 0xFF;
   2447 			charsize = 1;
   2448 		}
   2449 		if (show_ctrl && ISCONTROL(u)) {
   2450 			if (u & 0x80) {
   2451 				u &= 0x7f;
   2452 				tputc('^');
   2453 				tputc('[');
   2454 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2455 				u ^= 0x40;
   2456 				tputc('^');
   2457 			}
   2458 		}
   2459 		tputc(u);
   2460 	}
   2461 	return n;
   2462 }
   2463 
   2464 void
   2465 tresize(int col, int row)
   2466 {
   2467 	int i;
   2468 	int minrow = MIN(row, term.row);
   2469 	int mincol = MIN(col, term.col);
   2470 	int *bp;
   2471 	TCursor c;
   2472 
   2473 	if (col < 1 || row < 1) {
   2474 		fprintf(stderr,
   2475 		        "tresize: error resizing to %dx%d\n", col, row);
   2476 		return;
   2477 	}
   2478 
   2479 	/*
   2480 	 * slide screen to keep cursor where we expect it -
   2481 	 * tscrollup would work here, but we can optimize to
   2482 	 * memmove because we're freeing the earlier lines
   2483 	 */
   2484 	for (i = 0; i <= term.c.y - row; i++) {
   2485 		free(term.line[i]);
   2486 		free(term.alt[i]);
   2487 	}
   2488 	/* ensure that both src and dst are not NULL */
   2489 	if (i > 0) {
   2490 		memmove(term.line, term.line + i, row * sizeof(Line));
   2491 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2492 	}
   2493 	for (i += row; i < term.row; i++) {
   2494 		free(term.line[i]);
   2495 		free(term.alt[i]);
   2496 	}
   2497 
   2498 	/* resize to new height */
   2499 	term.line = xrealloc(term.line, row * sizeof(Line));
   2500 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2501 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2502 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2503 
   2504 	/* resize each row to new width, zero-pad if needed */
   2505 	for (i = 0; i < minrow; i++) {
   2506 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2507 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2508 	}
   2509 
   2510 	/* allocate any new rows */
   2511 	for (/* i = minrow */; i < row; i++) {
   2512 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2513 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2514 	}
   2515 	if (col > term.col) {
   2516 		bp = term.tabs + term.col;
   2517 
   2518 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2519 		while (--bp > term.tabs && !*bp)
   2520 			/* nothing */ ;
   2521 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2522 			*bp = 1;
   2523 	}
   2524 	/* update terminal size */
   2525 	term.col = col;
   2526 	term.row = row;
   2527 	/* reset scrolling region */
   2528 	tsetscroll(0, row-1);
   2529 	/* make use of the LIMIT in tmoveto */
   2530 	tmoveto(term.c.x, term.c.y);
   2531 	/* Clearing both screens (it makes dirty all lines) */
   2532 	c = term.c;
   2533 	for (i = 0; i < 2; i++) {
   2534 		if (mincol < col && 0 < minrow) {
   2535 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2536 		}
   2537 		if (0 < col && minrow < row) {
   2538 			tclearregion(0, minrow, col - 1, row - 1);
   2539 		}
   2540 		tswapscreen();
   2541 		tcursor(CURSOR_LOAD);
   2542 	}
   2543 	term.c = c;
   2544 }
   2545 
   2546 void
   2547 resettitle(void)
   2548 {
   2549 	xsettitle(NULL);
   2550 }
   2551 
   2552 void
   2553 drawregion(int x1, int y1, int x2, int y2)
   2554 {
   2555 	int y;
   2556 
   2557 	for (y = y1; y < y2; y++) {
   2558 		if (!term.dirty[y])
   2559 			continue;
   2560 
   2561 		term.dirty[y] = 0;
   2562 		xdrawline(term.line[y], x1, y, x2);
   2563 	}
   2564 }
   2565 
   2566 void
   2567 draw(void)
   2568 {
   2569 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2570 
   2571 	if (!xstartdraw())
   2572 		return;
   2573 
   2574 	/* adjust cursor position */
   2575 	LIMIT(term.ocx, 0, term.col-1);
   2576 	LIMIT(term.ocy, 0, term.row-1);
   2577 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2578 		term.ocx--;
   2579 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2580 		cx--;
   2581 
   2582 	drawregion(0, 0, term.col, term.row);
   2583 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2584 			term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2585 	term.ocx = cx;
   2586 	term.ocy = term.c.y;
   2587 	xfinishdraw();
   2588 	if (ocx != term.ocx || ocy != term.ocy)
   2589 		xximspot(term.ocx, term.ocy);
   2590 }
   2591 
   2592 void
   2593 redraw(void)
   2594 {
   2595 	tfulldirt();
   2596 	draw();
   2597 }