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