st

My fork of st.
git clone git://git.alex.balgavy.eu/st.git
Log | Files | Refs | README | LICENSE

st.c (59107B)


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