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