st-noxz

[fork] suckless st - personal fork
git clone https://noxz.tech/git/st-noxz.git
st-noxz

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