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