st

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

st.c (58906B)


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