st

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

st.c (57760B)


      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; /* terminal column */
     90 	int y; /* terminal row */
     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 		LIMIT(csiescseq.arg[0], 1, 65535);
   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 		if (csiescseq.priv) break;
   1732 		DEFAULT(csiescseq.arg[0], 1);
   1733 		tscrollup(term.top, csiescseq.arg[0]);
   1734 		break;
   1735 	case 'T': /* SD -- Scroll <n> line down */
   1736 		DEFAULT(csiescseq.arg[0], 1);
   1737 		tscrolldown(term.top, csiescseq.arg[0]);
   1738 		break;
   1739 	case 'L': /* IL -- Insert <n> blank lines */
   1740 		DEFAULT(csiescseq.arg[0], 1);
   1741 		tinsertblankline(csiescseq.arg[0]);
   1742 		break;
   1743 	case 'l': /* RM -- Reset Mode */
   1744 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1745 		break;
   1746 	case 'M': /* DL -- Delete <n> lines */
   1747 		DEFAULT(csiescseq.arg[0], 1);
   1748 		tdeleteline(csiescseq.arg[0]);
   1749 		break;
   1750 	case 'X': /* ECH -- Erase <n> char */
   1751 		DEFAULT(csiescseq.arg[0], 1);
   1752 		tclearregion(term.c.x, term.c.y,
   1753 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1754 		break;
   1755 	case 'P': /* DCH -- Delete <n> char */
   1756 		DEFAULT(csiescseq.arg[0], 1);
   1757 		tdeletechar(csiescseq.arg[0]);
   1758 		break;
   1759 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   1760 		DEFAULT(csiescseq.arg[0], 1);
   1761 		tputtab(-csiescseq.arg[0]);
   1762 		break;
   1763 	case 'd': /* VPA -- Move to <row> */
   1764 		DEFAULT(csiescseq.arg[0], 1);
   1765 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   1766 		break;
   1767 	case 'h': /* SM -- Set terminal mode */
   1768 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   1769 		break;
   1770 	case 'm': /* SGR -- Terminal attribute (color) */
   1771 		tsetattr(csiescseq.arg, csiescseq.narg);
   1772 		break;
   1773 	case 'n': /* DSR -- Device Status Report */
   1774 		switch (csiescseq.arg[0]) {
   1775 		case 5: /* Status Report "OK" `0n` */
   1776 			ttywrite("\033[0n", sizeof("\033[0n") - 1, 0);
   1777 			break;
   1778 		case 6: /* Report Cursor Position (CPR) "<row>;<column>R" */
   1779 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   1780 			               term.c.y+1, term.c.x+1);
   1781 			ttywrite(buf, len, 0);
   1782 			break;
   1783 		default:
   1784 			goto unknown;
   1785 		}
   1786 		break;
   1787 	case 'r': /* DECSTBM -- Set Scrolling Region */
   1788 		if (csiescseq.priv) {
   1789 			goto unknown;
   1790 		} else {
   1791 			DEFAULT(csiescseq.arg[0], 1);
   1792 			DEFAULT(csiescseq.arg[1], term.row);
   1793 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   1794 			tmoveato(0, 0);
   1795 		}
   1796 		break;
   1797 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   1798 		tcursor(CURSOR_SAVE);
   1799 		break;
   1800 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   1801 		tcursor(CURSOR_LOAD);
   1802 		break;
   1803 	case ' ':
   1804 		switch (csiescseq.mode[1]) {
   1805 		case 'q': /* DECSCUSR -- Set Cursor Style */
   1806 			if (xsetcursor(csiescseq.arg[0]))
   1807 				goto unknown;
   1808 			break;
   1809 		default:
   1810 			goto unknown;
   1811 		}
   1812 		break;
   1813 	}
   1814 }
   1815 
   1816 void
   1817 csidump(void)
   1818 {
   1819 	size_t i;
   1820 	uint c;
   1821 
   1822 	fprintf(stderr, "ESC[");
   1823 	for (i = 0; i < csiescseq.len; i++) {
   1824 		c = csiescseq.buf[i] & 0xff;
   1825 		if (isprint(c)) {
   1826 			putc(c, stderr);
   1827 		} else if (c == '\n') {
   1828 			fprintf(stderr, "(\\n)");
   1829 		} else if (c == '\r') {
   1830 			fprintf(stderr, "(\\r)");
   1831 		} else if (c == 0x1b) {
   1832 			fprintf(stderr, "(\\e)");
   1833 		} else {
   1834 			fprintf(stderr, "(%02x)", c);
   1835 		}
   1836 	}
   1837 	putc('\n', stderr);
   1838 }
   1839 
   1840 void
   1841 csireset(void)
   1842 {
   1843 	memset(&csiescseq, 0, sizeof(csiescseq));
   1844 }
   1845 
   1846 void
   1847 osc_color_response(int num, int index, int is_osc4)
   1848 {
   1849 	int n;
   1850 	char buf[32];
   1851 	unsigned char r, g, b;
   1852 
   1853 	if (xgetcolor(is_osc4 ? num : index, &r, &g, &b)) {
   1854 		fprintf(stderr, "erresc: failed to fetch %s color %d\n",
   1855 		        is_osc4 ? "osc4" : "osc",
   1856 		        is_osc4 ? num : index);
   1857 		return;
   1858 	}
   1859 
   1860 	n = snprintf(buf, sizeof buf, "\033]%s%d;rgb:%02x%02x/%02x%02x/%02x%02x\007",
   1861 	             is_osc4 ? "4;" : "", num, r, r, g, g, b, b);
   1862 	if (n < 0 || n >= sizeof(buf)) {
   1863 		fprintf(stderr, "error: %s while printing %s response\n",
   1864 		        n < 0 ? "snprintf failed" : "truncation occurred",
   1865 		        is_osc4 ? "osc4" : "osc");
   1866 	} else {
   1867 		ttywrite(buf, n, 1);
   1868 	}
   1869 }
   1870 
   1871 void
   1872 strhandle(void)
   1873 {
   1874 	char *p = NULL, *dec;
   1875 	int j, narg, par;
   1876 	const struct { int idx; char *str; } osc_table[] = {
   1877 		{ defaultfg, "foreground" },
   1878 		{ defaultbg, "background" },
   1879 		{ defaultcs, "cursor" }
   1880 	};
   1881 
   1882 	term.esc &= ~(ESC_STR_END|ESC_STR);
   1883 	strparse();
   1884 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   1885 
   1886 	switch (strescseq.type) {
   1887 	case ']': /* OSC -- Operating System Command */
   1888 		switch (par) {
   1889 		case 0:
   1890 			if (narg > 1) {
   1891 				xsettitle(strescseq.args[1]);
   1892 				xseticontitle(strescseq.args[1]);
   1893 			}
   1894 			return;
   1895 		case 1:
   1896 			if (narg > 1)
   1897 				xseticontitle(strescseq.args[1]);
   1898 			return;
   1899 		case 2:
   1900 			if (narg > 1)
   1901 				xsettitle(strescseq.args[1]);
   1902 			return;
   1903 		case 52:
   1904 			if (narg > 2 && allowwindowops) {
   1905 				dec = base64dec(strescseq.args[2]);
   1906 				if (dec) {
   1907 					xsetsel(dec);
   1908 					xclipcopy();
   1909 				} else {
   1910 					fprintf(stderr, "erresc: invalid base64\n");
   1911 				}
   1912 			}
   1913 			return;
   1914 		case 10:
   1915 		case 11:
   1916 		case 12:
   1917 			if (narg < 2)
   1918 				break;
   1919 			p = strescseq.args[1];
   1920 			if ((j = par - 10) < 0 || j >= LEN(osc_table))
   1921 				break; /* shouldn't be possible */
   1922 
   1923 			if (!strcmp(p, "?")) {
   1924 				osc_color_response(par, osc_table[j].idx, 0);
   1925 			} else if (xsetcolorname(osc_table[j].idx, p)) {
   1926 				fprintf(stderr, "erresc: invalid %s color: %s\n",
   1927 				        osc_table[j].str, p);
   1928 			} else {
   1929 				tfulldirt();
   1930 			}
   1931 			return;
   1932 		case 4: /* color set */
   1933 			if (narg < 3)
   1934 				break;
   1935 			p = strescseq.args[2];
   1936 			/* FALLTHROUGH */
   1937 		case 104: /* color reset */
   1938 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   1939 
   1940 			if (p && !strcmp(p, "?")) {
   1941 				osc_color_response(j, 0, 1);
   1942 			} else if (xsetcolorname(j, p)) {
   1943 				if (par == 104 && narg <= 1) {
   1944 					xloadcols();
   1945 					return; /* color reset without parameter */
   1946 				}
   1947 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   1948 				        j, p ? p : "(null)");
   1949 			} else {
   1950 				/*
   1951 				 * TODO if defaultbg color is changed, borders
   1952 				 * are dirty
   1953 				 */
   1954 				tfulldirt();
   1955 			}
   1956 			return;
   1957 		}
   1958 		break;
   1959 	case 'k': /* old title set compatibility */
   1960 		xsettitle(strescseq.args[0]);
   1961 		return;
   1962 	case 'P': /* DCS -- Device Control String */
   1963 	case '_': /* APC -- Application Program Command */
   1964 	case '^': /* PM -- Privacy Message */
   1965 		return;
   1966 	}
   1967 
   1968 	fprintf(stderr, "erresc: unknown str ");
   1969 	strdump();
   1970 }
   1971 
   1972 void
   1973 strparse(void)
   1974 {
   1975 	int c;
   1976 	char *p = strescseq.buf;
   1977 
   1978 	strescseq.narg = 0;
   1979 	strescseq.buf[strescseq.len] = '\0';
   1980 
   1981 	if (*p == '\0')
   1982 		return;
   1983 
   1984 	while (strescseq.narg < STR_ARG_SIZ) {
   1985 		strescseq.args[strescseq.narg++] = p;
   1986 		while ((c = *p) != ';' && c != '\0')
   1987 			++p;
   1988 		if (c == '\0')
   1989 			return;
   1990 		*p++ = '\0';
   1991 	}
   1992 }
   1993 
   1994 void
   1995 strdump(void)
   1996 {
   1997 	size_t i;
   1998 	uint c;
   1999 
   2000 	fprintf(stderr, "ESC%c", strescseq.type);
   2001 	for (i = 0; i < strescseq.len; i++) {
   2002 		c = strescseq.buf[i] & 0xff;
   2003 		if (c == '\0') {
   2004 			putc('\n', stderr);
   2005 			return;
   2006 		} else if (isprint(c)) {
   2007 			putc(c, stderr);
   2008 		} else if (c == '\n') {
   2009 			fprintf(stderr, "(\\n)");
   2010 		} else if (c == '\r') {
   2011 			fprintf(stderr, "(\\r)");
   2012 		} else if (c == 0x1b) {
   2013 			fprintf(stderr, "(\\e)");
   2014 		} else {
   2015 			fprintf(stderr, "(%02x)", c);
   2016 		}
   2017 	}
   2018 	fprintf(stderr, "ESC\\\n");
   2019 }
   2020 
   2021 void
   2022 strreset(void)
   2023 {
   2024 	strescseq = (STREscape){
   2025 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   2026 		.siz = STR_BUF_SIZ,
   2027 	};
   2028 }
   2029 
   2030 void
   2031 sendbreak(const Arg *arg)
   2032 {
   2033 	if (tcsendbreak(cmdfd, 0))
   2034 		perror("Error sending break");
   2035 }
   2036 
   2037 void
   2038 tprinter(char *s, size_t len)
   2039 {
   2040 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   2041 		perror("Error writing to output file");
   2042 		close(iofd);
   2043 		iofd = -1;
   2044 	}
   2045 }
   2046 
   2047 void
   2048 toggleprinter(const Arg *arg)
   2049 {
   2050 	term.mode ^= MODE_PRINT;
   2051 }
   2052 
   2053 void
   2054 printscreen(const Arg *arg)
   2055 {
   2056 	tdump();
   2057 }
   2058 
   2059 void
   2060 printsel(const Arg *arg)
   2061 {
   2062 	tdumpsel();
   2063 }
   2064 
   2065 void
   2066 tdumpsel(void)
   2067 {
   2068 	char *ptr;
   2069 
   2070 	if ((ptr = getsel())) {
   2071 		tprinter(ptr, strlen(ptr));
   2072 		free(ptr);
   2073 	}
   2074 }
   2075 
   2076 void
   2077 tdumpline(int n)
   2078 {
   2079 	char buf[UTF_SIZ];
   2080 	const Glyph *bp, *end;
   2081 
   2082 	bp = &term.line[n][0];
   2083 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2084 	if (bp != end || bp->u != ' ') {
   2085 		for ( ; bp <= end; ++bp)
   2086 			tprinter(buf, utf8encode(bp->u, buf));
   2087 	}
   2088 	tprinter("\n", 1);
   2089 }
   2090 
   2091 void
   2092 tdump(void)
   2093 {
   2094 	int i;
   2095 
   2096 	for (i = 0; i < term.row; ++i)
   2097 		tdumpline(i);
   2098 }
   2099 
   2100 void
   2101 tputtab(int n)
   2102 {
   2103 	uint x = term.c.x;
   2104 
   2105 	if (n > 0) {
   2106 		while (x < term.col && n--)
   2107 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2108 				/* nothing */ ;
   2109 	} else if (n < 0) {
   2110 		while (x > 0 && n++)
   2111 			for (--x; x > 0 && !term.tabs[x]; --x)
   2112 				/* nothing */ ;
   2113 	}
   2114 	term.c.x = LIMIT(x, 0, term.col-1);
   2115 }
   2116 
   2117 void
   2118 tdefutf8(char ascii)
   2119 {
   2120 	if (ascii == 'G')
   2121 		term.mode |= MODE_UTF8;
   2122 	else if (ascii == '@')
   2123 		term.mode &= ~MODE_UTF8;
   2124 }
   2125 
   2126 void
   2127 tdeftran(char ascii)
   2128 {
   2129 	static char cs[] = "0B";
   2130 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2131 	char *p;
   2132 
   2133 	if ((p = strchr(cs, ascii)) == NULL) {
   2134 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2135 	} else {
   2136 		term.trantbl[term.icharset] = vcs[p - cs];
   2137 	}
   2138 }
   2139 
   2140 void
   2141 tdectest(char c)
   2142 {
   2143 	int x, y;
   2144 
   2145 	if (c == '8') { /* DEC screen alignment test. */
   2146 		for (x = 0; x < term.col; ++x) {
   2147 			for (y = 0; y < term.row; ++y)
   2148 				tsetchar('E', &term.c.attr, x, y);
   2149 		}
   2150 	}
   2151 }
   2152 
   2153 void
   2154 tstrsequence(uchar c)
   2155 {
   2156 	switch (c) {
   2157 	case 0x90:   /* DCS -- Device Control String */
   2158 		c = 'P';
   2159 		break;
   2160 	case 0x9f:   /* APC -- Application Program Command */
   2161 		c = '_';
   2162 		break;
   2163 	case 0x9e:   /* PM -- Privacy Message */
   2164 		c = '^';
   2165 		break;
   2166 	case 0x9d:   /* OSC -- Operating System Command */
   2167 		c = ']';
   2168 		break;
   2169 	}
   2170 	strreset();
   2171 	strescseq.type = c;
   2172 	term.esc |= ESC_STR;
   2173 }
   2174 
   2175 void
   2176 tcontrolcode(uchar ascii)
   2177 {
   2178 	size_t i;
   2179 
   2180 	switch (ascii) {
   2181 	case '\t':   /* HT */
   2182 		tputtab(1);
   2183 		return;
   2184 	case '\b':   /* BS */
   2185 		for (i = 1; term.c.x && term.line[term.c.y][term.c.x - i].u == 0; ++i)
   2186 			;
   2187 		tmoveto(term.c.x - i, term.c.y);
   2188 		return;
   2189 	case '\r':   /* CR */
   2190 		tmoveto(0, term.c.y);
   2191 		return;
   2192 	case '\f':   /* LF */
   2193 	case '\v':   /* VT */
   2194 	case '\n':   /* LF */
   2195 		/* go to first col if the mode is set */
   2196 		tnewline(IS_SET(MODE_CRLF));
   2197 		return;
   2198 	case '\a':   /* BEL */
   2199 		if (term.esc & ESC_STR_END) {
   2200 			/* backwards compatibility to xterm */
   2201 			strhandle();
   2202 		} else {
   2203 			xbell();
   2204 		}
   2205 		break;
   2206 	case '\033': /* ESC */
   2207 		csireset();
   2208 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2209 		term.esc |= ESC_START;
   2210 		return;
   2211 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2212 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2213 		term.charset = 1 - (ascii - '\016');
   2214 		return;
   2215 	case '\032': /* SUB */
   2216 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2217 		/* FALLTHROUGH */
   2218 	case '\030': /* CAN */
   2219 		csireset();
   2220 		break;
   2221 	case '\005': /* ENQ (IGNORED) */
   2222 	case '\000': /* NUL (IGNORED) */
   2223 	case '\021': /* XON (IGNORED) */
   2224 	case '\023': /* XOFF (IGNORED) */
   2225 	case 0177:   /* DEL (IGNORED) */
   2226 		return;
   2227 	case 0x80:   /* TODO: PAD */
   2228 	case 0x81:   /* TODO: HOP */
   2229 	case 0x82:   /* TODO: BPH */
   2230 	case 0x83:   /* TODO: NBH */
   2231 	case 0x84:   /* TODO: IND */
   2232 		break;
   2233 	case 0x85:   /* NEL -- Next line */
   2234 		tnewline(1); /* always go to first col */
   2235 		break;
   2236 	case 0x86:   /* TODO: SSA */
   2237 	case 0x87:   /* TODO: ESA */
   2238 		break;
   2239 	case 0x88:   /* HTS -- Horizontal tab stop */
   2240 		term.tabs[term.c.x] = 1;
   2241 		break;
   2242 	case 0x89:   /* TODO: HTJ */
   2243 	case 0x8a:   /* TODO: VTS */
   2244 	case 0x8b:   /* TODO: PLD */
   2245 	case 0x8c:   /* TODO: PLU */
   2246 	case 0x8d:   /* TODO: RI */
   2247 	case 0x8e:   /* TODO: SS2 */
   2248 	case 0x8f:   /* TODO: SS3 */
   2249 	case 0x91:   /* TODO: PU1 */
   2250 	case 0x92:   /* TODO: PU2 */
   2251 	case 0x93:   /* TODO: STS */
   2252 	case 0x94:   /* TODO: CCH */
   2253 	case 0x95:   /* TODO: MW */
   2254 	case 0x96:   /* TODO: SPA */
   2255 	case 0x97:   /* TODO: EPA */
   2256 	case 0x98:   /* TODO: SOS */
   2257 	case 0x99:   /* TODO: SGCI */
   2258 		break;
   2259 	case 0x9a:   /* DECID -- Identify Terminal */
   2260 		ttywrite(vtiden, strlen(vtiden), 0);
   2261 		break;
   2262 	case 0x9b:   /* TODO: CSI */
   2263 	case 0x9c:   /* TODO: ST */
   2264 		break;
   2265 	case 0x90:   /* DCS -- Device Control String */
   2266 	case 0x9d:   /* OSC -- Operating System Command */
   2267 	case 0x9e:   /* PM -- Privacy Message */
   2268 	case 0x9f:   /* APC -- Application Program Command */
   2269 		tstrsequence(ascii);
   2270 		return;
   2271 	}
   2272 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2273 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2274 }
   2275 
   2276 /*
   2277  * returns 1 when the sequence is finished and it hasn't to read
   2278  * more characters for this sequence, otherwise 0
   2279  */
   2280 int
   2281 eschandle(uchar ascii)
   2282 {
   2283 	switch (ascii) {
   2284 	case '[':
   2285 		term.esc |= ESC_CSI;
   2286 		return 0;
   2287 	case '#':
   2288 		term.esc |= ESC_TEST;
   2289 		return 0;
   2290 	case '%':
   2291 		term.esc |= ESC_UTF8;
   2292 		return 0;
   2293 	case 'P': /* DCS -- Device Control String */
   2294 	case '_': /* APC -- Application Program Command */
   2295 	case '^': /* PM -- Privacy Message */
   2296 	case ']': /* OSC -- Operating System Command */
   2297 	case 'k': /* old title set compatibility */
   2298 		tstrsequence(ascii);
   2299 		return 0;
   2300 	case 'n': /* LS2 -- Locking shift 2 */
   2301 	case 'o': /* LS3 -- Locking shift 3 */
   2302 		term.charset = 2 + (ascii - 'n');
   2303 		break;
   2304 	case '(': /* GZD4 -- set primary charset G0 */
   2305 	case ')': /* G1D4 -- set secondary charset G1 */
   2306 	case '*': /* G2D4 -- set tertiary charset G2 */
   2307 	case '+': /* G3D4 -- set quaternary charset G3 */
   2308 		term.icharset = ascii - '(';
   2309 		term.esc |= ESC_ALTCHARSET;
   2310 		return 0;
   2311 	case 'D': /* IND -- Linefeed */
   2312 		if (term.c.y == term.bot) {
   2313 			tscrollup(term.top, 1);
   2314 		} else {
   2315 			tmoveto(term.c.x, term.c.y+1);
   2316 		}
   2317 		break;
   2318 	case 'E': /* NEL -- Next line */
   2319 		tnewline(1); /* always go to first col */
   2320 		break;
   2321 	case 'H': /* HTS -- Horizontal tab stop */
   2322 		term.tabs[term.c.x] = 1;
   2323 		break;
   2324 	case 'M': /* RI -- Reverse index */
   2325 		if (term.c.y == term.top) {
   2326 			tscrolldown(term.top, 1);
   2327 		} else {
   2328 			tmoveto(term.c.x, term.c.y-1);
   2329 		}
   2330 		break;
   2331 	case 'Z': /* DECID -- Identify Terminal */
   2332 		ttywrite(vtiden, strlen(vtiden), 0);
   2333 		break;
   2334 	case 'c': /* RIS -- Reset to initial state */
   2335 		treset();
   2336 		resettitle();
   2337 		xloadcols();
   2338 		xsetmode(0, MODE_HIDE);
   2339 		break;
   2340 	case '=': /* DECPAM -- Application keypad */
   2341 		xsetmode(1, MODE_APPKEYPAD);
   2342 		break;
   2343 	case '>': /* DECPNM -- Normal keypad */
   2344 		xsetmode(0, MODE_APPKEYPAD);
   2345 		break;
   2346 	case '7': /* DECSC -- Save Cursor */
   2347 		tcursor(CURSOR_SAVE);
   2348 		break;
   2349 	case '8': /* DECRC -- Restore Cursor */
   2350 		tcursor(CURSOR_LOAD);
   2351 		break;
   2352 	case '\\': /* ST -- String Terminator */
   2353 		if (term.esc & ESC_STR_END)
   2354 			strhandle();
   2355 		break;
   2356 	default:
   2357 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2358 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2359 		break;
   2360 	}
   2361 	return 1;
   2362 }
   2363 
   2364 void
   2365 tputc(Rune u)
   2366 {
   2367 	char c[UTF_SIZ];
   2368 	int control;
   2369 	int width, len;
   2370 	Glyph *gp;
   2371 
   2372 	control = ISCONTROL(u);
   2373 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2374 		c[0] = u;
   2375 		width = len = 1;
   2376 	} else {
   2377 		len = utf8encode(u, c);
   2378 		if (!control && (width = wcwidth(u)) == -1)
   2379 			width = 1;
   2380 	}
   2381 
   2382 	if (IS_SET(MODE_PRINT))
   2383 		tprinter(c, len);
   2384 
   2385 	/*
   2386 	 * STR sequence must be checked before anything else
   2387 	 * because it uses all following characters until it
   2388 	 * receives a ESC, a SUB, a ST or any other C1 control
   2389 	 * character.
   2390 	 */
   2391 	if (term.esc & ESC_STR) {
   2392 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2393 		   ISCONTROLC1(u)) {
   2394 			term.esc &= ~(ESC_START|ESC_STR);
   2395 			term.esc |= ESC_STR_END;
   2396 			goto check_control_code;
   2397 		}
   2398 
   2399 		if (strescseq.len+len >= strescseq.siz) {
   2400 			/*
   2401 			 * Here is a bug in terminals. If the user never sends
   2402 			 * some code to stop the str or esc command, then st
   2403 			 * will stop responding. But this is better than
   2404 			 * silently failing with unknown characters. At least
   2405 			 * then users will report back.
   2406 			 *
   2407 			 * In the case users ever get fixed, here is the code:
   2408 			 */
   2409 			/*
   2410 			 * term.esc = 0;
   2411 			 * strhandle();
   2412 			 */
   2413 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2414 				return;
   2415 			strescseq.siz *= 2;
   2416 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2417 		}
   2418 
   2419 		memmove(&strescseq.buf[strescseq.len], c, len);
   2420 		strescseq.len += len;
   2421 		return;
   2422 	}
   2423 
   2424 check_control_code:
   2425 	/*
   2426 	 * Actions of control codes must be performed as soon they arrive
   2427 	 * because they can be embedded inside a control sequence, and
   2428 	 * they must not cause conflicts with sequences.
   2429 	 */
   2430 	if (control) {
   2431 		/* in UTF-8 mode ignore handling C1 control characters */
   2432 		if (IS_SET(MODE_UTF8) && ISCONTROLC1(u))
   2433 			return;
   2434 		tcontrolcode(u);
   2435 		/*
   2436 		 * control codes are not shown ever
   2437 		 */
   2438 		if (!term.esc)
   2439 			term.lastc = 0;
   2440 		return;
   2441 	} else if (term.esc & ESC_START) {
   2442 		if (term.esc & ESC_CSI) {
   2443 			csiescseq.buf[csiescseq.len++] = u;
   2444 			if (BETWEEN(u, 0x40, 0x7E)
   2445 					|| csiescseq.len >= \
   2446 					sizeof(csiescseq.buf)-1) {
   2447 				term.esc = 0;
   2448 				csiparse();
   2449 				csihandle();
   2450 			}
   2451 			return;
   2452 		} else if (term.esc & ESC_UTF8) {
   2453 			tdefutf8(u);
   2454 		} else if (term.esc & ESC_ALTCHARSET) {
   2455 			tdeftran(u);
   2456 		} else if (term.esc & ESC_TEST) {
   2457 			tdectest(u);
   2458 		} else {
   2459 			if (!eschandle(u))
   2460 				return;
   2461 			/* sequence already finished */
   2462 		}
   2463 		term.esc = 0;
   2464 		/*
   2465 		 * All characters which form part of a sequence are not
   2466 		 * printed
   2467 		 */
   2468 		return;
   2469 	}
   2470 	if (selected(term.c.x, term.c.y))
   2471 		selclear();
   2472 
   2473 	gp = &term.line[term.c.y][term.c.x];
   2474 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2475 		gp->mode |= ATTR_WRAP;
   2476 		tnewline(1);
   2477 		gp = &term.line[term.c.y][term.c.x];
   2478 	}
   2479 
   2480 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col) {
   2481 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2482 		gp->mode &= ~ATTR_WIDE;
   2483 	}
   2484 
   2485 	if (term.c.x+width > term.col) {
   2486 		if (IS_SET(MODE_WRAP))
   2487 			tnewline(1);
   2488 		else
   2489 			tmoveto(term.col - width, term.c.y);
   2490 		gp = &term.line[term.c.y][term.c.x];
   2491 	}
   2492 
   2493 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2494 	term.lastc = u;
   2495 
   2496 	if (width == 2) {
   2497 		gp->mode |= ATTR_WIDE;
   2498 		if (term.c.x+1 < term.col) {
   2499 			if (gp[1].mode == ATTR_WIDE && term.c.x+2 < term.col) {
   2500 				gp[2].u = ' ';
   2501 				gp[2].mode &= ~ATTR_WDUMMY;
   2502 			}
   2503 			gp[1].u = '\0';
   2504 			gp[1].mode = ATTR_WDUMMY;
   2505 		}
   2506 	}
   2507 	if (term.c.x+width < term.col) {
   2508 		tmoveto(term.c.x+width, term.c.y);
   2509 	} else {
   2510 		term.c.state |= CURSOR_WRAPNEXT;
   2511 	}
   2512 }
   2513 
   2514 int
   2515 twrite(const char *buf, int buflen, int show_ctrl)
   2516 {
   2517 	int charsize;
   2518 	Rune u;
   2519 	int n;
   2520 
   2521 	for (n = 0; n < buflen; n += charsize) {
   2522 		if (IS_SET(MODE_UTF8)) {
   2523 			/* process a complete utf8 char */
   2524 			charsize = utf8decode(buf + n, &u, buflen - n);
   2525 			if (charsize == 0)
   2526 				break;
   2527 		} else {
   2528 			u = buf[n] & 0xFF;
   2529 			charsize = 1;
   2530 		}
   2531 		if (show_ctrl && ISCONTROL(u)) {
   2532 			if (u & 0x80) {
   2533 				u &= 0x7f;
   2534 				tputc('^');
   2535 				tputc('[');
   2536 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2537 				u ^= 0x40;
   2538 				tputc('^');
   2539 			}
   2540 		}
   2541 		tputc(u);
   2542 	}
   2543 	return n;
   2544 }
   2545 
   2546 void
   2547 tresize(int col, int row)
   2548 {
   2549 	int i;
   2550 	int minrow = MIN(row, term.row);
   2551 	int mincol = MIN(col, term.col);
   2552 	int *bp;
   2553 	TCursor c;
   2554 
   2555 	if (col < 1 || row < 1) {
   2556 		fprintf(stderr,
   2557 		        "tresize: error resizing to %dx%d\n", col, row);
   2558 		return;
   2559 	}
   2560 
   2561 	/*
   2562 	 * slide screen to keep cursor where we expect it -
   2563 	 * tscrollup would work here, but we can optimize to
   2564 	 * memmove because we're freeing the earlier lines
   2565 	 */
   2566 	for (i = 0; i <= term.c.y - row; i++) {
   2567 		free(term.line[i]);
   2568 		free(term.alt[i]);
   2569 	}
   2570 	/* ensure that both src and dst are not NULL */
   2571 	if (i > 0) {
   2572 		memmove(term.line, term.line + i, row * sizeof(Line));
   2573 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2574 	}
   2575 	for (i += row; i < term.row; i++) {
   2576 		free(term.line[i]);
   2577 		free(term.alt[i]);
   2578 	}
   2579 
   2580 	/* resize to new height */
   2581 	term.line = xrealloc(term.line, row * sizeof(Line));
   2582 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2583 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2584 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2585 
   2586 	/* resize each row to new width, zero-pad if needed */
   2587 	for (i = 0; i < minrow; i++) {
   2588 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2589 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2590 	}
   2591 
   2592 	/* allocate any new rows */
   2593 	for (/* i = minrow */; i < row; i++) {
   2594 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2595 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2596 	}
   2597 	if (col > term.col) {
   2598 		bp = term.tabs + term.col;
   2599 
   2600 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2601 		while (--bp > term.tabs && !*bp)
   2602 			/* nothing */ ;
   2603 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2604 			*bp = 1;
   2605 	}
   2606 	/* update terminal size */
   2607 	term.col = col;
   2608 	term.row = row;
   2609 	/* reset scrolling region */
   2610 	tsetscroll(0, row-1);
   2611 	/* make use of the LIMIT in tmoveto */
   2612 	tmoveto(term.c.x, term.c.y);
   2613 	/* Clearing both screens (it makes dirty all lines) */
   2614 	c = term.c;
   2615 	for (i = 0; i < 2; i++) {
   2616 		if (mincol < col && 0 < minrow) {
   2617 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2618 		}
   2619 		if (0 < col && minrow < row) {
   2620 			tclearregion(0, minrow, col - 1, row - 1);
   2621 		}
   2622 		tswapscreen();
   2623 		tcursor(CURSOR_LOAD);
   2624 	}
   2625 	term.c = c;
   2626 }
   2627 
   2628 void
   2629 resettitle(void)
   2630 {
   2631 	xsettitle(NULL);
   2632 }
   2633 
   2634 void
   2635 drawregion(int x1, int y1, int x2, int y2)
   2636 {
   2637 	int y;
   2638 
   2639 	for (y = y1; y < y2; y++) {
   2640 		if (!term.dirty[y])
   2641 			continue;
   2642 
   2643 		term.dirty[y] = 0;
   2644 		xdrawline(term.line[y], x1, y, x2);
   2645 	}
   2646 }
   2647 
   2648 void
   2649 draw(void)
   2650 {
   2651 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2652 
   2653 	if (!xstartdraw())
   2654 		return;
   2655 
   2656 	/* adjust cursor position */
   2657 	LIMIT(term.ocx, 0, term.col-1);
   2658 	LIMIT(term.ocy, 0, term.row-1);
   2659 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2660 		term.ocx--;
   2661 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2662 		cx--;
   2663 
   2664 	drawregion(0, 0, term.col, term.row);
   2665 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2666 			term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2667 	term.ocx = cx;
   2668 	term.ocy = term.c.y;
   2669 	xfinishdraw();
   2670 	if (ocx != term.ocx || ocy != term.ocy)
   2671 		xximspot(term.ocx, term.ocy);
   2672 }
   2673 
   2674 void
   2675 redraw(void)
   2676 {
   2677 	tfulldirt();
   2678 	draw();
   2679 }