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