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