st

simple terminal with alpha focus highlight and selenized colorscheme
git clone https://git.beauhilton.com/st.git
Log | Files | Refs | README | LICENSE

st.c (59076B)


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