st

OSHs st build
git clone git://git.oshgnacknak.de/st.git
Log | Files | Refs | README | LICENSE

st.c (56848B)


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