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