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