st

Suckless' st with my personal patches
git clone https://git.instinctive.eu/st.git
Log | Files | Refs | README | LICENSE

st.c (58401B)


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