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