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