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