create .gitignore
[fbpad.git] / term.c
blob480bee8817b1344f553be2dc5a4d500bc960e84f
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 int term_colors(char *path)
611 char t[256];
612 FILE *fp = fopen(path, "r");
613 if (fp == NULL)
614 return 1;
615 while (fscanf(fp, "%31s", t) == 1) {
616 if (!strcmp("color", t)) {
617 fscanf(fp, "%x %x", &clrfg, &clrbg);
618 } else if (!strcmp("color16", t)) {
619 int i;
620 for (i = 0; i < 16; i++)
621 fscanf(fp, "%x", &clr16[i]);
622 } else if (!strcmp("font", t)) {
623 char fr[256], fi[256], fb[256];
624 if (fscanf(fp, "%255s %255s %255s", fr, fi, fb) == 3)
625 pad_font(fr, fi, fb);
627 fgets(t, sizeof(t), fp);
629 return 0;
632 /* high-level drawing functions */
634 static void empty_rows(int sr, int er)
636 screen_reset(OFFSET(sr, 0), (er - sr) * pad_cols());
639 static void blank_rows(int sr, int er)
641 empty_rows(sr, er);
642 draw_rows(sr, er);
643 draw_cursor(1);
646 #define HISTROW(pos) (term->hist + ((term->hrow + NHIST - (pos)) % NHIST) * pad_cols())
648 static void scrl_rows(int nr)
650 int i;
651 for (i = 0; i < nr; i++) {
652 memcpy(HISTROW(0), screen + i * pad_cols(),
653 pad_cols() * sizeof(screen[0]));
654 term->hrow = (term->hrow + 1) % NHIST;
658 void term_scrl(int scrl)
660 int i, j;
661 int hpos = LIMIT(term->hpos + scrl, 0, NHIST);
662 term->hpos = hpos;
663 if (!hpos) {
664 lazy_flush();
665 return;
667 lazy_start();
668 memset(dirty, 1, pad_rows() * sizeof(*dirty));
669 for (i = 0; i < pad_rows(); i++) {
670 int off = (i - hpos) * pad_cols();
671 int *_scr = i < hpos ? HISTROW(hpos - i) : term->screen + off;
672 int *_clr = i < hpos ? NULL : term->clr + off;
673 for (j = 0; j < pad_cols(); j++) {
674 int c = _clr ? _clr[j] : CLR_MK(BG, FG);
675 pad_put(_scr[j], i, j, CLR_M(c) | clrmap(CLR_FG(c)), clrmap(CLR_BG(c)));
680 static void scroll_screen(int sr, int nr, int n)
682 draw_cursor(0);
683 if (sr + n == 0)
684 scrl_rows(sr);
685 screen_move(OFFSET(sr + n, 0), OFFSET(sr, 0), nr * pad_cols());
686 if (n > 0)
687 empty_rows(sr, sr + n);
688 else
689 empty_rows(sr + nr + n, sr + nr);
690 draw_rows(MIN(sr, sr + n), MAX(sr + nr, sr + nr + n));
691 draw_cursor(1);
694 static void insert_lines(int n)
696 int sr = MAX(top, row);
697 int nr = bot - row - n;
698 if (nr > 0)
699 scroll_screen(sr, nr, n);
702 static void delete_lines(int n)
704 int r = MAX(top, row);
705 int sr = r + n;
706 int nr = bot - r - n;
707 if (nr > 0)
708 scroll_screen(sr, nr, -n);
711 static int origin(void)
713 return mode & MODE_ORIGIN;
716 static void move_cursor(int r, int c)
718 int t, b;
719 draw_cursor(0);
720 t = origin() ? top : 0;
721 b = origin() ? bot : pad_rows();
722 row = LIMIT(r, t, b - 1);
723 col = LIMIT(c, 0, pad_cols() - 1);
724 draw_cursor(1);
725 mode = BIT_SET(mode, MODE_WRAPREADY, 0);
728 static void set_region(int t, int b)
730 top = LIMIT(t - 1, 0, pad_rows() - 1);
731 bot = LIMIT(b ? b : pad_rows(), top + 1, pad_rows());
732 if (origin())
733 move_cursor(top, 0);
736 static void setattr(int m)
738 if (!m || (m / 10) == 3)
739 mode |= MODE_CLR8;
740 switch (m) {
741 case 0:
742 fg = FG;
743 bg = BG;
744 mode &= ~ATTR_ALL;
745 break;
746 case 1:
747 mode |= ATTR_BOLD;
748 break;
749 case 3:
750 mode |= ATTR_ITALIC;
751 break;
752 case 7:
753 mode |= ATTR_REV;
754 break;
755 case 22:
756 mode &= ~ATTR_BOLD;
757 break;
758 case 23:
759 mode &= ~ATTR_ITALIC;
760 break;
761 case 27:
762 mode &= ~ATTR_REV;
763 break;
764 default:
765 if ((m / 10) == 3)
766 fg = m > 37 ? FG : m - 30;
767 if ((m / 10) == 4)
768 bg = m > 47 ? BG : m - 40;
769 if ((m / 10) == 9)
770 fg = 8 + m - 90;
771 if ((m / 10) == 10)
772 bg = 8 + m - 100;
776 static void kill_chars(int sc, int ec)
778 int i;
779 for (i = sc; i < ec; i++)
780 draw_char(0, row, i);
781 draw_cursor(1);
784 static void move_chars(int sc, int nc, int n)
786 draw_cursor(0);
787 screen_move(OFFSET(row, sc + n), OFFSET(row, sc), nc);
788 if (n > 0)
789 screen_reset(OFFSET(row, sc), n);
790 else
791 screen_reset(OFFSET(row, pad_cols() + n), -n);
792 draw_cols(row, MIN(sc, sc + n), pad_cols());
793 draw_cursor(1);
796 static void delete_chars(int n)
798 int sc = col + n;
799 int nc = pad_cols() - sc;
800 move_chars(sc, nc, -n);
803 static void insert_chars(int n)
805 int nc = pad_cols() - col - n;
806 move_chars(col, nc, n);
809 static void advance(int dr, int dc, int scrl)
811 int r = row + dr;
812 int c = col + dc;
813 if (dr && r >= bot && scrl) {
814 int n = bot - r - 1;
815 int nr = (bot - top) + n;
816 if (nr > 0)
817 scroll_screen(top + -n, nr, n);
819 if (dr && r < top && scrl) {
820 int n = top - r;
821 int nr = (bot - top) - n;
822 if (nr > 0)
823 scroll_screen(top, nr, n);
825 r = dr ? LIMIT(r, top, bot - 1) : r;
826 c = LIMIT(c, 0, pad_cols() - 1);
827 move_cursor(r, c);
830 static void insertchar(int c)
832 if (mode & MODE_WRAPREADY)
833 advance(1, -col, 1);
834 if (mode & MODE_INSERT)
835 insert_chars(1);
836 draw_char(c, row, col);
837 if (col == pad_cols() - 1)
838 mode = BIT_SET(mode, MODE_WRAPREADY, 1);
839 else
840 advance(0, 1, 1);
844 /* partial vt102 implementation */
846 static void escseq(void);
847 static void escseq_cs(void);
848 static void escseq_g0(void);
849 static void escseq_g1(void);
850 static void escseq_g2(void);
851 static void escseq_g3(void);
852 static void csiseq(void);
853 static void csiseq_da(int c);
854 static void csiseq_dsr(int c);
855 static void modeseq(int c, int set);
857 /* comments taken from: http://www.ivarch.com/programs/termvt102.shtml */
859 static int readutf8(int c)
861 int c1, c2, c3;
862 if (~c & 0xc0) /* ASCII or invalid */
863 return c;
864 c1 = readpty();
865 if (~c & 0x20)
866 return ((c & 0x1f) << 6) | (c1 & 0x3f);
867 c2 = readpty();
868 if (~c & 0x10)
869 return ((c & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f);
870 c3 = readpty();
871 if (~c & 0x08)
872 return ((c & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6) | (c3 & 0x3f);
873 return c;
876 #define unknown(ctl, c)
878 /* control sequences */
879 static void ctlseq(void)
881 int c = readpty();
882 switch (c) {
883 case 0x09: /* HT horizontal tab to next tab stop */
884 advance(0, 8 - col % 8, 0);
885 break;
886 case 0x0a: /* LF line feed */
887 case 0x0b: /* VT line feed */
888 case 0x0c: /* FF line feed */
889 advance(1, (mode & MODE_AUTOCR) ? -col : 0, 1);
890 break;
891 case 0x08: /* BS backspace one column */
892 advance(0, -1, 0);
893 break;
894 case 0x1b: /* ESC start escape sequence */
895 escseq();
896 break;
897 case 0x0d: /* CR carriage return */
898 advance(0, -col, 0);
899 break;
900 case 0x9b: /* CSI equivalent to ESC [ */
901 csiseq();
902 break;
903 case 0x00: /* NUL ignored */
904 case 0x07: /* BEL beep */
905 case 0x7f: /* DEL ignored */
906 break;
907 case 0x05: /* ENQ trigger answerback message */
908 case 0x0e: /* SO activate G1 character set & newline */
909 case 0x0f: /* SI activate G0 character set */
910 case 0x11: /* XON resume transmission */
911 case 0x13: /* XOFF stop transmission, ignore characters */
912 case 0x18: /* CAN interrupt escape sequence */
913 case 0x1a: /* SUB interrupt escape sequence */
914 unknown("ctlseq", c);
915 break;
916 default:
917 c = readutf8(c);
918 if (isdw(c) && col + 1 == pad_cols() && ~mode & MODE_WRAPREADY)
919 insertchar(0);
920 if (!iszw(c))
921 insertchar(c);
922 if (isdw(c))
923 insertchar(c | DWCHAR);
924 break;
928 #define ESCM(c) (((c) & 0xf0) == 0x20)
929 #define ESCF(c) ((c) > 0x30 && (c) < 0x7f)
931 /* escape sequences */
932 static void escseq(void)
934 int c = readpty();
935 while (ESCM(c))
936 c = readpty();
937 switch (c) {
938 case '[': /* CSI control sequence introducer */
939 csiseq();
940 break;
941 case '%': /* CS... escseq_cs table */
942 escseq_cs();
943 break;
944 case '(': /* G0... escseq_g0 table */
945 escseq_g0();
946 break;
947 case ')': /* G1... escseq_g1 table */
948 escseq_g1();
949 break;
950 case '*': /* G2... escseq_g2 table */
951 escseq_g2();
952 break;
953 case '+': /* G3... escseq_g3 table */
954 escseq_g3();
955 break;
956 case '7': /* DECSC save state (position, charset, attributes) */
957 misc_save(&term->sav);
958 break;
959 case '8': /* DECRC restore most recently saved state */
960 misc_load(&term->sav);
961 break;
962 case 'M': /* RI reverse line feed */
963 advance(-1, 0, 1);
964 break;
965 case 'D': /* IND line feed */
966 advance(1, 0, 1);
967 break;
968 case 'E': /* NEL newline */
969 advance(1, -col, 1);
970 break;
971 case 'c': /* RIS reset */
972 term_reset();
973 break;
974 case 'H': /* HTS set tab stop at current column */
975 case 'Z': /* DECID DEC private ID; return ESC [ ? 6 c (VT102) */
976 case '#': /* DECALN ("#8") DEC alignment test - fill screen with E's */
977 case '>': /* DECPNM set numeric keypad mode */
978 case '=': /* DECPAM set application keypad mode */
979 case 'N': /* SS2 select G2 charset for next char only */
980 case 'O': /* SS3 select G3 charset for next char only */
981 case 'P': /* DCS device control string (ended by ST) */
982 case 'X': /* SOS start of string */
983 case '^': /* PM privacy message (ended by ST) */
984 case '_': /* APC application program command (ended by ST) */
985 case '\\': /* ST string terminator */
986 case 'n': /* LS2 invoke G2 charset */
987 case 'o': /* LS3 invoke G3 charset */
988 case '|': /* LS3R invoke G3 charset as GR */
989 case '}': /* LS2R invoke G2 charset as GR */
990 case '~': /* LS1R invoke G1 charset as GR */
991 case ']': /* OSC operating system command */
992 case 'g': /* BEL alternate BEL */
993 default:
994 unknown("escseq", c);
995 break;
999 static void escseq_cs(void)
1001 int c = readpty();
1002 switch (c) {
1003 case '@': /* CSDFL select default charset (ISO646/8859-1) */
1004 case 'G': /* CSUTF8 select UTF-8 */
1005 case '8': /* CSUTF8 select UTF-8 (obsolete) */
1006 default:
1007 unknown("escseq_cs", c);
1008 break;
1012 static void escseq_g0(void)
1014 int c = readpty();
1015 switch (c) {
1016 case '8': /* G0DFL G0 charset = default mapping (ISO8859-1) */
1017 case '0': /* G0GFX G0 charset = VT100 graphics mapping */
1018 case 'U': /* G0ROM G0 charset = null mapping (straight to ROM) */
1019 case 'K': /* G0USR G0 charset = user defined mapping */
1020 case 'B': /* G0TXT G0 charset = ASCII mapping */
1021 default:
1022 unknown("escseq_g0", c);
1023 break;
1027 static void escseq_g1(void)
1029 int c = readpty();
1030 switch (c) {
1031 case '8': /* G1DFL G1 charset = default mapping (ISO8859-1) */
1032 case '0': /* G1GFX G1 charset = VT100 graphics mapping */
1033 case 'U': /* G1ROM G1 charset = null mapping (straight to ROM) */
1034 case 'K': /* G1USR G1 charset = user defined mapping */
1035 case 'B': /* G1TXT G1 charset = ASCII mapping */
1036 default:
1037 unknown("escseq_g1", c);
1038 break;
1042 static void escseq_g2(void)
1044 int c = readpty();
1045 switch (c) {
1046 case '8': /* G2DFL G2 charset = default mapping (ISO8859-1) */
1047 case '0': /* G2GFX G2 charset = VT100 graphics mapping */
1048 case 'U': /* G2ROM G2 charset = null mapping (straight to ROM) */
1049 case 'K': /* G2USR G2 charset = user defined mapping */
1050 default:
1051 unknown("escseq_g2", c);
1052 break;
1056 static void escseq_g3(void)
1058 int c = readpty();
1059 switch (c) {
1060 case '8': /* G3DFL G3 charset = default mapping (ISO8859-1) */
1061 case '0': /* G3GFX G3 charset = VT100 graphics mapping */
1062 case 'U': /* G3ROM G3 charset = null mapping (straight to ROM) */
1063 case 'K': /* G3USR G3 charset = user defined mapping */
1064 default:
1065 unknown("escseq_g3", c);
1066 break;
1070 static int absrow(int r)
1072 return origin() ? top + r : r;
1075 #define CSIP(c) (((c) & 0xf0) == 0x30)
1076 #define CSII(c) (((c) & 0xf0) == 0x20)
1077 #define CSIF(c) ((c) >= 0x40 && (c) < 0x80)
1079 #define MAXCSIARGS 32
1080 /* ECMA-48 CSI sequences */
1081 static void csiseq(void)
1083 int args[MAXCSIARGS + 8] = {0};
1084 int i;
1085 int n = 0;
1086 int c = readpty();
1087 int priv = 0;
1089 if (strchr("<=>?", c)) {
1090 priv = c;
1091 c = readpty();
1093 while (CSIP(c)) {
1094 int arg = 0;
1095 while (isdigit(c)) {
1096 arg = arg * 10 + (c - '0');
1097 c = readpty();
1099 if (CSIP(c))
1100 c = readpty();
1101 if (n < MAXCSIARGS)
1102 args[n++] = arg;
1104 while (CSII(c))
1105 c = readpty();
1106 switch (c) {
1107 case 'H': /* CUP move cursor to row, column */
1108 case 'f': /* HVP move cursor to row, column */
1109 move_cursor(absrow(MAX(0, args[0] - 1)), MAX(0, args[1] - 1));
1110 break;
1111 case 'J': /* ED erase display */
1112 switch (args[0]) {
1113 case 0:
1114 kill_chars(col, pad_cols());
1115 blank_rows(row + 1, pad_rows());
1116 break;
1117 case 1:
1118 kill_chars(0, col + 1);
1119 blank_rows(0, row - 1);
1120 break;
1121 case 2:
1122 term_blank();
1123 break;
1125 break;
1126 case 'A': /* CUU move cursor up */
1127 advance(-MAX(1, args[0]), 0, 0);
1128 break;
1129 case 'e': /* VPR move cursor down */
1130 case 'B': /* CUD move cursor down */
1131 advance(MAX(1, args[0]), 0, 0);
1132 break;
1133 case 'a': /* HPR move cursor right */
1134 case 'C': /* CUF move cursor right */
1135 advance(0, MAX(1, args[0]), 0);
1136 break;
1137 case 'D': /* CUB move cursor left */
1138 advance(0, -MAX(1, args[0]), 0);
1139 break;
1140 case 'K': /* EL erase line */
1141 switch (args[0]) {
1142 case 0:
1143 kill_chars(col, pad_cols());
1144 break;
1145 case 1:
1146 kill_chars(0, col + 1);
1147 break;
1148 case 2:
1149 kill_chars(0, pad_cols());
1150 break;
1152 break;
1153 case 'L': /* IL insert blank lines */
1154 if (row >= top && row < bot)
1155 insert_lines(MAX(1, args[0]));
1156 break;
1157 case 'M': /* DL delete lines */
1158 if (row >= top && row < bot)
1159 delete_lines(MAX(1, args[0]));
1160 break;
1161 case 'S': /* SU scroll up */
1162 i = MAX(1, args[0]);
1163 scroll_screen(i, pad_rows() - i, -i);
1164 break;
1165 case 'T': /* SD scroll down */
1166 i = MAX(1, args[0]);
1167 scroll_screen(0, pad_rows() - i, i);
1168 break;
1169 case 'd': /* VPA move to row (current column) */
1170 move_cursor(absrow(MAX(1, args[0]) - 1), col);
1171 break;
1172 case 'm': /* SGR set graphic rendition */
1173 if (!n)
1174 setattr(0);
1175 for (i = 0; i < n; i++) {
1176 if (args[i] == 38 && args[i + 1] == 2) {
1177 mode &= ~MODE_CLR8;
1178 fg = (args[i + 2] << 16) |
1179 (args[i + 3] << 8) | args[i + 4];
1180 i += 5;
1181 continue;
1183 if (args[i] == 38) {
1184 mode &= ~MODE_CLR8;
1185 fg = args[i + 2];
1186 i += 2;
1187 continue;
1189 if (args[i] == 48 && args[i + 1] == 2) {
1190 bg = (args[i + 2] << 16) |
1191 (args[i + 3] << 8) | args[i + 4];
1192 i += 5;
1193 continue;
1195 if (args[i] == 48) {
1196 bg = args[i + 2];
1197 i += 2;
1198 continue;
1200 setattr(args[i]);
1202 if (mode & MODE_CLR8 && mode & ATTR_BOLD && BRIGHTEN)
1203 for (i = 0; i < 8; i++)
1204 if (clr16[i] == fg)
1205 fg = clr16[8 + i];
1206 break;
1207 case 'r': /* DECSTBM set scrolling region to (top, bottom) rows */
1208 set_region(args[0], args[1]);
1209 break;
1210 case 'c': /* DA return ESC [ ? 6 c (VT102) */
1211 csiseq_da(priv == '?' ? args[0] | 0x80 : args[0]);
1212 break;
1213 case 'h': /* SM set mode */
1214 for (i = 0; i < n; i++)
1215 modeseq(priv == '?' ? args[i] | 0x80 : args[i], 1);
1216 draw_cursor(1);
1217 break;
1218 case 'l': /* RM reset mode */
1219 for (i = 0; i < n; i++)
1220 modeseq(priv == '?' ? args[i] | 0x80 : args[i], 0);
1221 draw_cursor(1);
1222 break;
1223 case 'P': /* DCH delete characters on current line */
1224 delete_chars(LIMIT(args[0], 1, pad_cols() - col));
1225 break;
1226 case '@': /* ICH insert blank characters */
1227 insert_chars(LIMIT(args[0], 1, pad_cols() - col));
1228 break;
1229 case 'n': /* DSR device status report */
1230 csiseq_dsr(args[0]);
1231 break;
1232 case 'G': /* CHA move cursor to column in current row */
1233 advance(0, MAX(0, args[0] - 1) - col, 0);
1234 break;
1235 case 'X': /* ECH erase characters on current line */
1236 kill_chars(col, MIN(col + MAX(1, args[0]), pad_cols()));
1237 break;
1238 case '[': /* IGN ignored control sequence */
1239 case 'E': /* CNL move cursor down and to column 1 */
1240 case 'F': /* CPL move cursor up and to column 1 */
1241 case 'g': /* TBC clear tab stop (CSI 3 g = clear all stops) */
1242 case 'q': /* DECLL set keyboard LEDs */
1243 case 's': /* CUPSV save cursor position */
1244 case 'u': /* CUPRS restore cursor position */
1245 case '`': /* HPA move cursor to column in current row */
1246 default:
1247 unknown("csiseq", c);
1248 break;
1252 static void csiseq_da(int c)
1254 switch (c) {
1255 case 0x00:
1256 term_sendstr("\x1b[?6c");
1257 break;
1258 default:
1259 /* we don't care much about cursor shape */
1260 /* printf("csiseq_da <0x%x>\n", c); */
1261 break;
1265 static void csiseq_dsr(int c)
1267 char status[1 << 5];
1268 switch (c) {
1269 case 0x05:
1270 term_sendstr("\x1b[0n");
1271 break;
1272 case 0x06:
1273 sprintf(status, "\x1b[%d;%dR",
1274 (origin() ? row - top : row) + 1, col + 1);
1275 term_sendstr(status);
1276 break;
1277 default:
1278 unknown("csiseq_dsr", c);
1279 break;
1283 /* ANSI/DEC specified modes for SM/RM ANSI Specified Modes */
1284 static void modeseq(int c, int set)
1286 switch (c) {
1287 case 0x87: /* DECAWM Auto Wrap */
1288 mode = BIT_SET(mode, MODE_WRAP, set);
1289 break;
1290 case 0x99: /* DECTCEM Cursor on (set); Cursor off (reset) */
1291 mode = BIT_SET(mode, MODE_CURSOR, set);
1292 break;
1293 case 0x86: /* DECOM Sets relative coordinates (set); Sets absolute coordinates (reset) */
1294 mode = BIT_SET(mode, MODE_ORIGIN, set);
1295 break;
1296 case 0x14: /* LNM Line Feed / New Line Mode */
1297 mode = BIT_SET(mode, MODE_AUTOCR, set);
1298 break;
1299 case 0x04: /* IRM insertion/replacement mode (always reset) */
1300 mode = BIT_SET(mode, MODE_INSERT, set);
1301 break;
1302 case 0x00: /* IGN error (ignored) */
1303 case 0x01: /* GATM guarded-area transfer mode (ignored) */
1304 case 0x02: /* KAM keyboard action mode (always reset) */
1305 case 0x03: /* CRM control representation mode (always reset) */
1306 case 0x05: /* SRTM status-reporting transfer mode */
1307 case 0x06: /* ERM erasure mode (always set) */
1308 case 0x07: /* VEM vertical editing mode (ignored) */
1309 case 0x08: /* BDSM bi-directional support mode */
1310 case 0x0a: /* HEM horizontal editing mode */
1311 case 0x0b: /* PUM positioning unit mode */
1312 case 0x0c: /* SRM send/receive mode (echo on/off) */
1313 case 0x0d: /* FEAM format effector action mode */
1314 case 0x0e: /* FETM format effector transfer mode */
1315 case 0x0f: /* MATM multiple area transfer mode */
1316 case 0x10: /* TTM transfer termination mode */
1317 case 0x11: /* SATM selected area transfer mode */
1318 case 0x12: /* TSM tabulation stop mode */
1319 case 0x13: /* EBM editing boundary mode */
1320 /* DEC Private Modes: "?NUM" -> (NUM | 0x80) */
1321 case 0x80: /* IGN error (ignored) */
1322 case 0x81: /* DECCKM cursorkeys application (set); cursorkeys normal (reset) */
1323 case 0x82: /* DECANM ANSI (set); VT52 (reset) */
1324 case 0x83: /* DECCOLM 132 columns (set); 80 columns (reset) */
1325 case 0x84: /* DECSCLM jump scroll (set); smooth scroll (reset) */
1326 case 0x85: /* DECSCNM reverse screen (set); normal screen (reset) */
1327 case 0x88: /* DECARM auto repeat */
1328 case 0x89: /* DECINLM interlace */
1329 case 0x92: /* DECPFF send FF to printer after print screen (set); no char after PS (reset) */
1330 case 0x93: /* DECPEX print screen: prints full screen (set); prints scroll region (reset) */
1331 default:
1332 unknown("modeseq", c);
1333 break;