Xinqi Bao's Git

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