Xinqi Bao's Git

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