st

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

st.c (61451B)


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