Xinqi Bao's Git

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