Xinqi Bao's Git

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