st

Custom st fork
git clone git://git.deurzen.net/st
Log | Files | Refs | README | LICENSE

st.c (57271B)


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