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