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