st

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

st.c (59319B)


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