Xinqi Bao's Git

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