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