3 This program is free software; you can redistribute it and/or
4 modify it under the terms of the GNU Lesser General Public
5 License (LGPL) as published by the Free Software Foundation.
7 Please refer to the COPYING file for more information.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public
15 License along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 Copyright © 2004 Bruno T. C. de Oliveira
19 Copyright © 2006 Pierre Habouzit
30 #include <sys/ioctl.h>
34 static char const * const keytable[KEY_MAX+1] = {
40 [KEY_BACKSPACE] = "\b",
45 [KEY_PPAGE] = "\e[5~",
46 [KEY_NPAGE] = "\e[6~",
47 [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */
53 [KEY_F(6)] = "\e[17~",
54 [KEY_F(7)] = "\e[18~",
55 [KEY_F(8)] = "\e[19~",
56 [KEY_F(9)] = "\e[20~",
57 [KEY_F(10)] = "\e[21~",
60 static void mtty_row_set(mtty_row_t *row, int start, int len, uint16_t attr)
62 wmemset(row->text + start, 0, len);
63 for (int i = start; i < len + start; i++) {
68 static void mtty_row_roll(mtty_row_t *start, mtty_row_t *end, int count)
77 mtty_row_t *buf = alloca(count * sizeof(mtty_row_t));
79 memcpy(buf, start, count * sizeof(mtty_row_t));
80 memmove(start, start + count, (n - count) * sizeof(mtty_row_t));
81 memcpy(end - count, buf, count * sizeof(mtty_row_t));
85 static void clamp_cursor_to_bounds(madtty_t *rt)
87 if (rt->curs_row < rt->lines) {
88 rt->curs_row = rt->lines;
90 if (rt->curs_row >= rt->lines + rt->rows) {
91 rt->curs_row = rt->lines + rt->rows - 1;
94 if (rt->curs_col < 0) {
97 if (rt->curs_col >= rt->cols) {
98 rt->curs_col = rt->cols - 1;
102 static void cursor_line_down(madtty_t *rt)
105 if (rt->curs_row < rt->scroll_bot)
108 rt->curs_row = rt->scroll_bot - 1;
109 mtty_row_roll(rt->scroll_top, rt->scroll_bot, 1);
110 mtty_row_set(rt->curs_row, 0, rt->cols, 0);
113 static void cursor_line_up(madtty_t *rt)
116 if (rt->curs_row >= rt->scroll_top)
119 /* must scroll the scrolling region up by 1 line, and put cursor on
120 * first line of it */
121 rt->curs_row = rt->scroll_top;
122 mtty_row_roll(rt->scroll_top, rt->scroll_bot, -1);
123 mtty_row_set(rt->curs_row, 0, rt->cols, 0);
126 __attribute__((const))
127 static uint16_t build_attrs(unsigned curattrs)
129 return ((curattrs & ~A_COLOR) | COLOR_PAIR(curattrs & 0xff))
130 >> NCURSES_ATTR_SHIFT;
133 static void put_normal_char(madtty_t *rt, wchar_t c)
137 if (rt->curs_col >= rt->cols) {
139 cursor_line_down(rt);
145 wmemmove(tmp->text + rt->curs_col + 1, tmp->text + rt->curs_col,
146 (rt->cols - rt->curs_col - 1));
147 memmove(tmp->attr + rt->curs_col + 1, tmp->attr + rt->curs_col,
148 (rt->cols - rt->curs_col - 1) * sizeof(tmp->attr[0]));
151 tmp->text[rt->curs_col] = c;
152 tmp->attr[rt->curs_col] = build_attrs(rt->curattrs);
156 static void put_graphmode_char(madtty_t *rt, int c)
159 /* do some very pitiful translation to regular ascii chars */
161 case 'j': case 'k': case 'l': case 'm': case 'n': case 't':
162 case 'u': case 'v': case 'w':
170 put_normal_char(rt, nc);
173 static void new_escape_sequence(madtty_t *rt)
180 static void cancel_escape_sequence(madtty_t *rt)
187 static void handle_control_char(madtty_t *rt, int c)
190 case '\r': /* carriage return */
194 case '\n': /* line feed */
196 cursor_line_down(rt);
199 case '\b': /* backspace */
200 if (rt->curs_col > 0)
205 rt->curs_col = (rt->curs_col + 8) & ~7;
206 clamp_cursor_to_bounds(rt);
209 case '\x1b': /* begin escape sequence (aborting previous one if any) */
210 new_escape_sequence(rt);
213 case '\x0e': /* enter graphical character mode */
214 rt->graphmode = true;
217 case '\x0f': /* exit graphical character mode */
218 rt->graphmode = false;
221 case '\x9b': /* CSI character. Equivalent to ESC [ */
222 new_escape_sequence(rt);
223 rt->esbuf[rt->esbuf_len++] = '[';
227 case '\x1a': /* these interrupt escape sequences */
228 cancel_escape_sequence(rt);
231 case '\a': /* bell */
232 /* do nothing for now... maybe a visual bell would be nice? */
237 static bool is_valid_csi_ender(int c)
239 return (c >= 'a' && c <= 'z')
240 || (c >= 'A' && c <= 'Z')
241 || (c == '@' || c == '`');
244 /* interprets a 'set attribute' (SGR) CSI escape sequence */
245 static void interpret_csi_SGR(madtty_t *rt, int param[], int pcount)
250 /* special case: reset attributes */
251 rt->curattrs = A_NORMAL;
255 for (i = 0; i < pcount; i++) {
257 #define CASE(x, op) case x: op; break
258 CASE(0, rt->curattrs = A_NORMAL);
259 CASE(1, rt->curattrs |= A_BOLD);
260 CASE(4, rt->curattrs |= A_UNDERLINE);
261 CASE(5, rt->curattrs |= A_BLINK);
262 CASE(6, rt->curattrs |= A_BLINK);
263 CASE(7, rt->curattrs |= A_REVERSE);
264 CASE(8, rt->curattrs |= A_INVIS);
265 CASE(22, rt->curattrs &= ~A_BOLD);
266 CASE(24, rt->curattrs &= ~A_UNDERLINE);
267 CASE(25, rt->curattrs &= ~A_BLINK);
268 CASE(27, rt->curattrs &= ~A_REVERSE);
269 CASE(28, rt->curattrs &= ~A_INVIS);
272 rt->curattrs &= ~0xf0;
273 rt->curattrs |= (param[i] - 29) << 4;
277 rt->curattrs &= ~0xf0;
281 rt->curattrs &= ~0x0f;
282 rt->curattrs |= (param[i] - 39);
286 rt->curattrs &= ~0x0f;
295 /* interprets an 'erase display' (ED) escape sequence */
296 static void interpret_csi_ED(madtty_t *rt, int param[], int pcount)
298 mtty_row_t *row, *start, *end;
299 attr_t attr = build_attrs(rt->curattrs);
302 if (pcount && param[0] == 2) {
304 end = rt->lines + rt->rows;
306 if (pcount && param[0] == 1) {
309 mtty_row_set(rt->curs_row, 0, rt->curs_col + 1, attr);
311 mtty_row_set(rt->curs_row, rt->curs_col,
312 rt->cols - rt->curs_col, attr);
313 start = rt->curs_row + 1;
314 end = rt->lines + rt->rows;
317 for (row = start; row < end; row++) {
318 mtty_row_set(row, 0, rt->cols, attr);
322 /* interprets a 'move cursor' (CUP) escape sequence */
323 static void interpret_csi_CUP(madtty_t *rt, int param[], int pcount)
327 rt->curs_row = rt->lines;
332 return; /* malformed */
335 rt->curs_row = rt->lines + param[0] - 1; /* convert from 1-based to 0-based */
336 rt->curs_col = param[1] - 1; /* convert from 1-based to 0-based */
338 clamp_cursor_to_bounds(rt);
341 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
342 * CPL, CHA, HPR, VPA, VPR, HPA */
343 static void interpret_csi_C(madtty_t *rt, char verb, int param[], int pcount)
345 int n = (pcount && param[0] > 0) ? param[0] : 1;
348 case 'A': rt->curs_row -= n; break;
349 case 'B': case 'e': rt->curs_row += n; break;
350 case 'C': case 'a': rt->curs_col += n; break;
351 case 'D': rt->curs_col -= n; break;
352 case 'E': rt->curs_row += n; rt->curs_col = 0; break;
353 case 'F': rt->curs_row -= n; rt->curs_col = 0; break;
354 case 'G': case '`': rt->curs_col = param[0] - 1; break;
355 case 'd': rt->curs_row = rt->lines + param[0] - 1; break;
358 clamp_cursor_to_bounds(rt);
361 /* Interpret the 'erase line' escape sequence */
362 static void interpret_csi_EL(madtty_t *rt, int param[], int pcount)
365 int cmd = pcount ? param[0] : 0;
370 len = rt->curs_col + 1;
377 start = rt->curs_col;
378 len = rt->cols - start;
382 mtty_row_set(rt->curs_row, start, len, build_attrs(rt->curattrs));
385 /* Interpret the 'insert blanks' sequence (ICH) */
386 static void interpret_csi_ICH(madtty_t *rt, int param[], int pcount)
388 mtty_row_t *row = rt->curs_row;
389 int n = (pcount && param[0] > 0) ? param[0] : 1;
392 if (rt->curs_col + n > rt->cols) {
393 n = rt->cols - rt->curs_col;
396 for (i = rt->cols - 1; i >= rt->curs_col + n; i--) {
397 row->text[i] = row->text[i - n];
398 row->attr[i] = row->attr[i - n];
401 mtty_row_set(row, rt->curs_col, n, build_attrs(rt->curattrs));
404 /* Interpret the 'delete chars' sequence (DCH) */
405 static void interpret_csi_DCH(madtty_t *rt, int param[], int pcount)
407 mtty_row_t *row = rt->curs_row;
408 int n = (pcount && param[0] > 0) ? param[0] : 1;
411 if (rt->curs_col + n > rt->cols) {
412 n = rt->cols - rt->curs_col;
415 for (i = rt->curs_col; i < rt->cols - n; i++) {
416 row->text[i] = row->text[i + n];
417 row->attr[i] = row->attr[i + n];
420 mtty_row_set(row, rt->cols - n, n, build_attrs(rt->curattrs));
423 /* Interpret an 'insert line' sequence (IL) */
424 static void interpret_csi_IL(madtty_t *rt, int param[], int pcount)
426 int n = (pcount && param[0] > 0) ? param[0] : 1;
428 if (rt->curs_row + n >= rt->scroll_bot) {
429 for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
430 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
433 mtty_row_roll(rt->curs_row, rt->scroll_bot, n);
434 for (mtty_row_t *row = rt->curs_row; row < rt->curs_row + n; row++) {
435 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
441 /* Interpret a 'delete line' sequence (DL) */
442 static void interpret_csi_DL(madtty_t *rt, int param[], int pcount)
444 int n = (pcount && param[0] > 0) ? param[0] : 1;
446 if (rt->curs_row + n >= rt->scroll_bot) {
447 for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
448 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
451 mtty_row_roll(rt->curs_row, rt->scroll_bot, -n);
452 for (mtty_row_t *row = rt->scroll_bot - n; row < rt->scroll_bot; row++) {
453 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
458 /* Interpret an 'erase characters' (ECH) sequence */
459 static void interpret_csi_ECH(madtty_t *rt, int param[], int pcount)
461 int n = (pcount && param[0] > 0) ? param[0] : 1;
463 if (rt->curs_col + n < rt->cols) {
464 n = rt->cols - rt->curs_col;
466 mtty_row_set(rt->curs_row, rt->curs_col, n, build_attrs(rt->curattrs));
469 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
470 static void interpret_csi_DECSTBM(madtty_t *rt, int param[], int pcount)
472 int new_top, new_bot;
476 rt->scroll_top = rt->lines;
477 rt->scroll_bot = rt->lines + rt->rows;
480 return; /* malformed */
483 new_top = param[0] - 1;
486 /* clamp to bounds */
489 if (new_top >= rt->rows)
490 new_top = rt->rows - 1;
493 if (new_bot >= rt->rows)
496 /* check for range validity */
497 if (new_top < new_bot) {
498 rt->scroll_top = rt->lines + new_top;
499 rt->scroll_bot = rt->lines + new_bot;
505 static void es_interpret_csi(madtty_t *rt)
507 static int csiparam[MAX_CSI_ES_PARAMS];
509 const char *p = rt->esbuf + 1;
510 char verb = rt->esbuf[rt->esbuf_len - 1];
512 if (!strncmp(rt->esbuf, "[?", 2)) { /* private-mode CSI, ignore */
516 /* parse numeric parameters */
517 while (isdigit((unsigned char)*p) || *p == ';') {
519 if (param_count >= MAX_CSI_ES_PARAMS) return; /* too long! */
520 csiparam[param_count++] = 0;
522 if (param_count == 0) csiparam[param_count++] = 0;
523 csiparam[param_count - 1] *= 10;
524 csiparam[param_count - 1] += *p - '0';
530 /* delegate handling depending on command character (verb) */
533 if (param_count == 1 && csiparam[0] == 4) /* insert mode */
537 if (param_count == 1 && csiparam[0] == 4) /* replace mode */
540 case 'm': /* it's a 'set attribute' sequence */
541 interpret_csi_SGR(rt, csiparam, param_count); break;
542 case 'J': /* it's an 'erase display' sequence */
543 interpret_csi_ED(rt, csiparam, param_count); break;
544 case 'H': case 'f': /* it's a 'move cursor' sequence */
545 interpret_csi_CUP(rt, csiparam, param_count); break;
546 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
547 case 'e': case 'a': case 'd': case '`':
548 /* it is a 'relative move' */
549 interpret_csi_C(rt, verb, csiparam, param_count); break;
550 case 'K': /* erase line */
551 interpret_csi_EL(rt, csiparam, param_count); break;
552 case '@': /* insert characters */
553 interpret_csi_ICH(rt, csiparam, param_count); break;
554 case 'P': /* delete characters */
555 interpret_csi_DCH(rt, csiparam, param_count); break;
556 case 'L': /* insert lines */
557 interpret_csi_IL(rt, csiparam, param_count); break;
558 case 'M': /* delete lines */
559 interpret_csi_DL(rt, csiparam, param_count); break;
560 case 'X': /* erase chars */
561 interpret_csi_ECH(rt, csiparam, param_count); break;
562 case 'r': /* set scrolling region */
563 interpret_csi_DECSTBM(rt, csiparam, param_count); break;
564 case 's': /* save cursor location */
565 rt->curs_srow = rt->curs_row - rt->lines;
566 rt->curs_scol = rt->curs_col;
568 case 'u': /* restore cursor location */
569 rt->curs_row = rt->lines + rt->curs_srow;
570 rt->curs_col = rt->curs_scol;
571 clamp_cursor_to_bounds(rt);
578 static void try_interpret_escape_seq(madtty_t *rt)
580 char firstchar = rt->esbuf[0];
581 char lastchar = rt->esbuf[rt->esbuf_len-1];
584 return; /* too early to do anything */
586 /* interpret ESC-M as reverse line-feed */
587 if (firstchar == 'M') {
589 cancel_escape_sequence(rt);
593 if (firstchar != '[' && firstchar != ']') {
594 /* unrecognized escape sequence. Let's forget about it. */
595 cancel_escape_sequence(rt);
599 if (firstchar == '[' && is_valid_csi_ender(lastchar)) {
600 es_interpret_csi(rt);
601 cancel_escape_sequence(rt);
602 } else if (firstchar == ']' && lastchar == '\a') {
603 /* we have an xterm escape sequence: interpret it */
605 /* es_interpret_xterm_es(rt); -- TODO!*/
606 cancel_escape_sequence(rt);
609 /* if the escape sequence took up all available space and could
610 * not yet be parsed, abort it */
611 if (rt->esbuf_len + 1 >= ESEQ_BUF_SIZE)
612 cancel_escape_sequence(rt);
615 int madtty_process(madtty_t *rt)
624 res = read(rt->pty, rt->rbuf + rt->rbuf_len,
625 sizeof(rt->rbuf) - rt->rbuf_len);
630 while (pos < rt->rbuf_len) {
634 len = (ssize_t)mbrtowc(&wc, rt->rbuf + pos, rt->rbuf_len - pos,
638 memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
639 return len == -2 ? 0 : -1;
642 pos += len ? len : 1;
645 handle_control_char(rt, wc);
649 if (rt->escaped && rt->esbuf_len < ESEQ_BUF_SIZE) {
650 /* append character to ongoing escape sequence */
651 rt->esbuf[rt->esbuf_len] = wc;
652 rt->esbuf[++rt->esbuf_len] = 0;
654 try_interpret_escape_seq(rt);
657 put_graphmode_char(rt, wc);
659 put_normal_char(rt, wc);
664 memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
668 madtty_t *madtty_create(int rows, int cols)
673 if (rows <= 0 || cols <= 0)
676 rt = (madtty_t*)calloc(sizeof(madtty_t), 1);
680 /* record dimensions */
684 /* default mode is replace */
687 /* create the cell matrix */
688 rt->lines = (mtty_row_t*)calloc(sizeof(mtty_row_t), rt->rows);
689 for (i = 0; i < rt->rows; i++) {
690 rt->lines[i].text = (wchar_t *)calloc(sizeof(wchar_t), rt->cols);
691 rt->lines[i].attr = (uint16_t *)calloc(sizeof(uint16_t), rt->cols);
694 rt->pty = -1; /* no pty for now */
696 /* initialization of other public fields */
697 rt->curs_row = rt->lines;
699 rt->curattrs = A_NORMAL; /* white text over black background */
701 /* initial scrolling area is the whole window */
702 rt->scroll_top = rt->lines;
703 rt->scroll_bot = rt->lines + rt->rows;
708 void madtty_destroy(madtty_t *rt)
714 for (i = 0; i < rt->rows; i++) {
715 free(rt->lines[i].text);
716 free(rt->lines[i].attr);
722 void madtty_draw(madtty_t *rt, WINDOW *win, int srow, int scol)
726 for (i = 0; i < rt->rows; i++) {
727 mtty_row_t *row = rt->lines + i;
728 wmove(win, srow + i, scol);
730 for (j = 0; j < rt->cols; j++) {
731 if (!j || row->attr[j] != row->attr[j - 1])
732 wattrset(win, (attr_t)row->attr[j] << NCURSES_ATTR_SHIFT);
733 if (row->text[j] >= 128) {
734 char buf[MB_CUR_MAX + 1];
737 len = wcrtomb(buf, row->text[j], NULL);
738 waddnstr(win, buf, len);
740 waddch(win, row->text[j] > ' ' ? row->text[j] : ' ');
745 wmove(win, srow + rt->curs_row - rt->lines, scol + rt->curs_col);
748 /******************************************************/
750 pid_t madtty_forkpty(madtty_t *rt, const char *path, const char *argv[])
755 ws.ws_row = rt->rows;
756 ws.ws_col = rt->cols;
757 ws.ws_xpixel = ws.ws_ypixel = 0;
759 pid = forkpty(&rt->pty, NULL, NULL, &ws);
766 setenv("TERM", "linux", 1);
767 execv(path, (char *const*)argv);
768 fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
772 return rt->childpid = pid;
775 static int madtty_write(madtty_t *rt, const char *data, int len)
785 res = write(rt->pty, data, len);
787 if (errno == EINTR || errno == EAGAIN)
794 void madtty_keypress(madtty_t *rt, int keycode)
796 char c = (char)keycode;
800 if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
801 buf = keytable[keycode];
802 len = strlen(keytable[keycode]);
809 int res = madtty_write(rt, buf, len);
818 void madtty_initialize(void)
820 setlocale(LC_ALL, "");
824 use_default_colors();
826 nodelay(stdscr, TRUE);
827 keypad(stdscr, TRUE);
829 for (int i = -1; i < 8; i++) {
830 for (int j = -1; j < 8; j++) {
831 init_pair((i + 1) * 16 + j + 1, i, j);