Xinqi Bao's Git

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