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