term: preserve font boldness when scrolling
[fbpad.git] / term.c
blob9c189cf34ca19aea6f051637b13644c0a9e6045c
1 #include <ctype.h>
2 #include <errno.h>
3 #include <fcntl.h>
4 #include <poll.h>
5 #include <signal.h>
6 #include <stdlib.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <sys/types.h>
10 #include <sys/ioctl.h>
11 #include <termios.h>
12 #include <unistd.h>
13 #include "conf.h"
14 #include "fbpad.h"
16 #define MODE_CURSOR 0x01
17 #define MODE_WRAP 0x02
18 #define MODE_ORIGIN 0x04
19 #define MODE_AUTOCR 0x08
20 #define ATTR_BOLD 0x10
21 #define ATTR_ITALIC 0x20
22 #define ATTR_REV 0x40
23 #define ATTR_ALL (ATTR_BOLD | ATTR_ITALIC | ATTR_REV)
24 #define MODE_INSERT 0x100
25 #define MODE_WRAPREADY 0x200
26 #define MODE_CLR8 0x400 /* colours 0-7 */
28 #define CLR_MK(fg, bg) ((fg) | ((bg) << 10))
29 #define CLR_FG(c) ((c) & 0x3ff)
30 #define CLR_BG(c) (((c) >> 10) & 0x3ff)
31 #define CLR_M(c) ((c) & (CLR_B | CLR_I))
32 #define CLR_B FN_B
33 #define CLR_I FN_I
34 #define FG 0x100
35 #define BG 0x101
37 #define LIMIT(n, a, b) ((n) < (a) ? (a) : ((n) > (b) ? (b) : (n)))
38 #define BIT_SET(i, b, val) ((val) ? ((i) | (b)) : ((i) & ~(b)))
39 #define OFFSET(r, c) ((r) * pad_cols() + (c))
41 struct term_state {
42 int row, col;
43 int fg, bg;
44 int mode;
47 struct term {
48 int *screen; /* screen content */
49 int *hist; /* scrolling history */
50 int *clr; /* foreground/background color */
51 int *dirty; /* changed rows in lazy mode */
52 struct term_state cur, sav; /* terminal saved state */
53 int fd; /* terminal file descriptor */
54 int hrow; /* the next history row in hist[] */
55 int hpos; /* scrolling history; position */
56 int lazy; /* lazy mode */
57 int pid; /* pid of the terminal program */
58 int top, bot; /* terminal scrolling region */
59 int rows, cols;
60 int signal; /* send SIGUSR1 and SIGUSR2 */
63 static struct term *term;
64 static int *screen;
65 static int *clr;
66 static int *dirty;
67 static int lazy;
68 static int row, col;
69 static int fg, bg;
70 static int top, bot;
71 static int mode;
72 static int visible;
73 static int clrfg = FGCOLOR;
74 static int clrbg = BGCOLOR;
76 static unsigned int clr16[16] = {
77 COLOR0, COLOR1, COLOR2, COLOR3, COLOR4, COLOR5, COLOR6, COLOR7,
78 COLOR8, COLOR9, COLORA, COLORB, COLORC, COLORD, COLORE, COLORF,
81 static int clrmap(int c)
83 int g = (c - 232) * 10 + 8;
84 if (c < 16)
85 return clr16[c];
86 if (c == FG)
87 return clrfg;
88 if (c == BG)
89 return clrbg;
90 if (c < 232) {
91 int ri = (c - 16) / 36 ;
92 int gi = (c - 16) % 36 / 6;
93 int bi = (c - 16) % 6;
94 int rc = ri ? (ri * 40 + 55) : 0;
95 int gc = gi ? (gi * 40 + 55) : 0;
96 int bc = bi ? (bi * 40 + 55) : 0;
97 return (rc << 16) | (gc << 8) | bc;
99 return (g << 16) | (g << 8) | g;
102 /* low level drawing and lazy updating */
104 static int color(void)
106 int c = mode & ATTR_REV ? CLR_MK(bg, fg) : CLR_MK(fg, bg);
107 if (mode & ATTR_BOLD)
108 c |= CLR_B;
109 if (mode & ATTR_ITALIC)
110 c |= CLR_I;
111 return c;
114 /* assumes visible && !lazy */
115 static void _draw_pos(int r, int c, int cursor)
117 int rev = cursor && mode & MODE_CURSOR;
118 int i = OFFSET(r, c);
119 int fg = rev ? CLR_BG(clr[i]) : CLR_FG(clr[i]);
120 int bg = rev ? CLR_FG(clr[i]) : CLR_BG(clr[i]);
121 pad_put(screen[i], r, c, CLR_M(clr[i]) | clrmap(fg), clrmap(bg));
124 /* assumes visible && !lazy */
125 static void _draw_row(int r)
127 int cbg, cch; /* current background and character */
128 int fbg, fsc = -1; /* filling background and start column */
129 int i;
130 /* call pad_fill() only once for blank columns with identical backgrounds */
131 for (i = 0; i < pad_cols(); i++) {
132 cbg = CLR_BG(clr[OFFSET(r, i)]);
133 cch = screen[OFFSET(r, i)] ? screen[OFFSET(r, i)] : ' ';
134 if (fsc >= 0 && (cbg != fbg || cch != ' ')) {
135 pad_fill(r, r + 1, fsc, i, clrmap(fbg));
136 fsc = -1;
138 if (cch != ' ') {
139 _draw_pos(r, i, 0);
140 } else if (fsc < 0) {
141 fsc = i;
142 fbg = cbg;
145 pad_fill(r, r + 1, fsc >= 0 ? fsc : pad_cols(), -1, clrmap(cbg));
148 static int candraw(int sr, int er)
150 int i;
151 if (lazy)
152 for (i = sr; i < er; i++)
153 dirty[i] = 1;
154 return visible && !lazy;
157 static void draw_rows(int sr, int er)
159 int i;
160 if (candraw(sr, er))
161 for (i = sr; i < er; i++)
162 _draw_row(i);
165 static void draw_cols(int r, int sc, int ec)
167 int i;
168 if (candraw(r, r + 1))
169 for (i = sc; i < ec; i++)
170 _draw_pos(r, i, 0);
173 static void draw_char(int ch, int r, int c)
175 int i = OFFSET(r, c);
176 screen[i] = ch;
177 clr[i] = color();
178 if (candraw(r, r + 1))
179 _draw_pos(r, c, 0);
182 static void draw_cursor(int put)
184 if (candraw(row, row + 1))
185 _draw_pos(row, col, put);
188 static void lazy_start(void)
190 memset(dirty, 0, pad_rows() * sizeof(*dirty));
191 lazy = 1;
194 static void lazy_flush(void)
196 int i;
197 if (!visible || !lazy)
198 return;
199 for (i = 0; i < pad_rows(); i++)
200 if (dirty[i])
201 _draw_row(i);
202 if (dirty[row])
203 _draw_pos(row, col, 1);
204 lazy = 0;
205 term->hpos = 0;
208 static void screen_reset(int i, int n)
210 int c;
211 candraw(i / pad_cols(), (i + n) / pad_cols());
212 memset(screen + i, 0, n * sizeof(*screen));
213 for (c = 0; c < n; c++)
214 clr[i + c] = CLR_MK(fg, bg);
217 static void screen_move(int dst, int src, int n)
219 int srow = (MIN(src, dst) + (n > 0 ? 0 : n)) / pad_cols();
220 int drow = (MAX(src, dst) + (n > 0 ? n : 0)) / pad_cols();
221 candraw(srow, drow);
222 memmove(screen + dst, screen + src, n * sizeof(*screen));
223 memmove(clr + dst, clr + src, n * sizeof(*clr));
226 /* terminal input buffering */
228 #define PTYLEN (1 << 16)
230 static char ptybuf[PTYLEN]; /* always emptied in term_read() */
231 static int ptylen; /* buffer length */
232 static int ptycur; /* current offset */
234 static int waitpty(int us)
236 struct pollfd ufds[1];
237 ufds[0].fd = term->fd;
238 ufds[0].events = POLLIN;
239 return poll(ufds, 1, us) <= 0;
242 static int readpty(void)
244 int nr;
245 if (ptycur < ptylen)
246 return (unsigned char) ptybuf[ptycur++];
247 if (!term->fd)
248 return -1;
249 ptylen = 0;
250 while ((nr = read(term->fd, ptybuf + ptylen, PTYLEN - ptylen)) > 0)
251 ptylen += nr;
252 if (!ptylen && errno == EAGAIN && !waitpty(100))
253 ptylen = read(term->fd, ptybuf, PTYLEN);
254 ptycur = 1;
255 return ptylen > 0 ? (unsigned char) ptybuf[0] : -1;
258 /* term interface functions */
260 static void term_zero(struct term *term)
262 memset(term->screen, 0, pad_rows() * pad_cols() * sizeof(term->screen[0]));
263 memset(term->hist, 0, NHIST * pad_cols() * sizeof(term->hist[0]));
264 memset(term->clr, 0, pad_rows() * pad_cols() * sizeof(term->clr[0]));
265 memset(term->dirty, 0, pad_rows() * sizeof(term->dirty[0]));
266 memset(&term->cur, 0, sizeof(term->cur));
267 memset(&term->sav, 0, sizeof(term->sav));
268 term->fd = 0;
269 term->hrow = 0;
270 term->hpos = 0;
271 term->lazy = 0;
272 term->pid = 0;
273 term->top = 0;
274 term->bot = 0;
275 term->rows = 0;
276 term->cols = 0;
277 term->signal = 0;
280 struct term *term_make(void)
282 struct term *term = malloc(sizeof(*term));
283 term->screen = malloc(pad_rows() * pad_cols() * sizeof(term->screen[0]));
284 term->hist = malloc(NHIST * pad_cols() * sizeof(term->hist[0]));
285 term->clr = malloc(pad_rows() * pad_cols() * sizeof(term->clr[0]));
286 term->dirty = malloc(pad_rows() * sizeof(term->dirty[0]));
287 term_zero(term);
288 return term;
291 void term_free(struct term *term)
293 free(term->screen);
294 free(term->hist);
295 free(term->clr);
296 free(term->dirty);
297 free(term);
300 int term_fd(struct term *term)
302 return term->fd;
305 void term_send(int c)
307 char b = c;
308 if (term->fd)
309 write(term->fd, &b, 1);
312 static void term_sendstr(char *s)
314 if (term->fd)
315 write(term->fd, s, strlen(s));
318 static void term_blank(void)
320 screen_reset(0, pad_rows() * pad_cols());
321 if (visible)
322 pad_fill(0, -1, 0, -1, clrmap(CLR_BG(color())));
325 static void ctlseq(void);
326 void term_read(void)
328 ctlseq();
329 while (ptycur < ptylen) {
330 if (visible && !lazy && ptylen - ptycur > 15)
331 lazy_start();
332 ctlseq();
334 lazy_flush();
337 static void term_reset(void)
339 row = col = 0;
340 top = 0;
341 bot = pad_rows();
342 mode = MODE_CURSOR | MODE_WRAP | MODE_CLR8;
343 fg = FG;
344 bg = BG;
345 term_blank();
348 static int _openpty(int *master, int *slave)
350 int unlock = 0;
351 int ptyno = 0;
352 char name[20];
353 if ((*master = open("/dev/ptmx", O_RDWR)) == -1)
354 return -1;
355 if (ioctl(*master, TIOCSPTLCK, &unlock) == -1)
356 return -1;
357 if (ioctl(*master, TIOCGPTN, &ptyno) == -1)
358 return -1;
359 sprintf(name, "/dev/pts/%d", ptyno);
360 *slave = open(name, O_RDWR | O_NOCTTY);
361 return 0;
364 static void tio_setsize(int fd)
366 struct winsize winp;
367 winp.ws_col = pad_cols();
368 winp.ws_row = pad_rows();
369 winp.ws_xpixel = 0;
370 winp.ws_ypixel = 0;
371 ioctl(fd, TIOCSWINSZ, &winp);
374 static void tio_login(int fd)
376 setsid();
377 ioctl(fd, TIOCSCTTY, NULL);
378 tio_setsize(fd);
379 dup2(fd, 0);
380 dup2(fd, 1);
381 dup2(fd, 2);
382 if (fd > 2)
383 close(fd);
386 static void execvep(char *cmd, char **argv, char **envp)
388 char path[512];
389 char *p = getenv("PATH");
390 execve(cmd, argv, envp);
391 while (*p) {
392 char *s = path;
393 while (*p && *p != ':')
394 *s++ = *p++;
395 *s++ = '/';
396 strcpy(s, cmd);
397 execve(path, argv, envp);
398 while (*p == ':')
399 p++;
403 static void envcpy(char **d, char **s, int len)
405 int i = 0;
406 for (i = 0; i < len - 1 && s[i]; i++)
407 d[i] = s[i];
408 d[i] = NULL;
411 static void envset(char **d, char *env)
413 int i;
414 int len = strchr(env, '=') - env;
415 for (i = 0; d[i]; i++) {
416 if (memcmp(d[i], env, len))
417 break;
419 d[i] = env;
422 extern char **environ;
423 void term_exec(char **args, int swsig)
425 int master, slave;
426 term_zero(term);
427 if (_openpty(&master, &slave) == -1)
428 return;
429 if ((term->pid = fork()) == -1)
430 return;
431 if (!term->pid) {
432 char *envp[256] = {NULL};
433 char pgid[32];
434 snprintf(pgid, sizeof(pgid), "TERM_PGID=%d", getpid());
435 envcpy(envp, environ, LEN(envp) - 3);
436 envset(envp, "TERM=" TERM);
437 envset(envp, pad_fbdev());
438 if (swsig)
439 envset(envp, pgid);
440 tio_login(slave);
441 close(master);
442 execvep(args[0], args, envp);
443 exit(1);
445 close(slave);
446 term->fd = master;
447 term->rows = pad_rows();
448 term->cols = pad_cols();
449 term->signal = swsig;
450 fcntl(term->fd, F_SETFD, fcntl(term->fd, F_GETFD) | FD_CLOEXEC);
451 fcntl(term->fd, F_SETFL, fcntl(term->fd, F_GETFL) | O_NONBLOCK);
452 term_reset();
453 memset(term->hist, 0, NHIST * pad_cols() * sizeof(term->hist[0]));
456 static void misc_save(struct term_state *state)
458 state->row = row;
459 state->col = col;
460 state->fg = fg;
461 state->bg = bg;
462 state->mode = mode;
465 static void misc_load(struct term_state *state)
467 row = state->row;
468 col = state->col;
469 fg = state->fg;
470 bg = state->bg;
471 mode = state->mode;
474 void term_save(struct term *term)
476 visible = 0;
477 if (!lazy)
478 lazy_start();
479 misc_save(&term->cur);
480 term->top = top;
481 term->bot = bot;
482 term->lazy = lazy;
485 void term_hide(struct term *term)
487 if (term->pid > 0 && term->signal)
488 kill(-term->pid, SIGUSR1);
491 void term_show(struct term *term)
493 if (term->pid > 0 && term->signal)
494 kill(-term->pid, SIGUSR2);
497 void term_signal(struct term *term)
499 term->signal = 1;
502 static void resizeupdate(int or, int oc, int nr, int nc)
504 int dr = row >= nr ? row - nr + 1 : 0;
505 int dst = nc <= oc ? 0 : nr * nc - 1;
506 while (dst >= 0 && dst < nr * nc) {
507 int r = dst / nc;
508 int c = dst % nc;
509 int src = dr + r < or && c < oc ? (dr + r) * oc + c : -1;
510 term->screen[dst] = src >= 0 ? term->screen[src] : 0;
511 term->clr[dst] = src >= 0 ? term->clr[src] : color();
512 dst = nc <= oc ? dst + 1 : dst - 1;
516 /* redraw the screen; if all is zero, update changed lines only */
517 void term_redraw(int all)
519 if (term->fd) {
520 if (term->rows != pad_rows() || term->cols != pad_cols()) {
521 tio_setsize(term->fd);
522 resizeupdate(term->rows, term->cols, pad_rows(), pad_cols());
523 if (bot == term->rows)
524 bot = pad_rows();
525 term->rows = pad_rows();
526 term->cols = pad_cols();
527 top = MIN(top, term->rows);
528 bot = MIN(bot, term->rows);
529 row = MIN(row, term->rows - 1);
530 col = MIN(col, term->cols - 1);
532 if (all) {
533 pad_fill(pad_rows(), -1, 0, -1, clrbg);
534 lazy_start();
535 memset(dirty, 1, pad_rows() * sizeof(*dirty));
537 if (all || !term->hpos)
538 lazy_flush();
539 } else {
540 if (all)
541 pad_fill(0, -1, 0, -1, 0);
545 void term_load(struct term *t, int flags)
547 term = t;
548 misc_load(&term->cur);
549 screen = term->screen;
550 clr = term->clr;
551 visible = flags;
552 top = term->top;
553 bot = term->bot;
554 lazy = term->lazy;
555 dirty = term->dirty;
558 void term_end(void)
560 if (term->fd)
561 close(term->fd);
562 term_zero(term);
563 term_load(term, visible);
564 if (visible)
565 term_redraw(1);
568 static int writeutf8(char *dst, int c)
570 char *d = dst;
571 if (c < 0x80) {
572 *d++ = c > 0 ? c : ' ';
573 return 1;
575 if (c < 0x800) {
576 *d++ = 0xc0 | (c >> 6);
577 *d++ = 0x80 | (c & 0x3f);
578 return 2;
580 if (c < 0xffff) {
581 *d++ = 0xe0 | (c >> 12);
582 *d++ = 0x80 | ((c >> 6) & 0x3f);
583 *d++ = 0x80 | (c & 0x3f);
584 return 3;
586 *d++ = 0xf0 | (c >> 18);
587 *d++ = 0x80 | ((c >> 12) & 0x3f);
588 *d++ = 0x80 | ((c >> 6) & 0x3f);
589 *d++ = 0x80 | (c & 0x3f);
590 return 4;
593 void term_screenshot(char *path)
595 char buf[1 << 11];
596 int fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
597 int i, j;
598 for (i = 0; i < pad_rows(); i++) {
599 char *s = buf;
600 for (j = 0; j < pad_cols(); j++)
601 if (~screen[OFFSET(i, j)] & DWCHAR)
602 s += writeutf8(s, screen[OFFSET(i, j)]);
603 *s++ = '\n';
604 write(fd, buf, s - buf);
606 close(fd);
609 void term_colors(char *path)
611 FILE *fp = fopen(path, "r");
612 if (fp != NULL) {
613 int i;
614 fscanf(fp, "%x %x", &clrfg, &clrbg);
615 for (i = 0; i < 16; i++)
616 fscanf(fp, "%x", &clr16[i]);
617 fclose(fp);
621 /* high-level drawing functions */
623 static void empty_rows(int sr, int er)
625 screen_reset(OFFSET(sr, 0), (er - sr) * pad_cols());
628 static void blank_rows(int sr, int er)
630 empty_rows(sr, er);
631 draw_rows(sr, er);
632 draw_cursor(1);
635 #define HISTROW(pos) (term->hist + ((term->hrow + NHIST - (pos)) % NHIST) * pad_cols())
637 static void scrl_rows(int nr)
639 int i;
640 for (i = 0; i < nr; i++) {
641 memcpy(HISTROW(0), screen + i * pad_cols(),
642 pad_cols() * sizeof(screen[0]));
643 term->hrow = (term->hrow + 1) % NHIST;
647 void term_scrl(int scrl)
649 int i, j;
650 int hpos = LIMIT(term->hpos + scrl, 0, NHIST);
651 term->hpos = hpos;
652 if (!hpos) {
653 lazy_flush();
654 return;
656 lazy_start();
657 memset(dirty, 1, pad_rows() * sizeof(*dirty));
658 for (i = 0; i < pad_rows(); i++) {
659 int off = (i - hpos) * pad_cols();
660 int *_scr = i < hpos ? HISTROW(hpos - i) : term->screen + off;
661 int *_clr = i < hpos ? NULL : term->clr + off;
662 for (j = 0; j < pad_cols(); j++) {
663 int c = _clr ? _clr[j] : CLR_MK(BG, FG);
664 pad_put(_scr[j], i, j, CLR_M(c) | clrmap(CLR_FG(c)), clrmap(CLR_BG(c)));
669 static void scroll_screen(int sr, int nr, int n)
671 draw_cursor(0);
672 if (sr + n == 0)
673 scrl_rows(sr);
674 screen_move(OFFSET(sr + n, 0), OFFSET(sr, 0), nr * pad_cols());
675 if (n > 0)
676 empty_rows(sr, sr + n);
677 else
678 empty_rows(sr + nr + n, sr + nr);
679 draw_rows(MIN(sr, sr + n), MAX(sr + nr, sr + nr + n));
680 draw_cursor(1);
683 static void insert_lines(int n)
685 int sr = MAX(top, row);
686 int nr = bot - row - n;
687 if (nr > 0)
688 scroll_screen(sr, nr, n);
691 static void delete_lines(int n)
693 int r = MAX(top, row);
694 int sr = r + n;
695 int nr = bot - r - n;
696 if (nr > 0)
697 scroll_screen(sr, nr, -n);
700 static int origin(void)
702 return mode & MODE_ORIGIN;
705 static void move_cursor(int r, int c)
707 int t, b;
708 draw_cursor(0);
709 t = origin() ? top : 0;
710 b = origin() ? bot : pad_rows();
711 row = LIMIT(r, t, b - 1);
712 col = LIMIT(c, 0, pad_cols() - 1);
713 draw_cursor(1);
714 mode = BIT_SET(mode, MODE_WRAPREADY, 0);
717 static void set_region(int t, int b)
719 top = LIMIT(t - 1, 0, pad_rows() - 1);
720 bot = LIMIT(b ? b : pad_rows(), top + 1, pad_rows());
721 if (origin())
722 move_cursor(top, 0);
725 static void setattr(int m)
727 if (!m || (m / 10) == 3)
728 mode |= MODE_CLR8;
729 switch (m) {
730 case 0:
731 fg = FG;
732 bg = BG;
733 mode &= ~ATTR_ALL;
734 break;
735 case 1:
736 mode |= ATTR_BOLD;
737 break;
738 case 3:
739 mode |= ATTR_ITALIC;
740 break;
741 case 7:
742 mode |= ATTR_REV;
743 break;
744 case 22:
745 mode &= ~ATTR_BOLD;
746 break;
747 case 23:
748 mode &= ~ATTR_ITALIC;
749 break;
750 case 27:
751 mode &= ~ATTR_REV;
752 break;
753 default:
754 if ((m / 10) == 3)
755 fg = m > 37 ? FG : m - 30;
756 if ((m / 10) == 4)
757 bg = m > 47 ? BG : m - 40;
758 if ((m / 10) == 9)
759 fg = 8 + m - 90;
760 if ((m / 10) == 10)
761 bg = 8 + m - 100;
765 static void kill_chars(int sc, int ec)
767 int i;
768 for (i = sc; i < ec; i++)
769 draw_char(0, row, i);
770 draw_cursor(1);
773 static void move_chars(int sc, int nc, int n)
775 draw_cursor(0);
776 screen_move(OFFSET(row, sc + n), OFFSET(row, sc), nc);
777 if (n > 0)
778 screen_reset(OFFSET(row, sc), n);
779 else
780 screen_reset(OFFSET(row, pad_cols() + n), -n);
781 draw_cols(row, MIN(sc, sc + n), pad_cols());
782 draw_cursor(1);
785 static void delete_chars(int n)
787 int sc = col + n;
788 int nc = pad_cols() - sc;
789 move_chars(sc, nc, -n);
792 static void insert_chars(int n)
794 int nc = pad_cols() - col - n;
795 move_chars(col, nc, n);
798 static void advance(int dr, int dc, int scrl)
800 int r = row + dr;
801 int c = col + dc;
802 if (dr && r >= bot && scrl) {
803 int n = bot - r - 1;
804 int nr = (bot - top) + n;
805 if (nr > 0)
806 scroll_screen(top + -n, nr, n);
808 if (dr && r < top && scrl) {
809 int n = top - r;
810 int nr = (bot - top) - n;
811 if (nr > 0)
812 scroll_screen(top, nr, n);
814 r = dr ? LIMIT(r, top, bot - 1) : r;
815 c = LIMIT(c, 0, pad_cols() - 1);
816 move_cursor(r, c);
819 static void insertchar(int c)
821 if (mode & MODE_WRAPREADY)
822 advance(1, -col, 1);
823 if (mode & MODE_INSERT)
824 insert_chars(1);
825 draw_char(c, row, col);
826 if (col == pad_cols() - 1)
827 mode = BIT_SET(mode, MODE_WRAPREADY, 1);
828 else
829 advance(0, 1, 1);
833 /* partial vt102 implementation */
835 static void escseq(void);
836 static void escseq_cs(void);
837 static void escseq_g0(void);
838 static void escseq_g1(void);
839 static void escseq_g2(void);
840 static void escseq_g3(void);
841 static void csiseq(void);
842 static void csiseq_da(int c);
843 static void csiseq_dsr(int c);
844 static void modeseq(int c, int set);
846 /* comments taken from: http://www.ivarch.com/programs/termvt102.shtml */
848 static int readutf8(int c)
850 int c1, c2, c3;
851 if (~c & 0xc0) /* ASCII or invalid */
852 return c;
853 c1 = readpty();
854 if (~c & 0x20)
855 return ((c & 0x1f) << 6) | (c1 & 0x3f);
856 c2 = readpty();
857 if (~c & 0x10)
858 return ((c & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f);
859 c3 = readpty();
860 if (~c & 0x08)
861 return ((c & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f);
862 return c;
865 #define unknown(ctl, c)
867 /* control sequences */
868 static void ctlseq(void)
870 int c = readpty();
871 switch (c) {
872 case 0x09: /* HT horizontal tab to next tab stop */
873 advance(0, 8 - col % 8, 0);
874 break;
875 case 0x0a: /* LF line feed */
876 case 0x0b: /* VT line feed */
877 case 0x0c: /* FF line feed */
878 advance(1, (mode & MODE_AUTOCR) ? -col : 0, 1);
879 break;
880 case 0x08: /* BS backspace one column */
881 advance(0, -1, 0);
882 break;
883 case 0x1b: /* ESC start escape sequence */
884 escseq();
885 break;
886 case 0x0d: /* CR carriage return */
887 advance(0, -col, 0);
888 break;
889 case 0x9b: /* CSI equivalent to ESC [ */
890 csiseq();
891 break;
892 case 0x00: /* NUL ignored */
893 case 0x07: /* BEL beep */
894 case 0x7f: /* DEL ignored */
895 break;
896 case 0x05: /* ENQ trigger answerback message */
897 case 0x0e: /* SO activate G1 character set & newline */
898 case 0x0f: /* SI activate G0 character set */
899 case 0x11: /* XON resume transmission */
900 case 0x13: /* XOFF stop transmission, ignore characters */
901 case 0x18: /* CAN interrupt escape sequence */
902 case 0x1a: /* SUB interrupt escape sequence */
903 unknown("ctlseq", c);
904 break;
905 default:
906 c = readutf8(c);
907 if (isdw(c) && col + 1 == pad_cols() && ~mode & MODE_WRAPREADY)
908 insertchar(0);
909 if (!iszw(c))
910 insertchar(c);
911 if (isdw(c))
912 insertchar(c | DWCHAR);
913 break;
917 #define ESCM(c) (((c) & 0xf0) == 0x20)
918 #define ESCF(c) ((c) > 0x30 && (c) < 0x7f)
920 /* escape sequences */
921 static void escseq(void)
923 int c = readpty();
924 while (ESCM(c))
925 c = readpty();
926 switch (c) {
927 case '[': /* CSI control sequence introducer */
928 csiseq();
929 break;
930 case '%': /* CS... escseq_cs table */
931 escseq_cs();
932 break;
933 case '(': /* G0... escseq_g0 table */
934 escseq_g0();
935 break;
936 case ')': /* G1... escseq_g1 table */
937 escseq_g1();
938 break;
939 case '*': /* G2... escseq_g2 table */
940 escseq_g2();
941 break;
942 case '+': /* G3... escseq_g3 table */
943 escseq_g3();
944 break;
945 case '7': /* DECSC save state (position, charset, attributes) */
946 misc_save(&term->sav);
947 break;
948 case '8': /* DECRC restore most recently saved state */
949 misc_load(&term->sav);
950 break;
951 case 'M': /* RI reverse line feed */
952 advance(-1, 0, 1);
953 break;
954 case 'D': /* IND line feed */
955 advance(1, 0, 1);
956 break;
957 case 'E': /* NEL newline */
958 advance(1, -col, 1);
959 break;
960 case 'c': /* RIS reset */
961 term_reset();
962 break;
963 case 'H': /* HTS set tab stop at current column */
964 case 'Z': /* DECID DEC private ID; return ESC [ ? 6 c (VT102) */
965 case '#': /* DECALN ("#8") DEC alignment test - fill screen with E's */
966 case '>': /* DECPNM set numeric keypad mode */
967 case '=': /* DECPAM set application keypad mode */
968 case 'N': /* SS2 select G2 charset for next char only */
969 case 'O': /* SS3 select G3 charset for next char only */
970 case 'P': /* DCS device control string (ended by ST) */
971 case 'X': /* SOS start of string */
972 case '^': /* PM privacy message (ended by ST) */
973 case '_': /* APC application program command (ended by ST) */
974 case '\\': /* ST string terminator */
975 case 'n': /* LS2 invoke G2 charset */
976 case 'o': /* LS3 invoke G3 charset */
977 case '|': /* LS3R invoke G3 charset as GR */
978 case '}': /* LS2R invoke G2 charset as GR */
979 case '~': /* LS1R invoke G1 charset as GR */
980 case ']': /* OSC operating system command */
981 case 'g': /* BEL alternate BEL */
982 default:
983 unknown("escseq", c);
984 break;
988 static void escseq_cs(void)
990 int c = readpty();
991 switch (c) {
992 case '@': /* CSDFL select default charset (ISO646/8859-1) */
993 case 'G': /* CSUTF8 select UTF-8 */
994 case '8': /* CSUTF8 select UTF-8 (obsolete) */
995 default:
996 unknown("escseq_cs", c);
997 break;
1001 static void escseq_g0(void)
1003 int c = readpty();
1004 switch (c) {
1005 case '8': /* G0DFL G0 charset = default mapping (ISO8859-1) */
1006 case '0': /* G0GFX G0 charset = VT100 graphics mapping */
1007 case 'U': /* G0ROM G0 charset = null mapping (straight to ROM) */
1008 case 'K': /* G0USR G0 charset = user defined mapping */
1009 case 'B': /* G0TXT G0 charset = ASCII mapping */
1010 default:
1011 unknown("escseq_g0", c);
1012 break;
1016 static void escseq_g1(void)
1018 int c = readpty();
1019 switch (c) {
1020 case '8': /* G1DFL G1 charset = default mapping (ISO8859-1) */
1021 case '0': /* G1GFX G1 charset = VT100 graphics mapping */
1022 case 'U': /* G1ROM G1 charset = null mapping (straight to ROM) */
1023 case 'K': /* G1USR G1 charset = user defined mapping */
1024 case 'B': /* G1TXT G1 charset = ASCII mapping */
1025 default:
1026 unknown("escseq_g1", c);
1027 break;
1031 static void escseq_g2(void)
1033 int c = readpty();
1034 switch (c) {
1035 case '8': /* G2DFL G2 charset = default mapping (ISO8859-1) */
1036 case '0': /* G2GFX G2 charset = VT100 graphics mapping */
1037 case 'U': /* G2ROM G2 charset = null mapping (straight to ROM) */
1038 case 'K': /* G2USR G2 charset = user defined mapping */
1039 default:
1040 unknown("escseq_g2", c);
1041 break;
1045 static void escseq_g3(void)
1047 int c = readpty();
1048 switch (c) {
1049 case '8': /* G3DFL G3 charset = default mapping (ISO8859-1) */
1050 case '0': /* G3GFX G3 charset = VT100 graphics mapping */
1051 case 'U': /* G3ROM G3 charset = null mapping (straight to ROM) */
1052 case 'K': /* G3USR G3 charset = user defined mapping */
1053 default:
1054 unknown("escseq_g3", c);
1055 break;
1059 static int absrow(int r)
1061 return origin() ? top + r : r;
1064 #define CSIP(c) (((c) & 0xf0) == 0x30)
1065 #define CSII(c) (((c) & 0xf0) == 0x20)
1066 #define CSIF(c) ((c) >= 0x40 && (c) < 0x80)
1068 #define MAXCSIARGS 32
1069 /* ECMA-48 CSI sequences */
1070 static void csiseq(void)
1072 int args[MAXCSIARGS + 8] = {0};
1073 int i;
1074 int n = 0;
1075 int c = readpty();
1076 int priv = 0;
1078 if (strchr("<=>?", c)) {
1079 priv = c;
1080 c = readpty();
1082 while (CSIP(c)) {
1083 int arg = 0;
1084 while (isdigit(c)) {
1085 arg = arg * 10 + (c - '0');
1086 c = readpty();
1088 if (CSIP(c))
1089 c = readpty();
1090 if (n < MAXCSIARGS)
1091 args[n++] = arg;
1093 while (CSII(c))
1094 c = readpty();
1095 switch (c) {
1096 case 'H': /* CUP move cursor to row, column */
1097 case 'f': /* HVP move cursor to row, column */
1098 move_cursor(absrow(MAX(0, args[0] - 1)), MAX(0, args[1] - 1));
1099 break;
1100 case 'J': /* ED erase display */
1101 switch (args[0]) {
1102 case 0:
1103 kill_chars(col, pad_cols());
1104 blank_rows(row + 1, pad_rows());
1105 break;
1106 case 1:
1107 kill_chars(0, col + 1);
1108 blank_rows(0, row - 1);
1109 break;
1110 case 2:
1111 term_blank();
1112 break;
1114 break;
1115 case 'A': /* CUU move cursor up */
1116 advance(-MAX(1, args[0]), 0, 0);
1117 break;
1118 case 'e': /* VPR move cursor down */
1119 case 'B': /* CUD move cursor down */
1120 advance(MAX(1, args[0]), 0, 0);
1121 break;
1122 case 'a': /* HPR move cursor right */
1123 case 'C': /* CUF move cursor right */
1124 advance(0, MAX(1, args[0]), 0);
1125 break;
1126 case 'D': /* CUB move cursor left */
1127 advance(0, -MAX(1, args[0]), 0);
1128 break;
1129 case 'K': /* EL erase line */
1130 switch (args[0]) {
1131 case 0:
1132 kill_chars(col, pad_cols());
1133 break;
1134 case 1:
1135 kill_chars(0, col + 1);
1136 break;
1137 case 2:
1138 kill_chars(0, pad_cols());
1139 break;
1141 break;
1142 case 'L': /* IL insert blank lines */
1143 if (row >= top && row < bot)
1144 insert_lines(MAX(1, args[0]));
1145 break;
1146 case 'M': /* DL delete lines */
1147 if (row >= top && row < bot)
1148 delete_lines(MAX(1, args[0]));
1149 break;
1150 case 'S': /* SU scroll up */
1151 i = MAX(1, args[0]);
1152 scroll_screen(i, pad_rows() - i, -i);
1153 break;
1154 case 'T': /* SD scroll down */
1155 i = MAX(1, args[0]);
1156 scroll_screen(0, pad_rows() - i, i);
1157 break;
1158 case 'd': /* VPA move to row (current column) */
1159 move_cursor(absrow(MAX(1, args[0]) - 1), col);
1160 break;
1161 case 'm': /* SGR set graphic rendition */
1162 if (!n)
1163 setattr(0);
1164 for (i = 0; i < n; i++) {
1165 if (args[i] == 38 && args[i + 1] == 2) {
1166 mode &= ~MODE_CLR8;
1167 fg = (args[i + 2] << 16) |
1168 (args[i + 3] << 8) | args[i + 4];
1169 i += 5;
1170 continue;
1172 if (args[i] == 38) {
1173 mode &= ~MODE_CLR8;
1174 fg = args[i + 2];
1175 i += 2;
1176 continue;
1178 if (args[i] == 48 && args[i + 1] == 2) {
1179 bg = (args[i + 2] << 16) |
1180 (args[i + 3] << 8) | args[i + 4];
1181 i += 5;
1182 continue;
1184 if (args[i] == 48) {
1185 bg = args[i + 2];
1186 i += 2;
1187 continue;
1189 setattr(args[i]);
1191 if (mode & MODE_CLR8 && mode & ATTR_BOLD && BRIGHTEN)
1192 for (i = 0; i < 8; i++)
1193 if (clr16[i] == fg)
1194 fg = clr16[8 + i];
1195 break;
1196 case 'r': /* DECSTBM set scrolling region to (top, bottom) rows */
1197 set_region(args[0], args[1]);
1198 break;
1199 case 'c': /* DA return ESC [ ? 6 c (VT102) */
1200 csiseq_da(priv == '?' ? args[0] | 0x80 : args[0]);
1201 break;
1202 case 'h': /* SM set mode */
1203 for (i = 0; i < n; i++)
1204 modeseq(priv == '?' ? args[i] | 0x80 : args[i], 1);
1205 draw_cursor(1);
1206 break;
1207 case 'l': /* RM reset mode */
1208 for (i = 0; i < n; i++)
1209 modeseq(priv == '?' ? args[i] | 0x80 : args[i], 0);
1210 draw_cursor(1);
1211 break;
1212 case 'P': /* DCH delete characters on current line */
1213 delete_chars(LIMIT(args[0], 1, pad_cols() - col));
1214 break;
1215 case '@': /* ICH insert blank characters */
1216 insert_chars(LIMIT(args[0], 1, pad_cols() - col));
1217 break;
1218 case 'n': /* DSR device status report */
1219 csiseq_dsr(args[0]);
1220 break;
1221 case 'G': /* CHA move cursor to column in current row */
1222 advance(0, MAX(0, args[0] - 1) - col, 0);
1223 break;
1224 case 'X': /* ECH erase characters on current line */
1225 kill_chars(col, MIN(col + MAX(1, args[0]), pad_cols()));
1226 break;
1227 case '[': /* IGN ignored control sequence */
1228 case 'E': /* CNL move cursor down and to column 1 */
1229 case 'F': /* CPL move cursor up and to column 1 */
1230 case 'g': /* TBC clear tab stop (CSI 3 g = clear all stops) */
1231 case 'q': /* DECLL set keyboard LEDs */
1232 case 's': /* CUPSV save cursor position */
1233 case 'u': /* CUPRS restore cursor position */
1234 case '`': /* HPA move cursor to column in current row */
1235 default:
1236 unknown("csiseq", c);
1237 break;
1241 static void csiseq_da(int c)
1243 switch (c) {
1244 case 0x00:
1245 term_sendstr("\x1b[?6c");
1246 break;
1247 default:
1248 /* we don't care much about cursor shape */
1249 /* printf("csiseq_da <0x%x>\n", c); */
1250 break;
1254 static void csiseq_dsr(int c)
1256 char status[1 << 5];
1257 switch (c) {
1258 case 0x05:
1259 term_sendstr("\x1b[0n");
1260 break;
1261 case 0x06:
1262 sprintf(status, "\x1b[%d;%dR",
1263 (origin() ? row - top : row) + 1, col + 1);
1264 term_sendstr(status);
1265 break;
1266 default:
1267 unknown("csiseq_dsr", c);
1268 break;
1272 /* ANSI/DEC specified modes for SM/RM ANSI Specified Modes */
1273 static void modeseq(int c, int set)
1275 switch (c) {
1276 case 0x87: /* DECAWM Auto Wrap */
1277 mode = BIT_SET(mode, MODE_WRAP, set);
1278 break;
1279 case 0x99: /* DECTCEM Cursor on (set); Cursor off (reset) */
1280 mode = BIT_SET(mode, MODE_CURSOR, set);
1281 break;
1282 case 0x86: /* DECOM Sets relative coordinates (set); Sets absolute coordinates (reset) */
1283 mode = BIT_SET(mode, MODE_ORIGIN, set);
1284 break;
1285 case 0x14: /* LNM Line Feed / New Line Mode */
1286 mode = BIT_SET(mode, MODE_AUTOCR, set);
1287 break;
1288 case 0x04: /* IRM insertion/replacement mode (always reset) */
1289 mode = BIT_SET(mode, MODE_INSERT, set);
1290 break;
1291 case 0x00: /* IGN error (ignored) */
1292 case 0x01: /* GATM guarded-area transfer mode (ignored) */
1293 case 0x02: /* KAM keyboard action mode (always reset) */
1294 case 0x03: /* CRM control representation mode (always reset) */
1295 case 0x05: /* SRTM status-reporting transfer mode */
1296 case 0x06: /* ERM erasure mode (always set) */
1297 case 0x07: /* VEM vertical editing mode (ignored) */
1298 case 0x08: /* BDSM bi-directional support mode */
1299 case 0x0a: /* HEM horizontal editing mode */
1300 case 0x0b: /* PUM positioning unit mode */
1301 case 0x0c: /* SRM send/receive mode (echo on/off) */
1302 case 0x0d: /* FEAM format effector action mode */
1303 case 0x0e: /* FETM format effector transfer mode */
1304 case 0x0f: /* MATM multiple area transfer mode */
1305 case 0x10: /* TTM transfer termination mode */
1306 case 0x11: /* SATM selected area transfer mode */
1307 case 0x12: /* TSM tabulation stop mode */
1308 case 0x13: /* EBM editing boundary mode */
1309 /* DEC Private Modes: "?NUM" -> (NUM | 0x80) */
1310 case 0x80: /* IGN error (ignored) */
1311 case 0x81: /* DECCKM cursorkeys application (set); cursorkeys normal (reset) */
1312 case 0x82: /* DECANM ANSI (set); VT52 (reset) */
1313 case 0x83: /* DECCOLM 132 columns (set); 80 columns (reset) */
1314 case 0x84: /* DECSCLM jump scroll (set); smooth scroll (reset) */
1315 case 0x85: /* DECSCNM reverse screen (set); normal screen (reset) */
1316 case 0x88: /* DECARM auto repeat */
1317 case 0x89: /* DECINLM interlace */
1318 case 0x92: /* DECPFF send FF to printer after print screen (set); no char after PS (reset) */
1319 case 0x93: /* DECPEX print screen: prints full screen (set); prints scroll region (reset) */
1320 default:
1321 unknown("modeseq", c);
1322 break;