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