st

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

st.c (56111B)


      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 		close(m);
    797 		setsid(); /* create a new process group */
    798 		dup2(s, 0);
    799 		dup2(s, 1);
    800 		dup2(s, 2);
    801 		if (ioctl(s, TIOCSCTTY, NULL) < 0)
    802 			die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
    803 		if (s > 2)
    804 			close(s);
    805 #ifdef __OpenBSD__
    806 		if (pledge("stdio getpw proc exec", NULL) == -1)
    807 			die("pledge\n");
    808 #endif
    809 		execsh(cmd, args);
    810 		break;
    811 	default:
    812 #ifdef __OpenBSD__
    813 		if (pledge("stdio rpath tty proc", NULL) == -1)
    814 			die("pledge\n");
    815 #endif
    816 		close(s);
    817 		cmdfd = m;
    818 		signal(SIGCHLD, sigchld);
    819 		break;
    820 	}
    821 	return cmdfd;
    822 }
    823 
    824 size_t
    825 ttyread(void)
    826 {
    827 	static char buf[BUFSIZ];
    828 	static int buflen = 0;
    829 	int ret, written;
    830 
    831 	/* append read bytes to unprocessed bytes */
    832 	ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
    833 
    834 	switch (ret) {
    835 	case 0:
    836 		exit(0);
    837 	case -1:
    838 		die("couldn't read from shell: %s\n", strerror(errno));
    839 	default:
    840 		buflen += ret;
    841 		written = twrite(buf, buflen, 0);
    842 		buflen -= written;
    843 		/* keep any incomplete UTF-8 byte sequence for the next call */
    844 		if (buflen > 0)
    845 			memmove(buf, buf + written, buflen);
    846 		return ret;
    847 	}
    848 }
    849 
    850 void
    851 ttywrite(const char *s, size_t n, int may_echo)
    852 {
    853 	const char *next;
    854 
    855 	if (may_echo && IS_SET(MODE_ECHO))
    856 		twrite(s, n, 1);
    857 
    858 	if (!IS_SET(MODE_CRLF)) {
    859 		ttywriteraw(s, n);
    860 		return;
    861 	}
    862 
    863 	/* This is similar to how the kernel handles ONLCR for ttys */
    864 	while (n > 0) {
    865 		if (*s == '\r') {
    866 			next = s + 1;
    867 			ttywriteraw("\r\n", 2);
    868 		} else {
    869 			next = memchr(s, '\r', n);
    870 			DEFAULT(next, s + n);
    871 			ttywriteraw(s, next - s);
    872 		}
    873 		n -= next - s;
    874 		s = next;
    875 	}
    876 }
    877 
    878 void
    879 ttywriteraw(const char *s, size_t n)
    880 {
    881 	fd_set wfd, rfd;
    882 	ssize_t r;
    883 	size_t lim = 256;
    884 
    885 	/*
    886 	 * Remember that we are using a pty, which might be a modem line.
    887 	 * Writing too much will clog the line. That's why we are doing this
    888 	 * dance.
    889 	 * FIXME: Migrate the world to Plan 9.
    890 	 */
    891 	while (n > 0) {
    892 		FD_ZERO(&wfd);
    893 		FD_ZERO(&rfd);
    894 		FD_SET(cmdfd, &wfd);
    895 		FD_SET(cmdfd, &rfd);
    896 
    897 		/* Check if we can write. */
    898 		if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
    899 			if (errno == EINTR)
    900 				continue;
    901 			die("select failed: %s\n", strerror(errno));
    902 		}
    903 		if (FD_ISSET(cmdfd, &wfd)) {
    904 			/*
    905 			 * Only write the bytes written by ttywrite() or the
    906 			 * default of 256. This seems to be a reasonable value
    907 			 * for a serial line. Bigger values might clog the I/O.
    908 			 */
    909 			if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
    910 				goto write_error;
    911 			if (r < n) {
    912 				/*
    913 				 * We weren't able to write out everything.
    914 				 * This means the buffer is getting full
    915 				 * again. Empty it.
    916 				 */
    917 				if (n < lim)
    918 					lim = ttyread();
    919 				n -= r;
    920 				s += r;
    921 			} else {
    922 				/* All bytes have been written. */
    923 				break;
    924 			}
    925 		}
    926 		if (FD_ISSET(cmdfd, &rfd))
    927 			lim = ttyread();
    928 	}
    929 	return;
    930 
    931 write_error:
    932 	die("write error on tty: %s\n", strerror(errno));
    933 }
    934 
    935 void
    936 ttyresize(int tw, int th)
    937 {
    938 	struct winsize w;
    939 
    940 	w.ws_row = term.row;
    941 	w.ws_col = term.col;
    942 	w.ws_xpixel = tw;
    943 	w.ws_ypixel = th;
    944 	if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
    945 		fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
    946 }
    947 
    948 void
    949 ttyhangup()
    950 {
    951 	/* Send SIGHUP to shell */
    952 	kill(pid, SIGHUP);
    953 }
    954 
    955 int
    956 tattrset(int attr)
    957 {
    958 	int i, j;
    959 
    960 	for (i = 0; i < term.row-1; i++) {
    961 		for (j = 0; j < term.col-1; j++) {
    962 			if (term.line[i][j].mode & attr)
    963 				return 1;
    964 		}
    965 	}
    966 
    967 	return 0;
    968 }
    969 
    970 void
    971 tsetdirt(int top, int bot)
    972 {
    973 	int i;
    974 
    975 	LIMIT(top, 0, term.row-1);
    976 	LIMIT(bot, 0, term.row-1);
    977 
    978 	for (i = top; i <= bot; i++)
    979 		term.dirty[i] = 1;
    980 }
    981 
    982 void
    983 tsetdirtattr(int attr)
    984 {
    985 	int i, j;
    986 
    987 	for (i = 0; i < term.row-1; i++) {
    988 		for (j = 0; j < term.col-1; j++) {
    989 			if (term.line[i][j].mode & attr) {
    990 				tsetdirt(i, i);
    991 				break;
    992 			}
    993 		}
    994 	}
    995 }
    996 
    997 void
    998 tfulldirt(void)
    999 {
   1000 	tsetdirt(0, term.row-1);
   1001 }
   1002 
   1003 void
   1004 tcursor(int mode)
   1005 {
   1006 	static TCursor c[2];
   1007 	int alt = IS_SET(MODE_ALTSCREEN);
   1008 
   1009 	if (mode == CURSOR_SAVE) {
   1010 		c[alt] = term.c;
   1011 	} else if (mode == CURSOR_LOAD) {
   1012 		term.c = c[alt];
   1013 		tmoveto(c[alt].x, c[alt].y);
   1014 	}
   1015 }
   1016 
   1017 void
   1018 treset(void)
   1019 {
   1020 	uint i;
   1021 
   1022 	term.c = (TCursor){{
   1023 		.mode = ATTR_NULL,
   1024 		.fg = defaultfg,
   1025 		.bg = defaultbg
   1026 	}, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
   1027 
   1028 	memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1029 	for (i = tabspaces; i < term.col; i += tabspaces)
   1030 		term.tabs[i] = 1;
   1031 	term.top = 0;
   1032 	term.bot = term.row - 1;
   1033 	term.mode = MODE_WRAP|MODE_UTF8;
   1034 	memset(term.trantbl, CS_USA, sizeof(term.trantbl));
   1035 	term.charset = 0;
   1036 
   1037 	for (i = 0; i < 2; i++) {
   1038 		tmoveto(0, 0);
   1039 		tcursor(CURSOR_SAVE);
   1040 		tclearregion(0, 0, term.col-1, term.row-1);
   1041 		tswapscreen();
   1042 	}
   1043 }
   1044 
   1045 void
   1046 tnew(int col, int row)
   1047 {
   1048 	term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
   1049 	tresize(col, row);
   1050 	treset();
   1051 }
   1052 
   1053 void
   1054 tswapscreen(void)
   1055 {
   1056 	Line *tmp = term.line;
   1057 
   1058 	term.line = term.alt;
   1059 	term.alt = tmp;
   1060 	term.mode ^= MODE_ALTSCREEN;
   1061 	tfulldirt();
   1062 }
   1063 
   1064 void
   1065 tscrolldown(int orig, int n)
   1066 {
   1067 	int i;
   1068 	Line temp;
   1069 
   1070 	LIMIT(n, 0, term.bot-orig+1);
   1071 
   1072 	tsetdirt(orig, term.bot-n);
   1073 	tclearregion(0, term.bot-n+1, term.col-1, term.bot);
   1074 
   1075 	for (i = term.bot; i >= orig+n; i--) {
   1076 		temp = term.line[i];
   1077 		term.line[i] = term.line[i-n];
   1078 		term.line[i-n] = temp;
   1079 	}
   1080 
   1081 	selscroll(orig, n);
   1082 }
   1083 
   1084 void
   1085 tscrollup(int orig, int n)
   1086 {
   1087 	int i;
   1088 	Line temp;
   1089 
   1090 	LIMIT(n, 0, term.bot-orig+1);
   1091 
   1092 	tclearregion(0, orig, term.col-1, orig+n-1);
   1093 	tsetdirt(orig+n, term.bot);
   1094 
   1095 	for (i = orig; i <= term.bot-n; i++) {
   1096 		temp = term.line[i];
   1097 		term.line[i] = term.line[i+n];
   1098 		term.line[i+n] = temp;
   1099 	}
   1100 
   1101 	selscroll(orig, -n);
   1102 }
   1103 
   1104 void
   1105 selscroll(int orig, int n)
   1106 {
   1107 	if (sel.ob.x == -1)
   1108 		return;
   1109 
   1110 	if (BETWEEN(sel.nb.y, orig, term.bot) != BETWEEN(sel.ne.y, orig, term.bot)) {
   1111 		selclear();
   1112 	} else if (BETWEEN(sel.nb.y, orig, term.bot)) {
   1113 		sel.ob.y += n;
   1114 		sel.oe.y += n;
   1115 		if (sel.ob.y < term.top || sel.ob.y > term.bot ||
   1116 		    sel.oe.y < term.top || sel.oe.y > term.bot) {
   1117 			selclear();
   1118 		} else {
   1119 			selnormalize();
   1120 		}
   1121 	}
   1122 }
   1123 
   1124 void
   1125 tnewline(int first_col)
   1126 {
   1127 	int y = term.c.y;
   1128 
   1129 	if (y == term.bot) {
   1130 		tscrollup(term.top, 1);
   1131 	} else {
   1132 		y++;
   1133 	}
   1134 	tmoveto(first_col ? 0 : term.c.x, y);
   1135 }
   1136 
   1137 void
   1138 csiparse(void)
   1139 {
   1140 	char *p = csiescseq.buf, *np;
   1141 	long int v;
   1142 
   1143 	csiescseq.narg = 0;
   1144 	if (*p == '?') {
   1145 		csiescseq.priv = 1;
   1146 		p++;
   1147 	}
   1148 
   1149 	csiescseq.buf[csiescseq.len] = '\0';
   1150 	while (p < csiescseq.buf+csiescseq.len) {
   1151 		np = NULL;
   1152 		v = strtol(p, &np, 10);
   1153 		if (np == p)
   1154 			v = 0;
   1155 		if (v == LONG_MAX || v == LONG_MIN)
   1156 			v = -1;
   1157 		csiescseq.arg[csiescseq.narg++] = v;
   1158 		p = np;
   1159 		if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
   1160 			break;
   1161 		p++;
   1162 	}
   1163 	csiescseq.mode[0] = *p++;
   1164 	csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
   1165 }
   1166 
   1167 /* for absolute user moves, when decom is set */
   1168 void
   1169 tmoveato(int x, int y)
   1170 {
   1171 	tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
   1172 }
   1173 
   1174 void
   1175 tmoveto(int x, int y)
   1176 {
   1177 	int miny, maxy;
   1178 
   1179 	if (term.c.state & CURSOR_ORIGIN) {
   1180 		miny = term.top;
   1181 		maxy = term.bot;
   1182 	} else {
   1183 		miny = 0;
   1184 		maxy = term.row - 1;
   1185 	}
   1186 	term.c.state &= ~CURSOR_WRAPNEXT;
   1187 	term.c.x = LIMIT(x, 0, term.col-1);
   1188 	term.c.y = LIMIT(y, miny, maxy);
   1189 }
   1190 
   1191 void
   1192 tsetchar(Rune u, const Glyph *attr, int x, int y)
   1193 {
   1194 	static const char *vt100_0[62] = { /* 0x41 - 0x7e */
   1195 		"↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
   1196 		0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
   1197 		0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
   1198 		0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
   1199 		"◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
   1200 		"␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
   1201 		"⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
   1202 		"│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
   1203 	};
   1204 
   1205 	/*
   1206 	 * The table is proudly stolen from rxvt.
   1207 	 */
   1208 	if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
   1209 	   BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
   1210 		utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
   1211 
   1212 	if (term.line[y][x].mode & ATTR_WIDE) {
   1213 		if (x+1 < term.col) {
   1214 			term.line[y][x+1].u = ' ';
   1215 			term.line[y][x+1].mode &= ~ATTR_WDUMMY;
   1216 		}
   1217 	} else if (term.line[y][x].mode & ATTR_WDUMMY) {
   1218 		term.line[y][x-1].u = ' ';
   1219 		term.line[y][x-1].mode &= ~ATTR_WIDE;
   1220 	}
   1221 
   1222 	term.dirty[y] = 1;
   1223 	term.line[y][x] = *attr;
   1224 	term.line[y][x].u = u;
   1225 }
   1226 
   1227 void
   1228 tclearregion(int x1, int y1, int x2, int y2)
   1229 {
   1230 	int x, y, temp;
   1231 	Glyph *gp;
   1232 
   1233 	if (x1 > x2)
   1234 		temp = x1, x1 = x2, x2 = temp;
   1235 	if (y1 > y2)
   1236 		temp = y1, y1 = y2, y2 = temp;
   1237 
   1238 	LIMIT(x1, 0, term.col-1);
   1239 	LIMIT(x2, 0, term.col-1);
   1240 	LIMIT(y1, 0, term.row-1);
   1241 	LIMIT(y2, 0, term.row-1);
   1242 
   1243 	for (y = y1; y <= y2; y++) {
   1244 		term.dirty[y] = 1;
   1245 		for (x = x1; x <= x2; x++) {
   1246 			gp = &term.line[y][x];
   1247 			if (selected(x, y))
   1248 				selclear();
   1249 			gp->fg = term.c.attr.fg;
   1250 			gp->bg = term.c.attr.bg;
   1251 			gp->mode = 0;
   1252 			gp->u = ' ';
   1253 		}
   1254 	}
   1255 }
   1256 
   1257 void
   1258 tdeletechar(int n)
   1259 {
   1260 	int dst, src, size;
   1261 	Glyph *line;
   1262 
   1263 	LIMIT(n, 0, term.col - term.c.x);
   1264 
   1265 	dst = term.c.x;
   1266 	src = term.c.x + n;
   1267 	size = term.col - src;
   1268 	line = term.line[term.c.y];
   1269 
   1270 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1271 	tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
   1272 }
   1273 
   1274 void
   1275 tinsertblank(int n)
   1276 {
   1277 	int dst, src, size;
   1278 	Glyph *line;
   1279 
   1280 	LIMIT(n, 0, term.col - term.c.x);
   1281 
   1282 	dst = term.c.x + n;
   1283 	src = term.c.x;
   1284 	size = term.col - dst;
   1285 	line = term.line[term.c.y];
   1286 
   1287 	memmove(&line[dst], &line[src], size * sizeof(Glyph));
   1288 	tclearregion(src, term.c.y, dst - 1, term.c.y);
   1289 }
   1290 
   1291 void
   1292 tinsertblankline(int n)
   1293 {
   1294 	if (BETWEEN(term.c.y, term.top, term.bot))
   1295 		tscrolldown(term.c.y, n);
   1296 }
   1297 
   1298 void
   1299 tdeleteline(int n)
   1300 {
   1301 	if (BETWEEN(term.c.y, term.top, term.bot))
   1302 		tscrollup(term.c.y, n);
   1303 }
   1304 
   1305 int32_t
   1306 tdefcolor(const int *attr, int *npar, int l)
   1307 {
   1308 	int32_t idx = -1;
   1309 	uint r, g, b;
   1310 
   1311 	switch (attr[*npar + 1]) {
   1312 	case 2: /* direct color in RGB space */
   1313 		if (*npar + 4 >= l) {
   1314 			fprintf(stderr,
   1315 				"erresc(38): Incorrect number of parameters (%d)\n",
   1316 				*npar);
   1317 			break;
   1318 		}
   1319 		r = attr[*npar + 2];
   1320 		g = attr[*npar + 3];
   1321 		b = attr[*npar + 4];
   1322 		*npar += 4;
   1323 		if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
   1324 			fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
   1325 				r, g, b);
   1326 		else
   1327 			idx = TRUECOLOR(r, g, b);
   1328 		break;
   1329 	case 5: /* indexed color */
   1330 		if (*npar + 2 >= l) {
   1331 			fprintf(stderr,
   1332 				"erresc(38): Incorrect number of parameters (%d)\n",
   1333 				*npar);
   1334 			break;
   1335 		}
   1336 		*npar += 2;
   1337 		if (!BETWEEN(attr[*npar], 0, 255))
   1338 			fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
   1339 		else
   1340 			idx = attr[*npar];
   1341 		break;
   1342 	case 0: /* implemented defined (only foreground) */
   1343 	case 1: /* transparent */
   1344 	case 3: /* direct color in CMY space */
   1345 	case 4: /* direct color in CMYK space */
   1346 	default:
   1347 		fprintf(stderr,
   1348 		        "erresc(38): gfx attr %d unknown\n", attr[*npar]);
   1349 		break;
   1350 	}
   1351 
   1352 	return idx;
   1353 }
   1354 
   1355 void
   1356 tsetattr(const int *attr, int l)
   1357 {
   1358 	int i;
   1359 	int32_t idx;
   1360 
   1361 	for (i = 0; i < l; i++) {
   1362 		switch (attr[i]) {
   1363 		case 0:
   1364 			term.c.attr.mode &= ~(
   1365 				ATTR_BOLD       |
   1366 				ATTR_FAINT      |
   1367 				ATTR_ITALIC     |
   1368 				ATTR_UNDERLINE  |
   1369 				ATTR_BLINK      |
   1370 				ATTR_REVERSE    |
   1371 				ATTR_INVISIBLE  |
   1372 				ATTR_STRUCK     );
   1373 			term.c.attr.fg = defaultfg;
   1374 			term.c.attr.bg = defaultbg;
   1375 			break;
   1376 		case 1:
   1377 			term.c.attr.mode |= ATTR_BOLD;
   1378 			break;
   1379 		case 2:
   1380 			term.c.attr.mode |= ATTR_FAINT;
   1381 			break;
   1382 		case 3:
   1383 			term.c.attr.mode |= ATTR_ITALIC;
   1384 			break;
   1385 		case 4:
   1386 			term.c.attr.mode |= ATTR_UNDERLINE;
   1387 			break;
   1388 		case 5: /* slow blink */
   1389 			/* FALLTHROUGH */
   1390 		case 6: /* rapid blink */
   1391 			term.c.attr.mode |= ATTR_BLINK;
   1392 			break;
   1393 		case 7:
   1394 			term.c.attr.mode |= ATTR_REVERSE;
   1395 			break;
   1396 		case 8:
   1397 			term.c.attr.mode |= ATTR_INVISIBLE;
   1398 			break;
   1399 		case 9:
   1400 			term.c.attr.mode |= ATTR_STRUCK;
   1401 			break;
   1402 		case 22:
   1403 			term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
   1404 			break;
   1405 		case 23:
   1406 			term.c.attr.mode &= ~ATTR_ITALIC;
   1407 			break;
   1408 		case 24:
   1409 			term.c.attr.mode &= ~ATTR_UNDERLINE;
   1410 			break;
   1411 		case 25:
   1412 			term.c.attr.mode &= ~ATTR_BLINK;
   1413 			break;
   1414 		case 27:
   1415 			term.c.attr.mode &= ~ATTR_REVERSE;
   1416 			break;
   1417 		case 28:
   1418 			term.c.attr.mode &= ~ATTR_INVISIBLE;
   1419 			break;
   1420 		case 29:
   1421 			term.c.attr.mode &= ~ATTR_STRUCK;
   1422 			break;
   1423 		case 38:
   1424 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1425 				term.c.attr.fg = idx;
   1426 			break;
   1427 		case 39:
   1428 			term.c.attr.fg = defaultfg;
   1429 			break;
   1430 		case 48:
   1431 			if ((idx = tdefcolor(attr, &i, l)) >= 0)
   1432 				term.c.attr.bg = idx;
   1433 			break;
   1434 		case 49:
   1435 			term.c.attr.bg = defaultbg;
   1436 			break;
   1437 		default:
   1438 			if (BETWEEN(attr[i], 30, 37)) {
   1439 				term.c.attr.fg = attr[i] - 30;
   1440 			} else if (BETWEEN(attr[i], 40, 47)) {
   1441 				term.c.attr.bg = attr[i] - 40;
   1442 			} else if (BETWEEN(attr[i], 90, 97)) {
   1443 				term.c.attr.fg = attr[i] - 90 + 8;
   1444 			} else if (BETWEEN(attr[i], 100, 107)) {
   1445 				term.c.attr.bg = attr[i] - 100 + 8;
   1446 			} else {
   1447 				fprintf(stderr,
   1448 					"erresc(default): gfx attr %d unknown\n",
   1449 					attr[i]);
   1450 				csidump();
   1451 			}
   1452 			break;
   1453 		}
   1454 	}
   1455 }
   1456 
   1457 void
   1458 tsetscroll(int t, int b)
   1459 {
   1460 	int temp;
   1461 
   1462 	LIMIT(t, 0, term.row-1);
   1463 	LIMIT(b, 0, term.row-1);
   1464 	if (t > b) {
   1465 		temp = t;
   1466 		t = b;
   1467 		b = temp;
   1468 	}
   1469 	term.top = t;
   1470 	term.bot = b;
   1471 }
   1472 
   1473 void
   1474 tsetmode(int priv, int set, const int *args, int narg)
   1475 {
   1476 	int alt; const int *lim;
   1477 
   1478 	for (lim = args + narg; args < lim; ++args) {
   1479 		if (priv) {
   1480 			switch (*args) {
   1481 			case 1: /* DECCKM -- Cursor key */
   1482 				xsetmode(set, MODE_APPCURSOR);
   1483 				break;
   1484 			case 5: /* DECSCNM -- Reverse video */
   1485 				xsetmode(set, MODE_REVERSE);
   1486 				break;
   1487 			case 6: /* DECOM -- Origin */
   1488 				MODBIT(term.c.state, set, CURSOR_ORIGIN);
   1489 				tmoveato(0, 0);
   1490 				break;
   1491 			case 7: /* DECAWM -- Auto wrap */
   1492 				MODBIT(term.mode, set, MODE_WRAP);
   1493 				break;
   1494 			case 0:  /* Error (IGNORED) */
   1495 			case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
   1496 			case 3:  /* DECCOLM -- Column  (IGNORED) */
   1497 			case 4:  /* DECSCLM -- Scroll (IGNORED) */
   1498 			case 8:  /* DECARM -- Auto repeat (IGNORED) */
   1499 			case 18: /* DECPFF -- Printer feed (IGNORED) */
   1500 			case 19: /* DECPEX -- Printer extent (IGNORED) */
   1501 			case 42: /* DECNRCM -- National characters (IGNORED) */
   1502 			case 12: /* att610 -- Start blinking cursor (IGNORED) */
   1503 				break;
   1504 			case 25: /* DECTCEM -- Text Cursor Enable Mode */
   1505 				xsetmode(!set, MODE_HIDE);
   1506 				break;
   1507 			case 9:    /* X10 mouse compatibility mode */
   1508 				xsetpointermotion(0);
   1509 				xsetmode(0, MODE_MOUSE);
   1510 				xsetmode(set, MODE_MOUSEX10);
   1511 				break;
   1512 			case 1000: /* 1000: report button press */
   1513 				xsetpointermotion(0);
   1514 				xsetmode(0, MODE_MOUSE);
   1515 				xsetmode(set, MODE_MOUSEBTN);
   1516 				break;
   1517 			case 1002: /* 1002: report motion on button press */
   1518 				xsetpointermotion(0);
   1519 				xsetmode(0, MODE_MOUSE);
   1520 				xsetmode(set, MODE_MOUSEMOTION);
   1521 				break;
   1522 			case 1003: /* 1003: enable all mouse motions */
   1523 				xsetpointermotion(set);
   1524 				xsetmode(0, MODE_MOUSE);
   1525 				xsetmode(set, MODE_MOUSEMANY);
   1526 				break;
   1527 			case 1004: /* 1004: send focus events to tty */
   1528 				xsetmode(set, MODE_FOCUS);
   1529 				break;
   1530 			case 1006: /* 1006: extended reporting mode */
   1531 				xsetmode(set, MODE_MOUSESGR);
   1532 				break;
   1533 			case 1034:
   1534 				xsetmode(set, MODE_8BIT);
   1535 				break;
   1536 			case 1049: /* swap screen & set/restore cursor as xterm */
   1537 				if (!allowaltscreen)
   1538 					break;
   1539 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1540 				/* FALLTHROUGH */
   1541 			case 47: /* swap screen */
   1542 			case 1047:
   1543 				if (!allowaltscreen)
   1544 					break;
   1545 				alt = IS_SET(MODE_ALTSCREEN);
   1546 				if (alt) {
   1547 					tclearregion(0, 0, term.col-1,
   1548 							term.row-1);
   1549 				}
   1550 				if (set ^ alt) /* set is always 1 or 0 */
   1551 					tswapscreen();
   1552 				if (*args != 1049)
   1553 					break;
   1554 				/* FALLTHROUGH */
   1555 			case 1048:
   1556 				tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
   1557 				break;
   1558 			case 2004: /* 2004: bracketed paste mode */
   1559 				xsetmode(set, MODE_BRCKTPASTE);
   1560 				break;
   1561 			/* Not implemented mouse modes. See comments there. */
   1562 			case 1001: /* mouse highlight mode; can hang the
   1563 				      terminal by design when implemented. */
   1564 			case 1005: /* UTF-8 mouse mode; will confuse
   1565 				      applications not supporting UTF-8
   1566 				      and luit. */
   1567 			case 1015: /* urxvt mangled mouse mode; incompatible
   1568 				      and can be mistaken for other control
   1569 				      codes. */
   1570 				break;
   1571 			default:
   1572 				fprintf(stderr,
   1573 					"erresc: unknown private set/reset mode %d\n",
   1574 					*args);
   1575 				break;
   1576 			}
   1577 		} else {
   1578 			switch (*args) {
   1579 			case 0:  /* Error (IGNORED) */
   1580 				break;
   1581 			case 2:
   1582 				xsetmode(set, MODE_KBDLOCK);
   1583 				break;
   1584 			case 4:  /* IRM -- Insertion-replacement */
   1585 				MODBIT(term.mode, set, MODE_INSERT);
   1586 				break;
   1587 			case 12: /* SRM -- Send/Receive */
   1588 				MODBIT(term.mode, !set, MODE_ECHO);
   1589 				break;
   1590 			case 20: /* LNM -- Linefeed/new line */
   1591 				MODBIT(term.mode, set, MODE_CRLF);
   1592 				break;
   1593 			default:
   1594 				fprintf(stderr,
   1595 					"erresc: unknown set/reset mode %d\n",
   1596 					*args);
   1597 				break;
   1598 			}
   1599 		}
   1600 	}
   1601 }
   1602 
   1603 void
   1604 csihandle(void)
   1605 {
   1606 	char buf[40];
   1607 	int len;
   1608 
   1609 	switch (csiescseq.mode[0]) {
   1610 	default:
   1611 	unknown:
   1612 		fprintf(stderr, "erresc: unknown csi ");
   1613 		csidump();
   1614 		/* die(""); */
   1615 		break;
   1616 	case '@': /* ICH -- Insert <n> blank char */
   1617 		DEFAULT(csiescseq.arg[0], 1);
   1618 		tinsertblank(csiescseq.arg[0]);
   1619 		break;
   1620 	case 'A': /* CUU -- Cursor <n> Up */
   1621 		DEFAULT(csiescseq.arg[0], 1);
   1622 		tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
   1623 		break;
   1624 	case 'B': /* CUD -- Cursor <n> Down */
   1625 	case 'e': /* VPR --Cursor <n> Down */
   1626 		DEFAULT(csiescseq.arg[0], 1);
   1627 		tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
   1628 		break;
   1629 	case 'i': /* MC -- Media Copy */
   1630 		switch (csiescseq.arg[0]) {
   1631 		case 0:
   1632 			tdump();
   1633 			break;
   1634 		case 1:
   1635 			tdumpline(term.c.y);
   1636 			break;
   1637 		case 2:
   1638 			tdumpsel();
   1639 			break;
   1640 		case 4:
   1641 			term.mode &= ~MODE_PRINT;
   1642 			break;
   1643 		case 5:
   1644 			term.mode |= MODE_PRINT;
   1645 			break;
   1646 		}
   1647 		break;
   1648 	case 'c': /* DA -- Device Attributes */
   1649 		if (csiescseq.arg[0] == 0)
   1650 			ttywrite(vtiden, strlen(vtiden), 0);
   1651 		break;
   1652 	case 'b': /* REP -- if last char is printable print it <n> more times */
   1653 		DEFAULT(csiescseq.arg[0], 1);
   1654 		if (term.lastc)
   1655 			while (csiescseq.arg[0]-- > 0)
   1656 				tputc(term.lastc);
   1657 		break;
   1658 	case 'C': /* CUF -- Cursor <n> Forward */
   1659 	case 'a': /* HPR -- Cursor <n> Forward */
   1660 		DEFAULT(csiescseq.arg[0], 1);
   1661 		tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
   1662 		break;
   1663 	case 'D': /* CUB -- Cursor <n> Backward */
   1664 		DEFAULT(csiescseq.arg[0], 1);
   1665 		tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
   1666 		break;
   1667 	case 'E': /* CNL -- Cursor <n> Down and first col */
   1668 		DEFAULT(csiescseq.arg[0], 1);
   1669 		tmoveto(0, term.c.y+csiescseq.arg[0]);
   1670 		break;
   1671 	case 'F': /* CPL -- Cursor <n> Up and first col */
   1672 		DEFAULT(csiescseq.arg[0], 1);
   1673 		tmoveto(0, term.c.y-csiescseq.arg[0]);
   1674 		break;
   1675 	case 'g': /* TBC -- Tabulation clear */
   1676 		switch (csiescseq.arg[0]) {
   1677 		case 0: /* clear current tab stop */
   1678 			term.tabs[term.c.x] = 0;
   1679 			break;
   1680 		case 3: /* clear all the tabs */
   1681 			memset(term.tabs, 0, term.col * sizeof(*term.tabs));
   1682 			break;
   1683 		default:
   1684 			goto unknown;
   1685 		}
   1686 		break;
   1687 	case 'G': /* CHA -- Move to <col> */
   1688 	case '`': /* HPA */
   1689 		DEFAULT(csiescseq.arg[0], 1);
   1690 		tmoveto(csiescseq.arg[0]-1, term.c.y);
   1691 		break;
   1692 	case 'H': /* CUP -- Move to <row> <col> */
   1693 	case 'f': /* HVP */
   1694 		DEFAULT(csiescseq.arg[0], 1);
   1695 		DEFAULT(csiescseq.arg[1], 1);
   1696 		tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
   1697 		break;
   1698 	case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
   1699 		DEFAULT(csiescseq.arg[0], 1);
   1700 		tputtab(csiescseq.arg[0]);
   1701 		break;
   1702 	case 'J': /* ED -- Clear screen */
   1703 		switch (csiescseq.arg[0]) {
   1704 		case 0: /* below */
   1705 			tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
   1706 			if (term.c.y < term.row-1) {
   1707 				tclearregion(0, term.c.y+1, term.col-1,
   1708 						term.row-1);
   1709 			}
   1710 			break;
   1711 		case 1: /* above */
   1712 			if (term.c.y > 1)
   1713 				tclearregion(0, 0, term.col-1, term.c.y-1);
   1714 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1715 			break;
   1716 		case 2: /* all */
   1717 			tclearregion(0, 0, term.col-1, term.row-1);
   1718 			break;
   1719 		default:
   1720 			goto unknown;
   1721 		}
   1722 		break;
   1723 	case 'K': /* EL -- Clear line */
   1724 		switch (csiescseq.arg[0]) {
   1725 		case 0: /* right */
   1726 			tclearregion(term.c.x, term.c.y, term.col-1,
   1727 					term.c.y);
   1728 			break;
   1729 		case 1: /* left */
   1730 			tclearregion(0, term.c.y, term.c.x, term.c.y);
   1731 			break;
   1732 		case 2: /* all */
   1733 			tclearregion(0, term.c.y, term.col-1, term.c.y);
   1734 			break;
   1735 		}
   1736 		break;
   1737 	case 'S': /* SU -- Scroll <n> line up */
   1738 		DEFAULT(csiescseq.arg[0], 1);
   1739 		tscrollup(term.top, csiescseq.arg[0]);
   1740 		break;
   1741 	case 'T': /* SD -- Scroll <n> line down */
   1742 		DEFAULT(csiescseq.arg[0], 1);
   1743 		tscrolldown(term.top, csiescseq.arg[0]);
   1744 		break;
   1745 	case 'L': /* IL -- Insert <n> blank lines */
   1746 		DEFAULT(csiescseq.arg[0], 1);
   1747 		tinsertblankline(csiescseq.arg[0]);
   1748 		break;
   1749 	case 'l': /* RM -- Reset Mode */
   1750 		tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
   1751 		break;
   1752 	case 'M': /* DL -- Delete <n> lines */
   1753 		DEFAULT(csiescseq.arg[0], 1);
   1754 		tdeleteline(csiescseq.arg[0]);
   1755 		break;
   1756 	case 'X': /* ECH -- Erase <n> char */
   1757 		DEFAULT(csiescseq.arg[0], 1);
   1758 		tclearregion(term.c.x, term.c.y,
   1759 				term.c.x + csiescseq.arg[0] - 1, term.c.y);
   1760 		break;
   1761 	case 'P': /* DCH -- Delete <n> char */
   1762 		DEFAULT(csiescseq.arg[0], 1);
   1763 		tdeletechar(csiescseq.arg[0]);
   1764 		break;
   1765 	case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
   1766 		DEFAULT(csiescseq.arg[0], 1);
   1767 		tputtab(-csiescseq.arg[0]);
   1768 		break;
   1769 	case 'd': /* VPA -- Move to <row> */
   1770 		DEFAULT(csiescseq.arg[0], 1);
   1771 		tmoveato(term.c.x, csiescseq.arg[0]-1);
   1772 		break;
   1773 	case 'h': /* SM -- Set terminal mode */
   1774 		tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
   1775 		break;
   1776 	case 'm': /* SGR -- Terminal attribute (color) */
   1777 		tsetattr(csiescseq.arg, csiescseq.narg);
   1778 		break;
   1779 	case 'n': /* DSR – Device Status Report (cursor position) */
   1780 		if (csiescseq.arg[0] == 6) {
   1781 			len = snprintf(buf, sizeof(buf), "\033[%i;%iR",
   1782 					term.c.y+1, term.c.x+1);
   1783 			ttywrite(buf, len, 0);
   1784 		}
   1785 		break;
   1786 	case 'r': /* DECSTBM -- Set Scrolling Region */
   1787 		if (csiescseq.priv) {
   1788 			goto unknown;
   1789 		} else {
   1790 			DEFAULT(csiescseq.arg[0], 1);
   1791 			DEFAULT(csiescseq.arg[1], term.row);
   1792 			tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
   1793 			tmoveato(0, 0);
   1794 		}
   1795 		break;
   1796 	case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
   1797 		tcursor(CURSOR_SAVE);
   1798 		break;
   1799 	case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
   1800 		tcursor(CURSOR_LOAD);
   1801 		break;
   1802 	case ' ':
   1803 		switch (csiescseq.mode[1]) {
   1804 		case 'q': /* DECSCUSR -- Set Cursor Style */
   1805 			if (xsetcursor(csiescseq.arg[0]))
   1806 				goto unknown;
   1807 			break;
   1808 		default:
   1809 			goto unknown;
   1810 		}
   1811 		break;
   1812 	}
   1813 }
   1814 
   1815 void
   1816 csidump(void)
   1817 {
   1818 	size_t i;
   1819 	uint c;
   1820 
   1821 	fprintf(stderr, "ESC[");
   1822 	for (i = 0; i < csiescseq.len; i++) {
   1823 		c = csiescseq.buf[i] & 0xff;
   1824 		if (isprint(c)) {
   1825 			putc(c, stderr);
   1826 		} else if (c == '\n') {
   1827 			fprintf(stderr, "(\\n)");
   1828 		} else if (c == '\r') {
   1829 			fprintf(stderr, "(\\r)");
   1830 		} else if (c == 0x1b) {
   1831 			fprintf(stderr, "(\\e)");
   1832 		} else {
   1833 			fprintf(stderr, "(%02x)", c);
   1834 		}
   1835 	}
   1836 	putc('\n', stderr);
   1837 }
   1838 
   1839 void
   1840 csireset(void)
   1841 {
   1842 	memset(&csiescseq, 0, sizeof(csiescseq));
   1843 }
   1844 
   1845 void
   1846 strhandle(void)
   1847 {
   1848 	char *p = NULL, *dec;
   1849 	int j, narg, par;
   1850 
   1851 	term.esc &= ~(ESC_STR_END|ESC_STR);
   1852 	strparse();
   1853 	par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
   1854 
   1855 	switch (strescseq.type) {
   1856 	case ']': /* OSC -- Operating System Command */
   1857 		switch (par) {
   1858 		case 0:
   1859 			if (narg > 1) {
   1860 				xsettitle(strescseq.args[1]);
   1861 				xseticontitle(strescseq.args[1]);
   1862 			}
   1863 			return;
   1864 		case 1:
   1865 			if (narg > 1)
   1866 				xseticontitle(strescseq.args[1]);
   1867 			return;
   1868 		case 2:
   1869 			if (narg > 1)
   1870 				xsettitle(strescseq.args[1]);
   1871 			return;
   1872 		case 52:
   1873 			if (narg > 2 && allowwindowops) {
   1874 				dec = base64dec(strescseq.args[2]);
   1875 				if (dec) {
   1876 					xsetsel(dec);
   1877 					xclipcopy();
   1878 				} else {
   1879 					fprintf(stderr, "erresc: invalid base64\n");
   1880 				}
   1881 			}
   1882 			return;
   1883 		case 4: /* color set */
   1884 			if (narg < 3)
   1885 				break;
   1886 			p = strescseq.args[2];
   1887 			/* FALLTHROUGH */
   1888 		case 104: /* color reset, here p = NULL */
   1889 			j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
   1890 			if (xsetcolorname(j, p)) {
   1891 				if (par == 104 && narg <= 1)
   1892 					return; /* color reset without parameter */
   1893 				fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
   1894 				        j, p ? p : "(null)");
   1895 			} else {
   1896 				/*
   1897 				 * TODO if defaultbg color is changed, borders
   1898 				 * are dirty
   1899 				 */
   1900 				redraw();
   1901 			}
   1902 			return;
   1903 		}
   1904 		break;
   1905 	case 'k': /* old title set compatibility */
   1906 		xsettitle(strescseq.args[0]);
   1907 		return;
   1908 	case 'P': /* DCS -- Device Control String */
   1909 	case '_': /* APC -- Application Program Command */
   1910 	case '^': /* PM -- Privacy Message */
   1911 		return;
   1912 	}
   1913 
   1914 	fprintf(stderr, "erresc: unknown str ");
   1915 	strdump();
   1916 }
   1917 
   1918 void
   1919 strparse(void)
   1920 {
   1921 	int c;
   1922 	char *p = strescseq.buf;
   1923 
   1924 	strescseq.narg = 0;
   1925 	strescseq.buf[strescseq.len] = '\0';
   1926 
   1927 	if (*p == '\0')
   1928 		return;
   1929 
   1930 	while (strescseq.narg < STR_ARG_SIZ) {
   1931 		strescseq.args[strescseq.narg++] = p;
   1932 		while ((c = *p) != ';' && c != '\0')
   1933 			++p;
   1934 		if (c == '\0')
   1935 			return;
   1936 		*p++ = '\0';
   1937 	}
   1938 }
   1939 
   1940 void
   1941 strdump(void)
   1942 {
   1943 	size_t i;
   1944 	uint c;
   1945 
   1946 	fprintf(stderr, "ESC%c", strescseq.type);
   1947 	for (i = 0; i < strescseq.len; i++) {
   1948 		c = strescseq.buf[i] & 0xff;
   1949 		if (c == '\0') {
   1950 			putc('\n', stderr);
   1951 			return;
   1952 		} else if (isprint(c)) {
   1953 			putc(c, stderr);
   1954 		} else if (c == '\n') {
   1955 			fprintf(stderr, "(\\n)");
   1956 		} else if (c == '\r') {
   1957 			fprintf(stderr, "(\\r)");
   1958 		} else if (c == 0x1b) {
   1959 			fprintf(stderr, "(\\e)");
   1960 		} else {
   1961 			fprintf(stderr, "(%02x)", c);
   1962 		}
   1963 	}
   1964 	fprintf(stderr, "ESC\\\n");
   1965 }
   1966 
   1967 void
   1968 strreset(void)
   1969 {
   1970 	strescseq = (STREscape){
   1971 		.buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
   1972 		.siz = STR_BUF_SIZ,
   1973 	};
   1974 }
   1975 
   1976 void
   1977 sendbreak(const Arg *arg)
   1978 {
   1979 	if (tcsendbreak(cmdfd, 0))
   1980 		perror("Error sending break");
   1981 }
   1982 
   1983 void
   1984 tprinter(char *s, size_t len)
   1985 {
   1986 	if (iofd != -1 && xwrite(iofd, s, len) < 0) {
   1987 		perror("Error writing to output file");
   1988 		close(iofd);
   1989 		iofd = -1;
   1990 	}
   1991 }
   1992 
   1993 void
   1994 toggleprinter(const Arg *arg)
   1995 {
   1996 	term.mode ^= MODE_PRINT;
   1997 }
   1998 
   1999 void
   2000 printscreen(const Arg *arg)
   2001 {
   2002 	tdump();
   2003 }
   2004 
   2005 void
   2006 printsel(const Arg *arg)
   2007 {
   2008 	tdumpsel();
   2009 }
   2010 
   2011 void
   2012 tdumpsel(void)
   2013 {
   2014 	char *ptr;
   2015 
   2016 	if ((ptr = getsel())) {
   2017 		tprinter(ptr, strlen(ptr));
   2018 		free(ptr);
   2019 	}
   2020 }
   2021 
   2022 void
   2023 tdumpline(int n)
   2024 {
   2025 	char buf[UTF_SIZ];
   2026 	const Glyph *bp, *end;
   2027 
   2028 	bp = &term.line[n][0];
   2029 	end = &bp[MIN(tlinelen(n), term.col) - 1];
   2030 	if (bp != end || bp->u != ' ') {
   2031 		for ( ; bp <= end; ++bp)
   2032 			tprinter(buf, utf8encode(bp->u, buf));
   2033 	}
   2034 	tprinter("\n", 1);
   2035 }
   2036 
   2037 void
   2038 tdump(void)
   2039 {
   2040 	int i;
   2041 
   2042 	for (i = 0; i < term.row; ++i)
   2043 		tdumpline(i);
   2044 }
   2045 
   2046 void
   2047 tputtab(int n)
   2048 {
   2049 	uint x = term.c.x;
   2050 
   2051 	if (n > 0) {
   2052 		while (x < term.col && n--)
   2053 			for (++x; x < term.col && !term.tabs[x]; ++x)
   2054 				/* nothing */ ;
   2055 	} else if (n < 0) {
   2056 		while (x > 0 && n++)
   2057 			for (--x; x > 0 && !term.tabs[x]; --x)
   2058 				/* nothing */ ;
   2059 	}
   2060 	term.c.x = LIMIT(x, 0, term.col-1);
   2061 }
   2062 
   2063 void
   2064 tdefutf8(char ascii)
   2065 {
   2066 	if (ascii == 'G')
   2067 		term.mode |= MODE_UTF8;
   2068 	else if (ascii == '@')
   2069 		term.mode &= ~MODE_UTF8;
   2070 }
   2071 
   2072 void
   2073 tdeftran(char ascii)
   2074 {
   2075 	static char cs[] = "0B";
   2076 	static int vcs[] = {CS_GRAPHIC0, CS_USA};
   2077 	char *p;
   2078 
   2079 	if ((p = strchr(cs, ascii)) == NULL) {
   2080 		fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
   2081 	} else {
   2082 		term.trantbl[term.icharset] = vcs[p - cs];
   2083 	}
   2084 }
   2085 
   2086 void
   2087 tdectest(char c)
   2088 {
   2089 	int x, y;
   2090 
   2091 	if (c == '8') { /* DEC screen alignment test. */
   2092 		for (x = 0; x < term.col; ++x) {
   2093 			for (y = 0; y < term.row; ++y)
   2094 				tsetchar('E', &term.c.attr, x, y);
   2095 		}
   2096 	}
   2097 }
   2098 
   2099 void
   2100 tstrsequence(uchar c)
   2101 {
   2102 	switch (c) {
   2103 	case 0x90:   /* DCS -- Device Control String */
   2104 		c = 'P';
   2105 		break;
   2106 	case 0x9f:   /* APC -- Application Program Command */
   2107 		c = '_';
   2108 		break;
   2109 	case 0x9e:   /* PM -- Privacy Message */
   2110 		c = '^';
   2111 		break;
   2112 	case 0x9d:   /* OSC -- Operating System Command */
   2113 		c = ']';
   2114 		break;
   2115 	}
   2116 	strreset();
   2117 	strescseq.type = c;
   2118 	term.esc |= ESC_STR;
   2119 }
   2120 
   2121 void
   2122 tcontrolcode(uchar ascii)
   2123 {
   2124 	switch (ascii) {
   2125 	case '\t':   /* HT */
   2126 		tputtab(1);
   2127 		return;
   2128 	case '\b':   /* BS */
   2129 		tmoveto(term.c.x-1, term.c.y);
   2130 		return;
   2131 	case '\r':   /* CR */
   2132 		tmoveto(0, term.c.y);
   2133 		return;
   2134 	case '\f':   /* LF */
   2135 	case '\v':   /* VT */
   2136 	case '\n':   /* LF */
   2137 		/* go to first col if the mode is set */
   2138 		tnewline(IS_SET(MODE_CRLF));
   2139 		return;
   2140 	case '\a':   /* BEL */
   2141 		if (term.esc & ESC_STR_END) {
   2142 			/* backwards compatibility to xterm */
   2143 			strhandle();
   2144 		} else {
   2145 			xbell();
   2146 		}
   2147 		break;
   2148 	case '\033': /* ESC */
   2149 		csireset();
   2150 		term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
   2151 		term.esc |= ESC_START;
   2152 		return;
   2153 	case '\016': /* SO (LS1 -- Locking shift 1) */
   2154 	case '\017': /* SI (LS0 -- Locking shift 0) */
   2155 		term.charset = 1 - (ascii - '\016');
   2156 		return;
   2157 	case '\032': /* SUB */
   2158 		tsetchar('?', &term.c.attr, term.c.x, term.c.y);
   2159 		/* FALLTHROUGH */
   2160 	case '\030': /* CAN */
   2161 		csireset();
   2162 		break;
   2163 	case '\005': /* ENQ (IGNORED) */
   2164 	case '\000': /* NUL (IGNORED) */
   2165 	case '\021': /* XON (IGNORED) */
   2166 	case '\023': /* XOFF (IGNORED) */
   2167 	case 0177:   /* DEL (IGNORED) */
   2168 		return;
   2169 	case 0x80:   /* TODO: PAD */
   2170 	case 0x81:   /* TODO: HOP */
   2171 	case 0x82:   /* TODO: BPH */
   2172 	case 0x83:   /* TODO: NBH */
   2173 	case 0x84:   /* TODO: IND */
   2174 		break;
   2175 	case 0x85:   /* NEL -- Next line */
   2176 		tnewline(1); /* always go to first col */
   2177 		break;
   2178 	case 0x86:   /* TODO: SSA */
   2179 	case 0x87:   /* TODO: ESA */
   2180 		break;
   2181 	case 0x88:   /* HTS -- Horizontal tab stop */
   2182 		term.tabs[term.c.x] = 1;
   2183 		break;
   2184 	case 0x89:   /* TODO: HTJ */
   2185 	case 0x8a:   /* TODO: VTS */
   2186 	case 0x8b:   /* TODO: PLD */
   2187 	case 0x8c:   /* TODO: PLU */
   2188 	case 0x8d:   /* TODO: RI */
   2189 	case 0x8e:   /* TODO: SS2 */
   2190 	case 0x8f:   /* TODO: SS3 */
   2191 	case 0x91:   /* TODO: PU1 */
   2192 	case 0x92:   /* TODO: PU2 */
   2193 	case 0x93:   /* TODO: STS */
   2194 	case 0x94:   /* TODO: CCH */
   2195 	case 0x95:   /* TODO: MW */
   2196 	case 0x96:   /* TODO: SPA */
   2197 	case 0x97:   /* TODO: EPA */
   2198 	case 0x98:   /* TODO: SOS */
   2199 	case 0x99:   /* TODO: SGCI */
   2200 		break;
   2201 	case 0x9a:   /* DECID -- Identify Terminal */
   2202 		ttywrite(vtiden, strlen(vtiden), 0);
   2203 		break;
   2204 	case 0x9b:   /* TODO: CSI */
   2205 	case 0x9c:   /* TODO: ST */
   2206 		break;
   2207 	case 0x90:   /* DCS -- Device Control String */
   2208 	case 0x9d:   /* OSC -- Operating System Command */
   2209 	case 0x9e:   /* PM -- Privacy Message */
   2210 	case 0x9f:   /* APC -- Application Program Command */
   2211 		tstrsequence(ascii);
   2212 		return;
   2213 	}
   2214 	/* only CAN, SUB, \a and C1 chars interrupt a sequence */
   2215 	term.esc &= ~(ESC_STR_END|ESC_STR);
   2216 }
   2217 
   2218 /*
   2219  * returns 1 when the sequence is finished and it hasn't to read
   2220  * more characters for this sequence, otherwise 0
   2221  */
   2222 int
   2223 eschandle(uchar ascii)
   2224 {
   2225 	switch (ascii) {
   2226 	case '[':
   2227 		term.esc |= ESC_CSI;
   2228 		return 0;
   2229 	case '#':
   2230 		term.esc |= ESC_TEST;
   2231 		return 0;
   2232 	case '%':
   2233 		term.esc |= ESC_UTF8;
   2234 		return 0;
   2235 	case 'P': /* DCS -- Device Control String */
   2236 	case '_': /* APC -- Application Program Command */
   2237 	case '^': /* PM -- Privacy Message */
   2238 	case ']': /* OSC -- Operating System Command */
   2239 	case 'k': /* old title set compatibility */
   2240 		tstrsequence(ascii);
   2241 		return 0;
   2242 	case 'n': /* LS2 -- Locking shift 2 */
   2243 	case 'o': /* LS3 -- Locking shift 3 */
   2244 		term.charset = 2 + (ascii - 'n');
   2245 		break;
   2246 	case '(': /* GZD4 -- set primary charset G0 */
   2247 	case ')': /* G1D4 -- set secondary charset G1 */
   2248 	case '*': /* G2D4 -- set tertiary charset G2 */
   2249 	case '+': /* G3D4 -- set quaternary charset G3 */
   2250 		term.icharset = ascii - '(';
   2251 		term.esc |= ESC_ALTCHARSET;
   2252 		return 0;
   2253 	case 'D': /* IND -- Linefeed */
   2254 		if (term.c.y == term.bot) {
   2255 			tscrollup(term.top, 1);
   2256 		} else {
   2257 			tmoveto(term.c.x, term.c.y+1);
   2258 		}
   2259 		break;
   2260 	case 'E': /* NEL -- Next line */
   2261 		tnewline(1); /* always go to first col */
   2262 		break;
   2263 	case 'H': /* HTS -- Horizontal tab stop */
   2264 		term.tabs[term.c.x] = 1;
   2265 		break;
   2266 	case 'M': /* RI -- Reverse index */
   2267 		if (term.c.y == term.top) {
   2268 			tscrolldown(term.top, 1);
   2269 		} else {
   2270 			tmoveto(term.c.x, term.c.y-1);
   2271 		}
   2272 		break;
   2273 	case 'Z': /* DECID -- Identify Terminal */
   2274 		ttywrite(vtiden, strlen(vtiden), 0);
   2275 		break;
   2276 	case 'c': /* RIS -- Reset to initial state */
   2277 		treset();
   2278 		resettitle();
   2279 		xloadcols();
   2280 		break;
   2281 	case '=': /* DECPAM -- Application keypad */
   2282 		xsetmode(1, MODE_APPKEYPAD);
   2283 		break;
   2284 	case '>': /* DECPNM -- Normal keypad */
   2285 		xsetmode(0, MODE_APPKEYPAD);
   2286 		break;
   2287 	case '7': /* DECSC -- Save Cursor */
   2288 		tcursor(CURSOR_SAVE);
   2289 		break;
   2290 	case '8': /* DECRC -- Restore Cursor */
   2291 		tcursor(CURSOR_LOAD);
   2292 		break;
   2293 	case '\\': /* ST -- String Terminator */
   2294 		if (term.esc & ESC_STR_END)
   2295 			strhandle();
   2296 		break;
   2297 	default:
   2298 		fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
   2299 			(uchar) ascii, isprint(ascii)? ascii:'.');
   2300 		break;
   2301 	}
   2302 	return 1;
   2303 }
   2304 
   2305 void
   2306 tputc(Rune u)
   2307 {
   2308 	char c[UTF_SIZ];
   2309 	int control;
   2310 	int width, len;
   2311 	Glyph *gp;
   2312 
   2313 	control = ISCONTROL(u);
   2314 	if (u < 127 || !IS_SET(MODE_UTF8)) {
   2315 		c[0] = u;
   2316 		width = len = 1;
   2317 	} else {
   2318 		len = utf8encode(u, c);
   2319 		if (!control && (width = wcwidth(u)) == -1)
   2320 			width = 1;
   2321 	}
   2322 
   2323 	if (IS_SET(MODE_PRINT))
   2324 		tprinter(c, len);
   2325 
   2326 	/*
   2327 	 * STR sequence must be checked before anything else
   2328 	 * because it uses all following characters until it
   2329 	 * receives a ESC, a SUB, a ST or any other C1 control
   2330 	 * character.
   2331 	 */
   2332 	if (term.esc & ESC_STR) {
   2333 		if (u == '\a' || u == 030 || u == 032 || u == 033 ||
   2334 		   ISCONTROLC1(u)) {
   2335 			term.esc &= ~(ESC_START|ESC_STR);
   2336 			term.esc |= ESC_STR_END;
   2337 			goto check_control_code;
   2338 		}
   2339 
   2340 		if (strescseq.len+len >= strescseq.siz) {
   2341 			/*
   2342 			 * Here is a bug in terminals. If the user never sends
   2343 			 * some code to stop the str or esc command, then st
   2344 			 * will stop responding. But this is better than
   2345 			 * silently failing with unknown characters. At least
   2346 			 * then users will report back.
   2347 			 *
   2348 			 * In the case users ever get fixed, here is the code:
   2349 			 */
   2350 			/*
   2351 			 * term.esc = 0;
   2352 			 * strhandle();
   2353 			 */
   2354 			if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
   2355 				return;
   2356 			strescseq.siz *= 2;
   2357 			strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
   2358 		}
   2359 
   2360 		memmove(&strescseq.buf[strescseq.len], c, len);
   2361 		strescseq.len += len;
   2362 		return;
   2363 	}
   2364 
   2365 check_control_code:
   2366 	/*
   2367 	 * Actions of control codes must be performed as soon they arrive
   2368 	 * because they can be embedded inside a control sequence, and
   2369 	 * they must not cause conflicts with sequences.
   2370 	 */
   2371 	if (control) {
   2372 		tcontrolcode(u);
   2373 		/*
   2374 		 * control codes are not shown ever
   2375 		 */
   2376 		if (!term.esc)
   2377 			term.lastc = 0;
   2378 		return;
   2379 	} else if (term.esc & ESC_START) {
   2380 		if (term.esc & ESC_CSI) {
   2381 			csiescseq.buf[csiescseq.len++] = u;
   2382 			if (BETWEEN(u, 0x40, 0x7E)
   2383 					|| csiescseq.len >= \
   2384 					sizeof(csiescseq.buf)-1) {
   2385 				term.esc = 0;
   2386 				csiparse();
   2387 				csihandle();
   2388 			}
   2389 			return;
   2390 		} else if (term.esc & ESC_UTF8) {
   2391 			tdefutf8(u);
   2392 		} else if (term.esc & ESC_ALTCHARSET) {
   2393 			tdeftran(u);
   2394 		} else if (term.esc & ESC_TEST) {
   2395 			tdectest(u);
   2396 		} else {
   2397 			if (!eschandle(u))
   2398 				return;
   2399 			/* sequence already finished */
   2400 		}
   2401 		term.esc = 0;
   2402 		/*
   2403 		 * All characters which form part of a sequence are not
   2404 		 * printed
   2405 		 */
   2406 		return;
   2407 	}
   2408 	if (selected(term.c.x, term.c.y))
   2409 		selclear();
   2410 
   2411 	gp = &term.line[term.c.y][term.c.x];
   2412 	if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
   2413 		gp->mode |= ATTR_WRAP;
   2414 		tnewline(1);
   2415 		gp = &term.line[term.c.y][term.c.x];
   2416 	}
   2417 
   2418 	if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
   2419 		memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
   2420 
   2421 	if (term.c.x+width > term.col) {
   2422 		tnewline(1);
   2423 		gp = &term.line[term.c.y][term.c.x];
   2424 	}
   2425 
   2426 	tsetchar(u, &term.c.attr, term.c.x, term.c.y);
   2427 	term.lastc = u;
   2428 
   2429 	if (width == 2) {
   2430 		gp->mode |= ATTR_WIDE;
   2431 		if (term.c.x+1 < term.col) {
   2432 			gp[1].u = '\0';
   2433 			gp[1].mode = ATTR_WDUMMY;
   2434 		}
   2435 	}
   2436 	if (term.c.x+width < term.col) {
   2437 		tmoveto(term.c.x+width, term.c.y);
   2438 	} else {
   2439 		term.c.state |= CURSOR_WRAPNEXT;
   2440 	}
   2441 }
   2442 
   2443 int
   2444 twrite(const char *buf, int buflen, int show_ctrl)
   2445 {
   2446 	int charsize;
   2447 	Rune u;
   2448 	int n;
   2449 
   2450 	for (n = 0; n < buflen; n += charsize) {
   2451 		if (IS_SET(MODE_UTF8)) {
   2452 			/* process a complete utf8 char */
   2453 			charsize = utf8decode(buf + n, &u, buflen - n);
   2454 			if (charsize == 0)
   2455 				break;
   2456 		} else {
   2457 			u = buf[n] & 0xFF;
   2458 			charsize = 1;
   2459 		}
   2460 		if (show_ctrl && ISCONTROL(u)) {
   2461 			if (u & 0x80) {
   2462 				u &= 0x7f;
   2463 				tputc('^');
   2464 				tputc('[');
   2465 			} else if (u != '\n' && u != '\r' && u != '\t') {
   2466 				u ^= 0x40;
   2467 				tputc('^');
   2468 			}
   2469 		}
   2470 		tputc(u);
   2471 	}
   2472 	return n;
   2473 }
   2474 
   2475 void
   2476 tresize(int col, int row)
   2477 {
   2478 	int i;
   2479 	int minrow = MIN(row, term.row);
   2480 	int mincol = MIN(col, term.col);
   2481 	int *bp;
   2482 	TCursor c;
   2483 
   2484 	if (col < 1 || row < 1) {
   2485 		fprintf(stderr,
   2486 		        "tresize: error resizing to %dx%d\n", col, row);
   2487 		return;
   2488 	}
   2489 
   2490 	/*
   2491 	 * slide screen to keep cursor where we expect it -
   2492 	 * tscrollup would work here, but we can optimize to
   2493 	 * memmove because we're freeing the earlier lines
   2494 	 */
   2495 	for (i = 0; i <= term.c.y - row; i++) {
   2496 		free(term.line[i]);
   2497 		free(term.alt[i]);
   2498 	}
   2499 	/* ensure that both src and dst are not NULL */
   2500 	if (i > 0) {
   2501 		memmove(term.line, term.line + i, row * sizeof(Line));
   2502 		memmove(term.alt, term.alt + i, row * sizeof(Line));
   2503 	}
   2504 	for (i += row; i < term.row; i++) {
   2505 		free(term.line[i]);
   2506 		free(term.alt[i]);
   2507 	}
   2508 
   2509 	/* resize to new height */
   2510 	term.line = xrealloc(term.line, row * sizeof(Line));
   2511 	term.alt  = xrealloc(term.alt,  row * sizeof(Line));
   2512 	term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
   2513 	term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
   2514 
   2515 	/* resize each row to new width, zero-pad if needed */
   2516 	for (i = 0; i < minrow; i++) {
   2517 		term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
   2518 		term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
   2519 	}
   2520 
   2521 	/* allocate any new rows */
   2522 	for (/* i = minrow */; i < row; i++) {
   2523 		term.line[i] = xmalloc(col * sizeof(Glyph));
   2524 		term.alt[i] = xmalloc(col * sizeof(Glyph));
   2525 	}
   2526 	if (col > term.col) {
   2527 		bp = term.tabs + term.col;
   2528 
   2529 		memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
   2530 		while (--bp > term.tabs && !*bp)
   2531 			/* nothing */ ;
   2532 		for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
   2533 			*bp = 1;
   2534 	}
   2535 	/* update terminal size */
   2536 	term.col = col;
   2537 	term.row = row;
   2538 	/* reset scrolling region */
   2539 	tsetscroll(0, row-1);
   2540 	/* make use of the LIMIT in tmoveto */
   2541 	tmoveto(term.c.x, term.c.y);
   2542 	/* Clearing both screens (it makes dirty all lines) */
   2543 	c = term.c;
   2544 	for (i = 0; i < 2; i++) {
   2545 		if (mincol < col && 0 < minrow) {
   2546 			tclearregion(mincol, 0, col - 1, minrow - 1);
   2547 		}
   2548 		if (0 < col && minrow < row) {
   2549 			tclearregion(0, minrow, col - 1, row - 1);
   2550 		}
   2551 		tswapscreen();
   2552 		tcursor(CURSOR_LOAD);
   2553 	}
   2554 	term.c = c;
   2555 }
   2556 
   2557 void
   2558 resettitle(void)
   2559 {
   2560 	xsettitle(NULL);
   2561 }
   2562 
   2563 void
   2564 drawregion(int x1, int y1, int x2, int y2)
   2565 {
   2566 	int y;
   2567 
   2568 	for (y = y1; y < y2; y++) {
   2569 		if (!term.dirty[y])
   2570 			continue;
   2571 
   2572 		term.dirty[y] = 0;
   2573 		xdrawline(term.line[y], x1, y, x2);
   2574 	}
   2575 }
   2576 
   2577 void
   2578 draw(void)
   2579 {
   2580 	int cx = term.c.x, ocx = term.ocx, ocy = term.ocy;
   2581 
   2582 	if (!xstartdraw())
   2583 		return;
   2584 
   2585 	/* adjust cursor position */
   2586 	LIMIT(term.ocx, 0, term.col-1);
   2587 	LIMIT(term.ocy, 0, term.row-1);
   2588 	if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
   2589 		term.ocx--;
   2590 	if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
   2591 		cx--;
   2592 
   2593 	drawregion(0, 0, term.col, term.row);
   2594 	xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
   2595 			term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
   2596 	term.ocx = cx;
   2597 	term.ocy = term.c.y;
   2598 	xfinishdraw();
   2599 	if (ocx != term.ocx || ocy != term.ocy)
   2600 		xximspot(term.ocx, term.ocy);
   2601 }
   2602 
   2603 void
   2604 redraw(void)
   2605 {
   2606 	tfulldirt();
   2607 	draw();
   2608 }