st

Personal build of suckless' st
git clone git://git.gormless.xyz/st.git
Log | Files | Refs | README | LICENSE

st.c (56097B)


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