st

my customized branch of st
git clone git://git.jakekoroman.com/st
Log | Files | Refs | README | LICENSE

st.c (59321B)


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