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