st

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

st.c (55408B)


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