st

my build of st
Log | Files | Refs | README | LICENSE

st.c (57214B)


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