st

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

st.c (58843B)


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