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