st

Simple Terminal from suckless utilities
git clone git://git.thepablogq.xyz/st
Log | Files | Refs | README | LICENSE

st.c (57762B)


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