st

my st build
git clone https://git.afify.dev/st.git
Log | Files | Refs | README | LICENSE

st.c (58517B)


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