]> git.armaanb.net Git - st.git/blob - st.c
use iswspace()/iswpunct() to find word delimiters
[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;
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                                 char *dec;
1852
1853                                 dec = base64dec(strescseq.args[2]);
1854                                 if (dec) {
1855                                         xsetsel(dec);
1856                                         xclipcopy();
1857                                 } else {
1858                                         fprintf(stderr, "erresc: invalid base64\n");
1859                                 }
1860                         }
1861                         return;
1862                 case 4: /* color set */
1863                         if (narg < 3)
1864                                 break;
1865                         p = strescseq.args[2];
1866                         /* FALLTHROUGH */
1867                 case 104: /* color reset, here p = NULL */
1868                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1869                         if (xsetcolorname(j, p)) {
1870                                 fprintf(stderr, "erresc: invalid color %s\n", p);
1871                         } else {
1872                                 /*
1873                                  * TODO if defaultbg color is changed, borders
1874                                  * are dirty
1875                                  */
1876                                 redraw();
1877                         }
1878                         return;
1879                 }
1880                 break;
1881         case 'k': /* old title set compatibility */
1882                 xsettitle(strescseq.args[0]);
1883                 return;
1884         case 'P': /* DCS -- Device Control String */
1885                 term.mode |= ESC_DCS;
1886         case '_': /* APC -- Application Program Command */
1887         case '^': /* PM -- Privacy Message */
1888                 return;
1889         }
1890
1891         fprintf(stderr, "erresc: unknown str ");
1892         strdump();
1893 }
1894
1895 void
1896 strparse(void)
1897 {
1898         int c;
1899         char *p = strescseq.buf;
1900
1901         strescseq.narg = 0;
1902         strescseq.buf[strescseq.len] = '\0';
1903
1904         if (*p == '\0')
1905                 return;
1906
1907         while (strescseq.narg < STR_ARG_SIZ) {
1908                 strescseq.args[strescseq.narg++] = p;
1909                 while ((c = *p) != ';' && c != '\0')
1910                         ++p;
1911                 if (c == '\0')
1912                         return;
1913                 *p++ = '\0';
1914         }
1915 }
1916
1917 void
1918 strdump(void)
1919 {
1920         int i;
1921         uint c;
1922
1923         fprintf(stderr, "ESC%c", strescseq.type);
1924         for (i = 0; i < strescseq.len; i++) {
1925                 c = strescseq.buf[i] & 0xff;
1926                 if (c == '\0') {
1927                         putc('\n', stderr);
1928                         return;
1929                 } else if (isprint(c)) {
1930                         putc(c, stderr);
1931                 } else if (c == '\n') {
1932                         fprintf(stderr, "(\\n)");
1933                 } else if (c == '\r') {
1934                         fprintf(stderr, "(\\r)");
1935                 } else if (c == 0x1b) {
1936                         fprintf(stderr, "(\\e)");
1937                 } else {
1938                         fprintf(stderr, "(%02x)", c);
1939                 }
1940         }
1941         fprintf(stderr, "ESC\\\n");
1942 }
1943
1944 void
1945 strreset(void)
1946 {
1947         memset(&strescseq, 0, sizeof(strescseq));
1948 }
1949
1950 void
1951 sendbreak(const Arg *arg)
1952 {
1953         if (tcsendbreak(cmdfd, 0))
1954                 perror("Error sending break");
1955 }
1956
1957 void
1958 tprinter(char *s, size_t len)
1959 {
1960         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1961                 perror("Error writing to output file");
1962                 close(iofd);
1963                 iofd = -1;
1964         }
1965 }
1966
1967 void
1968 toggleprinter(const Arg *arg)
1969 {
1970         term.mode ^= MODE_PRINT;
1971 }
1972
1973 void
1974 printscreen(const Arg *arg)
1975 {
1976         tdump();
1977 }
1978
1979 void
1980 printsel(const Arg *arg)
1981 {
1982         tdumpsel();
1983 }
1984
1985 void
1986 tdumpsel(void)
1987 {
1988         char *ptr;
1989
1990         if ((ptr = getsel())) {
1991                 tprinter(ptr, strlen(ptr));
1992                 free(ptr);
1993         }
1994 }
1995
1996 void
1997 tdumpline(int n)
1998 {
1999         char buf[UTF_SIZ];
2000         Glyph *bp, *end;
2001
2002         bp = &term.line[n][0];
2003         end = &bp[MIN(tlinelen(n), term.col) - 1];
2004         if (bp != end || bp->u != ' ') {
2005                 for ( ;bp <= end; ++bp)
2006                         tprinter(buf, utf8encode(bp->u, buf));
2007         }
2008         tprinter("\n", 1);
2009 }
2010
2011 void
2012 tdump(void)
2013 {
2014         int i;
2015
2016         for (i = 0; i < term.row; ++i)
2017                 tdumpline(i);
2018 }
2019
2020 void
2021 tputtab(int n)
2022 {
2023         uint x = term.c.x;
2024
2025         if (n > 0) {
2026                 while (x < term.col && n--)
2027                         for (++x; x < term.col && !term.tabs[x]; ++x)
2028                                 /* nothing */ ;
2029         } else if (n < 0) {
2030                 while (x > 0 && n++)
2031                         for (--x; x > 0 && !term.tabs[x]; --x)
2032                                 /* nothing */ ;
2033         }
2034         term.c.x = LIMIT(x, 0, term.col-1);
2035 }
2036
2037 void
2038 tdefutf8(char ascii)
2039 {
2040         if (ascii == 'G')
2041                 term.mode |= MODE_UTF8;
2042         else if (ascii == '@')
2043                 term.mode &= ~MODE_UTF8;
2044 }
2045
2046 void
2047 tdeftran(char ascii)
2048 {
2049         static char cs[] = "0B";
2050         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2051         char *p;
2052
2053         if ((p = strchr(cs, ascii)) == NULL) {
2054                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2055         } else {
2056                 term.trantbl[term.icharset] = vcs[p - cs];
2057         }
2058 }
2059
2060 void
2061 tdectest(char c)
2062 {
2063         int x, y;
2064
2065         if (c == '8') { /* DEC screen alignment test. */
2066                 for (x = 0; x < term.col; ++x) {
2067                         for (y = 0; y < term.row; ++y)
2068                                 tsetchar('E', &term.c.attr, x, y);
2069                 }
2070         }
2071 }
2072
2073 void
2074 tstrsequence(uchar c)
2075 {
2076         strreset();
2077
2078         switch (c) {
2079         case 0x90:   /* DCS -- Device Control String */
2080                 c = 'P';
2081                 term.esc |= ESC_DCS;
2082                 break;
2083         case 0x9f:   /* APC -- Application Program Command */
2084                 c = '_';
2085                 break;
2086         case 0x9e:   /* PM -- Privacy Message */
2087                 c = '^';
2088                 break;
2089         case 0x9d:   /* OSC -- Operating System Command */
2090                 c = ']';
2091                 break;
2092         }
2093         strescseq.type = c;
2094         term.esc |= ESC_STR;
2095 }
2096
2097 void
2098 tcontrolcode(uchar ascii)
2099 {
2100         switch (ascii) {
2101         case '\t':   /* HT */
2102                 tputtab(1);
2103                 return;
2104         case '\b':   /* BS */
2105                 tmoveto(term.c.x-1, term.c.y);
2106                 return;
2107         case '\r':   /* CR */
2108                 tmoveto(0, term.c.y);
2109                 return;
2110         case '\f':   /* LF */
2111         case '\v':   /* VT */
2112         case '\n':   /* LF */
2113                 /* go to first col if the mode is set */
2114                 tnewline(IS_SET(MODE_CRLF));
2115                 return;
2116         case '\a':   /* BEL */
2117                 if (term.esc & ESC_STR_END) {
2118                         /* backwards compatibility to xterm */
2119                         strhandle();
2120                 } else {
2121                         xbell();
2122                 }
2123                 break;
2124         case '\033': /* ESC */
2125                 csireset();
2126                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2127                 term.esc |= ESC_START;
2128                 return;
2129         case '\016': /* SO (LS1 -- Locking shift 1) */
2130         case '\017': /* SI (LS0 -- Locking shift 0) */
2131                 term.charset = 1 - (ascii - '\016');
2132                 return;
2133         case '\032': /* SUB */
2134                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2135         case '\030': /* CAN */
2136                 csireset();
2137                 break;
2138         case '\005': /* ENQ (IGNORED) */
2139         case '\000': /* NUL (IGNORED) */
2140         case '\021': /* XON (IGNORED) */
2141         case '\023': /* XOFF (IGNORED) */
2142         case 0177:   /* DEL (IGNORED) */
2143                 return;
2144         case 0x80:   /* TODO: PAD */
2145         case 0x81:   /* TODO: HOP */
2146         case 0x82:   /* TODO: BPH */
2147         case 0x83:   /* TODO: NBH */
2148         case 0x84:   /* TODO: IND */
2149                 break;
2150         case 0x85:   /* NEL -- Next line */
2151                 tnewline(1); /* always go to first col */
2152                 break;
2153         case 0x86:   /* TODO: SSA */
2154         case 0x87:   /* TODO: ESA */
2155                 break;
2156         case 0x88:   /* HTS -- Horizontal tab stop */
2157                 term.tabs[term.c.x] = 1;
2158                 break;
2159         case 0x89:   /* TODO: HTJ */
2160         case 0x8a:   /* TODO: VTS */
2161         case 0x8b:   /* TODO: PLD */
2162         case 0x8c:   /* TODO: PLU */
2163         case 0x8d:   /* TODO: RI */
2164         case 0x8e:   /* TODO: SS2 */
2165         case 0x8f:   /* TODO: SS3 */
2166         case 0x91:   /* TODO: PU1 */
2167         case 0x92:   /* TODO: PU2 */
2168         case 0x93:   /* TODO: STS */
2169         case 0x94:   /* TODO: CCH */
2170         case 0x95:   /* TODO: MW */
2171         case 0x96:   /* TODO: SPA */
2172         case 0x97:   /* TODO: EPA */
2173         case 0x98:   /* TODO: SOS */
2174         case 0x99:   /* TODO: SGCI */
2175                 break;
2176         case 0x9a:   /* DECID -- Identify Terminal */
2177                 ttywrite(vtiden, strlen(vtiden), 0);
2178                 break;
2179         case 0x9b:   /* TODO: CSI */
2180         case 0x9c:   /* TODO: ST */
2181                 break;
2182         case 0x90:   /* DCS -- Device Control String */
2183         case 0x9d:   /* OSC -- Operating System Command */
2184         case 0x9e:   /* PM -- Privacy Message */
2185         case 0x9f:   /* APC -- Application Program Command */
2186                 tstrsequence(ascii);
2187                 return;
2188         }
2189         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2190         term.esc &= ~(ESC_STR_END|ESC_STR);
2191 }
2192
2193 /*
2194  * returns 1 when the sequence is finished and it hasn't to read
2195  * more characters for this sequence, otherwise 0
2196  */
2197 int
2198 eschandle(uchar ascii)
2199 {
2200         switch (ascii) {
2201         case '[':
2202                 term.esc |= ESC_CSI;
2203                 return 0;
2204         case '#':
2205                 term.esc |= ESC_TEST;
2206                 return 0;
2207         case '%':
2208                 term.esc |= ESC_UTF8;
2209                 return 0;
2210         case 'P': /* DCS -- Device Control String */
2211         case '_': /* APC -- Application Program Command */
2212         case '^': /* PM -- Privacy Message */
2213         case ']': /* OSC -- Operating System Command */
2214         case 'k': /* old title set compatibility */
2215                 tstrsequence(ascii);
2216                 return 0;
2217         case 'n': /* LS2 -- Locking shift 2 */
2218         case 'o': /* LS3 -- Locking shift 3 */
2219                 term.charset = 2 + (ascii - 'n');
2220                 break;
2221         case '(': /* GZD4 -- set primary charset G0 */
2222         case ')': /* G1D4 -- set secondary charset G1 */
2223         case '*': /* G2D4 -- set tertiary charset G2 */
2224         case '+': /* G3D4 -- set quaternary charset G3 */
2225                 term.icharset = ascii - '(';
2226                 term.esc |= ESC_ALTCHARSET;
2227                 return 0;
2228         case 'D': /* IND -- Linefeed */
2229                 if (term.c.y == term.bot) {
2230                         tscrollup(term.top, 1);
2231                 } else {
2232                         tmoveto(term.c.x, term.c.y+1);
2233                 }
2234                 break;
2235         case 'E': /* NEL -- Next line */
2236                 tnewline(1); /* always go to first col */
2237                 break;
2238         case 'H': /* HTS -- Horizontal tab stop */
2239                 term.tabs[term.c.x] = 1;
2240                 break;
2241         case 'M': /* RI -- Reverse index */
2242                 if (term.c.y == term.top) {
2243                         tscrolldown(term.top, 1);
2244                 } else {
2245                         tmoveto(term.c.x, term.c.y-1);
2246                 }
2247                 break;
2248         case 'Z': /* DECID -- Identify Terminal */
2249                 ttywrite(vtiden, strlen(vtiden), 0);
2250                 break;
2251         case 'c': /* RIS -- Reset to initial state */
2252                 treset();
2253                 resettitle();
2254                 xloadcols();
2255                 break;
2256         case '=': /* DECPAM -- Application keypad */
2257                 xsetmode(1, MODE_APPKEYPAD);
2258                 break;
2259         case '>': /* DECPNM -- Normal keypad */
2260                 xsetmode(0, MODE_APPKEYPAD);
2261                 break;
2262         case '7': /* DECSC -- Save Cursor */
2263                 tcursor(CURSOR_SAVE);
2264                 break;
2265         case '8': /* DECRC -- Restore Cursor */
2266                 tcursor(CURSOR_LOAD);
2267                 break;
2268         case '\\': /* ST -- String Terminator */
2269                 if (term.esc & ESC_STR_END)
2270                         strhandle();
2271                 break;
2272         default:
2273                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2274                         (uchar) ascii, isprint(ascii)? ascii:'.');
2275                 break;
2276         }
2277         return 1;
2278 }
2279
2280 void
2281 tputc(Rune u)
2282 {
2283         char c[UTF_SIZ];
2284         int control;
2285         int width, len;
2286         Glyph *gp;
2287
2288         control = ISCONTROL(u);
2289         if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2290                 c[0] = u;
2291                 width = len = 1;
2292         } else {
2293                 len = utf8encode(u, c);
2294                 if (!control && (width = wcwidth(u)) == -1) {
2295                         memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2296                         width = 1;
2297                 }
2298         }
2299
2300         if (IS_SET(MODE_PRINT))
2301                 tprinter(c, len);
2302
2303         /*
2304          * STR sequence must be checked before anything else
2305          * because it uses all following characters until it
2306          * receives a ESC, a SUB, a ST or any other C1 control
2307          * character.
2308          */
2309         if (term.esc & ESC_STR) {
2310                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2311                    ISCONTROLC1(u)) {
2312                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2313                         if (IS_SET(MODE_SIXEL)) {
2314                                 /* TODO: render sixel */;
2315                                 term.mode &= ~MODE_SIXEL;
2316                                 return;
2317                         }
2318                         term.esc |= ESC_STR_END;
2319                         goto check_control_code;
2320                 }
2321
2322                 if (IS_SET(MODE_SIXEL)) {
2323                         /* TODO: implement sixel mode */
2324                         return;
2325                 }
2326                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2327                         term.mode |= MODE_SIXEL;
2328
2329                 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2330                         /*
2331                          * Here is a bug in terminals. If the user never sends
2332                          * some code to stop the str or esc command, then st
2333                          * will stop responding. But this is better than
2334                          * silently failing with unknown characters. At least
2335                          * then users will report back.
2336                          *
2337                          * In the case users ever get fixed, here is the code:
2338                          */
2339                         /*
2340                          * term.esc = 0;
2341                          * strhandle();
2342                          */
2343                         return;
2344                 }
2345
2346                 memmove(&strescseq.buf[strescseq.len], c, len);
2347                 strescseq.len += len;
2348                 return;
2349         }
2350
2351 check_control_code:
2352         /*
2353          * Actions of control codes must be performed as soon they arrive
2354          * because they can be embedded inside a control sequence, and
2355          * they must not cause conflicts with sequences.
2356          */
2357         if (control) {
2358                 tcontrolcode(u);
2359                 /*
2360                  * control codes are not shown ever
2361                  */
2362                 return;
2363         } else if (term.esc & ESC_START) {
2364                 if (term.esc & ESC_CSI) {
2365                         csiescseq.buf[csiescseq.len++] = u;
2366                         if (BETWEEN(u, 0x40, 0x7E)
2367                                         || csiescseq.len >= \
2368                                         sizeof(csiescseq.buf)-1) {
2369                                 term.esc = 0;
2370                                 csiparse();
2371                                 csihandle();
2372                         }
2373                         return;
2374                 } else if (term.esc & ESC_UTF8) {
2375                         tdefutf8(u);
2376                 } else if (term.esc & ESC_ALTCHARSET) {
2377                         tdeftran(u);
2378                 } else if (term.esc & ESC_TEST) {
2379                         tdectest(u);
2380                 } else {
2381                         if (!eschandle(u))
2382                                 return;
2383                         /* sequence already finished */
2384                 }
2385                 term.esc = 0;
2386                 /*
2387                  * All characters which form part of a sequence are not
2388                  * printed
2389                  */
2390                 return;
2391         }
2392         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2393                 selclear();
2394
2395         gp = &term.line[term.c.y][term.c.x];
2396         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2397                 gp->mode |= ATTR_WRAP;
2398                 tnewline(1);
2399                 gp = &term.line[term.c.y][term.c.x];
2400         }
2401
2402         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2403                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2404
2405         if (term.c.x+width > term.col) {
2406                 tnewline(1);
2407                 gp = &term.line[term.c.y][term.c.x];
2408         }
2409
2410         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2411
2412         if (width == 2) {
2413                 gp->mode |= ATTR_WIDE;
2414                 if (term.c.x+1 < term.col) {
2415                         gp[1].u = '\0';
2416                         gp[1].mode = ATTR_WDUMMY;
2417                 }
2418         }
2419         if (term.c.x+width < term.col) {
2420                 tmoveto(term.c.x+width, term.c.y);
2421         } else {
2422                 term.c.state |= CURSOR_WRAPNEXT;
2423         }
2424 }
2425
2426 int
2427 twrite(const char *buf, int buflen, int show_ctrl)
2428 {
2429         int charsize;
2430         Rune u;
2431         int n;
2432
2433         for (n = 0; n < buflen; n += charsize) {
2434                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2435                         /* process a complete utf8 char */
2436                         charsize = utf8decode(buf + n, &u, buflen - n);
2437                         if (charsize == 0)
2438                                 break;
2439                 } else {
2440                         u = buf[n] & 0xFF;
2441                         charsize = 1;
2442                 }
2443                 if (show_ctrl && ISCONTROL(u)) {
2444                         if (u & 0x80) {
2445                                 u &= 0x7f;
2446                                 tputc('^');
2447                                 tputc('[');
2448                         } else if (u != '\n' && u != '\r' && u != '\t') {
2449                                 u ^= 0x40;
2450                                 tputc('^');
2451                         }
2452                 }
2453                 tputc(u);
2454         }
2455         return n;
2456 }
2457
2458 void
2459 tresize(int col, int row)
2460 {
2461         int i;
2462         int minrow = MIN(row, term.row);
2463         int mincol = MIN(col, term.col);
2464         int *bp;
2465         TCursor c;
2466
2467         if (col < 1 || row < 1) {
2468                 fprintf(stderr,
2469                         "tresize: error resizing to %dx%d\n", col, row);
2470                 return;
2471         }
2472
2473         /*
2474          * slide screen to keep cursor where we expect it -
2475          * tscrollup would work here, but we can optimize to
2476          * memmove because we're freeing the earlier lines
2477          */
2478         for (i = 0; i <= term.c.y - row; i++) {
2479                 free(term.line[i]);
2480                 free(term.alt[i]);
2481         }
2482         /* ensure that both src and dst are not NULL */
2483         if (i > 0) {
2484                 memmove(term.line, term.line + i, row * sizeof(Line));
2485                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2486         }
2487         for (i += row; i < term.row; i++) {
2488                 free(term.line[i]);
2489                 free(term.alt[i]);
2490         }
2491
2492         /* resize to new height */
2493         term.line = xrealloc(term.line, row * sizeof(Line));
2494         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2495         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2496         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2497
2498         /* resize each row to new width, zero-pad if needed */
2499         for (i = 0; i < minrow; i++) {
2500                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2501                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2502         }
2503
2504         /* allocate any new rows */
2505         for (/* i = minrow */; i < row; i++) {
2506                 term.line[i] = xmalloc(col * sizeof(Glyph));
2507                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2508         }
2509         if (col > term.col) {
2510                 bp = term.tabs + term.col;
2511
2512                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2513                 while (--bp > term.tabs && !*bp)
2514                         /* nothing */ ;
2515                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2516                         *bp = 1;
2517         }
2518         /* update terminal size */
2519         term.col = col;
2520         term.row = row;
2521         /* reset scrolling region */
2522         tsetscroll(0, row-1);
2523         /* make use of the LIMIT in tmoveto */
2524         tmoveto(term.c.x, term.c.y);
2525         /* Clearing both screens (it makes dirty all lines) */
2526         c = term.c;
2527         for (i = 0; i < 2; i++) {
2528                 if (mincol < col && 0 < minrow) {
2529                         tclearregion(mincol, 0, col - 1, minrow - 1);
2530                 }
2531                 if (0 < col && minrow < row) {
2532                         tclearregion(0, minrow, col - 1, row - 1);
2533                 }
2534                 tswapscreen();
2535                 tcursor(CURSOR_LOAD);
2536         }
2537         term.c = c;
2538 }
2539
2540 void
2541 resettitle(void)
2542 {
2543         xsettitle(NULL);
2544 }
2545
2546 void
2547 drawregion(int x1, int y1, int x2, int y2)
2548 {
2549         int y;
2550         for (y = y1; y < y2; y++) {
2551                 if (!term.dirty[y])
2552                         continue;
2553
2554                 term.dirty[y] = 0;
2555                 xdrawline(term.line[y], x1, y, x2);
2556         }
2557 }
2558
2559 void
2560 draw(void)
2561 {
2562         int cx = term.c.x;
2563
2564         if (!xstartdraw())
2565                 return;
2566
2567         /* adjust cursor position */
2568         LIMIT(term.ocx, 0, term.col-1);
2569         LIMIT(term.ocy, 0, term.row-1);
2570         if (term.line[term.ocy][term.ocx].mode & ATTR_WDUMMY)
2571                 term.ocx--;
2572         if (term.line[term.c.y][cx].mode & ATTR_WDUMMY)
2573                 cx--;
2574
2575         drawregion(0, 0, term.col, term.row);
2576         xdrawcursor(cx, term.c.y, term.line[term.c.y][cx],
2577                         term.ocx, term.ocy, term.line[term.ocy][term.ocx]);
2578         term.ocx = cx, term.ocy = term.c.y;
2579         xfinishdraw();
2580         xximspot(term.ocx, term.ocy);
2581 }
2582
2583 void
2584 redraw(void)
2585 {
2586         tfulldirt();
2587         draw();
2588 }