Xinqi Bao's Git

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