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