Xinqi Bao's Git

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