st

git clone git://mattcarlson.org/repos/st.git
Log | Files | Refs

st.c (58873B)


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