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