]> git.armaanb.net Git - st.git/blob - st.c
2ecf8f3b93135169172ee76141514de61a303f1e
[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 || utmp) {
686                 prog = scroll ? scroll : utmp;
687                 arg = scroll ? utmp : NULL;
688         } else {
689                 prog = sh;
690                 arg = NULL;
691         }
692         DEFAULT(args, ((char *[]) {prog, arg, NULL}));
693
694         unsetenv("COLUMNS");
695         unsetenv("LINES");
696         unsetenv("TERMCAP");
697         setenv("LOGNAME", pw->pw_name, 1);
698         setenv("USER", pw->pw_name, 1);
699         setenv("SHELL", sh, 1);
700         setenv("HOME", pw->pw_dir, 1);
701         setenv("TERM", termname, 1);
702
703         signal(SIGCHLD, SIG_DFL);
704         signal(SIGHUP, SIG_DFL);
705         signal(SIGINT, SIG_DFL);
706         signal(SIGQUIT, SIG_DFL);
707         signal(SIGTERM, SIG_DFL);
708         signal(SIGALRM, SIG_DFL);
709
710         execvp(prog, args);
711         _exit(1);
712 }
713
714 void
715 sigchld(int a)
716 {
717         int stat;
718         pid_t p;
719
720         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
721                 die("waiting for pid %hd failed: %s\n", pid, strerror(errno));
722
723         if (pid != p)
724                 return;
725
726         if (WIFEXITED(stat) && WEXITSTATUS(stat))
727                 die("child exited with status %d\n", WEXITSTATUS(stat));
728         else if (WIFSIGNALED(stat))
729                 die("child terminated due to signal %d\n", WTERMSIG(stat));
730         exit(0);
731 }
732
733 void
734 stty(char **args)
735 {
736         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
737         size_t n, siz;
738
739         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
740                 die("incorrect stty parameters\n");
741         memcpy(cmd, stty_args, n);
742         q = cmd + n;
743         siz = sizeof(cmd) - n;
744         for (p = args; p && (s = *p); ++p) {
745                 if ((n = strlen(s)) > siz-1)
746                         die("stty parameter length too long\n");
747                 *q++ = ' ';
748                 memcpy(q, s, n);
749                 q += n;
750                 siz -= n + 1;
751         }
752         *q = '\0';
753         if (system(cmd) != 0)
754                 perror("Couldn't call stty");
755 }
756
757 int
758 ttynew(char *line, char *cmd, char *out, char **args)
759 {
760         int m, s;
761
762         if (out) {
763                 term.mode |= MODE_PRINT;
764                 iofd = (!strcmp(out, "-")) ?
765                           1 : open(out, O_WRONLY | O_CREAT, 0666);
766                 if (iofd < 0) {
767                         fprintf(stderr, "Error opening %s:%s\n",
768                                 out, strerror(errno));
769                 }
770         }
771
772         if (line) {
773                 if ((cmdfd = open(line, O_RDWR)) < 0)
774                         die("open line '%s' failed: %s\n",
775                             line, strerror(errno));
776                 dup2(cmdfd, 0);
777                 stty(args);
778                 return cmdfd;
779         }
780
781         /* seems to work fine on linux, openbsd and freebsd */
782         if (openpty(&m, &s, NULL, NULL, NULL) < 0)
783                 die("openpty failed: %s\n", strerror(errno));
784
785         switch (pid = fork()) {
786         case -1:
787                 die("fork failed: %s\n", strerror(errno));
788                 break;
789         case 0:
790                 close(iofd);
791                 setsid(); /* create a new process group */
792                 dup2(s, 0);
793                 dup2(s, 1);
794                 dup2(s, 2);
795                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
796                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
797                 close(s);
798                 close(m);
799 #ifdef __OpenBSD__
800                 if (pledge("stdio getpw proc exec", NULL) == -1)
801                         die("pledge\n");
802 #endif
803                 execsh(cmd, args);
804                 break;
805         default:
806 #ifdef __OpenBSD__
807                 if (pledge("stdio rpath tty proc", NULL) == -1)
808                         die("pledge\n");
809 #endif
810                 close(s);
811                 cmdfd = m;
812                 signal(SIGCHLD, sigchld);
813                 break;
814         }
815         return cmdfd;
816 }
817
818 size_t
819 ttyread(void)
820 {
821         static char buf[BUFSIZ];
822         static int buflen = 0;
823         int written;
824         int ret;
825
826         /* append read bytes to unprocessed bytes */
827         ret = read(cmdfd, buf+buflen, LEN(buf)-buflen);
828
829         switch (ret) {
830         case 0:
831                 fputs("Found EOF in input\n", stderr);
832                 exit(0);
833         case -1:
834                 die("couldn't read from shell: %s\n", strerror(errno));
835         default:
836                 buflen += ret;
837                 written = twrite(buf, buflen, 0);
838                 buflen -= written;
839                 /* keep any uncomplete utf8 char for the next call */
840                 if (buflen > 0)
841                         memmove(buf, buf + written, buflen);
842                 return ret;
843
844         }
845 }
846
847 void
848 ttywrite(const char *s, size_t n, int may_echo)
849 {
850         const char *next;
851
852         if (may_echo && IS_SET(MODE_ECHO))
853                 twrite(s, n, 1);
854
855         if (!IS_SET(MODE_CRLF)) {
856                 ttywriteraw(s, n);
857                 return;
858         }
859
860         /* This is similar to how the kernel handles ONLCR for ttys */
861         while (n > 0) {
862                 if (*s == '\r') {
863                         next = s + 1;
864                         ttywriteraw("\r\n", 2);
865                 } else {
866                         next = memchr(s, '\r', n);
867                         DEFAULT(next, s + n);
868                         ttywriteraw(s, next - s);
869                 }
870                 n -= next - s;
871                 s = next;
872         }
873 }
874
875 void
876 ttywriteraw(const char *s, size_t n)
877 {
878         fd_set wfd, rfd;
879         ssize_t r;
880         size_t lim = 256;
881
882         /*
883          * Remember that we are using a pty, which might be a modem line.
884          * Writing too much will clog the line. That's why we are doing this
885          * dance.
886          * FIXME: Migrate the world to Plan 9.
887          */
888         while (n > 0) {
889                 FD_ZERO(&wfd);
890                 FD_ZERO(&rfd);
891                 FD_SET(cmdfd, &wfd);
892                 FD_SET(cmdfd, &rfd);
893
894                 /* Check if we can write. */
895                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
896                         if (errno == EINTR)
897                                 continue;
898                         die("select failed: %s\n", strerror(errno));
899                 }
900                 if (FD_ISSET(cmdfd, &wfd)) {
901                         /*
902                          * Only write the bytes written by ttywrite() or the
903                          * default of 256. This seems to be a reasonable value
904                          * for a serial line. Bigger values might clog the I/O.
905                          */
906                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
907                                 goto write_error;
908                         if (r < n) {
909                                 /*
910                                  * We weren't able to write out everything.
911                                  * This means the buffer is getting full
912                                  * again. Empty it.
913                                  */
914                                 if (n < lim)
915                                         lim = ttyread();
916                                 n -= r;
917                                 s += r;
918                         } else {
919                                 /* All bytes have been written. */
920                                 break;
921                         }
922                 }
923                 if (FD_ISSET(cmdfd, &rfd))
924                         lim = ttyread();
925         }
926         return;
927
928 write_error:
929         die("write error on tty: %s\n", strerror(errno));
930 }
931
932 void
933 ttyresize(int tw, int th)
934 {
935         struct winsize w;
936
937         w.ws_row = term.row;
938         w.ws_col = term.col;
939         w.ws_xpixel = tw;
940         w.ws_ypixel = th;
941         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
942                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
943 }
944
945 void
946 ttyhangup()
947 {
948         /* Send SIGHUP to shell */
949         kill(pid, SIGHUP);
950 }
951
952 int
953 tattrset(int attr)
954 {
955         int i, j;
956
957         for (i = 0; i < term.row-1; i++) {
958                 for (j = 0; j < term.col-1; j++) {
959                         if (term.line[i][j].mode & attr)
960                                 return 1;
961                 }
962         }
963
964         return 0;
965 }
966
967 void
968 tsetdirt(int top, int bot)
969 {
970         int i;
971
972         LIMIT(top, 0, term.row-1);
973         LIMIT(bot, 0, term.row-1);
974
975         for (i = top; i <= bot; i++)
976                 term.dirty[i] = 1;
977 }
978
979 void
980 tsetdirtattr(int attr)
981 {
982         int i, j;
983
984         for (i = 0; i < term.row-1; i++) {
985                 for (j = 0; j < term.col-1; j++) {
986                         if (term.line[i][j].mode & attr) {
987                                 tsetdirt(i, i);
988                                 break;
989                         }
990                 }
991         }
992 }
993
994 void
995 tfulldirt(void)
996 {
997         tsetdirt(0, term.row-1);
998 }
999
1000 void
1001 tcursor(int mode)
1002 {
1003         static TCursor c[2];
1004         int alt = IS_SET(MODE_ALTSCREEN);
1005
1006         if (mode == CURSOR_SAVE) {
1007                 c[alt] = term.c;
1008         } else if (mode == CURSOR_LOAD) {
1009                 term.c = c[alt];
1010                 tmoveto(c[alt].x, c[alt].y);
1011         }
1012 }
1013
1014 void
1015 treset(void)
1016 {
1017         uint i;
1018
1019         term.c = (TCursor){{
1020                 .mode = ATTR_NULL,
1021                 .fg = defaultfg,
1022                 .bg = defaultbg
1023         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1024
1025         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1026         for (i = tabspaces; i < term.col; i += tabspaces)
1027                 term.tabs[i] = 1;
1028         term.top = 0;
1029         term.bot = term.row - 1;
1030         term.mode = MODE_WRAP|MODE_UTF8;
1031         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1032         term.charset = 0;
1033
1034         for (i = 0; i < 2; i++) {
1035                 tmoveto(0, 0);
1036                 tcursor(CURSOR_SAVE);
1037                 tclearregion(0, 0, term.col-1, term.row-1);
1038                 tswapscreen();
1039         }
1040 }
1041
1042 void
1043 tnew(int col, int row)
1044 {
1045         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1046         tresize(col, row);
1047         treset();
1048 }
1049
1050 void
1051 tswapscreen(void)
1052 {
1053         Line *tmp = term.line;
1054
1055         term.line = term.alt;
1056         term.alt = tmp;
1057         term.mode ^= MODE_ALTSCREEN;
1058         tfulldirt();
1059 }
1060
1061 void
1062 tscrolldown(int orig, int n)
1063 {
1064         int i;
1065         Line temp;
1066
1067         LIMIT(n, 0, term.bot-orig+1);
1068
1069         tsetdirt(orig, term.bot-n);
1070         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1071
1072         for (i = term.bot; i >= orig+n; i--) {
1073                 temp = term.line[i];
1074                 term.line[i] = term.line[i-n];
1075                 term.line[i-n] = temp;
1076         }
1077
1078         selscroll(orig, n);
1079 }
1080
1081 void
1082 tscrollup(int orig, int n)
1083 {
1084         int i;
1085         Line temp;
1086
1087         LIMIT(n, 0, term.bot-orig+1);
1088
1089         tclearregion(0, orig, term.col-1, orig+n-1);
1090         tsetdirt(orig+n, term.bot);
1091
1092         for (i = orig; i <= term.bot-n; i++) {
1093                 temp = term.line[i];
1094                 term.line[i] = term.line[i+n];
1095                 term.line[i+n] = temp;
1096         }
1097
1098         selscroll(orig, -n);
1099 }
1100
1101 void
1102 selscroll(int orig, int n)
1103 {
1104         if (sel.ob.x == -1)
1105                 return;
1106
1107         if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1108                 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1109                         selclear();
1110                         return;
1111                 }
1112                 if (sel.type == SEL_RECTANGULAR) {
1113                         if (sel.ob.y < term.top)
1114                                 sel.ob.y = term.top;
1115                         if (sel.oe.y > term.bot)
1116                                 sel.oe.y = term.bot;
1117                 } else {
1118                         if (sel.ob.y < term.top) {
1119                                 sel.ob.y = term.top;
1120                                 sel.ob.x = 0;
1121                         }
1122                         if (sel.oe.y > term.bot) {
1123                                 sel.oe.y = term.bot;
1124                                 sel.oe.x = term.col;
1125                         }
1126                 }
1127                 selnormalize();
1128         }
1129 }
1130
1131 void
1132 tnewline(int first_col)
1133 {
1134         int y = term.c.y;
1135
1136         if (y == term.bot) {
1137                 tscrollup(term.top, 1);
1138         } else {
1139                 y++;
1140         }
1141         tmoveto(first_col ? 0 : term.c.x, y);
1142 }
1143
1144 void
1145 csiparse(void)
1146 {
1147         char *p = csiescseq.buf, *np;
1148         long int v;
1149
1150         csiescseq.narg = 0;
1151         if (*p == '?') {
1152                 csiescseq.priv = 1;
1153                 p++;
1154         }
1155
1156         csiescseq.buf[csiescseq.len] = '\0';
1157         while (p < csiescseq.buf+csiescseq.len) {
1158                 np = NULL;
1159                 v = strtol(p, &np, 10);
1160                 if (np == p)
1161                         v = 0;
1162                 if (v == LONG_MAX || v == LONG_MIN)
1163                         v = -1;
1164                 csiescseq.arg[csiescseq.narg++] = v;
1165                 p = np;
1166                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1167                         break;
1168                 p++;
1169         }
1170         csiescseq.mode[0] = *p++;
1171         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1172 }
1173
1174 /* for absolute user moves, when decom is set */
1175 void
1176 tmoveato(int x, int y)
1177 {
1178         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1179 }
1180
1181 void
1182 tmoveto(int x, int y)
1183 {
1184         int miny, maxy;
1185
1186         if (term.c.state & CURSOR_ORIGIN) {
1187                 miny = term.top;
1188                 maxy = term.bot;
1189         } else {
1190                 miny = 0;
1191                 maxy = term.row - 1;
1192         }
1193         term.c.state &= ~CURSOR_WRAPNEXT;
1194         term.c.x = LIMIT(x, 0, term.col-1);
1195         term.c.y = LIMIT(y, miny, maxy);
1196 }
1197
1198 void
1199 tsetchar(Rune u, Glyph *attr, int x, int y)
1200 {
1201         static char *vt100_0[62] = { /* 0x41 - 0x7e */
1202                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1203                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1204                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1205                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1206                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1207                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1208                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1209                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1210         };
1211
1212         /*
1213          * The table is proudly stolen from rxvt.
1214          */
1215         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1216            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1217                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1218
1219         if (term.line[y][x].mode & ATTR_WIDE) {
1220                 if (x+1 < term.col) {
1221                         term.line[y][x+1].u = ' ';
1222                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1223                 }
1224         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1225                 term.line[y][x-1].u = ' ';
1226                 term.line[y][x-1].mode &= ~ATTR_WIDE;
1227         }
1228
1229         term.dirty[y] = 1;
1230         term.line[y][x] = *attr;
1231         term.line[y][x].u = u;
1232 }
1233
1234 void
1235 tclearregion(int x1, int y1, int x2, int y2)
1236 {
1237         int x, y, temp;
1238         Glyph *gp;
1239
1240         if (x1 > x2)
1241                 temp = x1, x1 = x2, x2 = temp;
1242         if (y1 > y2)
1243                 temp = y1, y1 = y2, y2 = temp;
1244
1245         LIMIT(x1, 0, term.col-1);
1246         LIMIT(x2, 0, term.col-1);
1247         LIMIT(y1, 0, term.row-1);
1248         LIMIT(y2, 0, term.row-1);
1249
1250         for (y = y1; y <= y2; y++) {
1251                 term.dirty[y] = 1;
1252                 for (x = x1; x <= x2; x++) {
1253                         gp = &term.line[y][x];
1254                         if (selected(x, y))
1255                                 selclear();
1256                         gp->fg = term.c.attr.fg;
1257                         gp->bg = term.c.attr.bg;
1258                         gp->mode = 0;
1259                         gp->u = ' ';
1260                 }
1261         }
1262 }
1263
1264 void
1265 tdeletechar(int n)
1266 {
1267         int dst, src, size;
1268         Glyph *line;
1269
1270         LIMIT(n, 0, term.col - term.c.x);
1271
1272         dst = term.c.x;
1273         src = term.c.x + n;
1274         size = term.col - src;
1275         line = term.line[term.c.y];
1276
1277         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1278         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1279 }
1280
1281 void
1282 tinsertblank(int n)
1283 {
1284         int dst, src, size;
1285         Glyph *line;
1286
1287         LIMIT(n, 0, term.col - term.c.x);
1288
1289         dst = term.c.x + n;
1290         src = term.c.x;
1291         size = term.col - dst;
1292         line = term.line[term.c.y];
1293
1294         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1295         tclearregion(src, term.c.y, dst - 1, term.c.y);
1296 }
1297
1298 void
1299 tinsertblankline(int n)
1300 {
1301         if (BETWEEN(term.c.y, term.top, term.bot))
1302                 tscrolldown(term.c.y, n);
1303 }
1304
1305 void
1306 tdeleteline(int n)
1307 {
1308         if (BETWEEN(term.c.y, term.top, term.bot))
1309                 tscrollup(term.c.y, n);
1310 }
1311
1312 int32_t
1313 tdefcolor(int *attr, int *npar, int l)
1314 {
1315         int32_t idx = -1;
1316         uint r, g, b;
1317
1318         switch (attr[*npar + 1]) {
1319         case 2: /* direct color in RGB space */
1320                 if (*npar + 4 >= l) {
1321                         fprintf(stderr,
1322                                 "erresc(38): Incorrect number of parameters (%d)\n",
1323                                 *npar);
1324                         break;
1325                 }
1326                 r = attr[*npar + 2];
1327                 g = attr[*npar + 3];
1328                 b = attr[*npar + 4];
1329                 *npar += 4;
1330                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1331                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1332                                 r, g, b);
1333                 else
1334                         idx = TRUECOLOR(r, g, b);
1335                 break;
1336         case 5: /* indexed color */
1337                 if (*npar + 2 >= l) {
1338                         fprintf(stderr,
1339                                 "erresc(38): Incorrect number of parameters (%d)\n",
1340                                 *npar);
1341                         break;
1342                 }
1343                 *npar += 2;
1344                 if (!BETWEEN(attr[*npar], 0, 255))
1345                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1346                 else
1347                         idx = attr[*npar];
1348                 break;
1349         case 0: /* implemented defined (only foreground) */
1350         case 1: /* transparent */
1351         case 3: /* direct color in CMY space */
1352         case 4: /* direct color in CMYK space */
1353         default:
1354                 fprintf(stderr,
1355                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1356                 break;
1357         }
1358
1359         return idx;
1360 }
1361
1362 void
1363 tsetattr(int *attr, int l)
1364 {
1365         int i;
1366         int32_t idx;
1367
1368         for (i = 0; i < l; i++) {
1369                 switch (attr[i]) {
1370                 case 0:
1371                         term.c.attr.mode &= ~(
1372                                 ATTR_BOLD       |
1373                                 ATTR_FAINT      |
1374                                 ATTR_ITALIC     |
1375                                 ATTR_UNDERLINE  |
1376                                 ATTR_BLINK      |
1377                                 ATTR_REVERSE    |
1378                                 ATTR_INVISIBLE  |
1379                                 ATTR_STRUCK     );
1380                         term.c.attr.fg = defaultfg;
1381                         term.c.attr.bg = defaultbg;
1382                         break;
1383                 case 1:
1384                         term.c.attr.mode |= ATTR_BOLD;
1385                         break;
1386                 case 2:
1387                         term.c.attr.mode |= ATTR_FAINT;
1388                         break;
1389                 case 3:
1390                         term.c.attr.mode |= ATTR_ITALIC;
1391                         break;
1392                 case 4:
1393                         term.c.attr.mode |= ATTR_UNDERLINE;
1394                         break;
1395                 case 5: /* slow blink */
1396                         /* FALLTHROUGH */
1397                 case 6: /* rapid blink */
1398                         term.c.attr.mode |= ATTR_BLINK;
1399                         break;
1400                 case 7:
1401                         term.c.attr.mode |= ATTR_REVERSE;
1402                         break;
1403                 case 8:
1404                         term.c.attr.mode |= ATTR_INVISIBLE;
1405                         break;
1406                 case 9:
1407                         term.c.attr.mode |= ATTR_STRUCK;
1408                         break;
1409                 case 22:
1410                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1411                         break;
1412                 case 23:
1413                         term.c.attr.mode &= ~ATTR_ITALIC;
1414                         break;
1415                 case 24:
1416                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1417                         break;
1418                 case 25:
1419                         term.c.attr.mode &= ~ATTR_BLINK;
1420                         break;
1421                 case 27:
1422                         term.c.attr.mode &= ~ATTR_REVERSE;
1423                         break;
1424                 case 28:
1425                         term.c.attr.mode &= ~ATTR_INVISIBLE;
1426                         break;
1427                 case 29:
1428                         term.c.attr.mode &= ~ATTR_STRUCK;
1429                         break;
1430                 case 38:
1431                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1432                                 term.c.attr.fg = idx;
1433                         break;
1434                 case 39:
1435                         term.c.attr.fg = defaultfg;
1436                         break;
1437                 case 48:
1438                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1439                                 term.c.attr.bg = idx;
1440                         break;
1441                 case 49:
1442                         term.c.attr.bg = defaultbg;
1443                         break;
1444                 default:
1445                         if (BETWEEN(attr[i], 30, 37)) {
1446                                 term.c.attr.fg = attr[i] - 30;
1447                         } else if (BETWEEN(attr[i], 40, 47)) {
1448                                 term.c.attr.bg = attr[i] - 40;
1449                         } else if (BETWEEN(attr[i], 90, 97)) {
1450                                 term.c.attr.fg = attr[i] - 90 + 8;
1451                         } else if (BETWEEN(attr[i], 100, 107)) {
1452                                 term.c.attr.bg = attr[i] - 100 + 8;
1453                         } else {
1454                                 fprintf(stderr,
1455                                         "erresc(default): gfx attr %d unknown\n",
1456                                         attr[i]);
1457                                 csidump();
1458                         }
1459                         break;
1460                 }
1461         }
1462 }
1463
1464 void
1465 tsetscroll(int t, int b)
1466 {
1467         int temp;
1468
1469         LIMIT(t, 0, term.row-1);
1470         LIMIT(b, 0, term.row-1);
1471         if (t > b) {
1472                 temp = t;
1473                 t = b;
1474                 b = temp;
1475         }
1476         term.top = t;
1477         term.bot = b;
1478 }
1479
1480 void
1481 tsetmode(int priv, int set, int *args, int narg)
1482 {
1483         int alt, *lim;
1484
1485         for (lim = args + narg; args < lim; ++args) {
1486                 if (priv) {
1487                         switch (*args) {
1488                         case 1: /* DECCKM -- Cursor key */
1489                                 xsetmode(set, MODE_APPCURSOR);
1490                                 break;
1491                         case 5: /* DECSCNM -- Reverse video */
1492                                 xsetmode(set, MODE_REVERSE);
1493                                 break;
1494                         case 6: /* DECOM -- Origin */
1495                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1496                                 tmoveato(0, 0);
1497                                 break;
1498                         case 7: /* DECAWM -- Auto wrap */
1499                                 MODBIT(term.mode, set, MODE_WRAP);
1500                                 break;
1501                         case 0:  /* Error (IGNORED) */
1502                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1503                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1504                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1505                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1506                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1507                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1508                         case 42: /* DECNRCM -- National characters (IGNORED) */
1509                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1510                                 break;
1511                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1512                                 xsetmode(!set, MODE_HIDE);
1513                                 break;
1514                         case 9:    /* X10 mouse compatibility mode */
1515                                 xsetpointermotion(0);
1516                                 xsetmode(0, MODE_MOUSE);
1517                                 xsetmode(set, MODE_MOUSEX10);
1518                                 break;
1519                         case 1000: /* 1000: report button press */
1520                                 xsetpointermotion(0);
1521                                 xsetmode(0, MODE_MOUSE);
1522                                 xsetmode(set, MODE_MOUSEBTN);
1523                                 break;
1524                         case 1002: /* 1002: report motion on button press */
1525                                 xsetpointermotion(0);
1526                                 xsetmode(0, MODE_MOUSE);
1527                                 xsetmode(set, MODE_MOUSEMOTION);
1528                                 break;
1529                         case 1003: /* 1003: enable all mouse motions */
1530                                 xsetpointermotion(set);
1531                                 xsetmode(0, MODE_MOUSE);
1532                                 xsetmode(set, MODE_MOUSEMANY);
1533                                 break;
1534                         case 1004: /* 1004: send focus events to tty */
1535                                 xsetmode(set, MODE_FOCUS);
1536                                 break;
1537                         case 1006: /* 1006: extended reporting mode */
1538                                 xsetmode(set, MODE_MOUSESGR);
1539                                 break;
1540                         case 1034:
1541                                 xsetmode(set, MODE_8BIT);
1542                                 break;
1543                         case 1049: /* swap screen & set/restore cursor as xterm */
1544                                 if (!allowaltscreen)
1545                                         break;
1546                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1547                                 /* FALLTHROUGH */
1548                         case 47: /* swap screen */
1549                         case 1047:
1550                                 if (!allowaltscreen)
1551                                         break;
1552                                 alt = IS_SET(MODE_ALTSCREEN);
1553                                 if (alt) {
1554                                         tclearregion(0, 0, term.col-1,
1555                                                         term.row-1);
1556                                 }
1557                                 if (set ^ alt) /* set is always 1 or 0 */
1558                                         tswapscreen();
1559                                 if (*args != 1049)
1560                                         break;
1561                                 /* FALLTHROUGH */
1562                         case 1048:
1563                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1564                                 break;
1565                         case 2004: /* 2004: bracketed paste mode */
1566                                 xsetmode(set, MODE_BRCKTPASTE);
1567                                 break;
1568                         /* Not implemented mouse modes. See comments there. */
1569                         case 1001: /* mouse highlight mode; can hang the
1570                                       terminal by design when implemented. */
1571                         case 1005: /* UTF-8 mouse mode; will confuse
1572                                       applications not supporting UTF-8
1573                                       and luit. */
1574                         case 1015: /* urxvt mangled mouse mode; incompatible
1575                                       and can be mistaken for other control
1576                                       codes. */
1577                                 break;
1578                         default:
1579                                 fprintf(stderr,
1580                                         "erresc: unknown private set/reset mode %d\n",
1581                                         *args);
1582                                 break;
1583                         }
1584                 } else {
1585                         switch (*args) {
1586                         case 0:  /* Error (IGNORED) */
1587                                 break;
1588                         case 2:
1589                                 xsetmode(set, MODE_KBDLOCK);
1590                                 break;
1591                         case 4:  /* IRM -- Insertion-replacement */
1592                                 MODBIT(term.mode, set, MODE_INSERT);
1593                                 break;
1594                         case 12: /* SRM -- Send/Receive */
1595                                 MODBIT(term.mode, !set, MODE_ECHO);
1596                                 break;
1597                         case 20: /* LNM -- Linefeed/new line */
1598                                 MODBIT(term.mode, set, MODE_CRLF);
1599                                 break;
1600                         default:
1601                                 fprintf(stderr,
1602                                         "erresc: unknown set/reset mode %d\n",
1603                                         *args);
1604                                 break;
1605                         }
1606                 }
1607         }
1608 }
1609
1610 void
1611 csihandle(void)
1612 {
1613         char buf[40];
1614         int len;
1615
1616         switch (csiescseq.mode[0]) {
1617         default:
1618         unknown:
1619                 fprintf(stderr, "erresc: unknown csi ");
1620                 csidump();
1621                 /* die(""); */
1622                 break;
1623         case '@': /* ICH -- Insert <n> blank char */
1624                 DEFAULT(csiescseq.arg[0], 1);
1625                 tinsertblank(csiescseq.arg[0]);
1626                 break;
1627         case 'A': /* CUU -- Cursor <n> Up */
1628                 DEFAULT(csiescseq.arg[0], 1);
1629                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1630                 break;
1631         case 'B': /* CUD -- Cursor <n> Down */
1632         case 'e': /* VPR --Cursor <n> Down */
1633                 DEFAULT(csiescseq.arg[0], 1);
1634                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1635                 break;
1636         case 'i': /* MC -- Media Copy */
1637                 switch (csiescseq.arg[0]) {
1638                 case 0:
1639                         tdump();
1640                         break;
1641                 case 1:
1642                         tdumpline(term.c.y);
1643                         break;
1644                 case 2:
1645                         tdumpsel();
1646                         break;
1647                 case 4:
1648                         term.mode &= ~MODE_PRINT;
1649                         break;
1650                 case 5:
1651                         term.mode |= MODE_PRINT;
1652                         break;
1653                 }
1654                 break;
1655         case 'c': /* DA -- Device Attributes */
1656                 if (csiescseq.arg[0] == 0)
1657                         ttywrite(vtiden, strlen(vtiden), 0);
1658                 break;
1659         case 'C': /* CUF -- Cursor <n> Forward */
1660         case 'a': /* HPR -- Cursor <n> Forward */
1661                 DEFAULT(csiescseq.arg[0], 1);
1662                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1663                 break;
1664         case 'D': /* CUB -- Cursor <n> Backward */
1665                 DEFAULT(csiescseq.arg[0], 1);
1666                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1667                 break;
1668         case 'E': /* CNL -- Cursor <n> Down and first col */
1669                 DEFAULT(csiescseq.arg[0], 1);
1670                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1671                 break;
1672         case 'F': /* CPL -- Cursor <n> Up and first col */
1673                 DEFAULT(csiescseq.arg[0], 1);
1674                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1675                 break;
1676         case 'g': /* TBC -- Tabulation clear */
1677                 switch (csiescseq.arg[0]) {
1678                 case 0: /* clear current tab stop */
1679                         term.tabs[term.c.x] = 0;
1680                         break;
1681                 case 3: /* clear all the tabs */
1682                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1683                         break;
1684                 default:
1685                         goto unknown;
1686                 }
1687                 break;
1688         case 'G': /* CHA -- Move to <col> */
1689         case '`': /* HPA */
1690                 DEFAULT(csiescseq.arg[0], 1);
1691                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1692                 break;
1693         case 'H': /* CUP -- Move to <row> <col> */
1694         case 'f': /* HVP */
1695                 DEFAULT(csiescseq.arg[0], 1);
1696                 DEFAULT(csiescseq.arg[1], 1);
1697                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1698                 break;
1699         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1700                 DEFAULT(csiescseq.arg[0], 1);
1701                 tputtab(csiescseq.arg[0]);
1702                 break;
1703         case 'J': /* ED -- Clear screen */
1704                 switch (csiescseq.arg[0]) {
1705                 case 0: /* below */
1706                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1707                         if (term.c.y < term.row-1) {
1708                                 tclearregion(0, term.c.y+1, term.col-1,
1709                                                 term.row-1);
1710                         }
1711                         break;
1712                 case 1: /* above */
1713                         if (term.c.y > 1)
1714                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1715                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1716                         break;
1717                 case 2: /* all */
1718                         tclearregion(0, 0, term.col-1, term.row-1);
1719                         break;
1720                 default:
1721                         goto unknown;
1722                 }
1723                 break;
1724         case 'K': /* EL -- Clear line */
1725                 switch (csiescseq.arg[0]) {
1726                 case 0: /* right */
1727                         tclearregion(term.c.x, term.c.y, term.col-1,
1728                                         term.c.y);
1729                         break;
1730                 case 1: /* left */
1731                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1732                         break;
1733                 case 2: /* all */
1734                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1735                         break;
1736                 }
1737                 break;
1738         case 'S': /* SU -- Scroll <n> line up */
1739                 DEFAULT(csiescseq.arg[0], 1);
1740                 tscrollup(term.top, csiescseq.arg[0]);
1741                 break;
1742         case 'T': /* SD -- Scroll <n> line down */
1743                 DEFAULT(csiescseq.arg[0], 1);
1744                 tscrolldown(term.top, csiescseq.arg[0]);
1745                 break;
1746         case 'L': /* IL -- Insert <n> blank lines */
1747                 DEFAULT(csiescseq.arg[0], 1);
1748                 tinsertblankline(csiescseq.arg[0]);
1749                 break;
1750         case 'l': /* RM -- Reset Mode */
1751                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1752                 break;
1753         case 'M': /* DL -- Delete <n> lines */
1754                 DEFAULT(csiescseq.arg[0], 1);
1755                 tdeleteline(csiescseq.arg[0]);
1756                 break;
1757         case 'X': /* ECH -- Erase <n> char */
1758                 DEFAULT(csiescseq.arg[0], 1);
1759                 tclearregion(term.c.x, term.c.y,
1760                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1761                 break;
1762         case 'P': /* DCH -- Delete <n> char */
1763                 DEFAULT(csiescseq.arg[0], 1);
1764                 tdeletechar(csiescseq.arg[0]);
1765                 break;
1766         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1767                 DEFAULT(csiescseq.arg[0], 1);
1768                 tputtab(-csiescseq.arg[0]);
1769                 break;
1770         case 'd': /* VPA -- Move to <row> */
1771                 DEFAULT(csiescseq.arg[0], 1);
1772                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1773                 break;
1774         case 'h': /* SM -- Set terminal mode */
1775                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1776                 break;
1777         case 'm': /* SGR -- Terminal attribute (color) */
1778                 tsetattr(csiescseq.arg, csiescseq.narg);
1779                 break;
1780         case 'n': /* DSR – Device Status Report (cursor position) */
1781                 if (csiescseq.arg[0] == 6) {
1782                         len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1783                                         term.c.y+1, term.c.x+1);
1784                         ttywrite(buf, len, 0);
1785                 }
1786                 break;
1787         case 'r': /* DECSTBM -- Set Scrolling Region */
1788                 if (csiescseq.priv) {
1789                         goto unknown;
1790                 } else {
1791                         DEFAULT(csiescseq.arg[0], 1);
1792                         DEFAULT(csiescseq.arg[1], term.row);
1793                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1794                         tmoveato(0, 0);
1795                 }
1796                 break;
1797         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1798                 tcursor(CURSOR_SAVE);
1799                 break;
1800         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1801                 tcursor(CURSOR_LOAD);
1802                 break;
1803         case ' ':
1804                 switch (csiescseq.mode[1]) {
1805                 case 'q': /* DECSCUSR -- Set Cursor Style */
1806                         if (xsetcursor(csiescseq.arg[0]))
1807                                 goto unknown;
1808                         break;
1809                 default:
1810                         goto unknown;
1811                 }
1812                 break;
1813         }
1814 }
1815
1816 void
1817 csidump(void)
1818 {
1819         size_t i;
1820         uint c;
1821
1822         fprintf(stderr, "ESC[");
1823         for (i = 0; i < csiescseq.len; i++) {
1824                 c = csiescseq.buf[i] & 0xff;
1825                 if (isprint(c)) {
1826                         putc(c, stderr);
1827                 } else if (c == '\n') {
1828                         fprintf(stderr, "(\\n)");
1829                 } else if (c == '\r') {
1830                         fprintf(stderr, "(\\r)");
1831                 } else if (c == 0x1b) {
1832                         fprintf(stderr, "(\\e)");
1833                 } else {
1834                         fprintf(stderr, "(%02x)", c);
1835                 }
1836         }
1837         putc('\n', stderr);
1838 }
1839
1840 void
1841 csireset(void)
1842 {
1843         memset(&csiescseq, 0, sizeof(csiescseq));
1844 }
1845
1846 void
1847 strhandle(void)
1848 {
1849         char *p = NULL, *dec;
1850         int j, narg, par;
1851
1852         term.esc &= ~(ESC_STR_END|ESC_STR);
1853         strparse();
1854         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1855
1856         switch (strescseq.type) {
1857         case ']': /* OSC -- Operating System Command */
1858                 switch (par) {
1859                 case 0:
1860                 case 1:
1861                 case 2:
1862                         if (narg > 1)
1863                                 xsettitle(strescseq.args[1]);
1864                         return;
1865                 case 52:
1866                         if (narg > 2) {
1867                                 dec = base64dec(strescseq.args[2]);
1868                                 if (dec) {
1869                                         xsetsel(dec);
1870                                         xclipcopy();
1871                                 } else {
1872                                         fprintf(stderr, "erresc: invalid base64\n");
1873                                 }
1874                         }
1875                         return;
1876                 case 4: /* color set */
1877                         if (narg < 3)
1878                                 break;
1879                         p = strescseq.args[2];
1880                         /* FALLTHROUGH */
1881                 case 104: /* color reset, here p = NULL */
1882                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1883                         if (xsetcolorname(j, p)) {
1884                                 if (par == 104 && narg <= 1)
1885                                         return; /* color reset without parameter */
1886                                 fprintf(stderr, "erresc: invalid color j=%d, p=%s\n",
1887                                         j, p ? p : "(null)");
1888                         } else {
1889                                 /*
1890                                  * TODO if defaultbg color is changed, borders
1891                                  * are dirty
1892                                  */
1893                                 redraw();
1894                         }
1895                         return;
1896                 }
1897                 break;
1898         case 'k': /* old title set compatibility */
1899                 xsettitle(strescseq.args[0]);
1900                 return;
1901         case 'P': /* DCS -- Device Control String */
1902                 term.mode |= ESC_DCS;
1903         case '_': /* APC -- Application Program Command */
1904         case '^': /* PM -- Privacy Message */
1905                 return;
1906         }
1907
1908         fprintf(stderr, "erresc: unknown str ");
1909         strdump();
1910 }
1911
1912 void
1913 strparse(void)
1914 {
1915         int c;
1916         char *p = strescseq.buf;
1917
1918         strescseq.narg = 0;
1919         strescseq.buf[strescseq.len] = '\0';
1920
1921         if (*p == '\0')
1922                 return;
1923
1924         while (strescseq.narg < STR_ARG_SIZ) {
1925                 strescseq.args[strescseq.narg++] = p;
1926                 while ((c = *p) != ';' && c != '\0')
1927                         ++p;
1928                 if (c == '\0')
1929                         return;
1930                 *p++ = '\0';
1931         }
1932 }
1933
1934 void
1935 strdump(void)
1936 {
1937         size_t i;
1938         uint c;
1939
1940         fprintf(stderr, "ESC%c", strescseq.type);
1941         for (i = 0; i < strescseq.len; i++) {
1942                 c = strescseq.buf[i] & 0xff;
1943                 if (c == '\0') {
1944                         putc('\n', stderr);
1945                         return;
1946                 } else if (isprint(c)) {
1947                         putc(c, stderr);
1948                 } else if (c == '\n') {
1949                         fprintf(stderr, "(\\n)");
1950                 } else if (c == '\r') {
1951                         fprintf(stderr, "(\\r)");
1952                 } else if (c == 0x1b) {
1953                         fprintf(stderr, "(\\e)");
1954                 } else {
1955                         fprintf(stderr, "(%02x)", c);
1956                 }
1957         }
1958         fprintf(stderr, "ESC\\\n");
1959 }
1960
1961 void
1962 strreset(void)
1963 {
1964         strescseq = (STREscape){
1965                 .buf = xrealloc(strescseq.buf, STR_BUF_SIZ),
1966                 .siz = STR_BUF_SIZ,
1967         };
1968 }
1969
1970 void
1971 sendbreak(const Arg *arg)
1972 {
1973         if (tcsendbreak(cmdfd, 0))
1974                 perror("Error sending break");
1975 }
1976
1977 void
1978 tprinter(char *s, size_t len)
1979 {
1980         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1981                 perror("Error writing to output file");
1982                 close(iofd);
1983                 iofd = -1;
1984         }
1985 }
1986
1987 void
1988 toggleprinter(const Arg *arg)
1989 {
1990         term.mode ^= MODE_PRINT;
1991 }
1992
1993 void
1994 printscreen(const Arg *arg)
1995 {
1996         tdump();
1997 }
1998
1999 void
2000 printsel(const Arg *arg)
2001 {
2002         tdumpsel();
2003 }
2004
2005 void
2006 tdumpsel(void)
2007 {
2008         char *ptr;
2009
2010         if ((ptr = getsel())) {
2011                 tprinter(ptr, strlen(ptr));
2012                 free(ptr);
2013         }
2014 }
2015
2016 void
2017 tdumpline(int n)
2018 {
2019         char buf[UTF_SIZ];
2020         Glyph *bp, *end;
2021
2022         bp = &term.line[n][0];
2023         end = &bp[MIN(tlinelen(n), term.col) - 1];
2024         if (bp != end || bp->u != ' ') {
2025                 for ( ;bp <= end; ++bp)
2026                         tprinter(buf, utf8encode(bp->u, buf));
2027         }
2028         tprinter("\n", 1);
2029 }
2030
2031 void
2032 tdump(void)
2033 {
2034         int i;
2035
2036         for (i = 0; i < term.row; ++i)
2037                 tdumpline(i);
2038 }
2039
2040 void
2041 tputtab(int n)
2042 {
2043         uint x = term.c.x;
2044
2045         if (n > 0) {
2046                 while (x < term.col && n--)
2047                         for (++x; x < term.col && !term.tabs[x]; ++x)
2048                                 /* nothing */ ;
2049         } else if (n < 0) {
2050                 while (x > 0 && n++)
2051                         for (--x; x > 0 && !term.tabs[x]; --x)
2052                                 /* nothing */ ;
2053         }
2054         term.c.x = LIMIT(x, 0, term.col-1);
2055 }
2056
2057 void
2058 tdefutf8(char ascii)
2059 {
2060         if (ascii == 'G')
2061                 term.mode |= MODE_UTF8;
2062         else if (ascii == '@')
2063                 term.mode &= ~MODE_UTF8;
2064 }
2065
2066 void
2067 tdeftran(char ascii)
2068 {
2069         static char cs[] = "0B";
2070         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2071         char *p;
2072
2073         if ((p = strchr(cs, ascii)) == NULL) {
2074                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2075         } else {
2076                 term.trantbl[term.icharset] = vcs[p - cs];
2077         }
2078 }
2079
2080 void
2081 tdectest(char c)
2082 {
2083         int x, y;
2084
2085         if (c == '8') { /* DEC screen alignment test. */
2086                 for (x = 0; x < term.col; ++x) {
2087                         for (y = 0; y < term.row; ++y)
2088                                 tsetchar('E', &term.c.attr, x, y);
2089                 }
2090         }
2091 }
2092
2093 void
2094 tstrsequence(uchar c)
2095 {
2096         strreset();
2097
2098         switch (c) {
2099         case 0x90:   /* DCS -- Device Control String */
2100                 c = 'P';
2101                 term.esc |= ESC_DCS;
2102                 break;
2103         case 0x9f:   /* APC -- Application Program Command */
2104                 c = '_';
2105                 break;
2106         case 0x9e:   /* PM -- Privacy Message */
2107                 c = '^';
2108                 break;
2109         case 0x9d:   /* OSC -- Operating System Command */
2110                 c = ']';
2111                 break;
2112         }
2113         strescseq.type = c;
2114         term.esc |= ESC_STR;
2115 }
2116
2117 void
2118 tcontrolcode(uchar ascii)
2119 {
2120         switch (ascii) {
2121         case '\t':   /* HT */
2122                 tputtab(1);
2123                 return;
2124         case '\b':   /* BS */
2125                 tmoveto(term.c.x-1, term.c.y);
2126                 return;
2127         case '\r':   /* CR */
2128                 tmoveto(0, term.c.y);
2129                 return;
2130         case '\f':   /* LF */
2131         case '\v':   /* VT */
2132         case '\n':   /* LF */
2133                 /* go to first col if the mode is set */
2134                 tnewline(IS_SET(MODE_CRLF));
2135                 return;
2136         case '\a':   /* BEL */
2137                 if (term.esc & ESC_STR_END) {
2138                         /* backwards compatibility to xterm */
2139                         strhandle();
2140                 } else {
2141                         xbell();
2142                 }
2143                 break;
2144         case '\033': /* ESC */
2145                 csireset();
2146                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2147                 term.esc |= ESC_START;
2148                 return;
2149         case '\016': /* SO (LS1 -- Locking shift 1) */
2150         case '\017': /* SI (LS0 -- Locking shift 0) */
2151                 term.charset = 1 - (ascii - '\016');
2152                 return;
2153         case '\032': /* SUB */
2154                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2155         case '\030': /* CAN */
2156                 csireset();
2157                 break;
2158         case '\005': /* ENQ (IGNORED) */
2159         case '\000': /* NUL (IGNORED) */
2160         case '\021': /* XON (IGNORED) */
2161         case '\023': /* XOFF (IGNORED) */
2162         case 0177:   /* DEL (IGNORED) */
2163                 return;
2164         case 0x80:   /* TODO: PAD */
2165         case 0x81:   /* TODO: HOP */
2166         case 0x82:   /* TODO: BPH */
2167         case 0x83:   /* TODO: NBH */
2168         case 0x84:   /* TODO: IND */
2169                 break;
2170         case 0x85:   /* NEL -- Next line */
2171                 tnewline(1); /* always go to first col */
2172                 break;
2173         case 0x86:   /* TODO: SSA */
2174         case 0x87:   /* TODO: ESA */
2175                 break;
2176         case 0x88:   /* HTS -- Horizontal tab stop */
2177                 term.tabs[term.c.x] = 1;
2178                 break;
2179         case 0x89:   /* TODO: HTJ */
2180         case 0x8a:   /* TODO: VTS */
2181         case 0x8b:   /* TODO: PLD */
2182         case 0x8c:   /* TODO: PLU */
2183         case 0x8d:   /* TODO: RI */
2184         case 0x8e:   /* TODO: SS2 */
2185         case 0x8f:   /* TODO: SS3 */
2186         case 0x91:   /* TODO: PU1 */
2187         case 0x92:   /* TODO: PU2 */
2188         case 0x93:   /* TODO: STS */
2189         case 0x94:   /* TODO: CCH */
2190         case 0x95:   /* TODO: MW */
2191         case 0x96:   /* TODO: SPA */
2192         case 0x97:   /* TODO: EPA */
2193         case 0x98:   /* TODO: SOS */
2194         case 0x99:   /* TODO: SGCI */
2195                 break;
2196         case 0x9a:   /* DECID -- Identify Terminal */
2197                 ttywrite(vtiden, strlen(vtiden), 0);
2198                 break;
2199         case 0x9b:   /* TODO: CSI */
2200         case 0x9c:   /* TODO: ST */
2201                 break;
2202         case 0x90:   /* DCS -- Device Control String */
2203         case 0x9d:   /* OSC -- Operating System Command */
2204         case 0x9e:   /* PM -- Privacy Message */
2205         case 0x9f:   /* APC -- Application Program Command */
2206                 tstrsequence(ascii);
2207                 return;
2208         }
2209         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2210         term.esc &= ~(ESC_STR_END|ESC_STR);
2211 }
2212
2213 /*
2214  * returns 1 when the sequence is finished and it hasn't to read
2215  * more characters for this sequence, otherwise 0
2216  */
2217 int
2218 eschandle(uchar ascii)
2219 {
2220         switch (ascii) {
2221         case '[':
2222                 term.esc |= ESC_CSI;
2223                 return 0;
2224         case '#':
2225                 term.esc |= ESC_TEST;
2226                 return 0;
2227         case '%':
2228                 term.esc |= ESC_UTF8;
2229                 return 0;
2230         case 'P': /* DCS -- Device Control String */
2231         case '_': /* APC -- Application Program Command */
2232         case '^': /* PM -- Privacy Message */
2233         case ']': /* OSC -- Operating System Command */
2234         case 'k': /* old title set compatibility */
2235                 tstrsequence(ascii);
2236                 return 0;
2237         case 'n': /* LS2 -- Locking shift 2 */
2238         case 'o': /* LS3 -- Locking shift 3 */
2239                 term.charset = 2 + (ascii - 'n');
2240                 break;
2241         case '(': /* GZD4 -- set primary charset G0 */
2242         case ')': /* G1D4 -- set secondary charset G1 */
2243         case '*': /* G2D4 -- set tertiary charset G2 */
2244         case '+': /* G3D4 -- set quaternary charset G3 */
2245                 term.icharset = ascii - '(';
2246                 term.esc |= ESC_ALTCHARSET;
2247                 return 0;
2248         case 'D': /* IND -- Linefeed */
2249                 if (term.c.y == term.bot) {
2250                         tscrollup(term.top, 1);
2251                 } else {
2252                         tmoveto(term.c.x, term.c.y+1);
2253                 }
2254                 break;
2255         case 'E': /* NEL -- Next line */
2256                 tnewline(1); /* always go to first col */
2257                 break;
2258         case 'H': /* HTS -- Horizontal tab stop */
2259                 term.tabs[term.c.x] = 1;
2260                 break;
2261         case 'M': /* RI -- Reverse index */
2262                 if (term.c.y == term.top) {
2263                         tscrolldown(term.top, 1);
2264                 } else {
2265                         tmoveto(term.c.x, term.c.y-1);
2266                 }
2267                 break;
2268         case 'Z': /* DECID -- Identify Terminal */
2269                 ttywrite(vtiden, strlen(vtiden), 0);
2270                 break;
2271         case 'c': /* RIS -- Reset to initial state */
2272                 treset();
2273                 resettitle();
2274                 xloadcols();
2275                 break;
2276         case '=': /* DECPAM -- Application keypad */
2277                 xsetmode(1, MODE_APPKEYPAD);
2278                 break;
2279         case '>': /* DECPNM -- Normal keypad */
2280                 xsetmode(0, MODE_APPKEYPAD);
2281                 break;
2282         case '7': /* DECSC -- Save Cursor */
2283                 tcursor(CURSOR_SAVE);
2284                 break;
2285         case '8': /* DECRC -- Restore Cursor */
2286                 tcursor(CURSOR_LOAD);
2287                 break;
2288         case '\\': /* ST -- String Terminator */
2289                 if (term.esc & ESC_STR_END)
2290                         strhandle();
2291                 break;
2292         default:
2293                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2294                         (uchar) ascii, isprint(ascii)? ascii:'.');
2295                 break;
2296         }
2297         return 1;
2298 }
2299
2300 void
2301 tputc(Rune u)
2302 {
2303         char c[UTF_SIZ];
2304         int control;
2305         int width, len;
2306         Glyph *gp;
2307
2308         control = ISCONTROL(u);
2309         if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2310                 c[0] = u;
2311                 width = len = 1;
2312         } else {
2313                 len = utf8encode(u, c);
2314                 if (!control && (width = wcwidth(u)) == -1) {
2315                         memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2316                         width = 1;
2317                 }
2318         }
2319
2320         if (IS_SET(MODE_PRINT))
2321                 tprinter(c, len);
2322
2323         /*
2324          * STR sequence must be checked before anything else
2325          * because it uses all following characters until it
2326          * receives a ESC, a SUB, a ST or any other C1 control
2327          * character.
2328          */
2329         if (term.esc & ESC_STR) {
2330                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2331                    ISCONTROLC1(u)) {
2332                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2333                         if (IS_SET(MODE_SIXEL)) {
2334                                 /* TODO: render sixel */;
2335                                 term.mode &= ~MODE_SIXEL;
2336                                 return;
2337                         }
2338                         term.esc |= ESC_STR_END;
2339                         goto check_control_code;
2340                 }
2341
2342                 if (IS_SET(MODE_SIXEL)) {
2343                         /* TODO: implement sixel mode */
2344                         return;
2345                 }
2346                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2347                         term.mode |= MODE_SIXEL;
2348
2349                 if (strescseq.len+len >= strescseq.siz) {
2350                         /*
2351                          * Here is a bug in terminals. If the user never sends
2352                          * some code to stop the str or esc command, then st
2353                          * will stop responding. But this is better than
2354                          * silently failing with unknown characters. At least
2355                          * then users will report back.
2356                          *
2357                          * In the case users ever get fixed, here is the code:
2358                          */
2359                         /*
2360                          * term.esc = 0;
2361                          * strhandle();
2362                          */
2363                         if (strescseq.siz > (SIZE_MAX - UTF_SIZ) / 2)
2364                                 return;
2365                         strescseq.siz *= 2;
2366                         strescseq.buf = xrealloc(strescseq.buf, strescseq.siz);
2367                 }
2368
2369                 memmove(&strescseq.buf[strescseq.len], c, len);
2370                 strescseq.len += len;
2371                 return;
2372         }
2373
2374 check_control_code:
2375         /*
2376          * Actions of control codes must be performed as soon they arrive
2377          * because they can be embedded inside a control sequence, and
2378          * they must not cause conflicts with sequences.
2379          */
2380         if (control) {
2381                 tcontrolcode(u);
2382                 /*
2383                  * control codes are not shown ever
2384                  */
2385                 return;
2386         } else if (term.esc & ESC_START) {
2387                 if (term.esc & ESC_CSI) {
2388                         csiescseq.buf[csiescseq.len++] = u;
2389                         if (BETWEEN(u, 0x40, 0x7E)
2390                                         || csiescseq.len >= \
2391                                         sizeof(csiescseq.buf)-1) {
2392                                 term.esc = 0;
2393                                 csiparse();
2394                                 csihandle();
2395                         }
2396                         return;
2397                 } else if (term.esc & ESC_UTF8) {
2398                         tdefutf8(u);
2399                 } else if (term.esc & ESC_ALTCHARSET) {
2400                         tdeftran(u);
2401                 } else if (term.esc & ESC_TEST) {
2402                         tdectest(u);
2403                 } else {
2404                         if (!eschandle(u))
2405                                 return;
2406                         /* sequence already finished */
2407                 }
2408                 term.esc = 0;
2409                 /*
2410                  * All characters which form part of a sequence are not
2411                  * printed
2412                  */
2413                 return;
2414         }
2415         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2416                 selclear();
2417
2418         gp = &term.line[term.c.y][term.c.x];
2419         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2420                 gp->mode |= ATTR_WRAP;
2421                 tnewline(1);
2422                 gp = &term.line[term.c.y][term.c.x];
2423         }
2424
2425         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2426                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2427
2428         if (term.c.x+width > term.col) {
2429                 tnewline(1);
2430                 gp = &term.line[term.c.y][term.c.x];
2431         }
2432
2433         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2434
2435         if (width == 2) {
2436                 gp->mode |= ATTR_WIDE;
2437                 if (term.c.x+1 < term.col) {
2438                         gp[1].u = '\0';
2439                         gp[1].mode = ATTR_WDUMMY;
2440                 }
2441         }
2442         if (term.c.x+width < term.col) {
2443                 tmoveto(term.c.x+width, term.c.y);
2444         } else {
2445                 term.c.state |= CURSOR_WRAPNEXT;
2446         }
2447 }
2448
2449 int
2450 twrite(const char *buf, int buflen, int show_ctrl)
2451 {
2452         int charsize;
2453         Rune u;
2454         int n;
2455
2456         for (n = 0; n < buflen; n += charsize) {
2457                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2458                         /* process a complete utf8 char */
2459                         charsize = utf8decode(buf + n, &u, buflen - n);
2460                         if (charsize == 0)
2461                                 break;
2462                 } else {
2463                         u = buf[n] & 0xFF;
2464                         charsize = 1;
2465                 }
2466                 if (show_ctrl && ISCONTROL(u)) {
2467                         if (u & 0x80) {
2468                                 u &= 0x7f;
2469                                 tputc('^');
2470                                 tputc('[');
2471                         } else if (u != '\n' && u != '\r' && u != '\t') {
2472                                 u ^= 0x40;
2473                                 tputc('^');
2474                         }
2475                 }
2476                 tputc(u);
2477         }
2478         return n;
2479 }
2480
2481 void
2482 tresize(int col, int row)
2483 {
2484         int i;
2485         int minrow = MIN(row, term.row);
2486         int mincol = MIN(col, term.col);
2487         int *bp;
2488         TCursor c;
2489
2490         if (col < 1 || row < 1) {
2491                 fprintf(stderr,
2492                         "tresize: error resizing to %dx%d\n", col, row);
2493                 return;
2494         }
2495
2496         /*
2497          * slide screen to keep cursor where we expect it -
2498          * tscrollup would work here, but we can optimize to
2499          * memmove because we're freeing the earlier lines
2500          */
2501         for (i = 0; i <= term.c.y - row; i++) {
2502                 free(term.line[i]);
2503                 free(term.alt[i]);
2504         }
2505         /* ensure that both src and dst are not NULL */
2506         if (i > 0) {
2507                 memmove(term.line, term.line + i, row * sizeof(Line));
2508                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2509         }
2510         for (i += row; i < term.row; i++) {
2511                 free(term.line[i]);
2512                 free(term.alt[i]);
2513         }
2514
2515         /* resize to new height */
2516         term.line = xrealloc(term.line, row * sizeof(Line));
2517         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2518         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2519         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2520
2521         /* resize each row to new width, zero-pad if needed */
2522         for (i = 0; i < minrow; i++) {
2523                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2524                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2525         }
2526
2527         /* allocate any new rows */
2528         for (/* i = minrow */; i < row; i++) {
2529                 term.line[i] = xmalloc(col * sizeof(Glyph));
2530                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2531         }
2532         if (col > term.col) {
2533                 bp = term.tabs + term.col;
2534
2535                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2536                 while (--bp > term.tabs && !*bp)
2537                         /* nothing */ ;
2538                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2539                         *bp = 1;
2540         }
2541         /* update terminal size */
2542         term.col = col;
2543         term.row = row;
2544         /* reset scrolling region */
2545         tsetscroll(0, row-1);
2546         /* make use of the LIMIT in tmoveto */
2547         tmoveto(term.c.x, term.c.y);
2548         /* Clearing both screens (it makes dirty all lines) */
2549         c = term.c;
2550         for (i = 0; i < 2; i++) {
2551                 if (mincol < col && 0 < minrow) {
2552                         tclearregion(mincol, 0, col - 1, minrow - 1);
2553                 }
2554                 if (0 < col && minrow < row) {
2555                         tclearregion(0, minrow, col - 1, row - 1);
2556                 }
2557                 tswapscreen();
2558                 tcursor(CURSOR_LOAD);
2559         }
2560         term.c = c;
2561 }
2562
2563 void
2564 resettitle(void)
2565 {
2566         xsettitle(NULL);
2567 }
2568
2569 void
2570 drawregion(int x1, int y1, int x2, int y2)
2571 {
2572         int y;
2573         for (y = y1; y < y2; y++) {
2574                 if (!term.dirty[y])
2575                         continue;
2576
2577                 term.dirty[y] = 0;
2578                 xdrawline(term.line[y], x1, y, x2);
2579         }
2580 }
2581
2582 void
2583 draw(void)
2584 {
2585         int cx = term.c.x;
2586
2587         if (!xstartdraw())
2588                 return;
2589
2590         /* adjust cursor position */
2591         LIMIT(term.ocx, 0, term.col-1);
2592         LIMIT(term.ocy, 0, term.row-1);
2593         if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2594                 term.ocx--;
2595         if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2596                 cx--;
2597
2598         drawregion(0, 0, term.col, term.row);
2599         xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2600                         term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2601         term.ocx = cx, term.ocy = term.c.y;
2602         xfinishdraw();
2603         xximspot(term.ocx, term.ocy);
2604 }
2605
2606 void
2607 redraw(void)
2608 {
2609         tfulldirt();
2610         draw();
2611 }