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