st

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

st.c (55510B)


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