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 __attribute__((noinline))
69 static void mtty_row_roll(mtty_row_t *start, mtty_row_t *end, int count)
78 mtty_row_t *buf = alloca(count * sizeof(mtty_row_t));
80 memcpy(buf, start, count * sizeof(mtty_row_t));
81 memmove(start, start + count, (n - count) * sizeof(mtty_row_t));
82 memcpy(end - count, buf, count * sizeof(mtty_row_t));
86 static void clamp_cursor_to_bounds(madtty_t *rt)
88 if (rt->curs_row < rt->lines) {
89 rt->curs_row = rt->lines;
91 if (rt->curs_row >= rt->lines + rt->rows) {
92 rt->curs_row = rt->lines + rt->rows - 1;
95 if (rt->curs_col < 0) {
98 if (rt->curs_col >= rt->cols) {
99 rt->curs_col = rt->cols - 1;
103 static void cursor_line_down(madtty_t *rt)
106 if (rt->curs_row < rt->scroll_bot)
109 rt->curs_row = rt->scroll_bot - 1;
110 mtty_row_roll(rt->scroll_top, rt->scroll_bot, 1);
111 mtty_row_set(rt->curs_row, 0, rt->cols, 0);
114 static void cursor_line_up(madtty_t *rt)
117 if (rt->curs_row >= rt->scroll_top)
120 /* must scroll the scrolling region up by 1 line, and put cursor on
121 * first line of it */
122 rt->curs_row = rt->scroll_top;
123 mtty_row_roll(rt->scroll_top, rt->scroll_bot, -1);
124 mtty_row_set(rt->curs_row, 0, rt->cols, 0);
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)
299 int start_row, start_col, end_row, end_col;
302 if (pcount && param[0] == 2) {
305 end_row = rt->rows - 1;
306 end_col = rt->cols - 1;
308 if (pcount && param[0] == 1) {
311 end_row = rt->curs_row - rt->lines;
312 end_col = rt->curs_col;
314 start_row = rt->curs_row - rt->lines;
315 start_col = rt->curs_col;
316 end_row = rt->rows - 1;
317 end_col = rt->cols - 1;
321 mtty_row_set(rt->lines + start_row, start_col, rt->cols - start_col,
322 build_attrs(rt->curattrs));
323 for (r = start_row + 1; r < end_row; r++) {
324 mtty_row_set(rt->lines + r, 0, rt->cols, build_attrs(rt->curattrs));
326 mtty_row_set(rt->lines + end_row, 0, end_col + 1,
327 build_attrs(rt->curattrs));
330 /* interprets a 'move cursor' (CUP) escape sequence */
331 static void interpret_csi_CUP(madtty_t *rt, int param[], int pcount)
335 rt->curs_row = rt->lines;
340 return; /* malformed */
343 rt->curs_row = rt->lines + param[0] - 1; /* convert from 1-based to 0-based */
344 rt->curs_col = param[1] - 1; /* convert from 1-based to 0-based */
346 clamp_cursor_to_bounds(rt);
349 /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL,
350 * CPL, CHA, HPR, VPA, VPR, HPA */
351 static void interpret_csi_C(madtty_t *rt, char verb, int param[], int pcount)
353 int n = (pcount && param[0] > 0) ? param[0] : 1;
356 case 'A': rt->curs_row -= n; break;
357 case 'B': case 'e': rt->curs_row += n; break;
358 case 'C': case 'a': rt->curs_col += n; break;
359 case 'D': rt->curs_col -= n; break;
360 case 'E': rt->curs_row += n; rt->curs_col = 0; break;
361 case 'F': rt->curs_row -= n; rt->curs_col = 0; break;
362 case 'G': case '`': rt->curs_col = param[0] - 1; break;
363 case 'd': rt->curs_row = rt->lines + param[0] - 1; break;
366 clamp_cursor_to_bounds(rt);
369 /* Interpret the 'erase line' escape sequence */
370 static void interpret_csi_EL(madtty_t *rt, int param[], int pcount)
373 int cmd = pcount ? param[0] : 0;
378 len = rt->curs_col + 1;
385 start = rt->curs_col;
386 len = rt->cols - start;
390 mtty_row_set(rt->curs_row, start, len, build_attrs(rt->curattrs));
393 /* Interpret the 'insert blanks' sequence (ICH) */
394 static void interpret_csi_ICH(madtty_t *rt, int param[], int pcount)
396 mtty_row_t *row = rt->curs_row;
397 int n = (pcount && param[0] > 0) ? param[0] : 1;
400 for (i = rt->cols - 1; i >= rt->curs_col + n; i--) {
401 row->text[i] = row->text[i - n];
402 row->attr[i] = row->attr[i - n];
405 mtty_row_set(row, rt->curs_col, n, build_attrs(rt->curattrs));
408 /* Interpret the 'delete chars' sequence (DCH) */
409 static void interpret_csi_DCH(madtty_t *rt, int param[], int pcount)
411 mtty_row_t *row = rt->curs_row;
412 int n = (pcount && param[0] > 0) ? param[0] : 1;
415 for (i = rt->curs_col; i < rt->cols; i++) {
416 if (i + n < rt->cols) {
417 row->text[i] = row->text[i + n];
418 row->attr[i] = row->attr[i + n];
421 row->attr[i] = build_attrs(rt->curattrs);
426 /* Interpret an 'insert line' sequence (IL) */
427 static void interpret_csi_IL(madtty_t *rt, int param[], int pcount)
429 int n = (pcount && param[0] > 0) ? param[0] : 1;
431 if (rt->curs_row + n >= rt->scroll_bot) {
432 for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
433 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
436 mtty_row_roll(rt->curs_row, rt->scroll_bot, n);
437 for (mtty_row_t *row = rt->curs_row; row < rt->curs_row + n; row++) {
438 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
444 /* Interpret a 'delete line' sequence (DL) */
445 static void interpret_csi_DL(madtty_t *rt, int param[], int pcount)
447 int n = (pcount && param[0] > 0) ? param[0] : 1;
449 if (rt->curs_row + n >= rt->scroll_bot) {
450 for (mtty_row_t *row = rt->curs_row; row < rt->scroll_bot; row++) {
451 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
454 mtty_row_roll(rt->curs_row, rt->scroll_bot, -n);
456 for (mtty_row_t *row = rt->scroll_bot - n; row < rt->scroll_bot; row++) {
457 mtty_row_set(row, 0, rt->cols, build_attrs(rt->curattrs));
462 /* Interpret an 'erase characters' (ECH) sequence */
463 static void interpret_csi_ECH(madtty_t *rt, int param[], int pcount)
465 int n = (pcount && param[0] > 0) ? param[0] : 1;
467 if (rt->curs_col + n < rt->cols) {
468 n = rt->cols - rt->curs_col;
470 mtty_row_set(rt->curs_row, rt->curs_col, n, build_attrs(rt->curattrs));
473 /* Interpret a 'set scrolling region' (DECSTBM) sequence */
474 static void interpret_csi_DECSTBM(madtty_t *rt, int param[], int pcount)
476 int new_top, new_bot;
480 rt->scroll_top = rt->lines;
481 rt->scroll_bot = rt->lines + rt->rows;
484 return; /* malformed */
487 new_top = param[0] - 1;
488 new_bot = param[1] - 1;
490 /* clamp to bounds */
493 if (new_top >= rt->rows)
494 new_top = rt->rows - 1;
497 if (new_bot >= rt->rows)
500 /* check for range validity */
501 if (new_top < new_bot) {
502 rt->scroll_top = rt->lines + new_top;
503 rt->scroll_bot = rt->lines + new_bot;
509 static void es_interpret_csi(madtty_t *rt)
511 static int csiparam[MAX_CSI_ES_PARAMS];
513 const char *p = rt->esbuf + 1;
514 char verb = rt->esbuf[rt->esbuf_len - 1];
516 if (!strncmp(rt->esbuf, "[?", 2)) { /* private-mode CSI, ignore */
520 /* parse numeric parameters */
521 while (isdigit((unsigned char)*p) || *p == ';') {
523 if (param_count >= MAX_CSI_ES_PARAMS) return; /* too long! */
524 csiparam[param_count++] = 0;
526 if (param_count == 0) csiparam[param_count++] = 0;
527 csiparam[param_count - 1] *= 10;
528 csiparam[param_count - 1] += *p - '0';
534 /* delegate handling depending on command character (verb) */
537 if (param_count == 1 && csiparam[0] == 4) /* insert mode */
541 if (param_count == 1 && csiparam[0] == 4) /* replace mode */
544 case 'm': /* it's a 'set attribute' sequence */
545 interpret_csi_SGR(rt, csiparam, param_count); break;
546 case 'J': /* it's an 'erase display' sequence */
547 interpret_csi_ED(rt, csiparam, param_count); break;
548 case 'H': case 'f': /* it's a 'move cursor' sequence */
549 interpret_csi_CUP(rt, csiparam, param_count); break;
550 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
551 case 'e': case 'a': case 'd': case '`':
552 /* it is a 'relative move' */
553 interpret_csi_C(rt, verb, csiparam, param_count); break;
554 case 'K': /* erase line */
555 interpret_csi_EL(rt, csiparam, param_count); break;
556 case '@': /* insert characters */
557 interpret_csi_ICH(rt, csiparam, param_count); break;
558 case 'P': /* delete characters */
559 interpret_csi_DCH(rt, csiparam, param_count); break;
560 case 'L': /* insert lines */
561 interpret_csi_IL(rt, csiparam, param_count); break;
562 case 'M': /* delete lines */
563 interpret_csi_DL(rt, csiparam, param_count); break;
564 case 'X': /* erase chars */
565 interpret_csi_ECH(rt, csiparam, param_count); break;
566 case 'r': /* set scrolling region */
567 interpret_csi_DECSTBM(rt, csiparam, param_count); break;
568 case 's': /* save cursor location */
569 rt->curs_srow = rt->curs_row - rt->lines;
570 rt->curs_scol = rt->curs_col;
572 case 'u': /* restore cursor location */
573 rt->curs_row = rt->lines + rt->curs_srow;
574 rt->curs_col = rt->curs_scol;
575 clamp_cursor_to_bounds(rt);
582 static void try_interpret_escape_seq(madtty_t *rt)
584 char firstchar = rt->esbuf[0];
585 char lastchar = rt->esbuf[rt->esbuf_len-1];
588 return; /* too early to do anything */
590 /* interpret ESC-M as reverse line-feed */
591 if (firstchar == 'M') {
593 cancel_escape_sequence(rt);
597 if (firstchar != '[' && firstchar != ']') {
598 /* unrecognized escape sequence. Let's forget about it. */
599 cancel_escape_sequence(rt);
603 if (firstchar == '[' && is_valid_csi_ender(lastchar)) {
604 es_interpret_csi(rt);
605 cancel_escape_sequence(rt);
606 } else if (firstchar == ']' && lastchar == '\a') {
607 /* we have an xterm escape sequence: interpret it */
609 /* es_interpret_xterm_es(rt); -- TODO!*/
610 cancel_escape_sequence(rt);
613 /* if the escape sequence took up all available space and could
614 * not yet be parsed, abort it */
615 if (rt->esbuf_len + 1 >= ESEQ_BUF_SIZE)
616 cancel_escape_sequence(rt);
619 int madtty_process(madtty_t *rt)
628 res = read(rt->pty, rt->rbuf + rt->rbuf_len,
629 sizeof(rt->rbuf) - rt->rbuf_len);
634 while (pos < rt->rbuf_len) {
638 len = (ssize_t)mbrtowc(&wc, rt->rbuf + pos, rt->rbuf_len - pos,
642 memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
643 return len == -2 ? 0 : -1;
646 pos += len ? len : 1;
649 handle_control_char(rt, wc);
653 if (rt->escaped && rt->esbuf_len < ESEQ_BUF_SIZE) {
654 /* append character to ongoing escape sequence */
655 rt->esbuf[rt->esbuf_len] = wc;
656 rt->esbuf[++rt->esbuf_len] = 0;
658 try_interpret_escape_seq(rt);
661 put_graphmode_char(rt, wc);
663 put_normal_char(rt, wc);
668 memmove(rt->rbuf, rt->rbuf + pos, rt->rbuf_len);
672 madtty_t *madtty_create(int rows, int cols)
677 if (rows <= 0 || cols <= 0)
680 rt = (madtty_t*)calloc(sizeof(madtty_t), 1);
684 /* record dimensions */
688 /* default mode is replace */
691 /* create the cell matrix */
692 rt->lines = (mtty_row_t*)calloc(sizeof(mtty_row_t), rt->rows);
693 for (i = 0; i < rt->rows; i++) {
694 rt->lines[i].text = (wchar_t *)calloc(sizeof(wchar_t), rt->cols);
695 rt->lines[i].attr = (uint16_t *)calloc(sizeof(uint16_t), rt->cols);
698 rt->pty = -1; /* no pty for now */
700 /* initialization of other public fields */
701 rt->curs_row = rt->lines;
703 rt->curattrs = A_NORMAL; /* white text over black background */
705 /* initial scrolling area is the whole window */
706 rt->scroll_top = rt->lines;
707 rt->scroll_bot = rt->lines + rt->rows;
712 void madtty_destroy(madtty_t *rt)
718 for (i = 0; i < rt->rows; i++) {
719 free(rt->lines[i].text);
720 free(rt->lines[i].attr);
726 void madtty_draw(madtty_t *rt, WINDOW *win, int srow, int scol)
730 for (i = 0; i < rt->rows; i++) {
731 mtty_row_t *row = rt->lines + i;
732 wmove(win, srow + i, scol);
734 for (j = 0; j < rt->cols; j++) {
735 if (!j || row->attr[j] != row->attr[j - 1])
736 wattrset(win, (attr_t)row->attr[j] << NCURSES_ATTR_SHIFT);
737 if (row->text[j] >= 128) {
738 char buf[MB_CUR_MAX + 1];
741 len = wcrtomb(buf, row->text[j], NULL);
742 waddnstr(win, buf, len);
744 waddch(win, row->text[j] > ' ' ? row->text[j] : ' ');
749 wmove(win, srow + rt->curs_row - rt->lines, scol + rt->curs_col);
752 /******************************************************/
754 pid_t madtty_forkpty(madtty_t *rt, const char *path, const char *argv[])
759 ws.ws_row = rt->rows;
760 ws.ws_col = rt->cols;
761 ws.ws_xpixel = ws.ws_ypixel = 0;
763 pid = forkpty(&rt->pty, NULL, NULL, &ws);
770 setenv("TERM", "linux", 1);
771 execv(path, (char *const*)argv);
772 fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
776 return rt->childpid = pid;
779 static int madtty_write(madtty_t *rt, const char *data, int len)
789 res = write(rt->pty, data, len);
791 if (errno == EINTR || errno == EAGAIN)
798 void madtty_keypress(madtty_t *rt, int keycode)
800 char c = (char)keycode;
804 if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
805 buf = keytable[keycode];
806 len = strlen(keytable[keycode]);
813 int res = madtty_write(rt, buf, len);
822 void madtty_initialize(void)
824 setlocale(LC_ALL, "");
828 use_default_colors();
830 nodelay(stdscr, TRUE);
831 keypad(stdscr, TRUE);
833 for (int i = -1; i < 8; i++) {
834 for (int j = -1; j < 8; j++) {
835 init_pair((i + 1) * 16 + j + 1, i, j);