st

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

st.c (57593B)


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