]> git.armaanb.net Git - st.git/blob - st.c
Clean up #includes
[st.git] / st.c
1 /* See LICENSE for license details. */
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <pwd.h>
7 #include <stdarg.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <signal.h>
12 #include <sys/ioctl.h>
13 #include <sys/select.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
17 #include <wchar.h>
18
19 #include "st.h"
20 #include "win.h"
21
22 #if   defined(__linux)
23  #include <pty.h>
24 #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__)
25  #include <util.h>
26 #elif defined(__FreeBSD__) || defined(__DragonFly__)
27  #include <libutil.h>
28 #endif
29
30 /* Arbitrary sizes */
31 #define UTF_INVALID   0xFFFD
32 #define UTF_SIZ       4
33 #define ESC_BUF_SIZ   (128*UTF_SIZ)
34 #define ESC_ARG_SIZ   16
35 #define STR_BUF_SIZ   ESC_BUF_SIZ
36 #define STR_ARG_SIZ   ESC_ARG_SIZ
37
38 /* macros */
39 #define IS_SET(flag)            ((term.mode & (flag)) != 0)
40 #define NUMMAXLEN(x)            ((int)(sizeof(x) * 2.56 + 0.5) + 1)
41 #define ISCONTROLC0(c)          (BETWEEN(c, 0, 0x1f) || (c) == '\177')
42 #define ISCONTROLC1(c)          (BETWEEN(c, 0x80, 0x9f))
43 #define ISCONTROL(c)            (ISCONTROLC0(c) || ISCONTROLC1(c))
44 #define ISDELIM(u)              (utf8strchr(worddelimiters, u) != NULL)
45
46 /* constants */
47 #define ISO14755CMD             "dmenu -w \"$WINDOWID\" -p codepoint: </dev/null"
48
49 enum term_mode {
50         MODE_WRAP        = 1 << 0,
51         MODE_INSERT      = 1 << 1,
52         MODE_ALTSCREEN   = 1 << 2,
53         MODE_CRLF        = 1 << 3,
54         MODE_ECHO        = 1 << 4,
55         MODE_PRINT       = 1 << 5,
56         MODE_UTF8        = 1 << 6,
57         MODE_SIXEL       = 1 << 7,
58 };
59
60 enum cursor_movement {
61         CURSOR_SAVE,
62         CURSOR_LOAD
63 };
64
65 enum cursor_state {
66         CURSOR_DEFAULT  = 0,
67         CURSOR_WRAPNEXT = 1,
68         CURSOR_ORIGIN   = 2
69 };
70
71 enum charset {
72         CS_GRAPHIC0,
73         CS_GRAPHIC1,
74         CS_UK,
75         CS_USA,
76         CS_MULTI,
77         CS_GER,
78         CS_FIN
79 };
80
81 enum escape_state {
82         ESC_START      = 1,
83         ESC_CSI        = 2,
84         ESC_STR        = 4,  /* OSC, PM, APC */
85         ESC_ALTCHARSET = 8,
86         ESC_STR_END    = 16, /* a final string was encountered */
87         ESC_TEST       = 32, /* Enter in test mode */
88         ESC_UTF8       = 64,
89         ESC_DCS        =128,
90 };
91
92 typedef struct {
93         Glyph attr; /* current char attributes */
94         int x;
95         int y;
96         char state;
97 } TCursor;
98
99 typedef struct {
100         int mode;
101         int type;
102         int snap;
103         /*
104          * Selection variables:
105          * nb – normalized coordinates of the beginning of the selection
106          * ne – normalized coordinates of the end of the selection
107          * ob – original coordinates of the beginning of the selection
108          * oe – original coordinates of the end of the selection
109          */
110         struct {
111                 int x, y;
112         } nb, ne, ob, oe;
113
114         int alt;
115 } Selection;
116
117 /* Internal representation of the screen */
118 typedef struct {
119         int row;      /* nb row */
120         int col;      /* nb col */
121         Line *line;   /* screen */
122         Line *alt;    /* alternate screen */
123         int *dirty;   /* dirtyness of lines */
124         TCursor c;    /* cursor */
125         int ocx;      /* old cursor col */
126         int ocy;      /* old cursor row */
127         int top;      /* top    scroll limit */
128         int bot;      /* bottom scroll limit */
129         int mode;     /* terminal mode flags */
130         int esc;      /* escape state flags */
131         char trantbl[4]; /* charset table translation */
132         int charset;  /* current charset */
133         int icharset; /* selected charset for sequence */
134         int *tabs;
135 } Term;
136
137 /* CSI Escape sequence structs */
138 /* ESC '[' [[ [<priv>] <arg> [;]] <mode> [<mode>]] */
139 typedef struct {
140         char buf[ESC_BUF_SIZ]; /* raw string */
141         int len;               /* raw string length */
142         char priv;
143         int arg[ESC_ARG_SIZ];
144         int narg;              /* nb of args */
145         char mode[2];
146 } CSIEscape;
147
148 /* STR Escape sequence structs */
149 /* ESC type [[ [<priv>] <arg> [;]] <mode>] ESC '\' */
150 typedef struct {
151         char type;             /* ESC type ... */
152         char buf[STR_BUF_SIZ]; /* raw string */
153         int len;               /* raw string length */
154         char *args[STR_ARG_SIZ];
155         int narg;              /* nb of args */
156 } STREscape;
157
158 static void execsh(char *, char **);
159 static void stty(char **);
160 static void sigchld(int);
161 static void ttywriteraw(const char *, size_t);
162
163 static void csidump(void);
164 static void csihandle(void);
165 static void csiparse(void);
166 static void csireset(void);
167 static int eschandle(uchar);
168 static void strdump(void);
169 static void strhandle(void);
170 static void strparse(void);
171 static void strreset(void);
172
173 static void tprinter(char *, size_t);
174 static void tdumpsel(void);
175 static void tdumpline(int);
176 static void tdump(void);
177 static void tclearregion(int, int, int, int);
178 static void tcursor(int);
179 static void tdeletechar(int);
180 static void tdeleteline(int);
181 static void tinsertblank(int);
182 static void tinsertblankline(int);
183 static int tlinelen(int);
184 static void tmoveto(int, int);
185 static void tmoveato(int, int);
186 static void tnewline(int);
187 static void tputtab(int);
188 static void tputc(Rune);
189 static void treset(void);
190 static void tscrollup(int, int);
191 static void tscrolldown(int, int);
192 static void tsetattr(int *, int);
193 static void tsetchar(Rune, Glyph *, int, int);
194 static void tsetdirt(int, int);
195 static void tsetscroll(int, int);
196 static void tswapscreen(void);
197 static void tsetmode(int, int, int *, int);
198 static int twrite(const char *, int, int);
199 static void tfulldirt(void);
200 static void tcontrolcode(uchar );
201 static void tdectest(char );
202 static void tdefutf8(char);
203 static int32_t tdefcolor(int *, int *, int);
204 static void tdeftran(char);
205 static void tstrsequence(uchar);
206
207 static void drawregion(int, int, int, int);
208
209 static void selnormalize(void);
210 static void selscroll(int, int);
211 static void selsnap(int *, int *, int);
212
213 static size_t utf8decode(const char *, Rune *, size_t);
214 static Rune utf8decodebyte(char, size_t *);
215 static char utf8encodebyte(Rune, size_t);
216 static char *utf8strchr(char *, Rune);
217 static size_t utf8validate(Rune *, size_t);
218
219 static char *base64dec(const char *);
220 static char base64dec_getc(const char **);
221
222 static ssize_t xwrite(int, const char *, size_t);
223
224 /* Globals */
225 static Term term;
226 static Selection sel;
227 static CSIEscape csiescseq;
228 static STREscape strescseq;
229 static int iofd = 1;
230 static int cmdfd;
231 static pid_t pid;
232
233 static uchar utfbyte[UTF_SIZ + 1] = {0x80,    0, 0xC0, 0xE0, 0xF0};
234 static uchar utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
235 static Rune utfmin[UTF_SIZ + 1] = {       0,    0,  0x80,  0x800,  0x10000};
236 static Rune utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
237
238 ssize_t
239 xwrite(int fd, const char *s, size_t len)
240 {
241         size_t aux = len;
242         ssize_t r;
243
244         while (len > 0) {
245                 r = write(fd, s, len);
246                 if (r < 0)
247                         return r;
248                 len -= r;
249                 s += r;
250         }
251
252         return aux;
253 }
254
255 void *
256 xmalloc(size_t len)
257 {
258         void *p = malloc(len);
259
260         if (!p)
261                 die("Out of memory\n");
262
263         return p;
264 }
265
266 void *
267 xrealloc(void *p, size_t len)
268 {
269         if ((p = realloc(p, len)) == NULL)
270                 die("Out of memory\n");
271
272         return p;
273 }
274
275 char *
276 xstrdup(char *s)
277 {
278         if ((s = strdup(s)) == NULL)
279                 die("Out of memory\n");
280
281         return s;
282 }
283
284 size_t
285 utf8decode(const char *c, Rune *u, size_t clen)
286 {
287         size_t i, j, len, type;
288         Rune udecoded;
289
290         *u = UTF_INVALID;
291         if (!clen)
292                 return 0;
293         udecoded = utf8decodebyte(c[0], &len);
294         if (!BETWEEN(len, 1, UTF_SIZ))
295                 return 1;
296         for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
297                 udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
298                 if (type != 0)
299                         return j;
300         }
301         if (j < len)
302                 return 0;
303         *u = udecoded;
304         utf8validate(u, len);
305
306         return len;
307 }
308
309 Rune
310 utf8decodebyte(char c, size_t *i)
311 {
312         for (*i = 0; *i < LEN(utfmask); ++(*i))
313                 if (((uchar)c & utfmask[*i]) == utfbyte[*i])
314                         return (uchar)c & ~utfmask[*i];
315
316         return 0;
317 }
318
319 size_t
320 utf8encode(Rune u, char *c)
321 {
322         size_t len, i;
323
324         len = utf8validate(&u, 0);
325         if (len > UTF_SIZ)
326                 return 0;
327
328         for (i = len - 1; i != 0; --i) {
329                 c[i] = utf8encodebyte(u, 0);
330                 u >>= 6;
331         }
332         c[0] = utf8encodebyte(u, len);
333
334         return len;
335 }
336
337 char
338 utf8encodebyte(Rune u, size_t i)
339 {
340         return utfbyte[i] | (u & ~utfmask[i]);
341 }
342
343 char *
344 utf8strchr(char *s, Rune u)
345 {
346         Rune r;
347         size_t i, j, len;
348
349         len = strlen(s);
350         for (i = 0, j = 0; i < len; i += j) {
351                 if (!(j = utf8decode(&s[i], &r, len - i)))
352                         break;
353                 if (r == u)
354                         return &(s[i]);
355         }
356
357         return NULL;
358 }
359
360 size_t
361 utf8validate(Rune *u, size_t i)
362 {
363         if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
364                 *u = UTF_INVALID;
365         for (i = 1; *u > utfmax[i]; ++i)
366                 ;
367
368         return i;
369 }
370
371 static const char base64_digits[] = {
372         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
373         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0,
374         63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, -1, 0, 0, 0, 0, 1,
375         2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
376         22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34,
377         35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0,
378         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
379         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
380         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
381         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
382         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
383         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
384 };
385
386 char
387 base64dec_getc(const char **src)
388 {
389         while (**src && !isprint(**src)) (*src)++;
390         return *((*src)++);
391 }
392
393 char *
394 base64dec(const char *src)
395 {
396         size_t in_len = strlen(src);
397         char *result, *dst;
398
399         if (in_len % 4)
400                 in_len += 4 - (in_len % 4);
401         result = dst = xmalloc(in_len / 4 * 3 + 1);
402         while (*src) {
403                 int a = base64_digits[(unsigned char) base64dec_getc(&src)];
404                 int b = base64_digits[(unsigned char) base64dec_getc(&src)];
405                 int c = base64_digits[(unsigned char) base64dec_getc(&src)];
406                 int d = base64_digits[(unsigned char) base64dec_getc(&src)];
407
408                 *dst++ = (a << 2) | ((b & 0x30) >> 4);
409                 if (c == -1)
410                         break;
411                 *dst++ = ((b & 0x0f) << 4) | ((c & 0x3c) >> 2);
412                 if (d == -1)
413                         break;
414                 *dst++ = ((c & 0x03) << 6) | d;
415         }
416         *dst = '\0';
417         return result;
418 }
419
420 void
421 selinit(void)
422 {
423         sel.mode = SEL_IDLE;
424         sel.snap = 0;
425         sel.ob.x = -1;
426 }
427
428 int
429 tlinelen(int y)
430 {
431         int i = term.col;
432
433         if (term.line[y][i - 1].mode & ATTR_WRAP)
434                 return i;
435
436         while (i > 0 && term.line[y][i - 1].u == ' ')
437                 --i;
438
439         return i;
440 }
441
442 void
443 selstart(int col, int row, int snap)
444 {
445         selclear();
446         sel.mode = SEL_EMPTY;
447         sel.type = SEL_REGULAR;
448         sel.snap = snap;
449         sel.oe.x = sel.ob.x = col;
450         sel.oe.y = sel.ob.y = row;
451         selnormalize();
452
453         if (sel.snap != 0)
454                 sel.mode = SEL_READY;
455         tsetdirt(sel.nb.y, sel.ne.y);
456 }
457
458 void
459 selextend(int col, int row, int type, int done)
460 {
461         int oldey, oldex, oldsby, oldsey, oldtype;
462
463         if (!sel.mode)
464                 return;
465         if (done && sel.mode == SEL_EMPTY) {
466                 selclear();
467                 return;
468         }
469
470         oldey = sel.oe.y;
471         oldex = sel.oe.x;
472         oldsby = sel.nb.y;
473         oldsey = sel.ne.y;
474         oldtype = sel.type;
475
476         sel.alt = IS_SET(MODE_ALTSCREEN);
477         sel.oe.x = col;
478         sel.oe.y = row;
479         selnormalize();
480         sel.type = type;
481
482         if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type)
483                 tsetdirt(MIN(sel.nb.y, oldsby), MAX(sel.ne.y, oldsey));
484
485         sel.mode = done ? SEL_IDLE : SEL_READY;
486 }
487
488 void
489 selnormalize(void)
490 {
491         int i;
492
493         if (sel.type == SEL_REGULAR && sel.ob.y != sel.oe.y) {
494                 sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
495                 sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
496         } else {
497                 sel.nb.x = MIN(sel.ob.x, sel.oe.x);
498                 sel.ne.x = MAX(sel.ob.x, sel.oe.x);
499         }
500         sel.nb.y = MIN(sel.ob.y, sel.oe.y);
501         sel.ne.y = MAX(sel.ob.y, sel.oe.y);
502
503         selsnap(&sel.nb.x, &sel.nb.y, -1);
504         selsnap(&sel.ne.x, &sel.ne.y, +1);
505
506         /* expand selection over line breaks */
507         if (sel.type == SEL_RECTANGULAR)
508                 return;
509         i = tlinelen(sel.nb.y);
510         if (i < sel.nb.x)
511                 sel.nb.x = i;
512         if (tlinelen(sel.ne.y) <= sel.ne.x)
513                 sel.ne.x = term.col - 1;
514 }
515
516 int
517 selected(int x, int y)
518 {
519         if (sel.mode == SEL_EMPTY || sel.ob.x == -1 ||
520                         sel.alt != IS_SET(MODE_ALTSCREEN))
521                 return 0;
522
523         if (sel.type == SEL_RECTANGULAR)
524                 return BETWEEN(y, sel.nb.y, sel.ne.y)
525                     && BETWEEN(x, sel.nb.x, sel.ne.x);
526
527         return BETWEEN(y, sel.nb.y, sel.ne.y)
528             && (y != sel.nb.y || x >= sel.nb.x)
529             && (y != sel.ne.y || x <= sel.ne.x);
530 }
531
532 void
533 selsnap(int *x, int *y, int direction)
534 {
535         int newx, newy, xt, yt;
536         int delim, prevdelim;
537         Glyph *gp, *prevgp;
538
539         switch (sel.snap) {
540         case SNAP_WORD:
541                 /*
542                  * Snap around if the word wraps around at the end or
543                  * beginning of a line.
544                  */
545                 prevgp = &term.line[*y][*x];
546                 prevdelim = ISDELIM(prevgp->u);
547                 for (;;) {
548                         newx = *x + direction;
549                         newy = *y;
550                         if (!BETWEEN(newx, 0, term.col - 1)) {
551                                 newy += direction;
552                                 newx = (newx + term.col) % term.col;
553                                 if (!BETWEEN(newy, 0, term.row - 1))
554                                         break;
555
556                                 if (direction > 0)
557                                         yt = *y, xt = *x;
558                                 else
559                                         yt = newy, xt = newx;
560                                 if (!(term.line[yt][xt].mode & ATTR_WRAP))
561                                         break;
562                         }
563
564                         if (newx >= tlinelen(newy))
565                                 break;
566
567                         gp = &term.line[newy][newx];
568                         delim = ISDELIM(gp->u);
569                         if (!(gp->mode & ATTR_WDUMMY) && (delim != prevdelim
570                                         || (delim && gp->u != prevgp->u)))
571                                 break;
572
573                         *x = newx;
574                         *y = newy;
575                         prevgp = gp;
576                         prevdelim = delim;
577                 }
578                 break;
579         case SNAP_LINE:
580                 /*
581                  * Snap around if the the previous line or the current one
582                  * has set ATTR_WRAP at its end. Then the whole next or
583                  * previous line will be selected.
584                  */
585                 *x = (direction < 0) ? 0 : term.col - 1;
586                 if (direction < 0) {
587                         for (; *y > 0; *y += direction) {
588                                 if (!(term.line[*y-1][term.col-1].mode
589                                                 & ATTR_WRAP)) {
590                                         break;
591                                 }
592                         }
593                 } else if (direction > 0) {
594                         for (; *y < term.row-1; *y += direction) {
595                                 if (!(term.line[*y][term.col-1].mode
596                                                 & ATTR_WRAP)) {
597                                         break;
598                                 }
599                         }
600                 }
601                 break;
602         }
603 }
604
605 char *
606 getsel(void)
607 {
608         char *str, *ptr;
609         int y, bufsize, lastx, linelen;
610         Glyph *gp, *last;
611
612         if (sel.ob.x == -1)
613                 return NULL;
614
615         bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
616         ptr = str = xmalloc(bufsize);
617
618         /* append every set & selected glyph to the selection */
619         for (y = sel.nb.y; y <= sel.ne.y; y++) {
620                 if ((linelen = tlinelen(y)) == 0) {
621                         *ptr++ = '\n';
622                         continue;
623                 }
624
625                 if (sel.type == SEL_RECTANGULAR) {
626                         gp = &term.line[y][sel.nb.x];
627                         lastx = sel.ne.x;
628                 } else {
629                         gp = &term.line[y][sel.nb.y == y ? sel.nb.x : 0];
630                         lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
631                 }
632                 last = &term.line[y][MIN(lastx, linelen-1)];
633                 while (last >= gp && last->u == ' ')
634                         --last;
635
636                 for ( ; gp <= last; ++gp) {
637                         if (gp->mode & ATTR_WDUMMY)
638                                 continue;
639
640                         ptr += utf8encode(gp->u, ptr);
641                 }
642
643                 /*
644                  * Copy and pasting of line endings is inconsistent
645                  * in the inconsistent terminal and GUI world.
646                  * The best solution seems like to produce '\n' when
647                  * something is copied from st and convert '\n' to
648                  * '\r', when something to be pasted is received by
649                  * st.
650                  * FIXME: Fix the computer world.
651                  */
652                 if ((y < sel.ne.y || lastx >= linelen) && !(last->mode & ATTR_WRAP))
653                         *ptr++ = '\n';
654         }
655         *ptr = 0;
656         return str;
657 }
658
659 void
660 selclear(void)
661 {
662         if (sel.ob.x == -1)
663                 return;
664         sel.mode = SEL_IDLE;
665         sel.ob.x = -1;
666         tsetdirt(sel.nb.y, sel.ne.y);
667 }
668
669 void
670 die(const char *errstr, ...)
671 {
672         va_list ap;
673
674         va_start(ap, errstr);
675         vfprintf(stderr, errstr, ap);
676         va_end(ap);
677         exit(1);
678 }
679
680 void
681 execsh(char *cmd, char **args)
682 {
683         char *sh, *prog;
684         const struct passwd *pw;
685
686         errno = 0;
687         if ((pw = getpwuid(getuid())) == NULL) {
688                 if (errno)
689                         die("getpwuid:%s\n", strerror(errno));
690                 else
691                         die("who are you?\n");
692         }
693
694         if ((sh = getenv("SHELL")) == NULL)
695                 sh = (pw->pw_shell[0]) ? pw->pw_shell : cmd;
696
697         if (args)
698                 prog = args[0];
699         else if (utmp)
700                 prog = utmp;
701         else
702                 prog = sh;
703         DEFAULT(args, ((char *[]) {prog, NULL}));
704
705         unsetenv("COLUMNS");
706         unsetenv("LINES");
707         unsetenv("TERMCAP");
708         setenv("LOGNAME", pw->pw_name, 1);
709         setenv("USER", pw->pw_name, 1);
710         setenv("SHELL", sh, 1);
711         setenv("HOME", pw->pw_dir, 1);
712         setenv("TERM", termname, 1);
713
714         signal(SIGCHLD, SIG_DFL);
715         signal(SIGHUP, SIG_DFL);
716         signal(SIGINT, SIG_DFL);
717         signal(SIGQUIT, SIG_DFL);
718         signal(SIGTERM, SIG_DFL);
719         signal(SIGALRM, SIG_DFL);
720
721         execvp(prog, args);
722         _exit(1);
723 }
724
725 void
726 sigchld(int a)
727 {
728         int stat;
729         pid_t p;
730
731         if ((p = waitpid(pid, &stat, WNOHANG)) < 0)
732                 die("Waiting for pid %hd failed: %s\n", pid, strerror(errno));
733
734         if (pid != p)
735                 return;
736
737         if (!WIFEXITED(stat) || WEXITSTATUS(stat))
738                 die("child finished with error '%d'\n", stat);
739         exit(0);
740 }
741
742
743 void
744 stty(char **args)
745 {
746         char cmd[_POSIX_ARG_MAX], **p, *q, *s;
747         size_t n, siz;
748
749         if ((n = strlen(stty_args)) > sizeof(cmd)-1)
750                 die("incorrect stty parameters\n");
751         memcpy(cmd, stty_args, n);
752         q = cmd + n;
753         siz = sizeof(cmd) - n;
754         for (p = args; p && (s = *p); ++p) {
755                 if ((n = strlen(s)) > siz-1)
756                         die("stty parameter length too long\n");
757                 *q++ = ' ';
758                 memcpy(q, s, n);
759                 q += n;
760                 siz -= n + 1;
761         }
762         *q = '\0';
763         if (system(cmd) != 0)
764             perror("Couldn't call stty");
765 }
766
767 int
768 ttynew(char *line, char *cmd, char *out, char **args)
769 {
770         int m, s;
771
772         if (out) {
773                 term.mode |= MODE_PRINT;
774                 iofd = (!strcmp(out, "-")) ?
775                           1 : open(out, O_WRONLY | O_CREAT, 0666);
776                 if (iofd < 0) {
777                         fprintf(stderr, "Error opening %s:%s\n",
778                                 out, strerror(errno));
779                 }
780         }
781
782         if (line) {
783                 if ((cmdfd = open(line, O_RDWR)) < 0)
784                         die("open line failed: %s\n", strerror(errno));
785                 dup2(cmdfd, 0);
786                 stty(args);
787                 return cmdfd;
788         }
789
790         /* seems to work fine on linux, openbsd and freebsd */
791         if (openpty(&m, &s, NULL, NULL, NULL) < 0)
792                 die("openpty failed: %s\n", strerror(errno));
793
794         switch (pid = fork()) {
795         case -1:
796                 die("fork failed\n");
797                 break;
798         case 0:
799                 close(iofd);
800                 setsid(); /* create a new process group */
801                 dup2(s, 0);
802                 dup2(s, 1);
803                 dup2(s, 2);
804                 if (ioctl(s, TIOCSCTTY, NULL) < 0)
805                         die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
806                 close(s);
807                 close(m);
808                 execsh(cmd, args);
809                 break;
810         default:
811                 close(s);
812                 cmdfd = m;
813                 signal(SIGCHLD, sigchld);
814                 break;
815         }
816         return cmdfd;
817 }
818
819 size_t
820 ttyread(void)
821 {
822         static char buf[BUFSIZ];
823         static int buflen = 0;
824         int written;
825         int ret;
826
827         /* append read bytes to unprocessed bytes */
828         if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
829                 die("Couldn't read from shell: %s\n", strerror(errno));
830         buflen += ret;
831
832         written = twrite(buf, buflen, 0);
833         buflen -= written;
834         /* keep any uncomplete utf8 char for the next call */
835         if (buflen > 0)
836                 memmove(buf, buf + written, buflen);
837
838         return ret;
839 }
840
841 void
842 ttywrite(const char *s, size_t n, int may_echo)
843 {
844         const char *next;
845
846         if (may_echo && IS_SET(MODE_ECHO))
847                 twrite(s, n, 1);
848
849         if (!IS_SET(MODE_CRLF)) {
850                 ttywriteraw(s, n);
851                 return;
852         }
853
854         /* This is similar to how the kernel handles ONLCR for ttys */
855         while (n > 0) {
856                 if (*s == '\r') {
857                         next = s + 1;
858                         ttywriteraw("\r\n", 2);
859                 } else {
860                         next = memchr(s, '\r', n);
861                         DEFAULT(next, s + n);
862                         ttywriteraw(s, next - s);
863                 }
864                 n -= next - s;
865                 s = next;
866         }
867 }
868
869 void
870 ttywriteraw(const char *s, size_t n)
871 {
872         fd_set wfd, rfd;
873         ssize_t r;
874         size_t lim = 256;
875
876         /*
877          * Remember that we are using a pty, which might be a modem line.
878          * Writing too much will clog the line. That's why we are doing this
879          * dance.
880          * FIXME: Migrate the world to Plan 9.
881          */
882         while (n > 0) {
883                 FD_ZERO(&wfd);
884                 FD_ZERO(&rfd);
885                 FD_SET(cmdfd, &wfd);
886                 FD_SET(cmdfd, &rfd);
887
888                 /* Check if we can write. */
889                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
890                         if (errno == EINTR)
891                                 continue;
892                         die("select failed: %s\n", strerror(errno));
893                 }
894                 if (FD_ISSET(cmdfd, &wfd)) {
895                         /*
896                          * Only write the bytes written by ttywrite() or the
897                          * default of 256. This seems to be a reasonable value
898                          * for a serial line. Bigger values might clog the I/O.
899                          */
900                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
901                                 goto write_error;
902                         if (r < n) {
903                                 /*
904                                  * We weren't able to write out everything.
905                                  * This means the buffer is getting full
906                                  * again. Empty it.
907                                  */
908                                 if (n < lim)
909                                         lim = ttyread();
910                                 n -= r;
911                                 s += r;
912                         } else {
913                                 /* All bytes have been written. */
914                                 break;
915                         }
916                 }
917                 if (FD_ISSET(cmdfd, &rfd))
918                         lim = ttyread();
919         }
920         return;
921
922 write_error:
923         die("write error on tty: %s\n", strerror(errno));
924 }
925
926 void
927 ttyresize(int tw, int th)
928 {
929         struct winsize w;
930
931         w.ws_row = term.row;
932         w.ws_col = term.col;
933         w.ws_xpixel = tw;
934         w.ws_ypixel = th;
935         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
936                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
937 }
938
939 void
940 ttyhangup()
941 {
942         /* Send SIGHUP to shell */
943         kill(pid, SIGHUP);
944 }
945
946 int
947 tattrset(int attr)
948 {
949         int i, j;
950
951         for (i = 0; i < term.row-1; i++) {
952                 for (j = 0; j < term.col-1; j++) {
953                         if (term.line[i][j].mode & attr)
954                                 return 1;
955                 }
956         }
957
958         return 0;
959 }
960
961 void
962 tsetdirt(int top, int bot)
963 {
964         int i;
965
966         LIMIT(top, 0, term.row-1);
967         LIMIT(bot, 0, term.row-1);
968
969         for (i = top; i <= bot; i++)
970                 term.dirty[i] = 1;
971 }
972
973 void
974 tsetdirtattr(int attr)
975 {
976         int i, j;
977
978         for (i = 0; i < term.row-1; i++) {
979                 for (j = 0; j < term.col-1; j++) {
980                         if (term.line[i][j].mode & attr) {
981                                 tsetdirt(i, i);
982                                 break;
983                         }
984                 }
985         }
986 }
987
988 void
989 tfulldirt(void)
990 {
991         tsetdirt(0, term.row-1);
992 }
993
994 void
995 tcursor(int mode)
996 {
997         static TCursor c[2];
998         int alt = IS_SET(MODE_ALTSCREEN);
999
1000         if (mode == CURSOR_SAVE) {
1001                 c[alt] = term.c;
1002         } else if (mode == CURSOR_LOAD) {
1003                 term.c = c[alt];
1004                 tmoveto(c[alt].x, c[alt].y);
1005         }
1006 }
1007
1008 void
1009 treset(void)
1010 {
1011         uint i;
1012
1013         term.c = (TCursor){{
1014                 .mode = ATTR_NULL,
1015                 .fg = defaultfg,
1016                 .bg = defaultbg
1017         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
1018
1019         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1020         for (i = tabspaces; i < term.col; i += tabspaces)
1021                 term.tabs[i] = 1;
1022         term.top = 0;
1023         term.bot = term.row - 1;
1024         term.mode = MODE_WRAP|MODE_UTF8;
1025         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
1026         term.charset = 0;
1027
1028         for (i = 0; i < 2; i++) {
1029                 tmoveto(0, 0);
1030                 tcursor(CURSOR_SAVE);
1031                 tclearregion(0, 0, term.col-1, term.row-1);
1032                 tswapscreen();
1033         }
1034 }
1035
1036 void
1037 tnew(int col, int row)
1038 {
1039         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
1040         tresize(col, row);
1041         treset();
1042 }
1043
1044 void
1045 tswapscreen(void)
1046 {
1047         Line *tmp = term.line;
1048
1049         term.line = term.alt;
1050         term.alt = tmp;
1051         term.mode ^= MODE_ALTSCREEN;
1052         tfulldirt();
1053 }
1054
1055 void
1056 tscrolldown(int orig, int n)
1057 {
1058         int i;
1059         Line temp;
1060
1061         LIMIT(n, 0, term.bot-orig+1);
1062
1063         tsetdirt(orig, term.bot-n);
1064         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1065
1066         for (i = term.bot; i >= orig+n; i--) {
1067                 temp = term.line[i];
1068                 term.line[i] = term.line[i-n];
1069                 term.line[i-n] = temp;
1070         }
1071
1072         selscroll(orig, n);
1073 }
1074
1075 void
1076 tscrollup(int orig, int n)
1077 {
1078         int i;
1079         Line temp;
1080
1081         LIMIT(n, 0, term.bot-orig+1);
1082
1083         tclearregion(0, orig, term.col-1, orig+n-1);
1084         tsetdirt(orig+n, term.bot);
1085
1086         for (i = orig; i <= term.bot-n; i++) {
1087                 temp = term.line[i];
1088                 term.line[i] = term.line[i+n];
1089                 term.line[i+n] = temp;
1090         }
1091
1092         selscroll(orig, -n);
1093 }
1094
1095 void
1096 selscroll(int orig, int n)
1097 {
1098         if (sel.ob.x == -1)
1099                 return;
1100
1101         if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1102                 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1103                         selclear();
1104                         return;
1105                 }
1106                 if (sel.type == SEL_RECTANGULAR) {
1107                         if (sel.ob.y < term.top)
1108                                 sel.ob.y = term.top;
1109                         if (sel.oe.y > term.bot)
1110                                 sel.oe.y = term.bot;
1111                 } else {
1112                         if (sel.ob.y < term.top) {
1113                                 sel.ob.y = term.top;
1114                                 sel.ob.x = 0;
1115                         }
1116                         if (sel.oe.y > term.bot) {
1117                                 sel.oe.y = term.bot;
1118                                 sel.oe.x = term.col;
1119                         }
1120                 }
1121                 selnormalize();
1122         }
1123 }
1124
1125 void
1126 tnewline(int first_col)
1127 {
1128         int y = term.c.y;
1129
1130         if (y == term.bot) {
1131                 tscrollup(term.top, 1);
1132         } else {
1133                 y++;
1134         }
1135         tmoveto(first_col ? 0 : term.c.x, y);
1136 }
1137
1138 void
1139 csiparse(void)
1140 {
1141         char *p = csiescseq.buf, *np;
1142         long int v;
1143
1144         csiescseq.narg = 0;
1145         if (*p == '?') {
1146                 csiescseq.priv = 1;
1147                 p++;
1148         }
1149
1150         csiescseq.buf[csiescseq.len] = '\0';
1151         while (p < csiescseq.buf+csiescseq.len) {
1152                 np = NULL;
1153                 v = strtol(p, &np, 10);
1154                 if (np == p)
1155                         v = 0;
1156                 if (v == LONG_MAX || v == LONG_MIN)
1157                         v = -1;
1158                 csiescseq.arg[csiescseq.narg++] = v;
1159                 p = np;
1160                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1161                         break;
1162                 p++;
1163         }
1164         csiescseq.mode[0] = *p++;
1165         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1166 }
1167
1168 /* for absolute user moves, when decom is set */
1169 void
1170 tmoveato(int x, int y)
1171 {
1172         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1173 }
1174
1175 void
1176 tmoveto(int x, int y)
1177 {
1178         int miny, maxy;
1179
1180         if (term.c.state & CURSOR_ORIGIN) {
1181                 miny = term.top;
1182                 maxy = term.bot;
1183         } else {
1184                 miny = 0;
1185                 maxy = term.row - 1;
1186         }
1187         term.c.state &= ~CURSOR_WRAPNEXT;
1188         term.c.x = LIMIT(x, 0, term.col-1);
1189         term.c.y = LIMIT(y, miny, maxy);
1190 }
1191
1192 void
1193 tsetchar(Rune u, Glyph *attr, int x, int y)
1194 {
1195         static char *vt100_0[62] = { /* 0x41 - 0x7e */
1196                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1197                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1198                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1199                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1200                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1201                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1202                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1203                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1204         };
1205
1206         /*
1207          * The table is proudly stolen from rxvt.
1208          */
1209         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1210            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1211                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1212
1213         if (term.line[y][x].mode & ATTR_WIDE) {
1214                 if (x+1 < term.col) {
1215                         term.line[y][x+1].u = ' ';
1216                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1217                 }
1218         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1219                 term.line[y][x-1].u = ' ';
1220                 term.line[y][x-1].mode &= ~ATTR_WIDE;
1221         }
1222
1223         term.dirty[y] = 1;
1224         term.line[y][x] = *attr;
1225         term.line[y][x].u = u;
1226 }
1227
1228 void
1229 tclearregion(int x1, int y1, int x2, int y2)
1230 {
1231         int x, y, temp;
1232         Glyph *gp;
1233
1234         if (x1 > x2)
1235                 temp = x1, x1 = x2, x2 = temp;
1236         if (y1 > y2)
1237                 temp = y1, y1 = y2, y2 = temp;
1238
1239         LIMIT(x1, 0, term.col-1);
1240         LIMIT(x2, 0, term.col-1);
1241         LIMIT(y1, 0, term.row-1);
1242         LIMIT(y2, 0, term.row-1);
1243
1244         for (y = y1; y <= y2; y++) {
1245                 term.dirty[y] = 1;
1246                 for (x = x1; x <= x2; x++) {
1247                         gp = &term.line[y][x];
1248                         if (selected(x, y))
1249                                 selclear();
1250                         gp->fg = term.c.attr.fg;
1251                         gp->bg = term.c.attr.bg;
1252                         gp->mode = 0;
1253                         gp->u = ' ';
1254                 }
1255         }
1256 }
1257
1258 void
1259 tdeletechar(int n)
1260 {
1261         int dst, src, size;
1262         Glyph *line;
1263
1264         LIMIT(n, 0, term.col - term.c.x);
1265
1266         dst = term.c.x;
1267         src = term.c.x + n;
1268         size = term.col - src;
1269         line = term.line[term.c.y];
1270
1271         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1272         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1273 }
1274
1275 void
1276 tinsertblank(int n)
1277 {
1278         int dst, src, size;
1279         Glyph *line;
1280
1281         LIMIT(n, 0, term.col - term.c.x);
1282
1283         dst = term.c.x + n;
1284         src = term.c.x;
1285         size = term.col - dst;
1286         line = term.line[term.c.y];
1287
1288         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1289         tclearregion(src, term.c.y, dst - 1, term.c.y);
1290 }
1291
1292 void
1293 tinsertblankline(int n)
1294 {
1295         if (BETWEEN(term.c.y, term.top, term.bot))
1296                 tscrolldown(term.c.y, n);
1297 }
1298
1299 void
1300 tdeleteline(int n)
1301 {
1302         if (BETWEEN(term.c.y, term.top, term.bot))
1303                 tscrollup(term.c.y, n);
1304 }
1305
1306 int32_t
1307 tdefcolor(int *attr, int *npar, int l)
1308 {
1309         int32_t idx = -1;
1310         uint r, g, b;
1311
1312         switch (attr[*npar + 1]) {
1313         case 2: /* direct color in RGB space */
1314                 if (*npar + 4 >= l) {
1315                         fprintf(stderr,
1316                                 "erresc(38): Incorrect number of parameters (%d)\n",
1317                                 *npar);
1318                         break;
1319                 }
1320                 r = attr[*npar + 2];
1321                 g = attr[*npar + 3];
1322                 b = attr[*npar + 4];
1323                 *npar += 4;
1324                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1325                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1326                                 r, g, b);
1327                 else
1328                         idx = TRUECOLOR(r, g, b);
1329                 break;
1330         case 5: /* indexed color */
1331                 if (*npar + 2 >= l) {
1332                         fprintf(stderr,
1333                                 "erresc(38): Incorrect number of parameters (%d)\n",
1334                                 *npar);
1335                         break;
1336                 }
1337                 *npar += 2;
1338                 if (!BETWEEN(attr[*npar], 0, 255))
1339                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1340                 else
1341                         idx = attr[*npar];
1342                 break;
1343         case 0: /* implemented defined (only foreground) */
1344         case 1: /* transparent */
1345         case 3: /* direct color in CMY space */
1346         case 4: /* direct color in CMYK space */
1347         default:
1348                 fprintf(stderr,
1349                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1350                 break;
1351         }
1352
1353         return idx;
1354 }
1355
1356 void
1357 tsetattr(int *attr, int l)
1358 {
1359         int i;
1360         int32_t idx;
1361
1362         for (i = 0; i < l; i++) {
1363                 switch (attr[i]) {
1364                 case 0:
1365                         term.c.attr.mode &= ~(
1366                                 ATTR_BOLD       |
1367                                 ATTR_FAINT      |
1368                                 ATTR_ITALIC     |
1369                                 ATTR_UNDERLINE  |
1370                                 ATTR_BLINK      |
1371                                 ATTR_REVERSE    |
1372                                 ATTR_INVISIBLE  |
1373                                 ATTR_STRUCK     );
1374                         term.c.attr.fg = defaultfg;
1375                         term.c.attr.bg = defaultbg;
1376                         break;
1377                 case 1:
1378                         term.c.attr.mode |= ATTR_BOLD;
1379                         break;
1380                 case 2:
1381                         term.c.attr.mode |= ATTR_FAINT;
1382                         break;
1383                 case 3:
1384                         term.c.attr.mode |= ATTR_ITALIC;
1385                         break;
1386                 case 4:
1387                         term.c.attr.mode |= ATTR_UNDERLINE;
1388                         break;
1389                 case 5: /* slow blink */
1390                         /* FALLTHROUGH */
1391                 case 6: /* rapid blink */
1392                         term.c.attr.mode |= ATTR_BLINK;
1393                         break;
1394                 case 7:
1395                         term.c.attr.mode |= ATTR_REVERSE;
1396                         break;
1397                 case 8:
1398                         term.c.attr.mode |= ATTR_INVISIBLE;
1399                         break;
1400                 case 9:
1401                         term.c.attr.mode |= ATTR_STRUCK;
1402                         break;
1403                 case 22:
1404                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1405                         break;
1406                 case 23:
1407                         term.c.attr.mode &= ~ATTR_ITALIC;
1408                         break;
1409                 case 24:
1410                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1411                         break;
1412                 case 25:
1413                         term.c.attr.mode &= ~ATTR_BLINK;
1414                         break;
1415                 case 27:
1416                         term.c.attr.mode &= ~ATTR_REVERSE;
1417                         break;
1418                 case 28:
1419                         term.c.attr.mode &= ~ATTR_INVISIBLE;
1420                         break;
1421                 case 29:
1422                         term.c.attr.mode &= ~ATTR_STRUCK;
1423                         break;
1424                 case 38:
1425                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1426                                 term.c.attr.fg = idx;
1427                         break;
1428                 case 39:
1429                         term.c.attr.fg = defaultfg;
1430                         break;
1431                 case 48:
1432                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1433                                 term.c.attr.bg = idx;
1434                         break;
1435                 case 49:
1436                         term.c.attr.bg = defaultbg;
1437                         break;
1438                 default:
1439                         if (BETWEEN(attr[i], 30, 37)) {
1440                                 term.c.attr.fg = attr[i] - 30;
1441                         } else if (BETWEEN(attr[i], 40, 47)) {
1442                                 term.c.attr.bg = attr[i] - 40;
1443                         } else if (BETWEEN(attr[i], 90, 97)) {
1444                                 term.c.attr.fg = attr[i] - 90 + 8;
1445                         } else if (BETWEEN(attr[i], 100, 107)) {
1446                                 term.c.attr.bg = attr[i] - 100 + 8;
1447                         } else {
1448                                 fprintf(stderr,
1449                                         "erresc(default): gfx attr %d unknown\n",
1450                                         attr[i]), csidump();
1451                         }
1452                         break;
1453                 }
1454         }
1455 }
1456
1457 void
1458 tsetscroll(int t, int b)
1459 {
1460         int temp;
1461
1462         LIMIT(t, 0, term.row-1);
1463         LIMIT(b, 0, term.row-1);
1464         if (t > b) {
1465                 temp = t;
1466                 t = b;
1467                 b = temp;
1468         }
1469         term.top = t;
1470         term.bot = b;
1471 }
1472
1473 void
1474 tsetmode(int priv, int set, int *args, int narg)
1475 {
1476         int alt, *lim;
1477
1478         for (lim = args + narg; args < lim; ++args) {
1479                 if (priv) {
1480                         switch (*args) {
1481                         case 1: /* DECCKM -- Cursor key */
1482                                 xsetmode(set, MODE_APPCURSOR);
1483                                 break;
1484                         case 5: /* DECSCNM -- Reverse video */
1485                                 xsetmode(set, MODE_REVERSE);
1486                                 break;
1487                         case 6: /* DECOM -- Origin */
1488                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1489                                 tmoveato(0, 0);
1490                                 break;
1491                         case 7: /* DECAWM -- Auto wrap */
1492                                 MODBIT(term.mode, set, MODE_WRAP);
1493                                 break;
1494                         case 0:  /* Error (IGNORED) */
1495                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1496                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1497                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1498                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1499                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1500                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1501                         case 42: /* DECNRCM -- National characters (IGNORED) */
1502                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1503                                 break;
1504                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1505                                 xsetmode(!set, MODE_HIDE);
1506                                 break;
1507                         case 9:    /* X10 mouse compatibility mode */
1508                                 xsetpointermotion(0);
1509                                 xsetmode(0, MODE_MOUSE);
1510                                 xsetmode(set, MODE_MOUSEX10);
1511                                 break;
1512                         case 1000: /* 1000: report button press */
1513                                 xsetpointermotion(0);
1514                                 xsetmode(0, MODE_MOUSE);
1515                                 xsetmode(set, MODE_MOUSEBTN);
1516                                 break;
1517                         case 1002: /* 1002: report motion on button press */
1518                                 xsetpointermotion(0);
1519                                 xsetmode(0, MODE_MOUSE);
1520                                 xsetmode(set, MODE_MOUSEMOTION);
1521                                 break;
1522                         case 1003: /* 1003: enable all mouse motions */
1523                                 xsetpointermotion(set);
1524                                 xsetmode(0, MODE_MOUSE);
1525                                 xsetmode(set, MODE_MOUSEMANY);
1526                                 break;
1527                         case 1004: /* 1004: send focus events to tty */
1528                                 xsetmode(set, MODE_FOCUS);
1529                                 break;
1530                         case 1006: /* 1006: extended reporting mode */
1531                                 xsetmode(set, MODE_MOUSESGR);
1532                                 break;
1533                         case 1034:
1534                                 xsetmode(set, MODE_8BIT);
1535                                 break;
1536                         case 1049: /* swap screen & set/restore cursor as xterm */
1537                                 if (!allowaltscreen)
1538                                         break;
1539                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1540                                 /* FALLTHROUGH */
1541                         case 47: /* swap screen */
1542                         case 1047:
1543                                 if (!allowaltscreen)
1544                                         break;
1545                                 alt = IS_SET(MODE_ALTSCREEN);
1546                                 if (alt) {
1547                                         tclearregion(0, 0, term.col-1,
1548                                                         term.row-1);
1549                                 }
1550                                 if (set ^ alt) /* set is always 1 or 0 */
1551                                         tswapscreen();
1552                                 if (*args != 1049)
1553                                         break;
1554                                 /* FALLTHROUGH */
1555                         case 1048:
1556                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1557                                 break;
1558                         case 2004: /* 2004: bracketed paste mode */
1559                                 xsetmode(set, MODE_BRCKTPASTE);
1560                                 break;
1561                         /* Not implemented mouse modes. See comments there. */
1562                         case 1001: /* mouse highlight mode; can hang the
1563                                       terminal by design when implemented. */
1564                         case 1005: /* UTF-8 mouse mode; will confuse
1565                                       applications not supporting UTF-8
1566                                       and luit. */
1567                         case 1015: /* urxvt mangled mouse mode; incompatible
1568                                       and can be mistaken for other control
1569                                       codes. */
1570                         default:
1571                                 fprintf(stderr,
1572                                         "erresc: unknown private set/reset mode %d\n",
1573                                         *args);
1574                                 break;
1575                         }
1576                 } else {
1577                         switch (*args) {
1578                         case 0:  /* Error (IGNORED) */
1579                                 break;
1580                         case 2:
1581                                 xsetmode(set, MODE_KBDLOCK);
1582                                 break;
1583                         case 4:  /* IRM -- Insertion-replacement */
1584                                 MODBIT(term.mode, set, MODE_INSERT);
1585                                 break;
1586                         case 12: /* SRM -- Send/Receive */
1587                                 MODBIT(term.mode, !set, MODE_ECHO);
1588                                 break;
1589                         case 20: /* LNM -- Linefeed/new line */
1590                                 MODBIT(term.mode, set, MODE_CRLF);
1591                                 break;
1592                         default:
1593                                 fprintf(stderr,
1594                                         "erresc: unknown set/reset mode %d\n",
1595                                         *args);
1596                                 break;
1597                         }
1598                 }
1599         }
1600 }
1601
1602 void
1603 csihandle(void)
1604 {
1605         char buf[40];
1606         int len;
1607
1608         switch (csiescseq.mode[0]) {
1609         default:
1610         unknown:
1611                 fprintf(stderr, "erresc: unknown csi ");
1612                 csidump();
1613                 /* die(""); */
1614                 break;
1615         case '@': /* ICH -- Insert <n> blank char */
1616                 DEFAULT(csiescseq.arg[0], 1);
1617                 tinsertblank(csiescseq.arg[0]);
1618                 break;
1619         case 'A': /* CUU -- Cursor <n> Up */
1620                 DEFAULT(csiescseq.arg[0], 1);
1621                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1622                 break;
1623         case 'B': /* CUD -- Cursor <n> Down */
1624         case 'e': /* VPR --Cursor <n> Down */
1625                 DEFAULT(csiescseq.arg[0], 1);
1626                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1627                 break;
1628         case 'i': /* MC -- Media Copy */
1629                 switch (csiescseq.arg[0]) {
1630                 case 0:
1631                         tdump();
1632                         break;
1633                 case 1:
1634                         tdumpline(term.c.y);
1635                         break;
1636                 case 2:
1637                         tdumpsel();
1638                         break;
1639                 case 4:
1640                         term.mode &= ~MODE_PRINT;
1641                         break;
1642                 case 5:
1643                         term.mode |= MODE_PRINT;
1644                         break;
1645                 }
1646                 break;
1647         case 'c': /* DA -- Device Attributes */
1648                 if (csiescseq.arg[0] == 0)
1649                         ttywrite(vtiden, strlen(vtiden), 0);
1650                 break;
1651         case 'C': /* CUF -- Cursor <n> Forward */
1652         case 'a': /* HPR -- Cursor <n> Forward */
1653                 DEFAULT(csiescseq.arg[0], 1);
1654                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1655                 break;
1656         case 'D': /* CUB -- Cursor <n> Backward */
1657                 DEFAULT(csiescseq.arg[0], 1);
1658                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1659                 break;
1660         case 'E': /* CNL -- Cursor <n> Down and first col */
1661                 DEFAULT(csiescseq.arg[0], 1);
1662                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1663                 break;
1664         case 'F': /* CPL -- Cursor <n> Up and first col */
1665                 DEFAULT(csiescseq.arg[0], 1);
1666                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1667                 break;
1668         case 'g': /* TBC -- Tabulation clear */
1669                 switch (csiescseq.arg[0]) {
1670                 case 0: /* clear current tab stop */
1671                         term.tabs[term.c.x] = 0;
1672                         break;
1673                 case 3: /* clear all the tabs */
1674                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1675                         break;
1676                 default:
1677                         goto unknown;
1678                 }
1679                 break;
1680         case 'G': /* CHA -- Move to <col> */
1681         case '`': /* HPA */
1682                 DEFAULT(csiescseq.arg[0], 1);
1683                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1684                 break;
1685         case 'H': /* CUP -- Move to <row> <col> */
1686         case 'f': /* HVP */
1687                 DEFAULT(csiescseq.arg[0], 1);
1688                 DEFAULT(csiescseq.arg[1], 1);
1689                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1690                 break;
1691         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1692                 DEFAULT(csiescseq.arg[0], 1);
1693                 tputtab(csiescseq.arg[0]);
1694                 break;
1695         case 'J': /* ED -- Clear screen */
1696                 selclear();
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 }