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