Xinqi Bao's Git

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