Xinqi Bao's Git

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