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