st

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

st.c (58838B)


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