Xinqi Bao's Git

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