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