59db14483a01f4b153ad1d5ddb85253c9ea785bc
[st.git] / 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
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 ISCONTROLC0(c)          (BETWEEN(c, 0, 0x1f) || (c) == '\177')
42 #define ISCONTROLC1(c)          (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c)            (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u)              (u && wcschr(worddelimiters, u))
45
46 enum term_mode {
47         MODE_WRAP        = 1 << 0,
48         MODE_INSERT      = 1 << 1,
49         MODE_ALTSCREEN   = 1 << 2,
50         MODE_CRLF        = 1 << 3,
51         MODE_ECHO        = 1 << 4,
52         MODE_PRINT       = 1 << 5,
53         MODE_UTF8        = 1 << 6,
54         MODE_SIXEL       = 1 << 7,
55 };
56
57 enum cursor_movement {
58         CURSOR_SAVE,
59         CURSOR_LOAD
60 };
61
62 enum cursor_state {
63         CURSOR_DEFAULT  = 0,
64         CURSOR_WRAPNEXT = 1,
65         CURSOR_ORIGIN   = 2
66 };
67
68 enum charset {
69         CS_GRAPHIC0,
70         CS_GRAPHIC1,
71         CS_UK,
72         CS_USA,
73         CS_MULTI,
74         CS_GER,
75         CS_FIN
76 };
77
78 enum escape_state {
79         ESC_START      = 1,
80         ESC_CSI        = 2,
81         ESC_STR        = 4,  /* OSC, PM, APC */
82         ESC_ALTCHARSET = 8,
83         ESC_STR_END    = 16, /* a final string was encountered */
84         ESC_TEST       = 32, /* Enter in test mode */
85         ESC_UTF8       = 64,
86         ESC_DCS        =128,
87 };
88
89 typedef struct {
90         Glyph attr; /* current char attributes */
91         int x;
92         int y;
93         char state;
94 } TCursor;
95
96 typedef struct {
97         int mode;
98         int type;
99         int snap;
100         /*
101          * Selection variables:
102          * nb – normalized coordinates of the beginning of the selection
103          * ne – normalized coordinates of the end of the selection
104          * ob – original coordinates of the beginning of the selection
105          * oe – original coordinates of the end of the selection
106          */
107         struct {
108                 int x, y;
109         } nb, ne, ob, oe;
110
111         int alt;
112 } Selection;
113
114 /* Internal representation of the screen */
115 typedef struct {
116         int row;      /* nb row */
117         int col;      /* nb col */
118         Line *line;   /* screen */
119         Line *alt;    /* alternate screen */
120         int *dirty;   /* dirtyness of lines */
121         TCursor c;    /* cursor */
122         int ocx;      /* old cursor col */
123         int ocy;      /* old cursor row */
124         int top;      /* top    scroll limit */
125         int bot;      /* bottom scroll limit */
126         int mode;     /* terminal mode flags */
127         int esc;      /* escape state flags */
128         char trantbl[4]; /* charset table translation */
129         int charset;  /* current charset */
130         int icharset; /* selected charset for sequence */
131         int *tabs;
132 } Term;
133
134 /* CSI Escape sequence structs */
135 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
136 typedef struct {
137         char buf[ESC_BUF_SIZ]; /* raw string */
138         size_t len;            /* raw string length */
139         char priv;
140         int arg[ESC_ARG_SIZ];
141         int narg;              /* nb of args */
142         char mode[2];
143 } CSIEscape;
144
145 /* STR Escape sequence structs */
146 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
147 typedef struct {
148         char type;             /* ESC type ... */
149         char *buf;             /* allocated raw string */
150         size_t siz;            /* allocation size */
151         size_t len;            /* raw string length */
152         char *args[STR_ARG_SIZ];
153         int narg;              /* nb of args */
154 } STREscape;
155
156 static void execsh(char *, char **);
157 static void stty(char **);
158 static void sigchld(int);
159 static void ttywriteraw(const char *, size_t);
160
161 static void csidump(void);
162 static void csihandle(void);
163 static void csiparse(void);
164 static void csireset(void);
165 static int eschandle(uchar);
166 static void strdump(void);
167 static void strhandle(void);
168 static void strparse(void);
169 static void strreset(void);
170
171 static void tprinter(char *, size_t);
172 static void tdumpsel(void);
173 static void tdumpline(int);
174 static void tdump(void);
175 static void tclearregion(int, int, int, int);
176 static void tcursor(int);
177 static void tdeletechar(int);
178 static void tdeleteline(int);
179 static void tinsertblank(int);
180 static void tinsertblankline(int);
181 static int tlinelen(int);
182 static void tmoveto(int, int);
183 static void tmoveato(int, int);
184 static void tnewline(int);
185 static void tputtab(int);
186 static void tputc(Rune);
187 static void treset(void);
188 static void tscrollup(int, int);
189 static void tscrolldown(int, int);
190 static void tsetattr(int *, int);
191 static void tsetchar(Rune, Glyph *, int, int);
192 static void tsetdirt(int, int);
193 static void tsetscroll(int, int);
194 static void tswapscreen(void);
195 static void tsetmode(int, int, int *, int);
196 static int twrite(const char *, int, int);
197 static void tfulldirt(void);
198 static void tcontrolcode(uchar );
199 static void tdectest(char );
200 static void tdefutf8(char);
201 static int32_t tdefcolor(int *, int *, int);
202 static void tdeftran(char);
203 static void tstrsequence(uchar);
204
205 static void drawregion(int, int, int, int);
206
207 static void selnormalize(void);
208 static void selscroll(int, int);
209 static void selsnap(int *, int *, int);
210
211 static size_t utf8decode(const char *, Rune *, size_t);
212 static Rune utf8decodebyte(char, size_t *);
213 static char utf8encodebyte(Rune, size_t);
214 static size_t utf8validate(Rune *, size_t);
215
216 static char *base64dec(const char *);
217 static char base64dec_getc(const char **);
218
219 static ssize_t xwrite(int, const char *, size_t);
220
221 /* Globals */
222 static Term term;
223 static Selection sel;
224 static CSIEscape csiescseq;
225 static STREscape strescseq;
226 static int iofd = 1;
227 static int cmdfd;
228 static pid_t pid;
229
230 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
231 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
232 static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
233 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
234
235 ssize_t
236 xwrite(int fd, const char *s, size_t len)
237 {
238         size_t aux = len;
239         ssize_t r;
240
241         while (len > 0) {
242                 r = write(fd, s, len);
243                 if (r < 0)
244                         return r;
245                 len -= r;
246                 s += r;
247         }
248
249         return aux;
250 }
251
252 void *
253 xmalloc(size_t len)
254 {
255         void *p;
256
257         if (!(p = malloc(len)))
258                 die("malloc: %s\n", strerror(errno));
259
260         return p;
261 }
262
263 void *
264 xrealloc(void *p, size_t len)
265 {
266         if ((p = realloc(p, len)) == NULL)
267                 die("realloc: %s\n", strerror(errno));
268
269         return p;
270 }
271
272 char *
273 xstrdup(char *s)
274 {
275         if ((s = strdup(s)) == NULL)
276                 die("strdup: %s\n", strerror(errno));
277
278         return s;
279 }
280
281 size_t
282 utf8decode(const char *c, Rune *u, size_t clen)
283 {
284         size_t i, j, len, type;
285         Rune udecoded;
286
287         *u = UTF_INVALID;
288         if (!clen)
289                 return 0;
290         udecoded = utf8decodebyte(c[0], &len);
291         if (!BETWEEN(len, 1, UTF_SIZ))
292                 return 1;
293         for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
294                 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
295                 if (type != 0)
296                         return j;
297         }
298         if (j < len)
299                 return 0;
300         *u = udecoded;
301         utf8validate(u, len);
302
303         return len;
304 }
305
306 Rune
307 utf8decodebyte(char c, size_t *i)
308 {
309         for (*i = 0; *i < LEN(utfmask); ++(*i))
310                 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
311                         return (uchar)c & ~utfmask[*i];
312
313         return 0;
314 }
315
316 size_t
317 utf8encode(Rune u, char *c)
318 {
319         size_t len, i;
320
321         len = utf8validate(&u, 0);
322         if (len > UTF_SIZ)
323                 return 0;
324
325         for (i = len - 1; i != 0; --i) {
326                 c[i] = utf8encodebyte(u, 0);
327                 u >>= 6;
328         }
329         c[0] = utf8encodebyte(u, len);
330
331         return len;
332 }
333
334 char
335 utf8encodebyte(Rune u, size_t i)
336 {
337         return utfbyte[i] | (u & ~utfmask[i]);
338 }
339
340 size_t
341 utf8validate(Rune *u, size_t i)
342 {
343         if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
344                 *u = UTF_INVALID;
345         for (i = 1; *u > utfmax[i]; ++i)
346                 ;
347
348         return i;
349 }
350
351 static const char base64_digits[] = {
352         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
353         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
354         63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
355         2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
356         22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
357         35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
358         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
359         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
360         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
361         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
362         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
363         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
364 };
365
366 char
367 base64dec_getc(const char **src)
368 {
369         while (**src && !isprint(**src))
370                 (*src)++;
371         return **src ? *((*src)++) : '=';  /* emulate padding if string ends */
372 }
373
374 char *
375 base64dec(const char *src)
376 {
377         size_t in_len = strlen(src);
378         char *result, *dst;
379
380         if (in_len % 4)
381                 in_len += 4 - (in_len % 4);
382         result = dst = xmalloc(in_len / 4 * 3 + 1);
383         while (*src) {
384                 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
385                 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
386                 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
387                 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
388
389                 /* invalid input. 'a' can be -1, e.g. if src is "\n" (c-str) */
390                 if (a == -1 || b == -1)
391                         break;
392
393                 *dst++ = (a << 2) | ((b & 0x30) >> 4);
394                 if (c == -1)
395                         break;
396                 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
397                 if (d == -1)
398                         break;
399                 *dst++ = ((c & 0x03) << 6) | d;
400         }
401         *dst = '\0';
402         return result;
403 }
404
405 void
406 selinit(void)
407 {
408         sel.mode = SEL_IDLE;
409         sel.snap = 0;
410         sel.ob.x = -1;
411 }
412
413 int
414 tlinelen(int y)
415 {
416         int i = term.col;
417
418         if (term.line[y][i - 1].mode & ATTR_WRAP)
419                 return i;
420
421         while (i > 0 && term.line[y][i - 1].u == ' ')
422                 --i;
423
424         return i;
425 }
426
427 void
428 selstart(int col, int row, int snap)
429 {
430         selclear();
431         sel.mode = SEL_EMPTY;
432         sel.type = SEL_REGULAR;
433         sel.alt = IS_SET(MODE_ALTSCREEN);
434         sel.snap = snap;
435         sel.oe.x = sel.ob.x = col;
436         sel.oe.y = sel.ob.y = row;
437         selnormalize();
438
439         if (sel.snap != 0)
440                 sel.mode = SEL_READY;
441         tsetdirt(sel.nb.y, sel.ne.y);
442 }
443
444 void
445 selextend(int col, int row, int type, int done)
446 {
447         int oldey, oldex, oldsby, oldsey, oldtype;
448
449         if (sel.mode == SEL_IDLE)
450                 return;
451         if (done && sel.mode == SEL_EMPTY) {
452                 selclear();
453                 return;
454         }
455
456         oldey = sel.oe.y;
457         oldex = sel.oe.x;
458         oldsby = sel.nb.y;
459         oldsey = sel.ne.y;
460         oldtype = sel.type;
461
462         sel.oe.x = col;
463         sel.oe.y = row;
464         selnormalize();
465         sel.type = type;
466
467         if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SEL_EMPTY)
468                 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
469
470         sel.mode = done ? SEL_IDLE : SEL_READY;
471 }
472
473 void
474 selnormalize(void)
475 {
476         int i;
477
478         if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
479                 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
480                 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
481         } else {
482                 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
483                 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
484         }
485         sel.nb.y = MIN(sel.ob.y, sel.oe.y);
486         sel.ne.y = MAX(sel.ob.y, sel.oe.y);
487
488         selsnap(&sel.nb.x, &sel.nb.y, -1);
489         selsnap(&sel.ne.x, &sel.ne.y, +1);
490
491         /* expand selection over line breaks */
492         if (sel.type == SEL_RECTANGULAR)
493                 return;
494         i = tlinelen(sel.nb.y);
495         if (i < sel.nb.x)
496                 sel.nb.x = i;
497         if (tlinelen(sel.ne.y) <= sel.ne.x)
498                 sel.ne.x = term.col - 1;
499 }
500
501 int
502 selected(int x, int y)
503 {
504         if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
505                         sel.alt != IS_SET(MODE_ALTSCREEN))
506                 return 0;
507
508         if (sel.type == SEL_RECTANGULAR)
509                 return BETWEEN(y, sel.nb.y, sel.ne.y)
510                     && BETWEEN(x, sel.nb.x, sel.ne.x);
511
512         return BETWEEN(y, sel.nb.y, sel.ne.y)
513             && (y != sel.nb.y || x >= sel.nb.x)
514             && (y != sel.ne.y || x <= sel.ne.x);
515 }
516
517 void
518 selsnap(int *x, int *y, int direction)
519 {
520         int newx, newy, xt, yt;
521         int delim, prevdelim;
522         Glyph *gp, *prevgp;
523
524         switch (sel.snap) {
525         case SNAP_WORD:
526                 /*
527                  * Snap around if the word wraps around at the end or
528                  * beginning of a line.
529                  */
530                 prevgp = &term.line[*y][*x];
531                 prevdelim = ISDELIM(prevgp->u);
532                 for (;;) {
533                         newx = *x + direction;
534                         newy = *y;
535                         if (!BETWEEN(newx, 0, term.col - 1)) {
536                                 newy += direction;
537                                 newx = (newx + term.col) % term.col;
538                                 if (!BETWEEN(newy, 0, term.row - 1))
539                                         break;
540
541                                 if (direction > 0)
542                                         yt = *y, xt = *x;
543                                 else
544                                         yt = newy, xt = newx;
545                                 if (!(term.line[yt][xt].mode & ATTR_WRAP))
546                                         break;
547                         }
548
549                         if (newx >= tlinelen(newy))
550                                 break;
551
552                         gp = &term.line[newy][newx];
553                         delim = ISDELIM(gp->u);
554                         if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
555                                         || (delim && gp->u != prevgp->u)))
556                                 break;
557
558                         *x = newx;
559                         *y = newy;
560                         prevgp = gp;
561                         prevdelim = delim;
562                 }
563                 break;
564         case SNAP_LINE:
565                 /*
566                  * Snap around if the the previous line or the current one
567                  * has set ATTR_WRAP at its end. Then the whole next or
568                  * previous line will be selected.
569                  */
570                 *x = (direction < 0) ? 0 : term.col - 1;
571                 if (direction < 0) {
572                         for (; *y > 0; *y += direction) {
573                                 if (!(term.line[*y-1][term.col-1].mode
574                                                 & ATTR_WRAP)) {
575                                         break;
576                                 }
577                         }
578                 } else if (direction > 0) {
579                         for (; *y < term.row-1; *y += direction) {
580                                 if (!(term.line[*y][term.col-1].mode
581                                                 & ATTR_WRAP)) {
582                                         break;
583                                 }
584                         }
585                 }
586                 break;
587         }
588 }
589
590 char *
591 getsel(void)
592 {
593         char *str, *ptr;
594         int y, bufsize, lastx, linelen;
595         Glyph *gp, *last;
596
597         if (sel.ob.x == -1)
598                 return NULL;
599
600         bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
601         ptr = str = xmalloc(bufsize);
602
603         /* append every set & selected glyph to the selection */
604         for (y = sel.nb.y; y <= sel.ne.y; y++) {
605                 if ((linelen = tlinelen(y)) == 0) {
606                         *ptr++ = '\n';
607                         continue;
608                 }
609
610                 if (sel.type == SEL_RECTANGULAR) {
611                         gp = &term.line[y][sel.nb.x];
612                         lastx = sel.ne.x;
613                 } else {
614                         gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
615                         lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
616                 }
617                 last = &term.line[y][MIN(lastx, linelen-1)];
618                 while (last >= gp && last->u == ' ')
619                         --last;
620
621                 for ( ; gp <= last; ++gp) {
622                         if (gp->mode & ATTR_WDUMMY)
623                                 continue;
624
625                         ptr += utf8encode(gp->u, ptr);
626                 }
627
628                 /*
629                  * Copy and pasting of line endings is inconsistent
630                  * in the inconsistent terminal and GUI world.
631                  * The best solution seems like to produce '\n' when
632                  * something is copied from st and convert '\n' to
633                  * '\r', when something to be pasted is received by
634                  * st.
635                  * FIXME: Fix the computer world.
636                  */
637                 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
638                         *ptr++ = '\n';
639         }
640         *ptr = 0;
641         return str;
642 }
643
644 void
645 selclear(void)
646 {
647         if (sel.ob.x == -1)
648                 return;
649         sel.mode = SEL_IDLE;
650         sel.ob.x = -1;
651         tsetdirt(sel.nb.y, sel.ne.y);
652 }
653
654 void
655 die(const char *errstr, ...)
656 {
657         va_list ap;
658
659         va_start(ap, errstr);
660         vfprintf(stderr, errstr, ap);
661         va_end(ap);
662         exit(1);
663 }
664
665 void
666 execsh(char *cmd, char **args)
667 {
668         char *sh, *prog, *arg;
669         const struct passwd *pw;
670
671         errno = 0;
672         if ((pw = getpwuid(getuid())) == NULL) {
673                 if (errno)
674                         die("getpwuid: %s\n", strerror(errno));
675                 else
676                         die("who are you?\n");
677         }
678
679         if ((sh = getenv("SHELL")) == NULL)
680                 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
681
682         if (args) {
683                 prog = args[0];
684                 arg = NULL;
685         } else if (scroll) {
686                 prog = scroll;
687                 arg = utmp ? utmp : sh;
688         } else if (utmp) {
689                 prog = utmp;
690                 arg = NULL;
691         } else {
692                 prog = sh;
693                 arg = NULL;
694         }
695         DEFAULT(args, ((char *[]) {prog, arg, NULL}));
696
697         unsetenv("COLUMNS");
698         unsetenv("LINES");
699         unsetenv("TERMCAP");
700         setenv("LOGNAME", pw->pw_name, 1);
701         setenv("USER", pw->pw_name, 1);
702         setenv("SHELL", sh, 1);
703         setenv("HOME", pw->pw_dir, 1);
704         setenv("TERM", termname, 1);
705
706         signal(SIGCHLD, SIG_DFL);
707         signal(SIGHUP, SIG_DFL);
708         signal(SIGINT, SIG_DFL);
709         signal(SIGQUIT, SIG_DFL);
710         signal(SIGTERM, SIG_DFL);
711         signal(SIGALRM, SIG_DFL);
712
713         execvp(prog, args);
714         _exit(1);
715 }
716
717 void
718 sigchld(int a)
719 {
720         int stat;
721         pid_t p;
722
723         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
724                 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
725
726         if (pid != p)
727                 return;
728
729         if (WIFEXITED(stat) && WEXITSTATUS(stat))
730                 die("child exited with status %d\n", WEXITSTATUS(stat));
731         else if (WIFSIGNALED(stat))
732                 die("child terminated due to signal %d\n", WTERMSIG(stat));
733         exit(0);
734 }
735
736 void
737 stty(char **args)
738 {
739         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
740         size_t n, siz;
741
742         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
743                 die("incorrect stty parameters\n");
744         memcpy(cmd, stty_args, n);
745         q = cmd + n;
746         siz = sizeof(cmd) - n;
747         for (p = args; p && (s = *p); ++p) {
748                 if ((n = strlen(s)) > siz-1)
749                         die("stty parameter length too long\n");
750                 *q++ = ' ';
751                 memcpy(q, s, n);
752                 q += n;
753                 siz -= n + 1;
754         }
755         *q = '\0';
756         if (system(cmd) != 0)
757                 perror("Couldn't call stty");
758 }
759
760 int
761 ttynew(char *line, char *cmd, char *out, char **args)
762 {
763         int m, s;
764
765         if (out) {
766                 term.mode |= MODE_PRINT;
767                 iofd = (!strcmp(out, "-")) ?
768                           1 : open(out, O_WRONLY | O_CREAT, 0666);
769                 if (iofd < 0) {
770                         fprintf(stderr, "Error opening %s:%s\n",
771                                 out, strerror(errno));
772                 }
773         }
774
775         if (line) {
776                 if ((cmdfd = open(line, O_RDWR)) < 0)
777                         die("open line '%s' failed: %s\n",
778                             line, strerror(errno));
779                 dup2(cmdfd, 0);
780                 stty(args);
781                 return cmdfd;
782         }
783
784         /* seems to work fine on linux, openbsd and freebsd */
785         if (openpty(&m, &s, NULL, NULL, NULL) < 0)
786                 die("openpty failed: %s\n", strerror(errno));
787
788         switch (pid = fork()) {
789         case -1:
790                 die("fork failed: %s\n", strerror(errno));
791                 break;
792         case 0:
793                 close(iofd);
794                 setsid(); /* create a new process group */
795                 dup2(s, 0);
796                 dup2(s, 1);
797                 dup2(s, 2);
798                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
799                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
800                 close(s);
801                 close(m);
802 #ifdef __OpenBSD__
803                 if (pledge("stdio getpw proc exec", NULL) == -1)
804                         die("pledge\n");
805 #endif
806                 execsh(cmd, args);
807                 break;
808         default:
809 #ifdef __OpenBSD__
810                 if (pledge("stdio rpath tty proc", NULL) == -1)
811                         die("pledge\n");
812 #endif
813                 close(s);
814                 cmdfd = m;
815                 signal(SIGCHLD, sigchld);
816                 break;
817         }
818         return cmdfd;
819 }
820
821 size_t
822 ttyread(void)
823 {
824         static char buf[BUFSIZ];
825         static int buflen = 0;
826         int written;
827         int ret;
828
829         /* append read bytes to unprocessed bytes */
830         ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
831
832         switch (ret) {
833         case 0:
834                 fputs("Found EOF in input\n", stderr);
835                 exit(0);
836         case -1:
837                 die("couldn't read from shell: %s\n", strerror(errno));
838         default:
839                 buflen += ret;
840                 written = twrite(buf, buflen, 0);
841                 buflen -= written;
842                 /* keep any uncomplete utf8 char for the next call */
843                 if (buflen > 0)
844                         memmove(buf, buf + written, buflen);
845                 return ret;
846
847         }
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]);
1460                                 csidump();
1461                         }
1462                         break;
1463                 }
1464         }
1465 }
1466
1467 void
1468 tsetscroll(int t, int b)
1469 {
1470         int temp;
1471
1472         LIMIT(t, 0, term.row-1);
1473         LIMIT(b, 0, term.row-1);
1474         if (t > b) {
1475                 temp = t;
1476                 t = b;
1477                 b = temp;
1478         }
1479         term.top = t;
1480         term.bot = b;
1481 }
1482
1483 void
1484 tsetmode(int priv, int set, int *args, int narg)
1485 {
1486         int alt, *lim;
1487
1488         for (lim = args + narg; args < lim; ++args) {
1489                 if (priv) {
1490                         switch (*args) {
1491                         case 1: /* DECCKM -- Cursor key */
1492                                 xsetmode(set, MODE_APPCURSOR);
1493                                 break;
1494                         case 5: /* DECSCNM -- Reverse video */
1495                                 xsetmode(set, MODE_REVERSE);
1496                                 break;
1497                         case 6: /* DECOM -- Origin */
1498                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1499                                 tmoveato(0, 0);
1500                                 break;
1501                         case 7: /* DECAWM -- Auto wrap */
1502                                 MODBIT(term.mode, set, MODE_WRAP);
1503                                 break;
1504                         case 0:  /* Error (IGNORED) */
1505                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1506                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1507                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1508                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1509                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1510                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1511                         case 42: /* DECNRCM -- National characters (IGNORED) */
1512                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1513                                 break;
1514                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1515                                 xsetmode(!set, MODE_HIDE);
1516                                 break;
1517                         case 9:    /* X10 mouse compatibility mode */
1518                                 xsetpointermotion(0);
1519                                 xsetmode(0, MODE_MOUSE);
1520                                 xsetmode(set, MODE_MOUSEX10);
1521                                 break;
1522                         case 1000: /* 1000: report button press */
1523                                 xsetpointermotion(0);
1524                                 xsetmode(0, MODE_MOUSE);
1525                                 xsetmode(set, MODE_MOUSEBTN);
1526                                 break;
1527                         case 1002: /* 1002: report motion on button press */
1528                                 xsetpointermotion(0);
1529                                 xsetmode(0, MODE_MOUSE);
1530                                 xsetmode(set, MODE_MOUSEMOTION);
1531                                 break;
1532                         case 1003: /* 1003: enable all mouse motions */
1533                                 xsetpointermotion(set);
1534                                 xsetmode(0, MODE_MOUSE);
1535                                 xsetmode(set, MODE_MOUSEMANY);
1536                                 break;
1537                         case 1004: /* 1004: send focus events to tty */
1538                                 xsetmode(set, MODE_FOCUS);
1539                                 break;
1540                         case 1006: /* 1006: extended reporting mode */
1541                                 xsetmode(set, MODE_MOUSESGR);
1542                                 break;
1543                         case 1034:
1544                                 xsetmode(set, MODE_8BIT);
1545                                 break;
1546                         case 1049: /* swap screen & set/restore cursor as xterm */
1547                                 if (!allowaltscreen)
1548                                         break;
1549                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1550                                 /* FALLTHROUGH */
1551                         case 47: /* swap screen */
1552                         case 1047:
1553                                 if (!allowaltscreen)
1554                                         break;
1555                                 alt = IS_SET(MODE_ALTSCREEN);
1556                                 if (alt) {
1557                                         tclearregion(0, 0, term.col-1,
1558                                                         term.row-1);
1559                                 }
1560                                 if (set ^ alt) /* set is always 1 or 0 */
1561                                         tswapscreen();
1562                                 if (*args != 1049)
1563                                         break;
1564                                 /* FALLTHROUGH */
1565                         case 1048:
1566                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1567                                 break;
1568                         case 2004: /* 2004: bracketed paste mode */
1569                                 xsetmode(set, MODE_BRCKTPASTE);
1570                                 break;
1571                         /* Not implemented mouse modes. See comments there. */
1572                         case 1001: /* mouse highlight mode; can hang the
1573                                       terminal by design when implemented. */
1574                         case 1005: /* UTF-8 mouse mode; will confuse
1575                                       applications not supporting UTF-8
1576                                       and luit. */
1577                         case 1015: /* urxvt mangled mouse mode; incompatible
1578                                       and can be mistaken for other control
1579                                       codes. */
1580                                 break;
1581                         default:
1582                                 fprintf(stderr,
1583                                         "erresc: unknown private set/reset mode %d\n",
1584                                         *args);
1585                                 break;
1586                         }
1587                 } else {
1588                         switch (*args) {
1589                         case 0:  /* Error (IGNORED) */
1590                                 break;
1591                         case 2:
1592                                 xsetmode(set, MODE_KBDLOCK);
1593                                 break;
1594                         case 4:  /* IRM -- Insertion-replacement */
1595                                 MODBIT(term.mode, set, MODE_INSERT);
1596                                 break;
1597                         case 12: /* SRM -- Send/Receive */
1598                                 MODBIT(term.mode, !set, MODE_ECHO);
1599                                 break;
1600                         case 20: /* LNM -- Linefeed/new line */
1601                                 MODBIT(term.mode, set, MODE_CRLF);
1602                                 break;
1603                         default:
1604                                 fprintf(stderr,
1605                                         "erresc: unknown set/reset mode %d\n",
1606                                         *args);
1607                                 break;
1608                         }
1609                 }
1610         }
1611 }
1612
1613 void
1614 csihandle(void)
1615 {
1616         char buf[40];
1617         int len;
1618
1619         switch (csiescseq.mode[0]) {
1620         default:
1621         unknown:
1622                 fprintf(stderr, "erresc: unknown csi ");
1623                 csidump();
1624                 /* die(""); */
1625                 break;
1626         case '@': /* ICH -- Insert <n> blank char */
1627                 DEFAULT(csiescseq.arg[0], 1);
1628                 tinsertblank(csiescseq.arg[0]);
1629                 break;
1630         case 'A': /* CUU -- Cursor <n> Up */
1631                 DEFAULT(csiescseq.arg[0], 1);
1632                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1633                 break;
1634         case 'B': /* CUD -- Cursor <n> Down */
1635         case 'e': /* VPR --Cursor <n> Down */
1636                 DEFAULT(csiescseq.arg[0], 1);
1637                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1638                 break;
1639         case 'i': /* MC -- Media Copy */
1640                 switch (csiescseq.arg[0]) {
1641                 case 0:
1642                         tdump();
1643                         break;
1644                 case 1:
1645                         tdumpline(term.c.y);
1646                         break;
1647                 case 2:
1648                         tdumpsel();
1649                         break;
1650                 case 4:
1651                         term.mode &= ~MODE_PRINT;
1652                         break;
1653                 case 5:
1654                         term.mode |= MODE_PRINT;
1655                         break;
1656                 }
1657                 break;
1658         case 'c': /* DA -- Device Attributes */
1659                 if (csiescseq.arg[0] == 0)
1660                         ttywrite(vtiden, strlen(vtiden), 0);
1661                 break;
1662         case 'C': /* CUF -- Cursor <n> Forward */
1663         case 'a': /* HPR -- Cursor <n> Forward */
1664                 DEFAULT(csiescseq.arg[0], 1);
1665                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1666                 break;
1667         case 'D': /* CUB -- Cursor <n> Backward */
1668                 DEFAULT(csiescseq.arg[0], 1);
1669                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1670                 break;
1671         case 'E': /* CNL -- Cursor <n> Down and first col */
1672                 DEFAULT(csiescseq.arg[0], 1);
1673                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1674                 break;
1675         case 'F': /* CPL -- Cursor <n> Up and first col */
1676                 DEFAULT(csiescseq.arg[0], 1);
1677                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1678                 break;
1679         case 'g': /* TBC -- Tabulation clear */
1680                 switch (csiescseq.arg[0]) {
1681                 case 0: /* clear current tab stop */
1682                         term.tabs[term.c.x] = 0;
1683                         break;
1684                 case 3: /* clear all the tabs */
1685                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1686                         break;
1687                 default:
1688                         goto unknown;
1689                 }
1690                 break;
1691         case 'G': /* CHA -- Move to <col> */
1692         case '`': /* HPA */
1693                 DEFAULT(csiescseq.arg[0], 1);
1694                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1695                 break;
1696         case 'H': /* CUP -- Move to <row> <col> */
1697         case 'f': /* HVP */
1698                 DEFAULT(csiescseq.arg[0], 1);
1699                 DEFAULT(csiescseq.arg[1], 1);
1700                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1701                 break;
1702         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1703                 DEFAULT(csiescseq.arg[0], 1);
1704                 tputtab(csiescseq.arg[0]);
1705                 break;
1706         case 'J': /* ED -- Clear screen */
1707                 switch (csiescseq.arg[0]) {
1708                 case 0: /* below */
1709                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1710                         if (term.c.y < term.row-1) {
1711                                 tclearregion(0, term.c.y+1, term.col-1,
1712                                                 term.row-1);
1713                         }
1714                         break;
1715                 case 1: /* above */
1716                         if (term.c.y > 1)
1717                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1718                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1719                         break;
1720                 case 2: /* all */
1721                         tclearregion(0, 0, term.col-1, term.row-1);
1722                         break;
1723                 default:
1724                         goto unknown;
1725                 }
1726                 break;
1727         case 'K': /* EL -- Clear line */
1728                 switch (csiescseq.arg[0]) {
1729                 case 0: /* right */
1730                         tclearregion(term.c.x, term.c.y, term.col-1,
1731                                         term.c.y);
1732                         break;
1733                 case 1: /* left */
1734                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1735                         break;
1736                 case 2: /* all */
1737                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1738                         break;
1739                 }
1740                 break;
1741         case 'S': /* SU -- Scroll <n> line up */
1742                 DEFAULT(csiescseq.arg[0], 1);
1743                 tscrollup(term.top, csiescseq.arg[0]);
1744                 break;
1745         case 'T': /* SD -- Scroll <n> line down */
1746                 DEFAULT(csiescseq.arg[0], 1);
1747                 tscrolldown(term.top, csiescseq.arg[0]);
1748                 break;
1749         case 'L': /* IL -- Insert <n> blank lines */
1750                 DEFAULT(csiescseq.arg[0], 1);
1751                 tinsertblankline(csiescseq.arg[0]);
1752                 break;
1753         case 'l': /* RM -- Reset Mode */
1754                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1755                 break;
1756         case 'M': /* DL -- Delete <n> lines */
1757                 DEFAULT(csiescseq.arg[0], 1);
1758                 tdeleteline(csiescseq.arg[0]);
1759                 break;
1760         case 'X': /* ECH -- Erase <n> char */
1761                 DEFAULT(csiescseq.arg[0], 1);
1762                 tclearregion(term.c.x, term.c.y,
1763                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1764                 break;
1765         case 'P': /* DCH -- Delete <n> char */
1766                 DEFAULT(csiescseq.arg[0], 1);
1767                 tdeletechar(csiescseq.arg[0]);
1768                 break;
1769         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1770                 DEFAULT(csiescseq.arg[0], 1);
1771                 tputtab(-csiescseq.arg[0]);
1772                 break;
1773         case 'd': /* VPA -- Move to <row> */
1774                 DEFAULT(csiescseq.arg[0], 1);
1775                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1776                 break;
1777         case 'h': /* SM -- Set terminal mode */
1778                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1779                 break;
1780         case 'm': /* SGR -- Terminal attribute (color) */
1781                 tsetattr(csiescseq.arg, csiescseq.narg);
1782                 break;
1783         case 'n': /* DSR – Device Status Report (cursor position) */
1784                 if (csiescseq.arg[0] == 6) {
1785                         len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1786                                         term.c.y+1, term.c.x+1);
1787                         ttywrite(buf, len, 0);
1788                 }
1789                 break;
1790         case 'r': /* DECSTBM -- Set Scrolling Region */
1791                 if (csiescseq.priv) {
1792                         goto unknown;
1793                 } else {
1794                         DEFAULT(csiescseq.arg[0], 1);
1795                         DEFAULT(csiescseq.arg[1], term.row);
1796                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1797                         tmoveato(0, 0);
1798                 }
1799                 break;
1800         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1801                 tcursor(CURSOR_SAVE);
1802                 break;
1803         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1804                 tcursor(CURSOR_LOAD);
1805                 break;
1806         case ' ':
1807                 switch (csiescseq.mode[1]) {
1808                 case 'q': /* DECSCUSR -- Set Cursor Style */
1809                         if (xsetcursor(csiescseq.arg[0]))
1810                                 goto unknown;
1811                         break;
1812                 default:
1813                         goto unknown;
1814                 }
1815                 break;
1816         }
1817 }
1818
1819 void
1820 csidump(void)
1821 {
1822         size_t i;
1823         uint c;
1824
1825         fprintf(stderr, "ESC[");
1826         for (i = 0; i < csiescseq.len; i++) {
1827                 c = csiescseq.buf[i] & 0xff;
1828                 if (isprint(c)) {
1829                         putc(c, stderr);
1830                 } else if (c == '\n') {
1831                         fprintf(stderr, "(\\n)");
1832                 } else if (c == '\r') {
1833                         fprintf(stderr, "(\\r)");
1834                 } else if (c == 0x1b) {
1835                         fprintf(stderr, "(\\e)");
1836                 } else {
1837                         fprintf(stderr, "(%02x)", c);
1838                 }
1839         }
1840         putc('\n', stderr);
1841 }
1842
1843 void
1844 csireset(void)
1845 {
1846         memset(&csiescseq, 0, sizeof(csiescseq));
1847 }
1848
1849 void
1850 strhandle(void)
1851 {
1852         char *p = NULL, *dec;
1853         int j, narg, par;
1854
1855         term.esc &= ~(ESC_STR_END|ESC_STR);
1856         strparse();
1857         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1858
1859         switch (strescseq.type) {
1860         case ']': /* OSC -- Operating System Command */
1861                 switch (par) {
1862                 case 0:
1863                 case 1:
1864                 case 2:
1865                         if (narg > 1)
1866                                 xsettitle(strescseq.args[1]);
1867                         return;
1868                 case 52:
1869                         if (narg > 2) {
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                                 if (par == 104 && narg <= 1)
1888                                         return; /* color reset without parameter */
1889                                 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1890                                         j, p ? p : "(null)");
1891                         } else {
1892                                 /*
1893                                  * TODO if defaultbg color is changed, borders
1894                                  * are dirty
1895                                  */
1896                                 redraw();
1897                         }
1898                         return;
1899                 }
1900                 break;
1901         case 'k': /* old title set compatibility */
1902                 xsettitle(strescseq.args[0]);
1903                 return;
1904         case 'P': /* DCS -- Device Control String */
1905                 term.mode |= ESC_DCS;
1906         case '_': /* APC -- Application Program Command */
1907         case '^': /* PM -- Privacy Message */
1908                 return;
1909         }
1910
1911         fprintf(stderr, "erresc: unknown str ");
1912         strdump();
1913 }
1914
1915 void
1916 strparse(void)
1917 {
1918         int c;
1919         char *p = strescseq.buf;
1920
1921         strescseq.narg = 0;
1922         strescseq.buf[strescseq.len] = '\0';
1923
1924         if (*p == '\0')
1925                 return;
1926
1927         while (strescseq.narg < STR_ARG_SIZ) {
1928                 strescseq.args[strescseq.narg++] = p;
1929                 while ((c = *p) != ';' && c != '\0')
1930                         ++p;
1931                 if (c == '\0')
1932                         return;
1933                 *p++ = '\0';
1934         }
1935 }
1936
1937 void
1938 strdump(void)
1939 {
1940         size_t i;
1941         uint c;
1942
1943         fprintf(stderr, "ESC%c", strescseq.type);
1944         for (i = 0; i < strescseq.len; i++) {
1945                 c = strescseq.buf[i] & 0xff;
1946                 if (c == '\0') {
1947                         putc('\n', stderr);
1948                         return;
1949                 } else if (isprint(c)) {
1950                         putc(c, stderr);
1951                 } else if (c == '\n') {
1952                         fprintf(stderr, "(\\n)");
1953                 } else if (c == '\r') {
1954                         fprintf(stderr, "(\\r)");
1955                 } else if (c == 0x1b) {
1956                         fprintf(stderr, "(\\e)");
1957                 } else {
1958                         fprintf(stderr, "(%02x)", c);
1959                 }
1960         }
1961         fprintf(stderr, "ESC\\\n");
1962 }
1963
1964 void
1965 strreset(void)
1966 {
1967         strescseq = (STREscape){
1968                 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1969                 .siz = STR_BUF_SIZ,
1970         };
1971 }
1972
1973 void
1974 sendbreak(const Arg *arg)
1975 {
1976         if (tcsendbreak(cmdfd, 0))
1977                 perror("Error sending break");
1978 }
1979
1980 void
1981 tprinter(char *s, size_t len)
1982 {
1983         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1984                 perror("Error writing to output file");
1985                 close(iofd);
1986                 iofd = -1;
1987         }
1988 }
1989
1990 void
1991 toggleprinter(const Arg *arg)
1992 {
1993         term.mode ^= MODE_PRINT;
1994 }
1995
1996 void
1997 printscreen(const Arg *arg)
1998 {
1999         tdump();
2000 }
2001
2002 void
2003 printsel(const Arg *arg)
2004 {
2005         tdumpsel();
2006 }
2007
2008 void
2009 tdumpsel(void)
2010 {
2011         char *ptr;
2012
2013         if ((ptr = getsel())) {
2014                 tprinter(ptr, strlen(ptr));
2015                 free(ptr);
2016         }
2017 }
2018
2019 void
2020 tdumpline(int n)
2021 {
2022         char buf[UTF_SIZ];
2023         Glyph *bp, *end;
2024
2025         bp = &term.line[n][0];
2026         end = &bp[MIN(tlinelen(n), term.col) - 1];
2027         if (bp != end || bp->u != ' ') {
2028                 for ( ;bp <= end; ++bp)
2029                         tprinter(buf, utf8encode(bp->u, buf));
2030         }
2031         tprinter("\n", 1);
2032 }
2033
2034 void
2035 tdump(void)
2036 {
2037         int i;
2038
2039         for (i = 0; i < term.row; ++i)
2040                 tdumpline(i);
2041 }
2042
2043 void
2044 tputtab(int n)
2045 {
2046         uint x = term.c.x;
2047
2048         if (n > 0) {
2049                 while (x < term.col && n--)
2050                         for (++x; x < term.col && !term.tabs[x]; ++x)
2051                                 /* nothing */ ;
2052         } else if (n < 0) {
2053                 while (x > 0 && n++)
2054                         for (--x; x > 0 && !term.tabs[x]; --x)
2055                                 /* nothing */ ;
2056         }
2057         term.c.x = LIMIT(x, 0, term.col-1);
2058 }
2059
2060 void
2061 tdefutf8(char ascii)
2062 {
2063         if (ascii == 'G')
2064                 term.mode |= MODE_UTF8;
2065         else if (ascii == '@')
2066                 term.mode &= ~MODE_UTF8;
2067 }
2068
2069 void
2070 tdeftran(char ascii)
2071 {
2072         static char cs[] = "0B";
2073         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2074         char *p;
2075
2076         if ((p = strchr(cs, ascii)) == NULL) {
2077                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2078         } else {
2079                 term.trantbl[term.icharset] = vcs[p - cs];
2080         }
2081 }
2082
2083 void
2084 tdectest(char c)
2085 {
2086         int x, y;
2087
2088         if (c == '8') { /* DEC screen alignment test. */
2089                 for (x = 0; x < term.col; ++x) {
2090                         for (y = 0; y < term.row; ++y)
2091                                 tsetchar('E', &term.c.attr, x, y);
2092                 }
2093         }
2094 }
2095
2096 void
2097 tstrsequence(uchar c)
2098 {
2099         strreset();
2100
2101         switch (c) {
2102         case 0x90:   /* DCS -- Device Control String */
2103                 c = 'P';
2104                 term.esc |= ESC_DCS;
2105                 break;
2106         case 0x9f:   /* APC -- Application Program Command */
2107                 c = '_';
2108                 break;
2109         case 0x9e:   /* PM -- Privacy Message */
2110                 c = '^';
2111                 break;
2112         case 0x9d:   /* OSC -- Operating System Command */
2113                 c = ']';
2114                 break;
2115         }
2116         strescseq.type = c;
2117         term.esc |= ESC_STR;
2118 }
2119
2120 void
2121 tcontrolcode(uchar ascii)
2122 {
2123         switch (ascii) {
2124         case '\t':   /* HT */
2125                 tputtab(1);
2126                 return;
2127         case '\b':   /* BS */
2128                 tmoveto(term.c.x-1, term.c.y);
2129                 return;
2130         case '\r':   /* CR */
2131                 tmoveto(0, term.c.y);
2132                 return;
2133         case '\f':   /* LF */
2134         case '\v':   /* VT */
2135         case '\n':   /* LF */
2136                 /* go to first col if the mode is set */
2137                 tnewline(IS_SET(MODE_CRLF));
2138                 return;
2139         case '\a':   /* BEL */
2140                 if (term.esc & ESC_STR_END) {
2141                         /* backwards compatibility to xterm */
2142                         strhandle();
2143                 } else {
2144                         xbell();
2145                 }
2146                 break;
2147         case '\033': /* ESC */
2148                 csireset();
2149                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2150                 term.esc |= ESC_START;
2151                 return;
2152         case '\016': /* SO (LS1 -- Locking shift 1) */
2153         case '\017': /* SI (LS0 -- Locking shift 0) */
2154                 term.charset = 1 - (ascii - '\016');
2155                 return;
2156         case '\032': /* SUB */
2157                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2158         case '\030': /* CAN */
2159                 csireset();
2160                 break;
2161         case '\005': /* ENQ (IGNORED) */
2162         case '\000': /* NUL (IGNORED) */
2163         case '\021': /* XON (IGNORED) */
2164         case '\023': /* XOFF (IGNORED) */
2165         case 0177:   /* DEL (IGNORED) */
2166                 return;
2167         case 0x80:   /* TODO: PAD */
2168         case 0x81:   /* TODO: HOP */
2169         case 0x82:   /* TODO: BPH */
2170         case 0x83:   /* TODO: NBH */
2171         case 0x84:   /* TODO: IND */
2172                 break;
2173         case 0x85:   /* NEL -- Next line */
2174                 tnewline(1); /* always go to first col */
2175                 break;
2176         case 0x86:   /* TODO: SSA */
2177         case 0x87:   /* TODO: ESA */
2178                 break;
2179         case 0x88:   /* HTS -- Horizontal tab stop */
2180                 term.tabs[term.c.x] = 1;
2181                 break;
2182         case 0x89:   /* TODO: HTJ */
2183         case 0x8a:   /* TODO: VTS */
2184         case 0x8b:   /* TODO: PLD */
2185         case 0x8c:   /* TODO: PLU */
2186         case 0x8d:   /* TODO: RI */
2187         case 0x8e:   /* TODO: SS2 */
2188         case 0x8f:   /* TODO: SS3 */
2189         case 0x91:   /* TODO: PU1 */
2190         case 0x92:   /* TODO: PU2 */
2191         case 0x93:   /* TODO: STS */
2192         case 0x94:   /* TODO: CCH */
2193         case 0x95:   /* TODO: MW */
2194         case 0x96:   /* TODO: SPA */
2195         case 0x97:   /* TODO: EPA */
2196         case 0x98:   /* TODO: SOS */
2197         case 0x99:   /* TODO: SGCI */
2198                 break;
2199         case 0x9a:   /* DECID -- Identify Terminal */
2200                 ttywrite(vtiden, strlen(vtiden), 0);
2201                 break;
2202         case 0x9b:   /* TODO: CSI */
2203         case 0x9c:   /* TODO: ST */
2204                 break;
2205         case 0x90:   /* DCS -- Device Control String */
2206         case 0x9d:   /* OSC -- Operating System Command */
2207         case 0x9e:   /* PM -- Privacy Message */
2208         case 0x9f:   /* APC -- Application Program Command */
2209                 tstrsequence(ascii);
2210                 return;
2211         }
2212         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2213         term.esc &= ~(ESC_STR_END|ESC_STR);
2214 }
2215
2216 /*
2217  * returns 1 when the sequence is finished and it hasn't to read
2218  * more characters for this sequence, otherwise 0
2219  */
2220 int
2221 eschandle(uchar ascii)
2222 {
2223         switch (ascii) {
2224         case '[':
2225                 term.esc |= ESC_CSI;
2226                 return 0;
2227         case '#':
2228                 term.esc |= ESC_TEST;
2229                 return 0;
2230         case '%':
2231                 term.esc |= ESC_UTF8;
2232                 return 0;
2233         case 'P': /* DCS -- Device Control String */
2234         case '_': /* APC -- Application Program Command */
2235         case '^': /* PM -- Privacy Message */
2236         case ']': /* OSC -- Operating System Command */
2237         case 'k': /* old title set compatibility */
2238                 tstrsequence(ascii);
2239                 return 0;
2240         case 'n': /* LS2 -- Locking shift 2 */
2241         case 'o': /* LS3 -- Locking shift 3 */
2242                 term.charset = 2 + (ascii - 'n');
2243                 break;
2244         case '(': /* GZD4 -- set primary charset G0 */
2245         case ')': /* G1D4 -- set secondary charset G1 */
2246         case '*': /* G2D4 -- set tertiary charset G2 */
2247         case '+': /* G3D4 -- set quaternary charset G3 */
2248                 term.icharset = ascii - '(';
2249                 term.esc |= ESC_ALTCHARSET;
2250                 return 0;
2251         case 'D': /* IND -- Linefeed */
2252                 if (term.c.y == term.bot) {
2253                         tscrollup(term.top, 1);
2254                 } else {
2255                         tmoveto(term.c.x, term.c.y+1);
2256                 }
2257                 break;
2258         case 'E': /* NEL -- Next line */
2259                 tnewline(1); /* always go to first col */
2260                 break;
2261         case 'H': /* HTS -- Horizontal tab stop */
2262                 term.tabs[term.c.x] = 1;
2263                 break;
2264         case 'M': /* RI -- Reverse index */
2265                 if (term.c.y == term.top) {
2266                         tscrolldown(term.top, 1);
2267                 } else {
2268                         tmoveto(term.c.x, term.c.y-1);
2269                 }
2270                 break;
2271         case 'Z': /* DECID -- Identify Terminal */
2272                 ttywrite(vtiden, strlen(vtiden), 0);
2273                 break;
2274         case 'c': /* RIS -- Reset to initial state */
2275                 treset();
2276                 resettitle();
2277                 xloadcols();
2278                 break;
2279         case '=': /* DECPAM -- Application keypad */
2280                 xsetmode(1, MODE_APPKEYPAD);
2281                 break;
2282         case '>': /* DECPNM -- Normal keypad */
2283                 xsetmode(0, MODE_APPKEYPAD);
2284                 break;
2285         case '7': /* DECSC -- Save Cursor */
2286                 tcursor(CURSOR_SAVE);
2287                 break;
2288         case '8': /* DECRC -- Restore Cursor */
2289                 tcursor(CURSOR_LOAD);
2290                 break;
2291         case '\\': /* ST -- String Terminator */
2292                 if (term.esc & ESC_STR_END)
2293                         strhandle();
2294                 break;
2295         default:
2296                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2297                         (uchar) ascii, isprint(ascii)? ascii:'.');
2298                 break;
2299         }
2300         return 1;
2301 }
2302
2303 void
2304 tputc(Rune u)
2305 {
2306         char c[UTF_SIZ];
2307         int control;
2308         int width, len;
2309         Glyph *gp;
2310
2311         control = ISCONTROL(u);
2312         if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2313                 c[0] = u;
2314                 width = len = 1;
2315         } else {
2316                 len = utf8encode(u, c);
2317                 if (!control && (width = wcwidth(u)) == -1) {
2318                         memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2319                         width = 1;
2320                 }
2321         }
2322
2323         if (IS_SET(MODE_PRINT))
2324                 tprinter(c, len);
2325
2326         /*
2327          * STR sequence must be checked before anything else
2328          * because it uses all following characters until it
2329          * receives a ESC, a SUB, a ST or any other C1 control
2330          * character.
2331          */
2332         if (term.esc & ESC_STR) {
2333                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2334                    ISCONTROLC1(u)) {
2335                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2336                         if (IS_SET(MODE_SIXEL)) {
2337                                 /* TODO: render sixel */;
2338                                 term.mode &= ~MODE_SIXEL;
2339                                 return;
2340                         }
2341                         term.esc |= ESC_STR_END;
2342                         goto check_control_code;
2343                 }
2344
2345                 if (IS_SET(MODE_SIXEL)) {
2346                         /* TODO: implement sixel mode */
2347                         return;
2348                 }
2349                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2350                         term.mode |= MODE_SIXEL;
2351
2352                 if (strescseq.len+len >= strescseq.siz) {
2353                         /*
2354                          * Here is a bug in terminals. If the user never sends
2355                          * some code to stop the str or esc command, then st
2356                          * will stop responding. But this is better than
2357                          * silently failing with unknown characters. At least
2358                          * then users will report back.
2359                          *
2360                          * In the case users ever get fixed, here is the code:
2361                          */
2362                         /*
2363                          * term.esc = 0;
2364                          * strhandle();
2365                          */
2366                         if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2367                                 return;
2368                         strescseq.siz *= 2;
2369                         strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2370                 }
2371
2372                 memmove(&strescseq.buf[strescseq.len], c, len);
2373                 strescseq.len += len;
2374                 return;
2375         }
2376
2377 check_control_code:
2378         /*
2379          * Actions of control codes must be performed as soon they arrive
2380          * because they can be embedded inside a control sequence, and
2381          * they must not cause conflicts with sequences.
2382          */
2383         if (control) {
2384                 tcontrolcode(u);
2385                 /*
2386                  * control codes are not shown ever
2387                  */
2388                 return;
2389         } else if (term.esc & ESC_START) {
2390                 if (term.esc & ESC_CSI) {
2391                         csiescseq.buf[csiescseq.len++] = u;
2392                         if (BETWEEN(u, 0x40, 0x7E)
2393                                         || csiescseq.len >= \
2394                                         sizeof(csiescseq.buf)-1) {
2395                                 term.esc = 0;
2396                                 csiparse();
2397                                 csihandle();
2398                         }
2399                         return;
2400                 } else if (term.esc & ESC_UTF8) {
2401                         tdefutf8(u);
2402                 } else if (term.esc & ESC_ALTCHARSET) {
2403                         tdeftran(u);
2404                 } else if (term.esc & ESC_TEST) {
2405                         tdectest(u);
2406                 } else {
2407                         if (!eschandle(u))
2408                                 return;
2409                         /* sequence already finished */
2410                 }
2411                 term.esc = 0;
2412                 /*
2413                  * All characters which form part of a sequence are not
2414                  * printed
2415                  */
2416                 return;
2417         }
2418         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2419                 selclear();
2420
2421         gp = &term.line[term.c.y][term.c.x];
2422         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2423                 gp->mode |= ATTR_WRAP;
2424                 tnewline(1);
2425                 gp = &term.line[term.c.y][term.c.x];
2426         }
2427
2428         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2429                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2430
2431         if (term.c.x+width > term.col) {
2432                 tnewline(1);
2433                 gp = &term.line[term.c.y][term.c.x];
2434         }
2435
2436         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2437
2438         if (width == 2) {
2439                 gp->mode |= ATTR_WIDE;
2440                 if (term.c.x+1 < term.col) {
2441                         gp[1].u = '\0';
2442                         gp[1].mode = ATTR_WDUMMY;
2443                 }
2444         }
2445         if (term.c.x+width < term.col) {
2446                 tmoveto(term.c.x+width, term.c.y);
2447         } else {
2448                 term.c.state |= CURSOR_WRAPNEXT;
2449         }
2450 }
2451
2452 int
2453 twrite(const char *buf, int buflen, int show_ctrl)
2454 {
2455         int charsize;
2456         Rune u;
2457         int n;
2458
2459         for (n = 0; n < buflen; n += charsize) {
2460                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2461                         /* process a complete utf8 char */
2462                         charsize = utf8decode(buf + n, &u, buflen - n);
2463                         if (charsize == 0)
2464                                 break;
2465                 } else {
2466                         u = buf[n] & 0xFF;
2467                         charsize = 1;
2468                 }
2469                 if (show_ctrl && ISCONTROL(u)) {
2470                         if (u & 0x80) {
2471                                 u &= 0x7f;
2472                                 tputc('^');
2473                                 tputc('[');
2474                         } else if (u != '\n' && u != '\r' && u != '\t') {
2475                                 u ^= 0x40;
2476                                 tputc('^');
2477                         }
2478                 }
2479                 tputc(u);
2480         }
2481         return n;
2482 }
2483
2484 void
2485 tresize(int col, int row)
2486 {
2487         int i;
2488         int minrow = MIN(row, term.row);
2489         int mincol = MIN(col, term.col);
2490         int *bp;
2491         TCursor c;
2492
2493         if (col < 1 || row < 1) {
2494                 fprintf(stderr,
2495                         "tresize: error resizing to %dx%d\n", col, row);
2496                 return;
2497         }
2498
2499         /*
2500          * slide screen to keep cursor where we expect it -
2501          * tscrollup would work here, but we can optimize to
2502          * memmove because we're freeing the earlier lines
2503          */
2504         for (i = 0; i <= term.c.y - row; i++) {
2505                 free(term.line[i]);
2506                 free(term.alt[i]);
2507         }
2508         /* ensure that both src and dst are not NULL */
2509         if (i > 0) {
2510                 memmove(term.line, term.line + i, row * sizeof(Line));
2511                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2512         }
2513         for (i += row; i < term.row; i++) {
2514                 free(term.line[i]);
2515                 free(term.alt[i]);
2516         }
2517
2518         /* resize to new height */
2519         term.line = xrealloc(term.line, row * sizeof(Line));
2520         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2521         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2522         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2523
2524         /* resize each row to new width, zero-pad if needed */
2525         for (i = 0; i < minrow; i++) {
2526                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2527                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2528         }
2529
2530         /* allocate any new rows */
2531         for (/* i = minrow */; i < row; i++) {
2532                 term.line[i] = xmalloc(col * sizeof(Glyph));
2533                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2534         }
2535         if (col > term.col) {
2536                 bp = term.tabs + term.col;
2537
2538                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2539                 while (--bp > term.tabs && !*bp)
2540                         /* nothing */ ;
2541                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2542                         *bp = 1;
2543         }
2544         /* update terminal size */
2545         term.col = col;
2546         term.row = row;
2547         /* reset scrolling region */
2548         tsetscroll(0, row-1);
2549         /* make use of the LIMIT in tmoveto */
2550         tmoveto(term.c.x, term.c.y);
2551         /* Clearing both screens (it makes dirty all lines) */
2552         c = term.c;
2553         for (i = 0; i < 2; i++) {
2554                 if (mincol < col && 0 < minrow) {
2555                         tclearregion(mincol, 0, col - 1, minrow - 1);
2556                 }
2557                 if (0 < col && minrow < row) {
2558                         tclearregion(0, minrow, col - 1, row - 1);
2559                 }
2560                 tswapscreen();
2561                 tcursor(CURSOR_LOAD);
2562         }
2563         term.c = c;
2564 }
2565
2566 void
2567 resettitle(void)
2568 {
2569         xsettitle(NULL);
2570 }
2571
2572 void
2573 drawregion(int x1, int y1, int x2, int y2)
2574 {
2575         int y;
2576         for (y = y1; y < y2; y++) {
2577                 if (!term.dirty[y])
2578                         continue;
2579
2580                 term.dirty[y] = 0;
2581                 xdrawline(term.line[y], x1, y, x2);
2582         }
2583 }
2584
2585 void
2586 draw(void)
2587 {
2588         int cx = term.c.x;
2589
2590         if (!xstartdraw())
2591                 return;
2592
2593         /* adjust cursor position */
2594         LIMIT(term.ocx, 0, term.col-1);
2595         LIMIT(term.ocy, 0, term.row-1);
2596         if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2597                 term.ocx--;
2598         if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2599                 cx--;
2600
2601         drawregion(0, 0, term.col, term.row);
2602         xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2603                         term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2604         term.ocx = cx, term.ocy = term.c.y;
2605         xfinishdraw();
2606         xximspot(term.ocx, term.ocy);
2607 }
2608
2609 void
2610 redraw(void)
2611 {
2612         tfulldirt();
2613         draw();
2614 }