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