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