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