st

simple terminal
git clone git://git.suckless.org/st
Log | Files | Refs | README | LICENSE

st.c (55971B)


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