st

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

st.c (57204B)


      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 int
    236 subprocwd(char *path)
    237 {
    238 	if (snprintf(path, PATH_MAX, "/proc/%d/cwd", pid) < 0)
    239 		return -1;
    240 
    241 	return 0;
    242 }
    243 
    244 ssize_t
    245 xwrite(int fd, const char *s, size_t len)
    246 {
    247 	size_t aux = len;
    248 	ssize_t r;
    249 
    250 	while (len > 0) {
    251 		r = write(fd, s, len);
    252 		if (r < 0)
    253 			return r;
    254 		len -= r;
    255 		s += r;
    256 	}
    257 
    258 	return aux;
    259 }
    260 
    261 void *
    262 xmalloc(size_t len)
    263 {
    264 	void *p;
    265 
    266 	if (!(p = malloc(len)))
    267 		die("malloc: %s\n", strerror(errno));
    268 
    269 	return p;
    270 }
    271 
    272 void *
    273 xrealloc(void *p, size_t len)
    274 {
    275 	if ((p = realloc(p, len)) == NULL)
    276 		die("realloc: %s\n", strerror(errno));
    277 
    278 	return p;
    279 }
    280 
    281 char *
    282 xstrdup(const char *s)
    283 {
    284 	char *p;
    285 
    286 	if ((p = strdup(s)) == NULL)
    287 		die("strdup: %s\n", strerror(errno));
    288 
    289 	return p;
    290 }
    291 
    292 size_t
    293 utf8decode(const char *c, Rune *u, size_t clen)
    294 {
    295 	size_t i, j, len, type;
    296 	Rune udecoded;
    297 
    298 	*u = UTF_INVALID;
    299 	if (!clen)
    300 		return 0;
    301 	udecoded = utf8decodebyte(c[0], &len);
    302 	if (!BETWEEN(len, 1, UTF_SIZ))
    303 		return 1;
    304 	for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
    305 		udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
    306 		if (type != 0)
    307 			return j;
    308 	}
    309 	if (j < len)
    310 		return 0;
    311 	*u = udecoded;
    312 	utf8validate(u, len);
    313 
    314 	return len;
    315 }
    316 
    317 Rune
    318 utf8decodebyte(char c, size_t *i)
    319 {
    320 	for (*i = 0; *i < LEN(utfmask); ++(*i))
    321 		if (((uchar)c & utfmask[*i]) == utfbyte[*i])
    322 			return (uchar)c & ~utfmask[*i];
    323 
    324 	return 0;
    325 }
    326 
    327 size_t
    328 utf8encode(Rune u, char *c)
    329 {
    330 	size_t len, i;
    331 
    332 	len = utf8validate(&u, 0);
    333 	if (len > UTF_SIZ)
    334 		return 0;
    335 
    336 	for (i = len - 1; i != 0; --i) {
    337 		c[i] = utf8encodebyte(u, 0);
    338 		u >>= 6;
    339 	}
    340 	c[0] = utf8encodebyte(u, len);
    341 
    342 	return len;
    343 }
    344 
    345 char
    346 utf8encodebyte(Rune u, size_t i)
    347 {
    348 	return utfbyte[i] | (u & ~utfmask[i]);
    349 }
    350 
    351 size_t
    352 utf8validate(Rune *u, size_t i)
    353 {
    354 	if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
    355 		*u = UTF_INVALID;
    356 	for (i = 1; *u > utfmax[i]; ++i)
    357 		;
    358 
    359 	return i;
    360 }
    361 
    362 char
    363 base64dec_getc(const char **src)
    364 {
    365 	while (**src && !isprint((unsigned char)**src))
    366 		(*src)++;
    367 	return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
    368 }
    369 
    370 char *
    371 base64dec(const char *src)
    372 {
    373 	size_t in_len = strlen(src);
    374 	char *result, *dst;
    375 	static const char base64_digits[256] = {
    376 		[43] = 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
    377 		0, 0, 0, -1, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
    378 		13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0,
    379 		0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
    380 		40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    381 	};
    382 
    383 	if (in_len % 4)
    384 		in_len += 4 - (in_len % 4);
    385 	result = dst = xmalloc(in_len / 4 * 3 + 1);
    386 	while (*src) {
    387 		int a = base64_digits[(unsigned char) base64dec_getc(&src)];
    388 		int b = base64_digits[(unsigned char) base64dec_getc(&src)];
    389 		int c = base64_digits[(unsigned char) base64dec_getc(&src)];
    390 		int d = base64_digits[(unsigned char) base64dec_getc(&src)];
    391 
    392 		/* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
    393 		if (a == -1 || b == -1)
    394 			break;
    395 
    396 		*dst++ = (a << 2) | ((b & 0x30) >> 4);
    397 		if (c == -1)
    398 			break;
    399 		*dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
    400 		if (d == -1)
    401 			break;
    402 		*dst++ = ((c & 0x03) << 6) | d;
    403 	}
    404 	*dst = '\0';
    405 	return result;
    406 }
    407 
    408 void
    409 selinit(void)
    410 {
    411 	sel.mode = SEL_IDLE;
    412 	sel.snap = 0;
    413 	sel.ob.x = -1;
    414 }
    415 
    416 int
    417 tlinelen(int y)
    418 {
    419 	int i = term.col;
    420 
    421 	if (term.line[y][i - 1].mode & ATTR_WRAP)
    422 		return i;
    423 
    424 	while (i > 0 && term.line[y][i - 1].u == ' ')
    425 		--i;
    426 
    427 	return i;
    428 }
    429 
    430 void
    431 selstart(int col, int row, int snap)
    432 {
    433 	selclear();
    434 	sel.mode = SEL_EMPTY;
    435 	sel.type = SEL_REGULAR;
    436 	sel.alt = IS_SET(MODE_ALTSCREEN);
    437 	sel.snap = snap;
    438 	sel.oe.x = sel.ob.x = col;
    439 	sel.oe.y = sel.ob.y = row;
    440 	selnormalize();
    441 
    442 	if (sel.snap != 0)
    443 		sel.mode = SEL_READY;
    444 	tsetdirt(sel.nb.y, sel.ne.y);
    445 }
    446 
    447 void
    448 selextend(int col, int row, int type, int done)
    449 {
    450 	int oldey, oldex, oldsby, oldsey, oldtype;
    451 
    452 	if (sel.mode == SEL_IDLE)
    453 		return;
    454 	if (done && sel.mode == SEL_EMPTY) {
    455 		selclear();
    456 		return;
    457 	}
    458 
    459 	oldey = sel.oe.y;
    460 	oldex = sel.oe.x;
    461 	oldsby = sel.nb.y;
    462 	oldsey = sel.ne.y;
    463 	oldtype = sel.type;
    464 
    465 	sel.oe.x = col;
    466 	sel.oe.y = row;
    467 	selnormalize();
    468 	sel.type = type;
    469 
    470 	if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
    471 		tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
    472 
    473 	sel.mode = done ? SEL_IDLE : SEL_READY;
    474 }
    475 
    476 void
    477 selnormalize(void)
    478 {
    479 	int i;
    480 
    481 	if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
    482 		sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
    483 		sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
    484 	} else {
    485 		sel.nb.x = MIN(sel.ob.x, sel.oe.x);
    486 		sel.ne.x = MAX(sel.ob.x, sel.oe.x);
    487 	}
    488 	sel.nb.y = MIN(sel.ob.y, sel.oe.y);
    489 	sel.ne.y = MAX(sel.ob.y, sel.oe.y);
    490 
    491 	selsnap(&sel.nb.x, &sel.nb.y, -1);
    492 	selsnap(&sel.ne.x, &sel.ne.y, +1);
    493 
    494 	/* expand selection over line breaks */
    495 	if (sel.type == SEL_RECTANGULAR)
    496 		return;
    497 	i = tlinelen(sel.nb.y);
    498 	if (i < sel.nb.x)
    499 		sel.nb.x = i;
    500 	if (tlinelen(sel.ne.y) <= sel.ne.x)
    501 		sel.ne.x = term.col - 1;
    502 }
    503 
    504 int
    505 selected(int x, int y)
    506 {
    507 	if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
    508 			sel.alt != IS_SET(MODE_ALTSCREEN))
    509 		return 0;
    510 
    511 	if (sel.type == SEL_RECTANGULAR)
    512 		return BETWEEN(y, sel.nb.y, sel.ne.y)
    513 		    && BETWEEN(x, sel.nb.x, sel.ne.x);
    514 
    515 	return BETWEEN(y, sel.nb.y, sel.ne.y)
    516 	    && (y != sel.nb.y || x >= sel.nb.x)
    517 	    && (y != sel.ne.y || x <= sel.ne.x);
    518 }
    519 
    520 void
    521 selsnap(int *x, int *y, int direction)
    522 {
    523 	int newx, newy, xt, yt;
    524 	int delim, prevdelim;
    525 	const Glyph *gp, *prevgp;
    526 
    527 	switch (sel.snap) {
    528 	case SNAP_WORD:
    529 		/*
    530 		 * Snap around if the word wraps around at the end or
    531 		 * beginning of a line.
    532 		 */
    533 		prevgp = &term.line[*y][*x];
    534 		prevdelim = ISDELIM(prevgp->u);
    535 		for (;;) {
    536 			newx = *x + direction;
    537 			newy = *y;
    538 			if (!BETWEEN(newx, 0, term.col - 1)) {
    539 				newy += direction;
    540 				newx = (newx + term.col) % term.col;
    541 				if (!BETWEEN(newy, 0, term.row - 1))
    542 					break;
    543 
    544 				if (direction > 0)
    545 					yt = *y, xt = *x;
    546 				else
    547 					yt = newy, xt = newx;
    548 				if (!(term.line[yt][xt].mode & ATTR_WRAP))
    549 					break;
    550 			}
    551 
    552 			if (newx >= tlinelen(newy))
    553 				break;
    554 
    555 			gp = &term.line[newy][newx];
    556 			delim = ISDELIM(gp->u);
    557 			if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
    558 					|| (delim && gp->u != prevgp->u)))
    559 				break;
    560 
    561 			*x = newx;
    562 			*y = newy;
    563 			prevgp = gp;
    564 			prevdelim = delim;
    565 		}
    566 		break;
    567 	case SNAP_LINE:
    568 		/*
    569 		 * Snap around if the the previous line or the current one
    570 		 * has set ATTR_WRAP at its end. Then the whole next or
    571 		 * previous line will be selected.
    572 		 */
    573 		*x = (direction < 0) ? 0 : term.col - 1;
    574 		if (direction < 0) {
    575 			for (; *y > 0; *y += direction) {
    576 				if (!(term.line[*y-1][term.col-1].mode
    577 						& ATTR_WRAP)) {
    578 					break;
    579 				}
    580 			}
    581 		} else if (direction > 0) {
    582 			for (; *y < term.row-1; *y += direction) {
    583 				if (!(term.line[*y][term.col-1].mode
    584 						& ATTR_WRAP)) {
    585 					break;
    586 				}
    587 			}
    588 		}
    589 		break;
    590 	}
    591 }
    592 
    593 char *
    594 getsel(void)
    595 {
    596 	char *str, *ptr;
    597 	int y, bufsize, lastx, linelen;
    598 	const Glyph *gp, *last;
    599 
    600 	if (sel.ob.x == -1)
    601 		return NULL;
    602 
    603 	bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
    604 	ptr = str = xmalloc(bufsize);
    605 
    606 	/* append every set & selected glyph to the selection */
    607 	for (y = sel.nb.y; y <= sel.ne.y; y++) {
    608 		if ((linelen = tlinelen(y)) == 0) {
    609 			*ptr++ = '\n';
    610 			continue;
    611 		}
    612 
    613 		if (sel.type == SEL_RECTANGULAR) {
    614 			gp = &term.line[y][sel.nb.x];
    615 			lastx = sel.ne.x;
    616 		} else {
    617 			gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
    618 			lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
    619 		}
    620 		last = &term.line[y][MIN(lastx, linelen-1)];
    621 		while (last >= gp && last->u == ' ')
    622 			--last;
    623 
    624 		for ( ; gp <= last; ++gp) {
    625 			if (gp->mode & ATTR_WDUMMY)
    626 				continue;
    627 
    628 			ptr += utf8encode(gp->u, ptr);
    629 		}
    630 
    631 		/*
    632 		 * Copy and pasting of line endings is inconsistent
    633 		 * in the inconsistent terminal and GUI world.
    634 		 * The best solution seems like to produce '\n' when
    635 		 * something is copied from st and convert '\n' to
    636 		 * '\r', when something to be pasted is received by
    637 		 * st.
    638 		 * FIXME: Fix the computer world.
    639 		 */
    640 		if ((y < sel.ne.y || lastx >= linelen) &&
    641 		    (!(last->mode & ATTR_WRAP) || sel.type == SEL_RECTANGULAR))
    642 			*ptr++ = '\n';
    643 	}
    644 	*ptr = 0;
    645 	return str;
    646 }
    647 
    648 void
    649 selclear(void)
    650 {
    651 	if (sel.ob.x == -1)
    652 		return;
    653 	sel.mode = SEL_IDLE;
    654 	sel.ob.x = -1;
    655 	tsetdirt(sel.nb.y, sel.ne.y);
    656 }
    657 
    658 void
    659 die(const char *errstr, ...)
    660 {
    661 	va_list ap;
    662 
    663 	va_start(ap, errstr);
    664 	vfprintf(stderr, errstr, ap);
    665 	va_end(ap);
    666 	exit(1);
    667 }
    668 
    669 void
    670 execsh(char *cmd, char **args)
    671 {
    672 	char *sh, *prog, *arg;
    673 	const struct passwd *pw;
    674 
    675 	errno = 0;
    676 	if ((pw = getpwuid(getuid())) == NULL) {
    677 		if (errno)
    678 			die("getpwuid: %s\n", strerror(errno));
    679 		else
    680 			die("who are you?\n");
    681 	}
    682 
    683 	if ((sh = getenv("SHELL")) == NULL)
    684 		sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
    685 
    686 	if (args) {
    687 		prog = args[0];
    688 		arg = NULL;
    689 	} else if (scroll) {
    690 		prog = scroll;
    691 		arg = utmp ? utmp : sh;
    692 	} else if (utmp) {
    693 		prog = utmp;
    694 		arg = NULL;
    695 	} else {
    696 		prog = sh;
    697 		arg = NULL;
    698 	}
    699 	DEFAULT(args, ((char *[]) {prog, arg, NULL}));
    700 
    701 	unsetenv("COLUMNS");
    702 	unsetenv("LINES");
    703 	unsetenv("TERMCAP");
    704 	setenv("LOGNAME", pw->pw_name, 1);
    705 	setenv("USER", pw->pw_name, 1);
    706 	setenv("SHELL", sh, 1);
    707 	setenv("HOME", pw->pw_dir, 1);
    708 	setenv("TERM", termname, 1);
    709 
    710 	signal(SIGCHLD, SIG_DFL);
    711 	signal(SIGHUP, SIG_DFL);
    712 	signal(SIGINT, SIG_DFL);
    713 	signal(SIGQUIT, SIG_DFL);
    714 	signal(SIGTERM, SIG_DFL);
    715 	signal(SIGALRM, SIG_DFL);
    716 
    717 	execvp(prog, args);
    718 	_exit(1);
    719 }
    720 
    721 void
    722 sigchld(int a)
    723 {
    724 	int stat;
    725 	pid_t p;
    726 
    727 	if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
    728 		die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
    729 
    730 	if (pid != p)
    731 		return;
    732 
    733 	if (WIFEXITED(stat) && WEXITSTATUS(stat))
    734 		die("child exited with status %d\n", WEXITSTATUS(stat));
    735 	else if (WIFSIGNALED(stat))
    736 		die("child terminated due to signal %d\n", WTERMSIG(stat));
    737 	_exit(0);
    738 }
    739 
    740 void
    741 stty(char **args)
    742 {
    743 	char cmd[_POSIX_ARG_MAX], **p, *q, *s;
    744 	size_t n, siz;
    745 
    746 	if ((n = strlen(stty_args)) > sizeof(cmd)-1)
    747 		die("incorrect stty parameters\n");
    748 	memcpy(cmd, stty_args, n);
    749 	q = cmd + n;
    750 	siz = sizeof(cmd) - n;
    751 	for (p = args; p && (s = *p); ++p) {
    752 		if ((n = strlen(s)) > siz-1)
    753 			die("stty parameter length too long\n");
    754 		*q++ = ' ';
    755 		memcpy(q, s, n);
    756 		q += n;
    757 		siz -= n + 1;
    758 	}
    759 	*q = '\0';
    760 	if (system(cmd) != 0)
    761 		perror("Couldn't call stty");
    762 }
    763 
    764 int
    765 ttynew(const char *line, char *cmd, const char *out, char **args)
    766 {
    767 	int m, s;
    768 
    769 	if (out) {
    770 		term.mode |= MODE_PRINT;
    771 		iofd = (!strcmp(out, "-")) ?
    772 			  1 : open(out, O_WRONLY | O_CREAT, 0666);
    773 		if (iofd < 0) {
    774 			fprintf(stderr, "Error opening %s:%s\n",
    775 				out, strerror(errno));
    776 		}
    777 	}
    778 
    779 	if (line) {
    780 		if ((cmdfd = open(line, O_RDWR)) < 0)
    781 			die("open line '%s' failed: %s\n",
    782 			    line, strerror(errno));
    783 		dup2(cmdfd, 0);
    784 		stty(args);
    785 		return cmdfd;
    786 	}
    787 
    788 	/* seems to work fine on linux, openbsd and freebsd */
    789 	if (openpty(&m, &s, NULL, NULL, NULL) < 0)
    790 		die("openpty failed: %s\n", strerror(errno));
    791 
    792 	switch (pid = fork()) {
    793 	case -1:
    794 		die("fork failed: %s\n", strerror(errno));
    795 		break;
    796 	case 0:
    797 		close(iofd);
    798 		close(m);
    799 		setsid(); /* create a new process group */
    800 		dup2(s, 0);
    801 		dup2(s, 1);
    802 		dup2(s, 2);
    803 		if (ioctl(s, TIOCSCTTY, NULL) < 0)
    804 			die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
    805 		if (s > 2)
    806 			close(s);
    807 #ifdef __OpenBSD__
    808 		if (pledge("stdio getpw proc exec", NULL) == -1)
    809 			die("pledge\n");
    810 #endif
    811 		execsh(cmd, args);
    812 		break;
    813 	default:
    814 #ifdef __OpenBSD__
    815 		if (pledge("stdio rpath tty proc", NULL) == -1)
    816 			die("pledge\n");
    817 #endif
    818 		close(s);
    819 		cmdfd = m;
    820 		signal(SIGCHLD, sigchld);
    821 		break;
    822 	}
    823 	return cmdfd;
    824 }
    825 
    826 size_t
    827 ttyread(void)
    828 {
    829 	static char buf[BUFSIZ];
    830 	static int buflen = 0;
    831 	int ret, written;
    832 
    833 	/* append read bytes to unprocessed bytes */
    834 	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
    835 
    836 	switch (ret) {
    837 	case 0:
    838 		exit(0);
    839 	case -1:
    840 		die("couldn't read from shell: %s\n", strerror(errno));
    841 	default:
    842 		buflen += ret;
    843 		written = twrite(buf, buflen, 0);
    844 		buflen -= written;
    845 		/* keep any incomplete UTF-8 byte sequence for the next call */
    846 		if (buflen > 0)
    847 			memmove(buf, buf + written, buflen);
    848 		return ret;
    849 	}
    850 }
    851 
    852 void
    853 ttywrite(const char *s, size_t n, int may_echo)
    854 {
    855 	const char *next;
    856 
    857 	if (may_echo && IS_SET(MODE_ECHO))
    858 		twrite(s, n, 1);
    859 
    860 	if (!IS_SET(MODE_CRLF)) {
    861 		ttywriteraw(s, n);
    862 		return;
    863 	}
    864 
    865 	/* This is similar to how the kernel handles ONLCR for ttys */
    866 	while (n > 0) {
    867 		if (*s == '\r') {
    868 			next = s + 1;
    869 			ttywriteraw("\r\n", 2);
    870 		} else {
    871 			next = memchr(s, '\r', n);
    872 			DEFAULT(next, s + n);
    873 			ttywriteraw(s, next - s);
    874 		}
    875 		n -= next - s;
    876 		s = next;
    877 	}
    878 }
    879 
    880 void
    881 ttywriteraw(const char *s, size_t n)
    882 {
    883 	fd_set wfd, rfd;
    884 	ssize_t r;
    885 	size_t lim = 256;
    886 
    887 	/*
    888 	 * Remember that we are using a pty, which might be a modem line.
    889 	 * Writing too much will clog the line. That's why we are doing this
    890 	 * dance.
    891 	 * FIXME: Migrate the world to Plan 9.
    892 	 */
    893 	while (n > 0) {
    894 		FD_ZERO(&wfd);
    895 		FD_ZERO(&rfd);
    896 		FD_SET(cmdfd, &wfd);
    897 		FD_SET(cmdfd, &rfd);
    898 
    899 		/* Check if we can write. */
    900 		if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
    901 			if (errno == EINTR)
    902 				continue;
    903 			die("select failed: %s\n", strerror(errno));
    904 		}
    905 		if (FD_ISSET(cmdfd, &wfd)) {
    906 			/*
    907 			 * Only write the bytes written by ttywrite() or the
    908 			 * default of 256. This seems to be a reasonable value
    909 			 * for a serial line. Bigger values might clog the I/O.
    910 			 */
    911 			if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
    912 				goto write_error;
    913 			if (r < n) {
    914 				/*
    915 				 * We weren't able to write out everything.
    916 				 * This means the buffer is getting full
    917 				 * again. Empty it.
    918 				 */
    919 				if (n < lim)
    920 					lim = ttyread();
    921 				n -= r;
    922 				s += r;
    923 			} else {
    924 				/* All bytes have been written. */
    925 				break;
    926 			}
    927 		}
    928 		if (FD_ISSET(cmdfd, &rfd))
    929 			lim = ttyread();
    930 	}
    931 	return;
    932 
    933 write_error:
    934 	die("write error on tty: %s\n", strerror(errno));
    935 }
    936 
    937 void
    938 ttyresize(int tw, int th)
    939 {
    940 	struct winsize w;
    941 
    942 	w.ws_row = term.row;
    943 	w.ws_col = term.col;
    944 	w.ws_xpixel = tw;
    945 	w.ws_ypixel = th;
    946 	if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
    947 		fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
    948 }
    949 
    950 void
    951 ttyhangup(void)
    952 {
    953 	/* Send SIGHUP to shell */
    954 	kill(pid, SIGHUP);
    955 }
    956 
    957 int
    958 tattrset(int attr)
    959 {
    960 	int i, j;
    961 
    962 	for (i = 0; i < term.row-1; i++) {
    963 		for (j = 0; j < term.col-1; j++) {
    964 			if (term.line[i][j].mode & attr)
    965 				return 1;
    966 		}
    967 	}
    968 
    969 	return 0;
    970 }
    971 
    972 void
    973 tsetdirt(int top, int bot)
    974 {
    975 	int i;
    976 
    977 	LIMIT(top, 0, term.row-1);
    978 	LIMIT(bot, 0, term.row-1);
    979 
    980 	for (i = top; i <= bot; i++)
    981 		term.dirty[i] = 1;
    982 }
    983 
    984 void
    985 tsetdirtattr(int attr)
    986 {
    987 	int i, j;
    988 
    989 	for (i = 0; i < term.row-1; i++) {
    990 		for (j = 0; j < term.col-1; j++) {
    991 			if (term.line[i][j].mode & attr) {
    992 				tsetdirt(i, i);
    993 				break;
    994 			}
    995 		}
    996 	}
    997 }
    998 
    999 void
   1000 tfulldirt(void)
   1001 {
   1002 	tsetdirt(0, term.row-1);
   1003 }
   1004 
   1005 void
   1006 tcursor(int mode)
   1007 {
   1008 	static TCursor c[2];
   1009 	int alt = IS_SET(MODE_ALTSCREEN);
   1010 
   1011 	if (mode == CURSOR_SAVE) {
   1012 		c[alt] = term.c;
   1013 	} else if (mode == CURSOR_LOAD) {
   1014 		term.c = c[alt];
   1015 		tmoveto(c[alt].x, c[alt].y);
   1016 	}
   1017 }
   1018 
   1019 void
   1020 treset(void)
   1021 {
   1022 	uint i;
   1023 
   1024 	term.c = (TCursor){{
   1025 		.mode = ATTR_NULL,
   1026 		.fg = defaultfg,
   1027 		.bg = defaultbg
   1028 	}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
   1029 
   1030 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1031 	for (i = tabspaces; i < term.col; i += tabspaces)
   1032 		term.tabs[i] = 1;
   1033 	term.top = 0;
   1034 	term.bot = term.row - 1;
   1035 	term.mode = MODE_WRAP|MODE_UTF8;
   1036 	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
   1037 	term.charset = 0;
   1038 
   1039 	for (i = 0; i < 2; i++) {
   1040 		tmoveto(0, 0);
   1041 		tcursor(CURSOR_SAVE);
   1042 		tclearregion(0, 0, term.col-1, term.row-1);
   1043 		tswapscreen();
   1044 	}
   1045 }
   1046 
   1047 void
   1048 tnew(int col, int row)
   1049 {
   1050 	term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
   1051 	tresize(col, row);
   1052 	treset();
   1053 }
   1054 
   1055 void
   1056 tswapscreen(void)
   1057 {
   1058 	Line *tmp = term.line;
   1059 
   1060 	term.line = term.alt;
   1061 	term.alt = tmp;
   1062 	term.mode ^= MODE_ALTSCREEN;
   1063 	tfulldirt();
   1064 }
   1065 
   1066 void
   1067 tscrolldown(int orig, int n)
   1068 {
   1069 	int i;
   1070 	Line temp;
   1071 
   1072 	LIMIT(n, 0, term.bot-orig+1);
   1073 
   1074 	tsetdirt(orig, term.bot-n);
   1075 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
   1076 
   1077 	for (i = term.bot; i >= orig+n; i--) {
   1078 		temp = term.line[i];
   1079 		term.line[i] = term.line[i-n];
   1080 		term.line[i-n] = temp;
   1081 	}
   1082 
   1083 	selscroll(orig, n);
   1084 }
   1085 
   1086 void
   1087 tscrollup(int orig, int n)
   1088 {
   1089 	int i;
   1090 	Line temp;
   1091 
   1092 	LIMIT(n, 0, term.bot-orig+1);
   1093 
   1094 	tclearregion(0, orig, term.col-1, orig+n-1);
   1095 	tsetdirt(orig+n, term.bot);
   1096 
   1097 	for (i = orig; i <= term.bot-n; i++) {
   1098 		temp = term.line[i];
   1099 		term.line[i] = term.line[i+n];
   1100 		term.line[i+n] = temp;
   1101 	}
   1102 
   1103 	selscroll(orig, -n);
   1104 }
   1105 
   1106 void
   1107 selscroll(int orig, int n)
   1108 {
   1109 	if (sel.ob.x == -1)
   1110 		return;
   1111 
   1112 	if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
   1113 		selclear();
   1114 	} else if (BETWEEN(sel.nb.y, orig, term.bot)) {
   1115 		sel.ob.y += n;
   1116 		sel.oe.y += n;
   1117 		if (sel.ob.y < term.top || sel.ob.y > term.bot ||
   1118 		    sel.oe.y < term.top || sel.oe.y > term.bot) {
   1119 			selclear();
   1120 		} else {
   1121 			selnormalize();
   1122 		}
   1123 	}
   1124 }
   1125 
   1126 void
   1127 tnewline(int first_col)
   1128 {
   1129 	int y = term.c.y;
   1130 
   1131 	if (y == term.bot) {
   1132 		tscrollup(term.top, 1);
   1133 	} else {
   1134 		y++;
   1135 	}
   1136 	tmoveto(first_col ? 0 : term.c.x, y);
   1137 }
   1138 
   1139 void
   1140 csiparse(void)
   1141 {
   1142 	char *p = csiescseq.buf, *np;
   1143 	long int v;
   1144 
   1145 	csiescseq.narg = 0;
   1146 	if (*p == '?') {
   1147 		csiescseq.priv = 1;
   1148 		p++;
   1149 	}
   1150 
   1151 	csiescseq.buf[csiescseq.len] = '\0';
   1152 	while (p < csiescseq.buf+csiescseq.len) {
   1153 		np = NULL;
   1154 		v = strtol(p, &np, 10);
   1155 		if (np == p)
   1156 			v = 0;
   1157 		if (v == LONG_MAX || v == LONG_MIN)
   1158 			v = -1;
   1159 		csiescseq.arg[csiescseq.narg++] = v;
   1160 		p = np;
   1161 		if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
   1162 			break;
   1163 		p++;
   1164 	}
   1165 	csiescseq.mode[0] = *p++;
   1166 	csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
   1167 }
   1168 
   1169 /* for absolute user moves, when decom is set */
   1170 void
   1171 tmoveato(int x, int y)
   1172 {
   1173 	tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
   1174 }
   1175 
   1176 void
   1177 tmoveto(int x, int y)
   1178 {
   1179 	int miny, maxy;
   1180 
   1181 	if (term.c.state & CURSOR_ORIGIN) {
   1182 		miny = term.top;
   1183 		maxy = term.bot;
   1184 	} else {
   1185 		miny = 0;
   1186 		maxy = term.row - 1;
   1187 	}
   1188 	term.c.state &= ~CURSOR_WRAPNEXT;
   1189 	term.c.x = LIMIT(x, 0, term.col-1);
   1190 	term.c.y = LIMIT(y, miny, maxy);
   1191 }
   1192 
   1193 void
   1194 tsetchar(Rune u, const Glyph *attr, int x, int y)
   1195 {
   1196 	static const char *vt100_0[62] = { /* 0x41 - 0x7e */
   1197 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
   1198 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
   1199 		0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
   1200 		0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
   1201 		"◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
   1202 		"␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
   1203 		"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
   1204 		"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
   1205 	};
   1206 
   1207 	/*
   1208 	 * The table is proudly stolen from rxvt.
   1209 	 */
   1210 	if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
   1211 	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
   1212 		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
   1213 
   1214 	if (term.line[y][x].mode & ATTR_WIDE) {
   1215 		if (x+1 < term.col) {
   1216 			term.line[y][x+1].u = ' ';
   1217 			term.line[y][x+1].mode &= ~ATTR_WDUMMY;
   1218 		}
   1219 	} else if (term.line[y][x].mode & ATTR_WDUMMY) {
   1220 		term.line[y][x-1].u = ' ';
   1221 		term.line[y][x-1].mode &= ~ATTR_WIDE;
   1222 	}
   1223 
   1224 	term.dirty[y] = 1;
   1225 	term.line[y][x] = *attr;
   1226 	term.line[y][x].u = u;
   1227 }
   1228 
   1229 void
   1230 tclearregion(int x1, int y1, int x2, int y2)
   1231 {
   1232 	int x, y, temp;
   1233 	Glyph *gp;
   1234 
   1235 	if (x1 > x2)
   1236 		temp = x1, x1 = x2, x2 = temp;
   1237 	if (y1 > y2)
   1238 		temp = y1, y1 = y2, y2 = temp;
   1239 
   1240 	LIMIT(x1, 0, term.col-1);
   1241 	LIMIT(x2, 0, term.col-1);
   1242 	LIMIT(y1, 0, term.row-1);
   1243 	LIMIT(y2, 0, term.row-1);
   1244 
   1245 	for (y = y1; y <= y2; y++) {
   1246 		term.dirty[y] = 1;
   1247 		for (x = x1; x <= x2; x++) {
   1248 			gp = &term.line[y][x];
   1249 			if (selected(x, y))
   1250 				selclear();
   1251 			gp->fg = term.c.attr.fg;
   1252 			gp->bg = term.c.attr.bg;
   1253 			gp->mode = 0;
   1254 			gp->u = ' ';
   1255 		}
   1256 	}
   1257 }
   1258 
   1259 void
   1260 tdeletechar(int n)
   1261 {
   1262 	int dst, src, size;
   1263 	Glyph *line;
   1264 
   1265 	LIMIT(n, 0, term.col - term.c.x);
   1266 
   1267 	dst = term.c.x;
   1268 	src = term.c.x + n;
   1269 	size = term.col - src;
   1270 	line = term.line[term.c.y];
   1271 
   1272 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1273 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1274 }
   1275 
   1276 void
   1277 tinsertblank(int n)
   1278 {
   1279 	int dst, src, size;
   1280 	Glyph *line;
   1281 
   1282 	LIMIT(n, 0, term.col - term.c.x);
   1283 
   1284 	dst = term.c.x + n;
   1285 	src = term.c.x;
   1286 	size = term.col - dst;
   1287 	line = term.line[term.c.y];
   1288 
   1289 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1290 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1291 }
   1292 
   1293 void
   1294 tinsertblankline(int n)
   1295 {
   1296 	if (BETWEEN(term.c.y, term.top, term.bot))
   1297 		tscrolldown(term.c.y, n);
   1298 }
   1299 
   1300 void
   1301 tdeleteline(int n)
   1302 {
   1303 	if (BETWEEN(term.c.y, term.top, term.bot))
   1304 		tscrollup(term.c.y, n);
   1305 }
   1306 
   1307 int32_t
   1308 tdefcolor(const int *attr, int *npar, int l)
   1309 {
   1310 	int32_t idx = -1;
   1311 	uint r, g, b;
   1312 
   1313 	switch (attr[*npar + 1]) {
   1314 	case 2: /* direct color in RGB space */
   1315 		if (*npar + 4 >= l) {
   1316 			fprintf(stderr,
   1317 				"erresc(38): Incorrect number of parameters (%d)\n",
   1318 				*npar);
   1319 			break;
   1320 		}
   1321 		r = attr[*npar + 2];
   1322 		g = attr[*npar + 3];
   1323 		b = attr[*npar + 4];
   1324 		*npar += 4;
   1325 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1326 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1327 				r, g, b);
   1328 		else
   1329 			idx = TRUECOLOR(r, g, b);
   1330 		break;
   1331 	case 5: /* indexed color */
   1332 		if (*npar + 2 >= l) {
   1333 			fprintf(stderr,
   1334 				"erresc(38): Incorrect number of parameters (%d)\n",
   1335 				*npar);
   1336 			break;
   1337 		}
   1338 		*npar += 2;
   1339 		if (!BETWEEN(attr[*npar], 0, 255))
   1340 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1341 		else
   1342 			idx = attr[*npar];
   1343 		break;
   1344 	case 0: /* implemented defined (only foreground) */
   1345 	case 1: /* transparent */
   1346 	case 3: /* direct color in CMY space */
   1347 	case 4: /* direct color in CMYK space */
   1348 	default:
   1349 		fprintf(stderr,
   1350 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1351 		break;
   1352 	}
   1353 
   1354 	return idx;
   1355 }
   1356 
   1357 void
   1358 tsetattr(const int *attr, int l)
   1359 {
   1360 	int i;
   1361 	int32_t idx;
   1362 
   1363 	for (i = 0; i < l; i++) {
   1364 		switch (attr[i]) {
   1365 		case 0:
   1366 			term.c.attr.mode &= ~(
   1367 				ATTR_BOLD       |
   1368 				ATTR_FAINT      |
   1369 				ATTR_ITALIC     |
   1370 				ATTR_UNDERLINE  |
   1371 				ATTR_BLINK      |
   1372 				ATTR_REVERSE    |
   1373 				ATTR_INVISIBLE  |
   1374 				ATTR_STRUCK     );
   1375 			term.c.attr.fg = defaultfg;
   1376 			term.c.attr.bg = defaultbg;
   1377 			break;
   1378 		case 1:
   1379 			term.c.attr.mode |= ATTR_BOLD;
   1380 			break;
   1381 		case 2:
   1382 			term.c.attr.mode |= ATTR_FAINT;
   1383 			break;
   1384 		case 3:
   1385 			term.c.attr.mode |= ATTR_ITALIC;
   1386 			break;
   1387 		case 4:
   1388 			term.c.attr.mode |= ATTR_UNDERLINE;
   1389 			break;
   1390 		case 5: /* slow blink */
   1391 			/* FALLTHROUGH */
   1392 		case 6: /* rapid blink */
   1393 			term.c.attr.mode |= ATTR_BLINK;
   1394 			break;
   1395 		case 7:
   1396 			term.c.attr.mode |= ATTR_REVERSE;
   1397 			break;
   1398 		case 8:
   1399 			term.c.attr.mode |= ATTR_INVISIBLE;
   1400 			break;
   1401 		case 9:
   1402 			term.c.attr.mode |= ATTR_STRUCK;
   1403 			break;
   1404 		case 22:
   1405 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1406 			break;
   1407 		case 23:
   1408 			term.c.attr.mode &= ~ATTR_ITALIC;
   1409 			break;
   1410 		case 24:
   1411 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1412 			break;
   1413 		case 25:
   1414 			term.c.attr.mode &= ~ATTR_BLINK;
   1415 			break;
   1416 		case 27:
   1417 			term.c.attr.mode &= ~ATTR_REVERSE;
   1418 			break;
   1419 		case 28:
   1420 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1421 			break;
   1422 		case 29:
   1423 			term.c.attr.mode &= ~ATTR_STRUCK;
   1424 			break;
   1425 		case 38:
   1426 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1427 				term.c.attr.fg = idx;
   1428 			break;
   1429 		case 39:
   1430 			term.c.attr.fg = defaultfg;
   1431 			break;
   1432 		case 48:
   1433 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1434 				term.c.attr.bg = idx;
   1435 			break;
   1436 		case 49:
   1437 			term.c.attr.bg = defaultbg;
   1438 			break;
   1439 		default:
   1440 			if (BETWEEN(attr[i], 30, 37)) {
   1441 				term.c.attr.fg = attr[i] - 30;
   1442 			} else if (BETWEEN(attr[i], 40, 47)) {
   1443 				term.c.attr.bg = attr[i] - 40;
   1444 			} else if (BETWEEN(attr[i], 90, 97)) {
   1445 				term.c.attr.fg = attr[i] - 90 + 8;
   1446 			} else if (BETWEEN(attr[i], 100, 107)) {
   1447 				term.c.attr.bg = attr[i] - 100 + 8;
   1448 			} else {
   1449 				fprintf(stderr,
   1450 					"erresc(default): gfx attr %d unknown\n",
   1451 					attr[i]);
   1452 				csidump();
   1453 			}
   1454 			break;
   1455 		}
   1456 	}
   1457 }
   1458 
   1459 void
   1460 tsetscroll(int t, int b)
   1461 {
   1462 	int temp;
   1463 
   1464 	LIMIT(t, 0, term.row-1);
   1465 	LIMIT(b, 0, term.row-1);
   1466 	if (t > b) {
   1467 		temp = t;
   1468 		t = b;
   1469 		b = temp;
   1470 	}
   1471 	term.top = t;
   1472 	term.bot = b;
   1473 }
   1474 
   1475 void
   1476 tsetmode(int priv, int set, const int *args, int narg)
   1477 {
   1478 	int alt; const int *lim;
   1479 
   1480 	for (lim = args + narg; args < lim; ++args) {
   1481 		if (priv) {
   1482 			switch (*args) {
   1483 			case 1: /* DECCKM -- Cursor key */
   1484 				xsetmode(set, MODE_APPCURSOR);
   1485 				break;
   1486 			case 5: /* DECSCNM -- Reverse video */
   1487 				xsetmode(set, MODE_REVERSE);
   1488 				break;
   1489 			case 6: /* DECOM -- Origin */
   1490 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1491 				tmoveato(0, 0);
   1492 				break;
   1493 			case 7: /* DECAWM -- Auto wrap */
   1494 				MODBIT(term.mode, set, MODE_WRAP);
   1495 				break;
   1496 			case 0:  /* Error (IGNORED) */
   1497 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1498 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1499 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1500 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1501 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1502 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1503 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1504 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1505 				break;
   1506 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1507 				xsetmode(!set, MODE_HIDE);
   1508 				break;
   1509 			case 9:    /* X10 mouse compatibility mode */
   1510 				xsetpointermotion(0);
   1511 				xsetmode(0, MODE_MOUSE);
   1512 				xsetmode(set, MODE_MOUSEX10);
   1513 				break;
   1514 			case 1000: /* 1000: report button press */
   1515 				xsetpointermotion(0);
   1516 				xsetmode(0, MODE_MOUSE);
   1517 				xsetmode(set, MODE_MOUSEBTN);
   1518 				break;
   1519 			case 1002: /* 1002: report motion on button press */
   1520 				xsetpointermotion(0);
   1521 				xsetmode(0, MODE_MOUSE);
   1522 				xsetmode(set, MODE_MOUSEMOTION);
   1523 				break;
   1524 			case 1003: /* 1003: enable all mouse motions */
   1525 				xsetpointermotion(set);
   1526 				xsetmode(0, MODE_MOUSE);
   1527 				xsetmode(set, MODE_MOUSEMANY);
   1528 				break;
   1529 			case 1004: /* 1004: send focus events to tty */
   1530 				xsetmode(set, MODE_FOCUS);
   1531 				break;
   1532 			case 1006: /* 1006: extended reporting mode */
   1533 				xsetmode(set, MODE_MOUSESGR);
   1534 				break;
   1535 			case 1034:
   1536 				xsetmode(set, MODE_8BIT);
   1537 				break;
   1538 			case 1049: /* swap screen & set/restore cursor as xterm */
   1539 				if (!allowaltscreen)
   1540 					break;
   1541 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1542 				/* FALLTHROUGH */
   1543 			case 47: /* swap screen */
   1544 			case 1047:
   1545 				if (!allowaltscreen)
   1546 					break;
   1547 				alt = IS_SET(MODE_ALTSCREEN);
   1548 				if (alt) {
   1549 					tclearregion(0, 0, term.col-1,
   1550 							term.row-1);
   1551 				}
   1552 				if (set ^ alt) /* set is always 1 or 0 */
   1553 					tswapscreen();
   1554 				if (*args != 1049)
   1555 					break;
   1556 				/* FALLTHROUGH */
   1557 			case 1048:
   1558 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1559 				break;
   1560 			case 2004: /* 2004: bracketed paste mode */
   1561 				xsetmode(set, MODE_BRCKTPASTE);
   1562 				break;
   1563 			/* Not implemented mouse modes. See comments there. */
   1564 			case 1001: /* mouse highlight mode; can hang the
   1565 				      terminal by design when implemented. */
   1566 			case 1005: /* UTF-8 mouse mode; will confuse
   1567 				      applications not supporting UTF-8
   1568 				      and luit. */
   1569 			case 1015: /* urxvt mangled mouse mode; incompatible
   1570 				      and can be mistaken for other control
   1571 				      codes. */
   1572 				break;
   1573 			default:
   1574 				fprintf(stderr,
   1575 					"erresc: unknown private set/reset mode %d\n",
   1576 					*args);
   1577 				break;
   1578 			}
   1579 		} else {
   1580 			switch (*args) {
   1581 			case 0:  /* Error (IGNORED) */
   1582 				break;
   1583 			case 2:
   1584 				xsetmode(set, MODE_KBDLOCK);
   1585 				break;
   1586 			case 4:  /* IRM -- Insertion-replacement */
   1587 				MODBIT(term.mode, set, MODE_INSERT);
   1588 				break;
   1589 			case 12: /* SRM -- Send/Receive */
   1590 				MODBIT(term.mode, !set, MODE_ECHO);
   1591 				break;
   1592 			case 20: /* LNM -- Linefeed/new line */
   1593 				MODBIT(term.mode, set, MODE_CRLF);
   1594 				break;
   1595 			default:
   1596 				fprintf(stderr,
   1597 					"erresc: unknown set/reset mode %d\n",
   1598 					*args);
   1599 				break;
   1600 			}
   1601 		}
   1602 	}
   1603 }
   1604 
   1605 void
   1606 csihandle(void)
   1607 {
   1608 	char buf[40];
   1609 	int len;
   1610 
   1611 	switch (csiescseq.mode[0]) {
   1612 	default:
   1613 	unknown:
   1614 		fprintf(stderr, "erresc: unknown csi ");
   1615 		csidump();
   1616 		/* die(""); */
   1617 		break;
   1618 	case '@': /* ICH -- Insert <n> blank char */
   1619 		DEFAULT(csiescseq.arg[0], 1);
   1620 		tinsertblank(csiescseq.arg[0]);
   1621 		break;
   1622 	case 'A': /* CUU -- Cursor <n> Up */
   1623 		DEFAULT(csiescseq.arg[0], 1);
   1624 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   1625 		break;
   1626 	case 'B': /* CUD -- Cursor <n> Down */
   1627 	case 'e': /* VPR --Cursor <n> Down */
   1628 		DEFAULT(csiescseq.arg[0], 1);
   1629 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   1630 		break;
   1631 	case 'i': /* MC -- Media Copy */
   1632 		switch (csiescseq.arg[0]) {
   1633 		case 0:
   1634 			tdump();
   1635 			break;
   1636 		case 1:
   1637 			tdumpline(term.c.y);
   1638 			break;
   1639 		case 2:
   1640 			tdumpsel();
   1641 			break;
   1642 		case 4:
   1643 			term.mode &= ~MODE_PRINT;
   1644 			break;
   1645 		case 5:
   1646 			term.mode |= MODE_PRINT;
   1647 			break;
   1648 		}
   1649 		break;
   1650 	case 'c': /* DA -- Device Attributes */
   1651 		if (csiescseq.arg[0] == 0)
   1652 			ttywrite(vtiden, strlen(vtiden), 0);
   1653 		break;
   1654 	case 'b': /* REP -- if last char is printable print it <n> more times */
   1655 		DEFAULT(csiescseq.arg[0], 1);
   1656 		if (term.lastc)
   1657 			while (csiescseq.arg[0]-- > 0)
   1658 				tputc(term.lastc);
   1659 		break;
   1660 	case 'C': /* CUF -- Cursor <n> Forward */
   1661 	case 'a': /* HPR -- Cursor <n> Forward */
   1662 		DEFAULT(csiescseq.arg[0], 1);
   1663 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   1664 		break;
   1665 	case 'D': /* CUB -- Cursor <n> Backward */
   1666 		DEFAULT(csiescseq.arg[0], 1);
   1667 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   1668 		break;
   1669 	case 'E': /* CNL -- Cursor <n> Down and first col */
   1670 		DEFAULT(csiescseq.arg[0], 1);
   1671 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   1672 		break;
   1673 	case 'F': /* CPL -- Cursor <n> Up and first col */
   1674 		DEFAULT(csiescseq.arg[0], 1);
   1675 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   1676 		break;
   1677 	case 'g': /* TBC -- Tabulation clear */
   1678 		switch (csiescseq.arg[0]) {
   1679 		case 0: /* clear current tab stop */
   1680 			term.tabs[term.c.x] = 0;
   1681 			break;
   1682 		case 3: /* clear all the tabs */
   1683 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1684 			break;
   1685 		default:
   1686 			goto unknown;
   1687 		}
   1688 		break;
   1689 	case 'G': /* CHA -- Move to <col> */
   1690 	case '`': /* HPA */
   1691 		DEFAULT(csiescseq.arg[0], 1);
   1692 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   1693 		break;
   1694 	case 'H': /* CUP -- Move to <row> <col> */
   1695 	case 'f': /* HVP */
   1696 		DEFAULT(csiescseq.arg[0], 1);
   1697 		DEFAULT(csiescseq.arg[1], 1);
   1698 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   1699 		break;
   1700 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   1701 		DEFAULT(csiescseq.arg[0], 1);
   1702 		tputtab(csiescseq.arg[0]);
   1703 		break;
   1704 	case 'J': /* ED -- Clear screen */
   1705 		switch (csiescseq.arg[0]) {
   1706 		case 0: /* below */
   1707 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
   1708 			if (term.c.y < term.row-1) {
   1709 				tclearregion(0, term.c.y+1, term.col-1,
   1710 						term.row-1);
   1711 			}
   1712 			break;
   1713 		case 1: /* above */
   1714 			if (term.c.y > 1)
   1715 				tclearregion(0, 0, term.col-1, term.c.y-1);
   1716 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1717 			break;
   1718 		case 2: /* all */
   1719 			tclearregion(0, 0, term.col-1, term.row-1);
   1720 			break;
   1721 		default:
   1722 			goto unknown;
   1723 		}
   1724 		break;
   1725 	case 'K': /* EL -- Clear line */
   1726 		switch (csiescseq.arg[0]) {
   1727 		case 0: /* right */
   1728 			tclearregion(term.c.x, term.c.y, term.col-1,
   1729 					term.c.y);
   1730 			break;
   1731 		case 1: /* left */
   1732 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1733 			break;
   1734 		case 2: /* all */
   1735 			tclearregion(0, term.c.y, term.col-1, term.c.y);
   1736 			break;
   1737 		}
   1738 		break;
   1739 	case 'S': /* SU -- Scroll <n> line up */
   1740 		DEFAULT(csiescseq.arg[0], 1);
   1741 		tscrollup(term.top, csiescseq.arg[0]);
   1742 		break;
   1743 	case 'T': /* SD -- Scroll <n> line down */
   1744 		DEFAULT(csiescseq.arg[0], 1);
   1745 		tscrolldown(term.top, csiescseq.arg[0]);
   1746 		break;
   1747 	case 'L': /* IL -- Insert <n> blank lines */
   1748 		DEFAULT(csiescseq.arg[0], 1);
   1749 		tinsertblankline(csiescseq.arg[0]);
   1750 		break;
   1751 	case 'l': /* RM -- Reset Mode */
   1752 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1753 		break;
   1754 	case 'M': /* DL -- Delete <n> lines */
   1755 		DEFAULT(csiescseq.arg[0], 1);
   1756 		tdeleteline(csiescseq.arg[0]);
   1757 		break;
   1758 	case 'X': /* ECH -- Erase <n> char */
   1759 		DEFAULT(csiescseq.arg[0], 1);
   1760 		tclearregion(term.c.x, term.c.y,
   1761 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1762 		break;
   1763 	case 'P': /* DCH -- Delete <n> char */
   1764 		DEFAULT(csiescseq.arg[0], 1);
   1765 		tdeletechar(csiescseq.arg[0]);
   1766 		break;
   1767 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   1768 		DEFAULT(csiescseq.arg[0], 1);
   1769 		tputtab(-csiescseq.arg[0]);
   1770 		break;
   1771 	case 'd': /* VPA -- Move to <row> */
   1772 		DEFAULT(csiescseq.arg[0], 1);
   1773 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   1774 		break;
   1775 	case 'h': /* SM -- Set terminal mode */
   1776 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   1777 		break;
   1778 	case 'm': /* SGR -- Terminal attribute (color) */
   1779 		tsetattr(csiescseq.arg, csiescseq.narg);
   1780 		break;
   1781 	case 'n': /* DSR – Device Status Report (cursor position) */
   1782 		if (csiescseq.arg[0] == 6) {
   1783 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   1784 					term.c.y+1, term.c.x+1);
   1785 			ttywrite(buf, len, 0);
   1786 		}
   1787 		break;
   1788 	case 'r': /* DECSTBM -- Set Scrolling Region */
   1789 		if (csiescseq.priv) {
   1790 			goto unknown;
   1791 		} else {
   1792 			DEFAULT(csiescseq.arg[0], 1);
   1793 			DEFAULT(csiescseq.arg[1], term.row);
   1794 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   1795 			tmoveato(0, 0);
   1796 		}
   1797 		break;
   1798 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   1799 		tcursor(CURSOR_SAVE);
   1800 		break;
   1801 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   1802 		tcursor(CURSOR_LOAD);
   1803 		break;
   1804 	case ' ':
   1805 		switch (csiescseq.mode[1]) {
   1806 		case 'q': /* DECSCUSR -- Set Cursor Style */
   1807 			if (xsetcursor(csiescseq.arg[0]))
   1808 				goto unknown;
   1809 			break;
   1810 		default:
   1811 			goto unknown;
   1812 		}
   1813 		break;
   1814 	}
   1815 }
   1816 
   1817 void
   1818 csidump(void)
   1819 {
   1820 	size_t i;
   1821 	uint c;
   1822 
   1823 	fprintf(stderr, "ESC[");
   1824 	for (i = 0; i < csiescseq.len; i++) {
   1825 		c = csiescseq.buf[i] & 0xff;
   1826 		if (isprint(c)) {
   1827 			putc(c, stderr);
   1828 		} else if (c == '\n') {
   1829 			fprintf(stderr, "(\\n)");
   1830 		} else if (c == '\r') {
   1831 			fprintf(stderr, "(\\r)");
   1832 		} else if (c == 0x1b) {
   1833 			fprintf(stderr, "(\\e)");
   1834 		} else {
   1835 			fprintf(stderr, "(%02x)", c);
   1836 		}
   1837 	}
   1838 	putc('\n', stderr);
   1839 }
   1840 
   1841 void
   1842 csireset(void)
   1843 {
   1844 	memset(&csiescseq, 0, sizeof(csiescseq));
   1845 }
   1846 
   1847 void
   1848 osc_color_response(int num, int index, int is_osc4)
   1849 {
   1850 	int n;
   1851 	char buf[32];
   1852 	unsigned char r, g, b;
   1853 
   1854 	if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
   1855 		fprintf(stderr, "erresc: failed to fetch %s color %d\n",
   1856 		        is_osc4 ? "osc4" : "osc",
   1857 		        is_osc4 ? num : index);
   1858 		return;
   1859 	}
   1860 
   1861 	n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
   1862 	             is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
   1863 	if (n < 0 || n >= sizeof(buf)) {
   1864 		fprintf(stderr, "error: %s while printing %s response\n",
   1865 		        n < 0 ? "snprintf failed" : "truncation occurred",
   1866 		        is_osc4 ? "osc4" : "osc");
   1867 	} else {
   1868 		ttywrite(buf, n, 1);
   1869 	}
   1870 }
   1871 
   1872 void
   1873 strhandle(void)
   1874 {
   1875 	char *p = NULL, *dec;
   1876 	int j, narg, par;
   1877 	const struct { int idx; char *str; } osc_table[] = {
   1878 		{ defaultfg, "foreground" },
   1879 		{ defaultbg, "background" },
   1880 		{ defaultcs, "cursor" }
   1881 	};
   1882 
   1883 	term.esc &= ~(ESC_STR_END|ESC_STR);
   1884 	strparse();
   1885 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   1886 
   1887 	switch (strescseq.type) {
   1888 	case ']': /* OSC -- Operating System Command */
   1889 		switch (par) {
   1890 		case 0:
   1891 			if (narg > 1) {
   1892 				xsettitle(strescseq.args[1]);
   1893 				xseticontitle(strescseq.args[1]);
   1894 			}
   1895 			return;
   1896 		case 1:
   1897 			if (narg > 1)
   1898 				xseticontitle(strescseq.args[1]);
   1899 			return;
   1900 		case 2:
   1901 			if (narg > 1)
   1902 				xsettitle(strescseq.args[1]);
   1903 			return;
   1904 		case 52:
   1905 			if (narg > 2 && allowwindowops) {
   1906 				dec = base64dec(strescseq.args[2]);
   1907 				if (dec) {
   1908 					xsetsel(dec);
   1909 					xclipcopy();
   1910 				} else {
   1911 					fprintf(stderr, "erresc: invalid base64\n");
   1912 				}
   1913 			}
   1914 			return;
   1915 		case 10:
   1916 		case 11:
   1917 		case 12:
   1918 			if (narg < 2)
   1919 				break;
   1920 			p = strescseq.args[1];
   1921 			if ((j = par - 10) < 0 || j >= LEN(osc_table))
   1922 				break; /* shouldn't be possible */
   1923 
   1924 			if (!strcmp(p, "?")) {
   1925 				osc_color_response(par, osc_table[j].idx, 0);
   1926 			} else if (xsetcolorname(osc_table[j].idx, p)) {
   1927 				fprintf(stderr, "erresc: invalid %s color: %s\n",
   1928 				        osc_table[j].str, p);
   1929 			} else {
   1930 				tfulldirt();
   1931 			}
   1932 			return;
   1933 		case 4: /* color set */
   1934 			if (narg < 3)
   1935 				break;
   1936 			p = strescseq.args[2];
   1937 			/* FALLTHROUGH */
   1938 		case 104: /* color reset */
   1939 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   1940 
   1941 			if (p && !strcmp(p, "?")) {
   1942 				osc_color_response(j, 0, 1);
   1943 			} else if (xsetcolorname(j, p)) {
   1944 				if (par == 104 && narg <= 1)
   1945 					return; /* color reset without parameter */
   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 		break;
   2334 	case '=': /* DECPAM -- Application keypad */
   2335 		xsetmode(1, MODE_APPKEYPAD);
   2336 		break;
   2337 	case '>': /* DECPNM -- Normal keypad */
   2338 		xsetmode(0, MODE_APPKEYPAD);
   2339 		break;
   2340 	case '7': /* DECSC -- Save Cursor */
   2341 		tcursor(CURSOR_SAVE);
   2342 		break;
   2343 	case '8': /* DECRC -- Restore Cursor */
   2344 		tcursor(CURSOR_LOAD);
   2345 		break;
   2346 	case '\\': /* ST -- String Terminator */
   2347 		if (term.esc & ESC_STR_END)
   2348 			strhandle();
   2349 		break;
   2350 	default:
   2351 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2352 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2353 		break;
   2354 	}
   2355 	return 1;
   2356 }
   2357 
   2358 void
   2359 tputc(Rune u)
   2360 {
   2361 	char c[UTF_SIZ];
   2362 	int control;
   2363 	int width, len;
   2364 	Glyph *gp;
   2365 
   2366 	control = ISCONTROL(u);
   2367 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2368 		c[0] = u;
   2369 		width = len = 1;
   2370 	} else {
   2371 		len = utf8encode(u, c);
   2372 		if (!control && (width = wcwidth(u)) == -1)
   2373 			width = 1;
   2374 	}
   2375 
   2376 	if (IS_SET(MODE_PRINT))
   2377 		tprinter(c, len);
   2378 
   2379 	/*
   2380 	 * STR sequence must be checked before anything else
   2381 	 * because it uses all following characters until it
   2382 	 * receives a ESC, a SUB, a ST or any other C1 control
   2383 	 * character.
   2384 	 */
   2385 	if (term.esc & ESC_STR) {
   2386 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2387 		   ISCONTROLC1(u)) {
   2388 			term.esc &= ~(ESC_START|ESC_STR);
   2389 			term.esc |= ESC_STR_END;
   2390 			goto check_control_code;
   2391 		}
   2392 
   2393 		if (strescseq.len+len >= strescseq.siz) {
   2394 			/*
   2395 			 * Here is a bug in terminals. If the user never sends
   2396 			 * some code to stop the str or esc command, then st
   2397 			 * will stop responding. But this is better than
   2398 			 * silently failing with unknown characters. At least
   2399 			 * then users will report back.
   2400 			 *
   2401 			 * In the case users ever get fixed, here is the code:
   2402 			 */
   2403 			/*
   2404 			 * term.esc = 0;
   2405 			 * strhandle();
   2406 			 */
   2407 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2408 				return;
   2409 			strescseq.siz *= 2;
   2410 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2411 		}
   2412 
   2413 		memmove(&strescseq.buf[strescseq.len], c, len);
   2414 		strescseq.len += len;
   2415 		return;
   2416 	}
   2417 
   2418 check_control_code:
   2419 	/*
   2420 	 * Actions of control codes must be performed as soon they arrive
   2421 	 * because they can be embedded inside a control sequence, and
   2422 	 * they must not cause conflicts with sequences.
   2423 	 */
   2424 	if (control) {
   2425 		tcontrolcode(u);
   2426 		/*
   2427 		 * control codes are not shown ever
   2428 		 */
   2429 		if (!term.esc)
   2430 			term.lastc = 0;
   2431 		return;
   2432 	} else if (term.esc & ESC_START) {
   2433 		if (term.esc & ESC_CSI) {
   2434 			csiescseq.buf[csiescseq.len++] = u;
   2435 			if (BETWEEN(u, 0x40, 0x7E)
   2436 					|| csiescseq.len >= \
   2437 					sizeof(csiescseq.buf)-1) {
   2438 				term.esc = 0;
   2439 				csiparse();
   2440 				csihandle();
   2441 			}
   2442 			return;
   2443 		} else if (term.esc & ESC_UTF8) {
   2444 			tdefutf8(u);
   2445 		} else if (term.esc & ESC_ALTCHARSET) {
   2446 			tdeftran(u);
   2447 		} else if (term.esc & ESC_TEST) {
   2448 			tdectest(u);
   2449 		} else {
   2450 			if (!eschandle(u))
   2451 				return;
   2452 			/* sequence already finished */
   2453 		}
   2454 		term.esc = 0;
   2455 		/*
   2456 		 * All characters which form part of a sequence are not
   2457 		 * printed
   2458 		 */
   2459 		return;
   2460 	}
   2461 	if (selected(term.c.x, term.c.y))
   2462 		selclear();
   2463 
   2464 	gp = &term.line[term.c.y][term.c.x];
   2465 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2466 		gp->mode |= ATTR_WRAP;
   2467 		tnewline(1);
   2468 		gp = &term.line[term.c.y][term.c.x];
   2469 	}
   2470 
   2471 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
   2472 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2473 
   2474 	if (term.c.x+width > term.col) {
   2475 		tnewline(1);
   2476 		gp = &term.line[term.c.y][term.c.x];
   2477 	}
   2478 
   2479 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2480 	term.lastc = u;
   2481 
   2482 	if (width == 2) {
   2483 		gp->mode |= ATTR_WIDE;
   2484 		if (term.c.x+1 < term.col) {
   2485 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   2486 				gp[2].u = ' ';
   2487 				gp[2].mode &= ~ATTR_WDUMMY;
   2488 			}
   2489 			gp[1].u = '\0';
   2490 			gp[1].mode = ATTR_WDUMMY;
   2491 		}
   2492 	}
   2493 	if (term.c.x+width < term.col) {
   2494 		tmoveto(term.c.x+width, term.c.y);
   2495 	} else {
   2496 		term.c.state |= CURSOR_WRAPNEXT;
   2497 	}
   2498 }
   2499 
   2500 int
   2501 twrite(const char *buf, int buflen, int show_ctrl)
   2502 {
   2503 	int charsize;
   2504 	Rune u;
   2505 	int n;
   2506 
   2507 	for (n = 0; n < buflen; n += charsize) {
   2508 		if (IS_SET(MODE_UTF8)) {
   2509 			/* process a complete utf8 char */
   2510 			charsize = utf8decode(buf + n, &u, buflen - n);
   2511 			if (charsize == 0)
   2512 				break;
   2513 		} else {
   2514 			u = buf[n] & 0xFF;
   2515 			charsize = 1;
   2516 		}
   2517 		if (show_ctrl && ISCONTROL(u)) {
   2518 			if (u & 0x80) {
   2519 				u &= 0x7f;
   2520 				tputc('^');
   2521 				tputc('[');
   2522 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2523 				u ^= 0x40;
   2524 				tputc('^');
   2525 			}
   2526 		}
   2527 		tputc(u);
   2528 	}
   2529 	return n;
   2530 }
   2531 
   2532 void
   2533 tresize(int col, int row)
   2534 {
   2535 	int i;
   2536 	int minrow = MIN(row, term.row);
   2537 	int mincol = MIN(col, term.col);
   2538 	int *bp;
   2539 	TCursor c;
   2540 
   2541 	if (col < 1 || row < 1) {
   2542 		fprintf(stderr,
   2543 		        "tresize: error resizing to %dx%d\n", col, row);
   2544 		return;
   2545 	}
   2546 
   2547 	/*
   2548 	 * slide screen to keep cursor where we expect it -
   2549 	 * tscrollup would work here, but we can optimize to
   2550 	 * memmove because we're freeing the earlier lines
   2551 	 */
   2552 	for (i = 0; i <= term.c.y - row; i++) {
   2553 		free(term.line[i]);
   2554 		free(term.alt[i]);
   2555 	}
   2556 	/* ensure that both src and dst are not NULL */
   2557 	if (i > 0) {
   2558 		memmove(term.line, term.line + i, row * sizeof(Line));
   2559 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2560 	}
   2561 	for (i += row; i < term.row; i++) {
   2562 		free(term.line[i]);
   2563 		free(term.alt[i]);
   2564 	}
   2565 
   2566 	/* resize to new height */
   2567 	term.line = xrealloc(term.line, row * sizeof(Line));
   2568 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2569 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2570 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2571 
   2572 	/* resize each row to new width, zero-pad if needed */
   2573 	for (i = 0; i < minrow; i++) {
   2574 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2575 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2576 	}
   2577 
   2578 	/* allocate any new rows */
   2579 	for (/* i = minrow */; i < row; i++) {
   2580 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2581 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2582 	}
   2583 	if (col > term.col) {
   2584 		bp = term.tabs + term.col;
   2585 
   2586 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2587 		while (--bp > term.tabs && !*bp)
   2588 			/* nothing */ ;
   2589 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2590 			*bp = 1;
   2591 	}
   2592 	/* update terminal size */
   2593 	term.col = col;
   2594 	term.row = row;
   2595 	/* reset scrolling region */
   2596 	tsetscroll(0, row-1);
   2597 	/* make use of the LIMIT in tmoveto */
   2598 	tmoveto(term.c.x, term.c.y);
   2599 	/* Clearing both screens (it makes dirty all lines) */
   2600 	c = term.c;
   2601 	for (i = 0; i < 2; i++) {
   2602 		if (mincol < col && 0 < minrow) {
   2603 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2604 		}
   2605 		if (0 < col && minrow < row) {
   2606 			tclearregion(0, minrow, col - 1, row - 1);
   2607 		}
   2608 		tswapscreen();
   2609 		tcursor(CURSOR_LOAD);
   2610 	}
   2611 	term.c = c;
   2612 }
   2613 
   2614 void
   2615 resettitle(void)
   2616 {
   2617 	xsettitle(NULL);
   2618 }
   2619 
   2620 void
   2621 drawregion(int x1, int y1, int x2, int y2)
   2622 {
   2623 	int y;
   2624 
   2625 	for (y = y1; y < y2; y++) {
   2626 		if (!term.dirty[y])
   2627 			continue;
   2628 
   2629 		term.dirty[y] = 0;
   2630 		xdrawline(term.line[y], x1, y, x2);
   2631 	}
   2632 }
   2633 
   2634 void
   2635 draw(void)
   2636 {
   2637 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2638 
   2639 	if (!xstartdraw())
   2640 		return;
   2641 
   2642 	/* adjust cursor position */
   2643 	LIMIT(term.ocx, 0, term.col-1);
   2644 	LIMIT(term.ocy, 0, term.row-1);
   2645 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2646 		term.ocx--;
   2647 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2648 		cx--;
   2649 
   2650 	drawregion(0, 0, term.col, term.row);
   2651 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2652 			term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2653 	term.ocx = cx;
   2654 	term.ocy = term.c.y;
   2655 	xfinishdraw();
   2656 	if (ocx != term.ocx || ocy != term.ocy)
   2657 		xximspot(term.ocx, term.ocy);
   2658 }
   2659 
   2660 void
   2661 redraw(void)
   2662 {
   2663 	tfulldirt();
   2664 	draw();
   2665 }