st

Personal fork of ST from suckless.org; st is a simple terminal implementation for X.
git clone git://git.swab.dev/st.git
Log | Files | Refs | README | LICENSE

st.c (55962B)


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