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