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