# 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 }