]> git.armaanb.net Git - st.git/blob - st.c
Move opt_* into same file as main()/run()
[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 void tfulldirt(void);
165 static void techo(Rune);
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(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         char *ptr;
772         int charsize; /* size of utf8 char in bytes */
773         Rune unicodep;
774         int ret;
775
776         /* append read bytes to unprocessed bytes */
777         if ((ret = read(cmdfd, buf+buflen, LEN(buf)-buflen)) < 0)
778                 die("Couldn't read from shell: %s\n", strerror(errno));
779
780         buflen += ret;
781         ptr = buf;
782
783         for (;;) {
784                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
785                         /* process a complete utf8 char */
786                         charsize = utf8decode(ptr, &unicodep, buflen);
787                         if (charsize == 0)
788                                 break;
789                         tputc(unicodep);
790                         ptr += charsize;
791                         buflen -= charsize;
792
793                 } else {
794                         if (buflen <= 0)
795                                 break;
796                         tputc(*ptr++ & 0xFF);
797                         buflen--;
798                 }
799         }
800         /* keep any uncomplete utf8 char for the next call */
801         if (buflen > 0)
802                 memmove(buf, ptr, buflen);
803
804         return ret;
805 }
806
807 void
808 ttywrite(const char *s, size_t n)
809 {
810         fd_set wfd, rfd;
811         ssize_t r;
812         size_t lim = 256;
813
814         /*
815          * Remember that we are using a pty, which might be a modem line.
816          * Writing too much will clog the line. That's why we are doing this
817          * dance.
818          * FIXME: Migrate the world to Plan 9.
819          */
820         while (n > 0) {
821                 FD_ZERO(&wfd);
822                 FD_ZERO(&rfd);
823                 FD_SET(cmdfd, &wfd);
824                 FD_SET(cmdfd, &rfd);
825
826                 /* Check if we can write. */
827                 if (pselect(cmdfd+1, &rfd, &wfd, NULL, NULL, NULL) < 0) {
828                         if (errno == EINTR)
829                                 continue;
830                         die("select failed: %s\n", strerror(errno));
831                 }
832                 if (FD_ISSET(cmdfd, &wfd)) {
833                         /*
834                          * Only write the bytes written by ttywrite() or the
835                          * default of 256. This seems to be a reasonable value
836                          * for a serial line. Bigger values might clog the I/O.
837                          */
838                         if ((r = write(cmdfd, s, (n < lim)? n : lim)) < 0)
839                                 goto write_error;
840                         if (r < n) {
841                                 /*
842                                  * We weren't able to write out everything.
843                                  * This means the buffer is getting full
844                                  * again. Empty it.
845                                  */
846                                 if (n < lim)
847                                         lim = ttyread();
848                                 n -= r;
849                                 s += r;
850                         } else {
851                                 /* All bytes have been written. */
852                                 break;
853                         }
854                 }
855                 if (FD_ISSET(cmdfd, &rfd))
856                         lim = ttyread();
857         }
858         return;
859
860 write_error:
861         die("write error on tty: %s\n", strerror(errno));
862 }
863
864 void
865 ttysend(char *s, size_t n)
866 {
867         int len;
868         char *t, *lim;
869         Rune u;
870
871         ttywrite(s, n);
872         if (!IS_SET(MODE_ECHO))
873                 return;
874
875         lim = &s[n];
876         for (t = s; t < lim; t += len) {
877                 if (IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
878                         len = utf8decode(t, &u, n);
879                 } else {
880                         u = *t & 0xFF;
881                         len = 1;
882                 }
883                 if (len <= 0)
884                         break;
885                 techo(u);
886                 n -= len;
887         }
888 }
889
890 void
891 ttyresize(int tw, int th)
892 {
893         struct winsize w;
894
895         w.ws_row = term.row;
896         w.ws_col = term.col;
897         w.ws_xpixel = tw;
898         w.ws_ypixel = th;
899         if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
900                 fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
901 }
902
903 int
904 tattrset(int attr)
905 {
906         int i, j;
907
908         for (i = 0; i < term.row-1; i++) {
909                 for (j = 0; j < term.col-1; j++) {
910                         if (term.line[i][j].mode & attr)
911                                 return 1;
912                 }
913         }
914
915         return 0;
916 }
917
918 void
919 tsetdirt(int top, int bot)
920 {
921         int i;
922
923         LIMIT(top, 0, term.row-1);
924         LIMIT(bot, 0, term.row-1);
925
926         for (i = top; i <= bot; i++)
927                 term.dirty[i] = 1;
928 }
929
930 void
931 tsetdirtattr(int attr)
932 {
933         int i, j;
934
935         for (i = 0; i < term.row-1; i++) {
936                 for (j = 0; j < term.col-1; j++) {
937                         if (term.line[i][j].mode & attr) {
938                                 tsetdirt(i, i);
939                                 break;
940                         }
941                 }
942         }
943 }
944
945 void
946 tfulldirt(void)
947 {
948         tsetdirt(0, term.row-1);
949 }
950
951 void
952 tcursor(int mode)
953 {
954         static TCursor c[2];
955         int alt = IS_SET(MODE_ALTSCREEN);
956
957         if (mode == CURSOR_SAVE) {
958                 c[alt] = term.c;
959         } else if (mode == CURSOR_LOAD) {
960                 term.c = c[alt];
961                 tmoveto(c[alt].x, c[alt].y);
962         }
963 }
964
965 void
966 treset(void)
967 {
968         uint i;
969
970         term.c = (TCursor){{
971                 .mode = ATTR_NULL,
972                 .fg = defaultfg,
973                 .bg = defaultbg
974         }, .x = 0, .y = 0, .state = CURSOR_DEFAULT};
975
976         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
977         for (i = tabspaces; i < term.col; i += tabspaces)
978                 term.tabs[i] = 1;
979         term.top = 0;
980         term.bot = term.row - 1;
981         term.mode = MODE_WRAP|MODE_UTF8;
982         memset(term.trantbl, CS_USA, sizeof(term.trantbl));
983         term.charset = 0;
984
985         for (i = 0; i < 2; i++) {
986                 tmoveto(0, 0);
987                 tcursor(CURSOR_SAVE);
988                 tclearregion(0, 0, term.col-1, term.row-1);
989                 tswapscreen();
990         }
991 }
992
993 void
994 tnew(int col, int row)
995 {
996         term = (Term){ .c = { .attr = { .fg = defaultfg, .bg = defaultbg } } };
997         tresize(col, row);
998         term.numlock = 1;
999
1000         treset();
1001 }
1002
1003 void
1004 tswapscreen(void)
1005 {
1006         Line *tmp = term.line;
1007
1008         term.line = term.alt;
1009         term.alt = tmp;
1010         term.mode ^= MODE_ALTSCREEN;
1011         tfulldirt();
1012 }
1013
1014 void
1015 tscrolldown(int orig, int n)
1016 {
1017         int i;
1018         Line temp;
1019
1020         LIMIT(n, 0, term.bot-orig+1);
1021
1022         tsetdirt(orig, term.bot-n);
1023         tclearregion(0, term.bot-n+1, term.col-1, term.bot);
1024
1025         for (i = term.bot; i >= orig+n; i--) {
1026                 temp = term.line[i];
1027                 term.line[i] = term.line[i-n];
1028                 term.line[i-n] = temp;
1029         }
1030
1031         selscroll(orig, n);
1032 }
1033
1034 void
1035 tscrollup(int orig, int n)
1036 {
1037         int i;
1038         Line temp;
1039
1040         LIMIT(n, 0, term.bot-orig+1);
1041
1042         tclearregion(0, orig, term.col-1, orig+n-1);
1043         tsetdirt(orig+n, term.bot);
1044
1045         for (i = orig; i <= term.bot-n; i++) {
1046                 temp = term.line[i];
1047                 term.line[i] = term.line[i+n];
1048                 term.line[i+n] = temp;
1049         }
1050
1051         selscroll(orig, -n);
1052 }
1053
1054 void
1055 selscroll(int orig, int n)
1056 {
1057         if (sel.ob.x == -1)
1058                 return;
1059
1060         if (BETWEEN(sel.ob.y, orig, term.bot) || BETWEEN(sel.oe.y, orig, term.bot)) {
1061                 if ((sel.ob.y += n) > term.bot || (sel.oe.y += n) < term.top) {
1062                         selclear();
1063                         return;
1064                 }
1065                 if (sel.type == SEL_RECTANGULAR) {
1066                         if (sel.ob.y < term.top)
1067                                 sel.ob.y = term.top;
1068                         if (sel.oe.y > term.bot)
1069                                 sel.oe.y = term.bot;
1070                 } else {
1071                         if (sel.ob.y < term.top) {
1072                                 sel.ob.y = term.top;
1073                                 sel.ob.x = 0;
1074                         }
1075                         if (sel.oe.y > term.bot) {
1076                                 sel.oe.y = term.bot;
1077                                 sel.oe.x = term.col;
1078                         }
1079                 }
1080                 selnormalize();
1081         }
1082 }
1083
1084 void
1085 tnewline(int first_col)
1086 {
1087         int y = term.c.y;
1088
1089         if (y == term.bot) {
1090                 tscrollup(term.top, 1);
1091         } else {
1092                 y++;
1093         }
1094         tmoveto(first_col ? 0 : term.c.x, y);
1095 }
1096
1097 void
1098 csiparse(void)
1099 {
1100         char *p = csiescseq.buf, *np;
1101         long int v;
1102
1103         csiescseq.narg = 0;
1104         if (*p == '?') {
1105                 csiescseq.priv = 1;
1106                 p++;
1107         }
1108
1109         csiescseq.buf[csiescseq.len] = '\0';
1110         while (p < csiescseq.buf+csiescseq.len) {
1111                 np = NULL;
1112                 v = strtol(p, &np, 10);
1113                 if (np == p)
1114                         v = 0;
1115                 if (v == LONG_MAX || v == LONG_MIN)
1116                         v = -1;
1117                 csiescseq.arg[csiescseq.narg++] = v;
1118                 p = np;
1119                 if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ)
1120                         break;
1121                 p++;
1122         }
1123         csiescseq.mode[0] = *p++;
1124         csiescseq.mode[1] = (p < csiescseq.buf+csiescseq.len) ? *p : '\0';
1125 }
1126
1127 /* for absolute user moves, when decom is set */
1128 void
1129 tmoveato(int x, int y)
1130 {
1131         tmoveto(x, y + ((term.c.state & CURSOR_ORIGIN) ? term.top: 0));
1132 }
1133
1134 void
1135 tmoveto(int x, int y)
1136 {
1137         int miny, maxy;
1138
1139         if (term.c.state & CURSOR_ORIGIN) {
1140                 miny = term.top;
1141                 maxy = term.bot;
1142         } else {
1143                 miny = 0;
1144                 maxy = term.row - 1;
1145         }
1146         term.c.state &= ~CURSOR_WRAPNEXT;
1147         term.c.x = LIMIT(x, 0, term.col-1);
1148         term.c.y = LIMIT(y, miny, maxy);
1149 }
1150
1151 void
1152 tsetchar(Rune u, Glyph *attr, int x, int y)
1153 {
1154         static char *vt100_0[62] = { /* 0x41 - 0x7e */
1155                 "↑", "↓", "→", "←", "█", "▚", "☃", /* A - G */
1156                 0, 0, 0, 0, 0, 0, 0, 0, /* H - O */
1157                 0, 0, 0, 0, 0, 0, 0, 0, /* P - W */
1158                 0, 0, 0, 0, 0, 0, 0, " ", /* X - _ */
1159                 "◆", "▒", "␉", "␌", "␍", "␊", "°", "±", /* ` - g */
1160                 "␤", "␋", "┘", "┐", "┌", "└", "┼", "⎺", /* h - o */
1161                 "⎻", "─", "⎼", "⎽", "├", "┤", "┴", "┬", /* p - w */
1162                 "│", "≤", "≥", "π", "≠", "£", "·", /* x - ~ */
1163         };
1164
1165         /*
1166          * The table is proudly stolen from rxvt.
1167          */
1168         if (term.trantbl[term.charset] == CS_GRAPHIC0 &&
1169            BETWEEN(u, 0x41, 0x7e) && vt100_0[u - 0x41])
1170                 utf8decode(vt100_0[u - 0x41], &u, UTF_SIZ);
1171
1172         if (term.line[y][x].mode & ATTR_WIDE) {
1173                 if (x+1 < term.col) {
1174                         term.line[y][x+1].u = ' ';
1175                         term.line[y][x+1].mode &= ~ATTR_WDUMMY;
1176                 }
1177         } else if (term.line[y][x].mode & ATTR_WDUMMY) {
1178                 term.line[y][x-1].u = ' ';
1179                 term.line[y][x-1].mode &= ~ATTR_WIDE;
1180         }
1181
1182         term.dirty[y] = 1;
1183         term.line[y][x] = *attr;
1184         term.line[y][x].u = u;
1185 }
1186
1187 void
1188 tclearregion(int x1, int y1, int x2, int y2)
1189 {
1190         int x, y, temp;
1191         Glyph *gp;
1192
1193         if (x1 > x2)
1194                 temp = x1, x1 = x2, x2 = temp;
1195         if (y1 > y2)
1196                 temp = y1, y1 = y2, y2 = temp;
1197
1198         LIMIT(x1, 0, term.col-1);
1199         LIMIT(x2, 0, term.col-1);
1200         LIMIT(y1, 0, term.row-1);
1201         LIMIT(y2, 0, term.row-1);
1202
1203         for (y = y1; y <= y2; y++) {
1204                 term.dirty[y] = 1;
1205                 for (x = x1; x <= x2; x++) {
1206                         gp = &term.line[y][x];
1207                         if (selected(x, y))
1208                                 selclear();
1209                         gp->fg = term.c.attr.fg;
1210                         gp->bg = term.c.attr.bg;
1211                         gp->mode = 0;
1212                         gp->u = ' ';
1213                 }
1214         }
1215 }
1216
1217 void
1218 tdeletechar(int n)
1219 {
1220         int dst, src, size;
1221         Glyph *line;
1222
1223         LIMIT(n, 0, term.col - term.c.x);
1224
1225         dst = term.c.x;
1226         src = term.c.x + n;
1227         size = term.col - src;
1228         line = term.line[term.c.y];
1229
1230         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1231         tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
1232 }
1233
1234 void
1235 tinsertblank(int n)
1236 {
1237         int dst, src, size;
1238         Glyph *line;
1239
1240         LIMIT(n, 0, term.col - term.c.x);
1241
1242         dst = term.c.x + n;
1243         src = term.c.x;
1244         size = term.col - dst;
1245         line = term.line[term.c.y];
1246
1247         memmove(&line[dst], &line[src], size * sizeof(Glyph));
1248         tclearregion(src, term.c.y, dst - 1, term.c.y);
1249 }
1250
1251 void
1252 tinsertblankline(int n)
1253 {
1254         if (BETWEEN(term.c.y, term.top, term.bot))
1255                 tscrolldown(term.c.y, n);
1256 }
1257
1258 void
1259 tdeleteline(int n)
1260 {
1261         if (BETWEEN(term.c.y, term.top, term.bot))
1262                 tscrollup(term.c.y, n);
1263 }
1264
1265 int32_t
1266 tdefcolor(int *attr, int *npar, int l)
1267 {
1268         int32_t idx = -1;
1269         uint r, g, b;
1270
1271         switch (attr[*npar + 1]) {
1272         case 2: /* direct color in RGB space */
1273                 if (*npar + 4 >= l) {
1274                         fprintf(stderr,
1275                                 "erresc(38): Incorrect number of parameters (%d)\n",
1276                                 *npar);
1277                         break;
1278                 }
1279                 r = attr[*npar + 2];
1280                 g = attr[*npar + 3];
1281                 b = attr[*npar + 4];
1282                 *npar += 4;
1283                 if (!BETWEEN(r, 0, 255) || !BETWEEN(g, 0, 255) || !BETWEEN(b, 0, 255))
1284                         fprintf(stderr, "erresc: bad rgb color (%u,%u,%u)\n",
1285                                 r, g, b);
1286                 else
1287                         idx = TRUECOLOR(r, g, b);
1288                 break;
1289         case 5: /* indexed color */
1290                 if (*npar + 2 >= l) {
1291                         fprintf(stderr,
1292                                 "erresc(38): Incorrect number of parameters (%d)\n",
1293                                 *npar);
1294                         break;
1295                 }
1296                 *npar += 2;
1297                 if (!BETWEEN(attr[*npar], 0, 255))
1298                         fprintf(stderr, "erresc: bad fgcolor %d\n", attr[*npar]);
1299                 else
1300                         idx = attr[*npar];
1301                 break;
1302         case 0: /* implemented defined (only foreground) */
1303         case 1: /* transparent */
1304         case 3: /* direct color in CMY space */
1305         case 4: /* direct color in CMYK space */
1306         default:
1307                 fprintf(stderr,
1308                         "erresc(38): gfx attr %d unknown\n", attr[*npar]);
1309                 break;
1310         }
1311
1312         return idx;
1313 }
1314
1315 void
1316 tsetattr(int *attr, int l)
1317 {
1318         int i;
1319         int32_t idx;
1320
1321         for (i = 0; i < l; i++) {
1322                 switch (attr[i]) {
1323                 case 0:
1324                         term.c.attr.mode &= ~(
1325                                 ATTR_BOLD       |
1326                                 ATTR_FAINT      |
1327                                 ATTR_ITALIC     |
1328                                 ATTR_UNDERLINE  |
1329                                 ATTR_BLINK      |
1330                                 ATTR_REVERSE    |
1331                                 ATTR_INVISIBLE  |
1332                                 ATTR_STRUCK     );
1333                         term.c.attr.fg = defaultfg;
1334                         term.c.attr.bg = defaultbg;
1335                         break;
1336                 case 1:
1337                         term.c.attr.mode |= ATTR_BOLD;
1338                         break;
1339                 case 2:
1340                         term.c.attr.mode |= ATTR_FAINT;
1341                         break;
1342                 case 3:
1343                         term.c.attr.mode |= ATTR_ITALIC;
1344                         break;
1345                 case 4:
1346                         term.c.attr.mode |= ATTR_UNDERLINE;
1347                         break;
1348                 case 5: /* slow blink */
1349                         /* FALLTHROUGH */
1350                 case 6: /* rapid blink */
1351                         term.c.attr.mode |= ATTR_BLINK;
1352                         break;
1353                 case 7:
1354                         term.c.attr.mode |= ATTR_REVERSE;
1355                         break;
1356                 case 8:
1357                         term.c.attr.mode |= ATTR_INVISIBLE;
1358                         break;
1359                 case 9:
1360                         term.c.attr.mode |= ATTR_STRUCK;
1361                         break;
1362                 case 22:
1363                         term.c.attr.mode &= ~(ATTR_BOLD | ATTR_FAINT);
1364                         break;
1365                 case 23:
1366                         term.c.attr.mode &= ~ATTR_ITALIC;
1367                         break;
1368                 case 24:
1369                         term.c.attr.mode &= ~ATTR_UNDERLINE;
1370                         break;
1371                 case 25:
1372                         term.c.attr.mode &= ~ATTR_BLINK;
1373                         break;
1374                 case 27:
1375                         term.c.attr.mode &= ~ATTR_REVERSE;
1376                         break;
1377                 case 28:
1378                         term.c.attr.mode &= ~ATTR_INVISIBLE;
1379                         break;
1380                 case 29:
1381                         term.c.attr.mode &= ~ATTR_STRUCK;
1382                         break;
1383                 case 38:
1384                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1385                                 term.c.attr.fg = idx;
1386                         break;
1387                 case 39:
1388                         term.c.attr.fg = defaultfg;
1389                         break;
1390                 case 48:
1391                         if ((idx = tdefcolor(attr, &i, l)) >= 0)
1392                                 term.c.attr.bg = idx;
1393                         break;
1394                 case 49:
1395                         term.c.attr.bg = defaultbg;
1396                         break;
1397                 default:
1398                         if (BETWEEN(attr[i], 30, 37)) {
1399                                 term.c.attr.fg = attr[i] - 30;
1400                         } else if (BETWEEN(attr[i], 40, 47)) {
1401                                 term.c.attr.bg = attr[i] - 40;
1402                         } else if (BETWEEN(attr[i], 90, 97)) {
1403                                 term.c.attr.fg = attr[i] - 90 + 8;
1404                         } else if (BETWEEN(attr[i], 100, 107)) {
1405                                 term.c.attr.bg = attr[i] - 100 + 8;
1406                         } else {
1407                                 fprintf(stderr,
1408                                         "erresc(default): gfx attr %d unknown\n",
1409                                         attr[i]), csidump();
1410                         }
1411                         break;
1412                 }
1413         }
1414 }
1415
1416 void
1417 tsetscroll(int t, int b)
1418 {
1419         int temp;
1420
1421         LIMIT(t, 0, term.row-1);
1422         LIMIT(b, 0, term.row-1);
1423         if (t > b) {
1424                 temp = t;
1425                 t = b;
1426                 b = temp;
1427         }
1428         term.top = t;
1429         term.bot = b;
1430 }
1431
1432 void
1433 tsetmode(int priv, int set, int *args, int narg)
1434 {
1435         int *lim, mode;
1436         int alt;
1437
1438         for (lim = args + narg; args < lim; ++args) {
1439                 if (priv) {
1440                         switch (*args) {
1441                         case 1: /* DECCKM -- Cursor key */
1442                                 MODBIT(term.mode, set, MODE_APPCURSOR);
1443                                 break;
1444                         case 5: /* DECSCNM -- Reverse video */
1445                                 mode = term.mode;
1446                                 MODBIT(term.mode, set, MODE_REVERSE);
1447                                 if (mode != term.mode)
1448                                         redraw();
1449                                 break;
1450                         case 6: /* DECOM -- Origin */
1451                                 MODBIT(term.c.state, set, CURSOR_ORIGIN);
1452                                 tmoveato(0, 0);
1453                                 break;
1454                         case 7: /* DECAWM -- Auto wrap */
1455                                 MODBIT(term.mode, set, MODE_WRAP);
1456                                 break;
1457                         case 0:  /* Error (IGNORED) */
1458                         case 2:  /* DECANM -- ANSI/VT52 (IGNORED) */
1459                         case 3:  /* DECCOLM -- Column  (IGNORED) */
1460                         case 4:  /* DECSCLM -- Scroll (IGNORED) */
1461                         case 8:  /* DECARM -- Auto repeat (IGNORED) */
1462                         case 18: /* DECPFF -- Printer feed (IGNORED) */
1463                         case 19: /* DECPEX -- Printer extent (IGNORED) */
1464                         case 42: /* DECNRCM -- National characters (IGNORED) */
1465                         case 12: /* att610 -- Start blinking cursor (IGNORED) */
1466                                 break;
1467                         case 25: /* DECTCEM -- Text Cursor Enable Mode */
1468                                 MODBIT(term.mode, !set, MODE_HIDE);
1469                                 break;
1470                         case 9:    /* X10 mouse compatibility mode */
1471                                 xsetpointermotion(0);
1472                                 MODBIT(term.mode, 0, MODE_MOUSE);
1473                                 MODBIT(term.mode, set, MODE_MOUSEX10);
1474                                 break;
1475                         case 1000: /* 1000: report button press */
1476                                 xsetpointermotion(0);
1477                                 MODBIT(term.mode, 0, MODE_MOUSE);
1478                                 MODBIT(term.mode, set, MODE_MOUSEBTN);
1479                                 break;
1480                         case 1002: /* 1002: report motion on button press */
1481                                 xsetpointermotion(0);
1482                                 MODBIT(term.mode, 0, MODE_MOUSE);
1483                                 MODBIT(term.mode, set, MODE_MOUSEMOTION);
1484                                 break;
1485                         case 1003: /* 1003: enable all mouse motions */
1486                                 xsetpointermotion(set);
1487                                 MODBIT(term.mode, 0, MODE_MOUSE);
1488                                 MODBIT(term.mode, set, MODE_MOUSEMANY);
1489                                 break;
1490                         case 1004: /* 1004: send focus events to tty */
1491                                 MODBIT(term.mode, set, MODE_FOCUS);
1492                                 break;
1493                         case 1006: /* 1006: extended reporting mode */
1494                                 MODBIT(term.mode, set, MODE_MOUSESGR);
1495                                 break;
1496                         case 1034:
1497                                 MODBIT(term.mode, set, MODE_8BIT);
1498                                 break;
1499                         case 1049: /* swap screen & set/restore cursor as xterm */
1500                                 if (!allowaltscreen)
1501                                         break;
1502                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1503                                 /* FALLTHROUGH */
1504                         case 47: /* swap screen */
1505                         case 1047:
1506                                 if (!allowaltscreen)
1507                                         break;
1508                                 alt = IS_SET(MODE_ALTSCREEN);
1509                                 if (alt) {
1510                                         tclearregion(0, 0, term.col-1,
1511                                                         term.row-1);
1512                                 }
1513                                 if (set ^ alt) /* set is always 1 or 0 */
1514                                         tswapscreen();
1515                                 if (*args != 1049)
1516                                         break;
1517                                 /* FALLTHROUGH */
1518                         case 1048:
1519                                 tcursor((set) ? CURSOR_SAVE : CURSOR_LOAD);
1520                                 break;
1521                         case 2004: /* 2004: bracketed paste mode */
1522                                 MODBIT(term.mode, set, MODE_BRCKTPASTE);
1523                                 break;
1524                         /* Not implemented mouse modes. See comments there. */
1525                         case 1001: /* mouse highlight mode; can hang the
1526                                       terminal by design when implemented. */
1527                         case 1005: /* UTF-8 mouse mode; will confuse
1528                                       applications not supporting UTF-8
1529                                       and luit. */
1530                         case 1015: /* urxvt mangled mouse mode; incompatible
1531                                       and can be mistaken for other control
1532                                       codes. */
1533                         default:
1534                                 fprintf(stderr,
1535                                         "erresc: unknown private set/reset mode %d\n",
1536                                         *args);
1537                                 break;
1538                         }
1539                 } else {
1540                         switch (*args) {
1541                         case 0:  /* Error (IGNORED) */
1542                                 break;
1543                         case 2:  /* KAM -- keyboard action */
1544                                 MODBIT(term.mode, set, MODE_KBDLOCK);
1545                                 break;
1546                         case 4:  /* IRM -- Insertion-replacement */
1547                                 MODBIT(term.mode, set, MODE_INSERT);
1548                                 break;
1549                         case 12: /* SRM -- Send/Receive */
1550                                 MODBIT(term.mode, !set, MODE_ECHO);
1551                                 break;
1552                         case 20: /* LNM -- Linefeed/new line */
1553                                 MODBIT(term.mode, set, MODE_CRLF);
1554                                 break;
1555                         default:
1556                                 fprintf(stderr,
1557                                         "erresc: unknown set/reset mode %d\n",
1558                                         *args);
1559                                 break;
1560                         }
1561                 }
1562         }
1563 }
1564
1565 void
1566 csihandle(void)
1567 {
1568         char buf[40];
1569         int len;
1570
1571         switch (csiescseq.mode[0]) {
1572         default:
1573         unknown:
1574                 fprintf(stderr, "erresc: unknown csi ");
1575                 csidump();
1576                 /* die(""); */
1577                 break;
1578         case '@': /* ICH -- Insert <n> blank char */
1579                 DEFAULT(csiescseq.arg[0], 1);
1580                 tinsertblank(csiescseq.arg[0]);
1581                 break;
1582         case 'A': /* CUU -- Cursor <n> Up */
1583                 DEFAULT(csiescseq.arg[0], 1);
1584                 tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
1585                 break;
1586         case 'B': /* CUD -- Cursor <n> Down */
1587         case 'e': /* VPR --Cursor <n> Down */
1588                 DEFAULT(csiescseq.arg[0], 1);
1589                 tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
1590                 break;
1591         case 'i': /* MC -- Media Copy */
1592                 switch (csiescseq.arg[0]) {
1593                 case 0:
1594                         tdump();
1595                         break;
1596                 case 1:
1597                         tdumpline(term.c.y);
1598                         break;
1599                 case 2:
1600                         tdumpsel();
1601                         break;
1602                 case 4:
1603                         term.mode &= ~MODE_PRINT;
1604                         break;
1605                 case 5:
1606                         term.mode |= MODE_PRINT;
1607                         break;
1608                 }
1609                 break;
1610         case 'c': /* DA -- Device Attributes */
1611                 if (csiescseq.arg[0] == 0)
1612                         ttywrite(vtiden, sizeof(vtiden) - 1);
1613                 break;
1614         case 'C': /* CUF -- Cursor <n> Forward */
1615         case 'a': /* HPR -- Cursor <n> Forward */
1616                 DEFAULT(csiescseq.arg[0], 1);
1617                 tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
1618                 break;
1619         case 'D': /* CUB -- Cursor <n> Backward */
1620                 DEFAULT(csiescseq.arg[0], 1);
1621                 tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
1622                 break;
1623         case 'E': /* CNL -- Cursor <n> Down and first col */
1624                 DEFAULT(csiescseq.arg[0], 1);
1625                 tmoveto(0, term.c.y+csiescseq.arg[0]);
1626                 break;
1627         case 'F': /* CPL -- Cursor <n> Up and first col */
1628                 DEFAULT(csiescseq.arg[0], 1);
1629                 tmoveto(0, term.c.y-csiescseq.arg[0]);
1630                 break;
1631         case 'g': /* TBC -- Tabulation clear */
1632                 switch (csiescseq.arg[0]) {
1633                 case 0: /* clear current tab stop */
1634                         term.tabs[term.c.x] = 0;
1635                         break;
1636                 case 3: /* clear all the tabs */
1637                         memset(term.tabs, 0, term.col * sizeof(*term.tabs));
1638                         break;
1639                 default:
1640                         goto unknown;
1641                 }
1642                 break;
1643         case 'G': /* CHA -- Move to <col> */
1644         case '`': /* HPA */
1645                 DEFAULT(csiescseq.arg[0], 1);
1646                 tmoveto(csiescseq.arg[0]-1, term.c.y);
1647                 break;
1648         case 'H': /* CUP -- Move to <row> <col> */
1649         case 'f': /* HVP */
1650                 DEFAULT(csiescseq.arg[0], 1);
1651                 DEFAULT(csiescseq.arg[1], 1);
1652                 tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
1653                 break;
1654         case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
1655                 DEFAULT(csiescseq.arg[0], 1);
1656                 tputtab(csiescseq.arg[0]);
1657                 break;
1658         case 'J': /* ED -- Clear screen */
1659                 selclear();
1660                 switch (csiescseq.arg[0]) {
1661                 case 0: /* below */
1662                         tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
1663                         if (term.c.y < term.row-1) {
1664                                 tclearregion(0, term.c.y+1, term.col-1,
1665                                                 term.row-1);
1666                         }
1667                         break;
1668                 case 1: /* above */
1669                         if (term.c.y > 1)
1670                                 tclearregion(0, 0, term.col-1, term.c.y-1);
1671                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1672                         break;
1673                 case 2: /* all */
1674                         tclearregion(0, 0, term.col-1, term.row-1);
1675                         break;
1676                 default:
1677                         goto unknown;
1678                 }
1679                 break;
1680         case 'K': /* EL -- Clear line */
1681                 switch (csiescseq.arg[0]) {
1682                 case 0: /* right */
1683                         tclearregion(term.c.x, term.c.y, term.col-1,
1684                                         term.c.y);
1685                         break;
1686                 case 1: /* left */
1687                         tclearregion(0, term.c.y, term.c.x, term.c.y);
1688                         break;
1689                 case 2: /* all */
1690                         tclearregion(0, term.c.y, term.col-1, term.c.y);
1691                         break;
1692                 }
1693                 break;
1694         case 'S': /* SU -- Scroll <n> line up */
1695                 DEFAULT(csiescseq.arg[0], 1);
1696                 tscrollup(term.top, csiescseq.arg[0]);
1697                 break;
1698         case 'T': /* SD -- Scroll <n> line down */
1699                 DEFAULT(csiescseq.arg[0], 1);
1700                 tscrolldown(term.top, csiescseq.arg[0]);
1701                 break;
1702         case 'L': /* IL -- Insert <n> blank lines */
1703                 DEFAULT(csiescseq.arg[0], 1);
1704                 tinsertblankline(csiescseq.arg[0]);
1705                 break;
1706         case 'l': /* RM -- Reset Mode */
1707                 tsetmode(csiescseq.priv, 0, csiescseq.arg, csiescseq.narg);
1708                 break;
1709         case 'M': /* DL -- Delete <n> lines */
1710                 DEFAULT(csiescseq.arg[0], 1);
1711                 tdeleteline(csiescseq.arg[0]);
1712                 break;
1713         case 'X': /* ECH -- Erase <n> char */
1714                 DEFAULT(csiescseq.arg[0], 1);
1715                 tclearregion(term.c.x, term.c.y,
1716                                 term.c.x + csiescseq.arg[0] - 1, term.c.y);
1717                 break;
1718         case 'P': /* DCH -- Delete <n> char */
1719                 DEFAULT(csiescseq.arg[0], 1);
1720                 tdeletechar(csiescseq.arg[0]);
1721                 break;
1722         case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
1723                 DEFAULT(csiescseq.arg[0], 1);
1724                 tputtab(-csiescseq.arg[0]);
1725                 break;
1726         case 'd': /* VPA -- Move to <row> */
1727                 DEFAULT(csiescseq.arg[0], 1);
1728                 tmoveato(term.c.x, csiescseq.arg[0]-1);
1729                 break;
1730         case 'h': /* SM -- Set terminal mode */
1731                 tsetmode(csiescseq.priv, 1, csiescseq.arg, csiescseq.narg);
1732                 break;
1733         case 'm': /* SGR -- Terminal attribute (color) */
1734                 tsetattr(csiescseq.arg, csiescseq.narg);
1735                 break;
1736         case 'n': /* DSR – Device Status Report (cursor position) */
1737                 if (csiescseq.arg[0] == 6) {
1738                         len = snprintf(buf, sizeof(buf),"\033[%i;%iR",
1739                                         term.c.y+1, term.c.x+1);
1740                         ttywrite(buf, len);
1741                 }
1742                 break;
1743         case 'r': /* DECSTBM -- Set Scrolling Region */
1744                 if (csiescseq.priv) {
1745                         goto unknown;
1746                 } else {
1747                         DEFAULT(csiescseq.arg[0], 1);
1748                         DEFAULT(csiescseq.arg[1], term.row);
1749                         tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
1750                         tmoveato(0, 0);
1751                 }
1752                 break;
1753         case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
1754                 tcursor(CURSOR_SAVE);
1755                 break;
1756         case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
1757                 tcursor(CURSOR_LOAD);
1758                 break;
1759         case ' ':
1760                 switch (csiescseq.mode[1]) {
1761                 case 'q': /* DECSCUSR -- Set Cursor Style */
1762                         DEFAULT(csiescseq.arg[0], 1);
1763                         if (!BETWEEN(csiescseq.arg[0], 0, 6)) {
1764                                 goto unknown;
1765                         }
1766                         win.cursor = csiescseq.arg[0];
1767                         break;
1768                 default:
1769                         goto unknown;
1770                 }
1771                 break;
1772         }
1773 }
1774
1775 void
1776 csidump(void)
1777 {
1778         int i;
1779         uint c;
1780
1781         fprintf(stderr, "ESC[");
1782         for (i = 0; i < csiescseq.len; i++) {
1783                 c = csiescseq.buf[i] & 0xff;
1784                 if (isprint(c)) {
1785                         putc(c, stderr);
1786                 } else if (c == '\n') {
1787                         fprintf(stderr, "(\\n)");
1788                 } else if (c == '\r') {
1789                         fprintf(stderr, "(\\r)");
1790                 } else if (c == 0x1b) {
1791                         fprintf(stderr, "(\\e)");
1792                 } else {
1793                         fprintf(stderr, "(%02x)", c);
1794                 }
1795         }
1796         putc('\n', stderr);
1797 }
1798
1799 void
1800 csireset(void)
1801 {
1802         memset(&csiescseq, 0, sizeof(csiescseq));
1803 }
1804
1805 void
1806 strhandle(void)
1807 {
1808         char *p = NULL;
1809         int j, narg, par;
1810
1811         term.esc &= ~(ESC_STR_END|ESC_STR);
1812         strparse();
1813         par = (narg = strescseq.narg) ? atoi(strescseq.args[0]) : 0;
1814
1815         switch (strescseq.type) {
1816         case ']': /* OSC -- Operating System Command */
1817                 switch (par) {
1818                 case 0:
1819                 case 1:
1820                 case 2:
1821                         if (narg > 1)
1822                                 xsettitle(strescseq.args[1]);
1823                         return;
1824                 case 52:
1825                         if (narg > 2) {
1826                                 char *dec;
1827
1828                                 dec = base64dec(strescseq.args[2]);
1829                                 if (dec) {
1830                                         xsetsel(dec, CurrentTime);
1831                                         clipcopy(NULL);
1832                                 } else {
1833                                         fprintf(stderr, "erresc: invalid base64\n");
1834                                 }
1835                         }
1836                         return;
1837                 case 4: /* color set */
1838                         if (narg < 3)
1839                                 break;
1840                         p = strescseq.args[2];
1841                         /* FALLTHROUGH */
1842                 case 104: /* color reset, here p = NULL */
1843                         j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
1844                         if (xsetcolorname(j, p)) {
1845                                 fprintf(stderr, "erresc: invalid color %s\n", p);
1846                         } else {
1847                                 /*
1848                                  * TODO if defaultbg color is changed, borders
1849                                  * are dirty
1850                                  */
1851                                 redraw();
1852                         }
1853                         return;
1854                 }
1855                 break;
1856         case 'k': /* old title set compatibility */
1857                 xsettitle(strescseq.args[0]);
1858                 return;
1859         case 'P': /* DCS -- Device Control String */
1860                 term.mode |= ESC_DCS;
1861         case '_': /* APC -- Application Program Command */
1862         case '^': /* PM -- Privacy Message */
1863                 return;
1864         }
1865
1866         fprintf(stderr, "erresc: unknown str ");
1867         strdump();
1868 }
1869
1870 void
1871 strparse(void)
1872 {
1873         int c;
1874         char *p = strescseq.buf;
1875
1876         strescseq.narg = 0;
1877         strescseq.buf[strescseq.len] = '\0';
1878
1879         if (*p == '\0')
1880                 return;
1881
1882         while (strescseq.narg < STR_ARG_SIZ) {
1883                 strescseq.args[strescseq.narg++] = p;
1884                 while ((c = *p) != ';' && c != '\0')
1885                         ++p;
1886                 if (c == '\0')
1887                         return;
1888                 *p++ = '\0';
1889         }
1890 }
1891
1892 void
1893 strdump(void)
1894 {
1895         int i;
1896         uint c;
1897
1898         fprintf(stderr, "ESC%c", strescseq.type);
1899         for (i = 0; i < strescseq.len; i++) {
1900                 c = strescseq.buf[i] & 0xff;
1901                 if (c == '\0') {
1902                         putc('\n', stderr);
1903                         return;
1904                 } else if (isprint(c)) {
1905                         putc(c, stderr);
1906                 } else if (c == '\n') {
1907                         fprintf(stderr, "(\\n)");
1908                 } else if (c == '\r') {
1909                         fprintf(stderr, "(\\r)");
1910                 } else if (c == 0x1b) {
1911                         fprintf(stderr, "(\\e)");
1912                 } else {
1913                         fprintf(stderr, "(%02x)", c);
1914                 }
1915         }
1916         fprintf(stderr, "ESC\\\n");
1917 }
1918
1919 void
1920 strreset(void)
1921 {
1922         memset(&strescseq, 0, sizeof(strescseq));
1923 }
1924
1925 void
1926 sendbreak(const Arg *arg)
1927 {
1928         if (tcsendbreak(cmdfd, 0))
1929                 perror("Error sending break");
1930 }
1931
1932 void
1933 tprinter(char *s, size_t len)
1934 {
1935         if (iofd != -1 && xwrite(iofd, s, len) < 0) {
1936                 perror("Error writing to output file");
1937                 close(iofd);
1938                 iofd = -1;
1939         }
1940 }
1941
1942 void
1943 iso14755(const Arg *arg)
1944 {
1945         FILE *p;
1946         char *us, *e, codepoint[9], uc[UTF_SIZ];
1947         unsigned long utf32;
1948
1949         if (!(p = popen(ISO14755CMD, "r")))
1950                 return;
1951
1952         us = fgets(codepoint, sizeof(codepoint), p);
1953         pclose(p);
1954
1955         if (!us || *us == '\0' || *us == '-' || strlen(us) > 7)
1956                 return;
1957         if ((utf32 = strtoul(us, &e, 16)) == ULONG_MAX ||
1958             (*e != '\n' && *e != '\0'))
1959                 return;
1960
1961         ttysend(uc, utf8encode(utf32, uc));
1962 }
1963
1964 void
1965 toggleprinter(const Arg *arg)
1966 {
1967         term.mode ^= MODE_PRINT;
1968 }
1969
1970 void
1971 printscreen(const Arg *arg)
1972 {
1973         tdump();
1974 }
1975
1976 void
1977 printsel(const Arg *arg)
1978 {
1979         tdumpsel();
1980 }
1981
1982 void
1983 tdumpsel(void)
1984 {
1985         char *ptr;
1986
1987         if ((ptr = getsel())) {
1988                 tprinter(ptr, strlen(ptr));
1989                 free(ptr);
1990         }
1991 }
1992
1993 void
1994 tdumpline(int n)
1995 {
1996         char buf[UTF_SIZ];
1997         Glyph *bp, *end;
1998
1999         bp = &term.line[n][0];
2000         end = &bp[MIN(tlinelen(n), term.col) - 1];
2001         if (bp != end || bp->u != ' ') {
2002                 for ( ;bp <= end; ++bp)
2003                         tprinter(buf, utf8encode(bp->u, buf));
2004         }
2005         tprinter("\n", 1);
2006 }
2007
2008 void
2009 tdump(void)
2010 {
2011         int i;
2012
2013         for (i = 0; i < term.row; ++i)
2014                 tdumpline(i);
2015 }
2016
2017 void
2018 tputtab(int n)
2019 {
2020         uint x = term.c.x;
2021
2022         if (n > 0) {
2023                 while (x < term.col && n--)
2024                         for (++x; x < term.col && !term.tabs[x]; ++x)
2025                                 /* nothing */ ;
2026         } else if (n < 0) {
2027                 while (x > 0 && n++)
2028                         for (--x; x > 0 && !term.tabs[x]; --x)
2029                                 /* nothing */ ;
2030         }
2031         term.c.x = LIMIT(x, 0, term.col-1);
2032 }
2033
2034 void
2035 techo(Rune u)
2036 {
2037         if (ISCONTROL(u)) { /* control code */
2038                 if (u & 0x80) {
2039                         u &= 0x7f;
2040                         tputc('^');
2041                         tputc('[');
2042                 } else if (u != '\n' && u != '\r' && u != '\t') {
2043                         u ^= 0x40;
2044                         tputc('^');
2045                 }
2046         }
2047         tputc(u);
2048 }
2049
2050 void
2051 tdefutf8(char ascii)
2052 {
2053         if (ascii == 'G')
2054                 term.mode |= MODE_UTF8;
2055         else if (ascii == '@')
2056                 term.mode &= ~MODE_UTF8;
2057 }
2058
2059 void
2060 tdeftran(char ascii)
2061 {
2062         static char cs[] = "0B";
2063         static int vcs[] = {CS_GRAPHIC0, CS_USA};
2064         char *p;
2065
2066         if ((p = strchr(cs, ascii)) == NULL) {
2067                 fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
2068         } else {
2069                 term.trantbl[term.icharset] = vcs[p - cs];
2070         }
2071 }
2072
2073 void
2074 tdectest(char c)
2075 {
2076         int x, y;
2077
2078         if (c == '8') { /* DEC screen alignment test. */
2079                 for (x = 0; x < term.col; ++x) {
2080                         for (y = 0; y < term.row; ++y)
2081                                 tsetchar('E', &term.c.attr, x, y);
2082                 }
2083         }
2084 }
2085
2086 void
2087 tstrsequence(uchar c)
2088 {
2089         strreset();
2090
2091         switch (c) {
2092         case 0x90:   /* DCS -- Device Control String */
2093                 c = 'P';
2094                 term.esc |= ESC_DCS;
2095                 break;
2096         case 0x9f:   /* APC -- Application Program Command */
2097                 c = '_';
2098                 break;
2099         case 0x9e:   /* PM -- Privacy Message */
2100                 c = '^';
2101                 break;
2102         case 0x9d:   /* OSC -- Operating System Command */
2103                 c = ']';
2104                 break;
2105         }
2106         strescseq.type = c;
2107         term.esc |= ESC_STR;
2108 }
2109
2110 void
2111 tcontrolcode(uchar ascii)
2112 {
2113         switch (ascii) {
2114         case '\t':   /* HT */
2115                 tputtab(1);
2116                 return;
2117         case '\b':   /* BS */
2118                 tmoveto(term.c.x-1, term.c.y);
2119                 return;
2120         case '\r':   /* CR */
2121                 tmoveto(0, term.c.y);
2122                 return;
2123         case '\f':   /* LF */
2124         case '\v':   /* VT */
2125         case '\n':   /* LF */
2126                 /* go to first col if the mode is set */
2127                 tnewline(IS_SET(MODE_CRLF));
2128                 return;
2129         case '\a':   /* BEL */
2130                 if (term.esc & ESC_STR_END) {
2131                         /* backwards compatibility to xterm */
2132                         strhandle();
2133                 } else {
2134                         xbell();
2135                 }
2136                 break;
2137         case '\033': /* ESC */
2138                 csireset();
2139                 term.esc &= ~(ESC_CSI|ESC_ALTCHARSET|ESC_TEST);
2140                 term.esc |= ESC_START;
2141                 return;
2142         case '\016': /* SO (LS1 -- Locking shift 1) */
2143         case '\017': /* SI (LS0 -- Locking shift 0) */
2144                 term.charset = 1 - (ascii - '\016');
2145                 return;
2146         case '\032': /* SUB */
2147                 tsetchar('?', &term.c.attr, term.c.x, term.c.y);
2148         case '\030': /* CAN */
2149                 csireset();
2150                 break;
2151         case '\005': /* ENQ (IGNORED) */
2152         case '\000': /* NUL (IGNORED) */
2153         case '\021': /* XON (IGNORED) */
2154         case '\023': /* XOFF (IGNORED) */
2155         case 0177:   /* DEL (IGNORED) */
2156                 return;
2157         case 0x80:   /* TODO: PAD */
2158         case 0x81:   /* TODO: HOP */
2159         case 0x82:   /* TODO: BPH */
2160         case 0x83:   /* TODO: NBH */
2161         case 0x84:   /* TODO: IND */
2162                 break;
2163         case 0x85:   /* NEL -- Next line */
2164                 tnewline(1); /* always go to first col */
2165                 break;
2166         case 0x86:   /* TODO: SSA */
2167         case 0x87:   /* TODO: ESA */
2168                 break;
2169         case 0x88:   /* HTS -- Horizontal tab stop */
2170                 term.tabs[term.c.x] = 1;
2171                 break;
2172         case 0x89:   /* TODO: HTJ */
2173         case 0x8a:   /* TODO: VTS */
2174         case 0x8b:   /* TODO: PLD */
2175         case 0x8c:   /* TODO: PLU */
2176         case 0x8d:   /* TODO: RI */
2177         case 0x8e:   /* TODO: SS2 */
2178         case 0x8f:   /* TODO: SS3 */
2179         case 0x91:   /* TODO: PU1 */
2180         case 0x92:   /* TODO: PU2 */
2181         case 0x93:   /* TODO: STS */
2182         case 0x94:   /* TODO: CCH */
2183         case 0x95:   /* TODO: MW */
2184         case 0x96:   /* TODO: SPA */
2185         case 0x97:   /* TODO: EPA */
2186         case 0x98:   /* TODO: SOS */
2187         case 0x99:   /* TODO: SGCI */
2188                 break;
2189         case 0x9a:   /* DECID -- Identify Terminal */
2190                 ttywrite(vtiden, sizeof(vtiden) - 1);
2191                 break;
2192         case 0x9b:   /* TODO: CSI */
2193         case 0x9c:   /* TODO: ST */
2194                 break;
2195         case 0x90:   /* DCS -- Device Control String */
2196         case 0x9d:   /* OSC -- Operating System Command */
2197         case 0x9e:   /* PM -- Privacy Message */
2198         case 0x9f:   /* APC -- Application Program Command */
2199                 tstrsequence(ascii);
2200                 return;
2201         }
2202         /* only CAN, SUB, \a and C1 chars interrupt a sequence */
2203         term.esc &= ~(ESC_STR_END|ESC_STR);
2204 }
2205
2206 /*
2207  * returns 1 when the sequence is finished and it hasn't to read
2208  * more characters for this sequence, otherwise 0
2209  */
2210 int
2211 eschandle(uchar ascii)
2212 {
2213         switch (ascii) {
2214         case '[':
2215                 term.esc |= ESC_CSI;
2216                 return 0;
2217         case '#':
2218                 term.esc |= ESC_TEST;
2219                 return 0;
2220         case '%':
2221                 term.esc |= ESC_UTF8;
2222                 return 0;
2223         case 'P': /* DCS -- Device Control String */
2224         case '_': /* APC -- Application Program Command */
2225         case '^': /* PM -- Privacy Message */
2226         case ']': /* OSC -- Operating System Command */
2227         case 'k': /* old title set compatibility */
2228                 tstrsequence(ascii);
2229                 return 0;
2230         case 'n': /* LS2 -- Locking shift 2 */
2231         case 'o': /* LS3 -- Locking shift 3 */
2232                 term.charset = 2 + (ascii - 'n');
2233                 break;
2234         case '(': /* GZD4 -- set primary charset G0 */
2235         case ')': /* G1D4 -- set secondary charset G1 */
2236         case '*': /* G2D4 -- set tertiary charset G2 */
2237         case '+': /* G3D4 -- set quaternary charset G3 */
2238                 term.icharset = ascii - '(';
2239                 term.esc |= ESC_ALTCHARSET;
2240                 return 0;
2241         case 'D': /* IND -- Linefeed */
2242                 if (term.c.y == term.bot) {
2243                         tscrollup(term.top, 1);
2244                 } else {
2245                         tmoveto(term.c.x, term.c.y+1);
2246                 }
2247                 break;
2248         case 'E': /* NEL -- Next line */
2249                 tnewline(1); /* always go to first col */
2250                 break;
2251         case 'H': /* HTS -- Horizontal tab stop */
2252                 term.tabs[term.c.x] = 1;
2253                 break;
2254         case 'M': /* RI -- Reverse index */
2255                 if (term.c.y == term.top) {
2256                         tscrolldown(term.top, 1);
2257                 } else {
2258                         tmoveto(term.c.x, term.c.y-1);
2259                 }
2260                 break;
2261         case 'Z': /* DECID -- Identify Terminal */
2262                 ttywrite(vtiden, sizeof(vtiden) - 1);
2263                 break;
2264         case 'c': /* RIS -- Reset to inital state */
2265                 treset();
2266                 resettitle();
2267                 xloadcols();
2268                 break;
2269         case '=': /* DECPAM -- Application keypad */
2270                 term.mode |= MODE_APPKEYPAD;
2271                 break;
2272         case '>': /* DECPNM -- Normal keypad */
2273                 term.mode &= ~MODE_APPKEYPAD;
2274                 break;
2275         case '7': /* DECSC -- Save Cursor */
2276                 tcursor(CURSOR_SAVE);
2277                 break;
2278         case '8': /* DECRC -- Restore Cursor */
2279                 tcursor(CURSOR_LOAD);
2280                 break;
2281         case '\\': /* ST -- String Terminator */
2282                 if (term.esc & ESC_STR_END)
2283                         strhandle();
2284                 break;
2285         default:
2286                 fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
2287                         (uchar) ascii, isprint(ascii)? ascii:'.');
2288                 break;
2289         }
2290         return 1;
2291 }
2292
2293 void
2294 tputc(Rune u)
2295 {
2296         char c[UTF_SIZ];
2297         int control;
2298         int width, len;
2299         Glyph *gp;
2300
2301         control = ISCONTROL(u);
2302         if (!IS_SET(MODE_UTF8) && !IS_SET(MODE_SIXEL)) {
2303                 c[0] = u;
2304                 width = len = 1;
2305         } else {
2306                 len = utf8encode(u, c);
2307                 if (!control && (width = wcwidth(u)) == -1) {
2308                         memcpy(c, "\357\277\275", 4); /* UTF_INVALID */
2309                         width = 1;
2310                 }
2311         }
2312
2313         if (IS_SET(MODE_PRINT))
2314                 tprinter(c, len);
2315
2316         /*
2317          * STR sequence must be checked before anything else
2318          * because it uses all following characters until it
2319          * receives a ESC, a SUB, a ST or any other C1 control
2320          * character.
2321          */
2322         if (term.esc & ESC_STR) {
2323                 if (u == '\a' || u == 030 || u == 032 || u == 033 ||
2324                    ISCONTROLC1(u)) {
2325                         term.esc &= ~(ESC_START|ESC_STR|ESC_DCS);
2326                         if (IS_SET(MODE_SIXEL)) {
2327                                 /* TODO: render sixel */;
2328                                 term.mode &= ~MODE_SIXEL;
2329                                 return;
2330                         }
2331                         term.esc |= ESC_STR_END;
2332                         goto check_control_code;
2333                 }
2334
2335
2336                 if (IS_SET(MODE_SIXEL)) {
2337                         /* TODO: implement sixel mode */
2338                         return;
2339                 }
2340                 if (term.esc&ESC_DCS && strescseq.len == 0 && u == 'q')
2341                         term.mode |= MODE_SIXEL;
2342
2343                 if (strescseq.len+len >= sizeof(strescseq.buf)-1) {
2344                         /*
2345                          * Here is a bug in terminals. If the user never sends
2346                          * some code to stop the str or esc command, then st
2347                          * will stop responding. But this is better than
2348                          * silently failing with unknown characters. At least
2349                          * then users will report back.
2350                          *
2351                          * In the case users ever get fixed, here is the code:
2352                          */
2353                         /*
2354                          * term.esc = 0;
2355                          * strhandle();
2356                          */
2357                         return;
2358                 }
2359
2360                 memmove(&strescseq.buf[strescseq.len], c, len);
2361                 strescseq.len += len;
2362                 return;
2363         }
2364
2365 check_control_code:
2366         /*
2367          * Actions of control codes must be performed as soon they arrive
2368          * because they can be embedded inside a control sequence, and
2369          * they must not cause conflicts with sequences.
2370          */
2371         if (control) {
2372                 tcontrolcode(u);
2373                 /*
2374                  * control codes are not shown ever
2375                  */
2376                 return;
2377         } else if (term.esc & ESC_START) {
2378                 if (term.esc & ESC_CSI) {
2379                         csiescseq.buf[csiescseq.len++] = u;
2380                         if (BETWEEN(u, 0x40, 0x7E)
2381                                         || csiescseq.len >= \
2382                                         sizeof(csiescseq.buf)-1) {
2383                                 term.esc = 0;
2384                                 csiparse();
2385                                 csihandle();
2386                         }
2387                         return;
2388                 } else if (term.esc & ESC_UTF8) {
2389                         tdefutf8(u);
2390                 } else if (term.esc & ESC_ALTCHARSET) {
2391                         tdeftran(u);
2392                 } else if (term.esc & ESC_TEST) {
2393                         tdectest(u);
2394                 } else {
2395                         if (!eschandle(u))
2396                                 return;
2397                         /* sequence already finished */
2398                 }
2399                 term.esc = 0;
2400                 /*
2401                  * All characters which form part of a sequence are not
2402                  * printed
2403                  */
2404                 return;
2405         }
2406         if (sel.ob.x != -1 && BETWEEN(term.c.y, sel.ob.y, sel.oe.y))
2407                 selclear();
2408
2409         gp = &term.line[term.c.y][term.c.x];
2410         if (IS_SET(MODE_WRAP) && (term.c.state & CURSOR_WRAPNEXT)) {
2411                 gp->mode |= ATTR_WRAP;
2412                 tnewline(1);
2413                 gp = &term.line[term.c.y][term.c.x];
2414         }
2415
2416         if (IS_SET(MODE_INSERT) && term.c.x+width < term.col)
2417                 memmove(gp+width, gp, (term.col - term.c.x - width) * sizeof(Glyph));
2418
2419         if (term.c.x+width > term.col) {
2420                 tnewline(1);
2421                 gp = &term.line[term.c.y][term.c.x];
2422         }
2423
2424         tsetchar(u, &term.c.attr, term.c.x, term.c.y);
2425
2426         if (width == 2) {
2427                 gp->mode |= ATTR_WIDE;
2428                 if (term.c.x+1 < term.col) {
2429                         gp[1].u = '\0';
2430                         gp[1].mode = ATTR_WDUMMY;
2431                 }
2432         }
2433         if (term.c.x+width < term.col) {
2434                 tmoveto(term.c.x+width, term.c.y);
2435         } else {
2436                 term.c.state |= CURSOR_WRAPNEXT;
2437         }
2438 }
2439
2440 void
2441 tresize(int col, int row)
2442 {
2443         int i;
2444         int minrow = MIN(row, term.row);
2445         int mincol = MIN(col, term.col);
2446         int *bp;
2447         TCursor c;
2448
2449         if (col < 1 || row < 1) {
2450                 fprintf(stderr,
2451                         "tresize: error resizing to %dx%d\n", col, row);
2452                 return;
2453         }
2454
2455         /*
2456          * slide screen to keep cursor where we expect it -
2457          * tscrollup would work here, but we can optimize to
2458          * memmove because we're freeing the earlier lines
2459          */
2460         for (i = 0; i <= term.c.y - row; i++) {
2461                 free(term.line[i]);
2462                 free(term.alt[i]);
2463         }
2464         /* ensure that both src and dst are not NULL */
2465         if (i > 0) {
2466                 memmove(term.line, term.line + i, row * sizeof(Line));
2467                 memmove(term.alt, term.alt + i, row * sizeof(Line));
2468         }
2469         for (i += row; i < term.row; i++) {
2470                 free(term.line[i]);
2471                 free(term.alt[i]);
2472         }
2473
2474         /* resize to new height */
2475         term.line = xrealloc(term.line, row * sizeof(Line));
2476         term.alt  = xrealloc(term.alt,  row * sizeof(Line));
2477         term.dirty = xrealloc(term.dirty, row * sizeof(*term.dirty));
2478         term.tabs = xrealloc(term.tabs, col * sizeof(*term.tabs));
2479
2480         /* resize each row to new width, zero-pad if needed */
2481         for (i = 0; i < minrow; i++) {
2482                 term.line[i] = xrealloc(term.line[i], col * sizeof(Glyph));
2483                 term.alt[i]  = xrealloc(term.alt[i],  col * sizeof(Glyph));
2484         }
2485
2486         /* allocate any new rows */
2487         for (/* i = minrow */; i < row; i++) {
2488                 term.line[i] = xmalloc(col * sizeof(Glyph));
2489                 term.alt[i] = xmalloc(col * sizeof(Glyph));
2490         }
2491         if (col > term.col) {
2492                 bp = term.tabs + term.col;
2493
2494                 memset(bp, 0, sizeof(*term.tabs) * (col - term.col));
2495                 while (--bp > term.tabs && !*bp)
2496                         /* nothing */ ;
2497                 for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
2498                         *bp = 1;
2499         }
2500         /* update terminal size */
2501         term.col = col;
2502         term.row = row;
2503         /* reset scrolling region */
2504         tsetscroll(0, row-1);
2505         /* make use of the LIMIT in tmoveto */
2506         tmoveto(term.c.x, term.c.y);
2507         /* Clearing both screens (it makes dirty all lines) */
2508         c = term.c;
2509         for (i = 0; i < 2; i++) {
2510                 if (mincol < col && 0 < minrow) {
2511                         tclearregion(mincol, 0, col - 1, minrow - 1);
2512                 }
2513                 if (0 < col && minrow < row) {
2514                         tclearregion(0, minrow, col - 1, row - 1);
2515                 }
2516                 tswapscreen();
2517                 tcursor(CURSOR_LOAD);
2518         }
2519         term.c = c;
2520 }
2521
2522 void
2523 resettitle(void)
2524 {
2525         xsettitle(NULL);
2526 }
2527
2528 void
2529 redraw(void)
2530 {
2531         tfulldirt();
2532         draw();
2533 }
2534
2535 void
2536 numlock(const Arg *dummy)
2537 {
2538         term.numlock ^= 1;
2539 }