From 7e9bf69df0c4b6cd9a94172e71d7c6a584341f52 Mon Sep 17 00:00:00 2001 From: Pierre Habouzit Date: Fri, 10 Aug 2007 10:52:57 +0200 Subject: [PATCH] more refactoring Signed-off-by: Pierre Habouzit --- configure.ac | 20 +- demo/boxshell.c | 47 +--- madtty/madtty.c | 651 +++++++++++++++++++++--------------------------- madtty/madtty.h | 188 +++----------- 4 files changed, 335 insertions(+), 571 deletions(-) diff --git a/configure.ac b/configure.ac index a4906fe..eef258c 100644 --- a/configure.ac +++ b/configure.ac @@ -8,33 +8,17 @@ AC_SUBST(PACKAGE_VERSION) AC_CONFIG_SRCDIR([madtty/madtty.h]) dnl Checks for programs. -AC_PROG_CC +AC_PROG_CC_C99 +AC_GNU_SOURCE dnl Checks for header files. AC_HEADER_STDC AC_HEADER_STDBOOL AC_CHECK_HEADERS([sys/types.h unistd.h stdlib.h string.h]) -dnl Check for libraries -AC_ARG_ENABLE([ncurses], - AS_HELP_STRING([--disable-ncurses], [disables ncurses support (default enabled)])) -if test "${enable_curses}" != "no"; then - AC_CHECK_LIB([ncurses], [initscr], [], - AC_MSG_ERROR([Need ncurses to compile ncurses support.])) - CFLAGS="$CFLAGS -DUSE_NCURSES" -fi - -dnl Checks for library functions -AC_CHECK_FUNCS([memset select setenv]) -AC_FUNC_MALLOC -AC_FUNC_SELECT_ARGTYPES - dnl Checks for typedefs, structures, and compiler characteristics. AC_C_CONST -AC_HEADER_TIME AC_C_INLINE -AC_TYPE_PID_T -AC_TYPE_SIGNAL AC_CONFIG_FILES([am/vars.mk madtty/madtty.pc diff --git a/demo/boxshell.c b/demo/boxshell.c index a1d746e..44a9d9c 100644 --- a/demo/boxshell.c +++ b/demo/boxshell.c @@ -6,7 +6,6 @@ */ #include -#include #include #include #include @@ -27,7 +26,7 @@ void sigchld(int signo __attribute__((unused))) int main(int argc, char *argv[]) { struct timeval next = { 0, 0 }; - RoteTerm *rt; + madtty_t *rt; int i, j, ch, w, h, pos; char buf[BUFSIZ]; @@ -42,51 +41,27 @@ int main(int argc, char *argv[]) h = strtol(p, &p, 10); } - setlocale(LC_ALL, ""); - initscr(); - noecho(); - start_color(); - raw(); - nodelay(stdscr, TRUE); /* prevents getch() from blocking; rather - * it will return ERR when there is no - * keypress available */ - - keypad(stdscr, TRUE); /* necessary to use rote_vt_keypress */ + madtty_initialize(); getmaxyx(stdscr, screen_h, screen_w); - /* initialize the color pairs the way rote_vt_draw expects it. You might - * initialize them differently, but in that case you would need - * to supply a custom conversion function for rote_vt_draw to - * call when setting attributes. The idea of this "default" mapping - * is to map (fg, bg) to the color pair bg * 8 + 7 - fg. This way, - * the pair (white, black) ends up mapped to 0, which means that - * it does not need a color pair (since it is the default). Since - * there are only 63 available color pairs (and 64 possible fg/bg - * combinations), we really have to save 1 pair by assigning no pair - * to the combination white/black. */ - for (i = 0; i < 8; i++) for (j = 0; j < 8; j++) - if (i != 7 || j != 0) - init_pair(j*8+7-i, i, j); - /* paint the screen blue */ - attrset(COLOR_PAIR(32)); + attrset(COLOR_PAIR(004)); for (i = 0; i < screen_h; i++) for (j = 0; j < screen_w; j++) addch(' '); refresh(); /* create a window with a frame */ - term_win = newwin(h + 2, w + 2, 2, 3); - wborder(term_win, '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'); + term_win = newwin(h, w, 2, 3); mvwprintw(term_win, 0, 27, " Term In a Box "); wrefresh(term_win); - rt = rote_vt_create(h, w); + rt = madtty_create(h, w); { const char *path = getenv("SHELL") ?: "/bin/sh"; const char *args[] = { path, "--login", NULL}; - rote_vt_forkpty(rt, path, args); + madtty_forkpty(rt, path, args); } /* keep reading keypresses from the user and passing them to the terminal; @@ -103,12 +78,12 @@ int main(int argc, char *argv[]) if (select(rt->pty + 1, &rfds, NULL, NULL, &tv) > 0) { int nb; - nb = rote_vt_read(rt, buf + pos, sizeof(buf) - pos); + nb = madtty_read(rt, buf + pos, sizeof(buf) - pos); if (nb <= 0) continue; pos += nb; - nb = rote_vt_inject(rt, buf, pos); + nb = madtty_inject(rt, buf, pos); if (nb <= 0) continue; memmove(buf, buf + nb, pos - nb); @@ -116,15 +91,15 @@ int main(int argc, char *argv[]) } while ((ch = getch()) != ERR) { - rote_vt_keypress(rt, ch); /* pass the keypress for handling */ + madtty_keypress(rt, ch); /* pass the keypress for handling */ } gettimeofday(&t, NULL); if (timercmp(&t, &next, >=)) { - rote_vt_draw(rt, term_win, 1, 1); + madtty_draw(rt, term_win, 0, 0); wrefresh(term_win); gettimeofday(&next, NULL); - next.tv_usec += 1000 * 1000 / 100; + next.tv_usec += 1000 * 1000 / 50; if (next.tv_usec > 1000 * 1000) { next.tv_usec -= 1000 * 1000; next.tv_sec++; diff --git a/madtty/madtty.c b/madtty/madtty.c index d10b917..e6d9dde 100644 --- a/madtty/madtty.c +++ b/madtty/madtty.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -30,128 +31,93 @@ #include "madtty.h" -#define MAX_CSI_ES_PARAMS 32 -#define ESEQ_BUF_SIZE 128 /* size of escape sequence buffer */ - -/* Terminal private data */ -struct RoteTermPrivate_ { - unsigned escaped : 1; /* whether we are currently reading an - * escape sequence */ - - unsigned graphmode : 1; /* whether terminal is in graphical - * character mode or not */ - - int scrolltop, scrollbottom; /* current scrolling region of terminal */ - int saved_x, saved_y; /* saved cursor position */ - - char esbuf[ESEQ_BUF_SIZE]; /* 0-terminated string. Does NOT include - * the initial escape (\x1B) character. */ - int esbuf_len; /* length of buffer. The following property - * is always kept: esbuf[esbuf_len] == '\0' */ -}; - -static void clamp_cursor_to_bounds(RoteTerm *rt) +static void clamp_cursor_to_bounds(madtty_t *rt) { - if (rt->crow < 0) { - rt->curpos_dirty = true; - rt->crow = 0; + if (rt->curs_row < 0) { + rt->curs_row = 0; } - if (rt->ccol < 0) { - rt->curpos_dirty = true; - rt->ccol = 0; + if (rt->curs_col < 0) { + rt->curs_col = 0; } - if (rt->crow >= rt->rows) { - rt->curpos_dirty = true; - rt->crow = rt->rows - 1; + if (rt->curs_row >= rt->rows) { + rt->curs_row = rt->rows - 1; } - if (rt->ccol >= rt->cols) { - rt->curpos_dirty = true; - rt->ccol = rt->cols - 1; + if (rt->curs_col >= rt->cols) { + rt->curs_col = rt->cols - 1; } } -static void cursor_line_down(RoteTerm *rt) +static void cursor_line_down(madtty_t *rt) { int i; - rt->crow++; - rt->curpos_dirty = true; - if (rt->crow <= rt->pd->scrollbottom) + rt->curs_row++; + if (rt->curs_row <= rt->scrollbottom) return; /* must scroll the scrolling region up by 1 line, and put cursor on * last line of it */ - rt->crow = rt->pd->scrollbottom; + rt->curs_row = rt->scrollbottom; - for (i = rt->pd->scrolltop; i < rt->pd->scrollbottom; i++) { - rt->line_dirty[i] = true; + for (i = rt->scrolltop; i < rt->scrollbottom; i++) { memcpy(rt->cells[i], rt->cells[i+1], sizeof(RoteCell) * rt->cols); } - rt->line_dirty[rt->pd->scrollbottom] = true; - /* clear last row of the scrolling region */ for (i = 0; i < rt->cols; i++) { - rt->cells[rt->pd->scrollbottom][i].s[0] = 0x20; - rt->cells[rt->pd->scrollbottom][i].len = 1; - rt->cells[rt->pd->scrollbottom][i].attr = 0x70; + rt->cells[rt->scrollbottom][i].s[0] = 0x20; + rt->cells[rt->scrollbottom][i].len = 1; + rt->cells[rt->scrollbottom][i].attrs = A_NORMAL; } } -static void cursor_line_up(RoteTerm *rt) +static void cursor_line_up(madtty_t *rt) { int i; - rt->crow--; - rt->curpos_dirty = true; - if (rt->crow >= rt->pd->scrolltop) + rt->curs_row--; + if (rt->curs_row >= rt->scrolltop) return; /* must scroll the scrolling region up by 1 line, and put cursor on * first line of it */ - rt->crow = rt->pd->scrolltop; + rt->curs_row = rt->scrolltop; - for (i = rt->pd->scrollbottom; i > rt->pd->scrolltop; i--) { - rt->line_dirty[i] = true; + for (i = rt->scrollbottom; i > rt->scrolltop; i--) { memcpy(rt->cells[i], rt->cells[i-1], sizeof(RoteCell) * rt->cols); } - rt->line_dirty[rt->pd->scrolltop] = true; - /* clear first row of the scrolling region */ for (i = 0; i < rt->cols; i++) { - rt->cells[rt->pd->scrolltop][i].s[0] = 0x20; - rt->cells[rt->pd->scrolltop][i].len = 1; - rt->cells[rt->pd->scrolltop][i].attr = 0x70; + rt->cells[rt->scrolltop][i].s[0] = 0x20; + rt->cells[rt->scrolltop][i].len = 1; + rt->cells[rt->scrolltop][i].attrs = A_NORMAL; } } -static void put_normal_char(RoteTerm *rt, const char *s, int len) +static void put_normal_char(madtty_t *rt, const char *s, int len) { - if (rt->ccol >= rt->cols) { - rt->ccol = 0; + if (rt->curs_col >= rt->cols) { + rt->curs_col = 0; cursor_line_down(rt); } if (rt->insert) { int i; - for (i = rt->cols - 1; i >= rt->ccol+1; i--) { - rt->cells[rt->crow][i] = rt->cells[rt->crow][i-1]; + for (i = rt->cols - 1; i >= rt->curs_col+1; i--) { + rt->cells[rt->curs_row][i] = rt->cells[rt->curs_row][i-1]; } } - memcpy(rt->cells[rt->crow][rt->ccol].s, s, len); - rt->cells[rt->crow][rt->ccol].len = len; - rt->cells[rt->crow][rt->ccol].attr = rt->curattr; - rt->ccol++; - - rt->line_dirty[rt->crow] = true; - rt->curpos_dirty = true; + memcpy(rt->cells[rt->curs_row][rt->curs_col].s, s, len); + rt->cells[rt->curs_row][rt->curs_col].len = len; + rt->cells[rt->curs_row][rt->curs_col].attrs = rt->curattrs; + rt->curs_col++; } -static void put_graphmode_char(RoteTerm *rt, int c) +static void put_graphmode_char(madtty_t *rt, int c) { char nc; /* do some very pitiful translation to regular ascii chars */ @@ -168,41 +134,39 @@ static void put_graphmode_char(RoteTerm *rt, int c) put_normal_char(rt, &nc, 1); } -static void new_escape_sequence(RoteTerm *rt) +static void new_escape_sequence(madtty_t *rt) { - rt->pd->escaped = true; - rt->pd->esbuf_len = 0; - rt->pd->esbuf[0] = '\0'; + rt->escaped = true; + rt->esbuf_len = 0; + rt->esbuf[0] = '\0'; } -static void cancel_escape_sequence(RoteTerm *rt) +static void cancel_escape_sequence(madtty_t *rt) { - rt->pd->escaped = false; - rt->pd->esbuf_len = 0; - rt->pd->esbuf[0] = '\0'; + rt->escaped = false; + rt->esbuf_len = 0; + rt->esbuf[0] = '\0'; } -static void handle_control_char(RoteTerm *rt, int c) +static void handle_control_char(madtty_t *rt, int c) { switch (c) { case '\r': /* carriage return */ - rt->ccol = 0; + rt->curs_col = 0; break; case '\n': /* line feed */ - rt->ccol = 0; + rt->curs_col = 0; cursor_line_down(rt); - rt->curpos_dirty = true; break; case '\b': /* backspace */ - if (rt->ccol > 0) - rt->ccol--; - rt->curpos_dirty = true; + if (rt->curs_col > 0) + rt->curs_col--; break; case '\t': /* tab */ - rt->ccol = (rt->ccol + 8) & ~7; + rt->curs_col = (rt->curs_col + 8) & ~7; clamp_cursor_to_bounds(rt); break; @@ -211,16 +175,16 @@ static void handle_control_char(RoteTerm *rt, int c) break; case '\x0e': /* enter graphical character mode */ - rt->pd->graphmode = true; + rt->graphmode = true; break; case '\x0f': /* exit graphical character mode */ - rt->pd->graphmode = false; + rt->graphmode = false; break; case '\x9b': /* CSI character. Equivalent to ESC [ */ new_escape_sequence(rt); - rt->pd->esbuf[rt->pd->esbuf_len++] = '['; + rt->esbuf[rt->esbuf_len++] = '['; break; case '\x18': @@ -241,92 +205,14 @@ static bool is_valid_csi_ender(int c) || (c == '@' || c == '`'); } -static void rote_es_interpret_csi(RoteTerm *rt); - -static void try_interpret_escape_seq(RoteTerm *rt) -{ - char firstchar = rt->pd->esbuf[0]; - char lastchar = rt->pd->esbuf[rt->pd->esbuf_len-1]; - - if (!firstchar) - return; /* too early to do anything */ - - /* interpret ESC-M as reverse line-feed */ - if (firstchar == 'M') { - cursor_line_up(rt); - cancel_escape_sequence(rt); - return; - } - - if (firstchar != '[' && firstchar != ']') { - /* unrecognized escape sequence. Let's forget about it. */ - cancel_escape_sequence(rt); - return; - } - - if (firstchar == '[' && is_valid_csi_ender(lastchar)) { - /* we have a csi escape sequence: interpret it */ - rote_es_interpret_csi(rt); - cancel_escape_sequence(rt); - } else if (firstchar == ']' && lastchar == '\a') { - /* we have an xterm escape sequence: interpret it */ - - /* rote_es_interpret_xterm_es(rt); -- TODO!*/ - cancel_escape_sequence(rt); - } - - /* if the escape sequence took up all available space and could - * not yet be parsed, abort it */ - if (rt->pd->esbuf_len + 1 >= ESEQ_BUF_SIZE) - cancel_escape_sequence(rt); -} - -int rote_vt_inject(RoteTerm *rt, const char *data, int len) -{ - int pos; - - for (pos = 0; pos < len; pos++) { - if ((unsigned char)data[pos] <= 31) { - handle_control_char(rt, data[pos]); - continue; - } - - if (rt->pd->escaped && rt->pd->esbuf_len < ESEQ_BUF_SIZE) { - /* append character to ongoing escape sequence */ - rt->pd->esbuf[rt->pd->esbuf_len] = data[pos]; - rt->pd->esbuf[++rt->pd->esbuf_len] = 0; - - try_interpret_escape_seq(rt); - } else - if (rt->pd->graphmode) { - put_graphmode_char(rt, data[pos]); - } else { - static int8_t const lens[5] = { 1, -1, 2, 3, 4 }; - int bsf = __builtin_clz(~((unsigned char)data[pos] << 24)); - - if (pos + lens[bsf] > len) - return pos; - - put_normal_char(rt, data + pos, lens[bsf]); - pos += lens[bsf] - 1; - } - } - - return len; -} - -/****************************************************************************/ -/* CSI things */ -/****************************************************************************/ - /* interprets a 'set attribute' (SGR) CSI escape sequence */ -static void interpret_csi_SGR(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_SGR(madtty_t *rt, int param[], int pcount) { int i; if (pcount == 0) { /* special case: reset attributes */ - rt->curattr = 0x70; + rt->curattrs = A_NORMAL; return; } @@ -353,37 +239,46 @@ static void interpret_csi_SGR(RoteTerm *rt, int param[], int pcount) // 27 Negative image off // 28 Invisible image off - if (param[i] == 0) rt->curattr = 0x70; - else if (param[i] == 1 || param[i] == 2 || param[i] == 4) /* set bold */ - ROTE_ATTR_MOD_BOLD(rt->curattr,1); - else if (param[i] == 5) /* set blink */ - ROTE_ATTR_MOD_BLINK(rt->curattr,1); - else if (param[i] == 7 || param[i] == 27) { /* reverse video */ - int fg = ROTE_ATTR_FG(rt->curattr); - int bg = ROTE_ATTR_BG(rt->curattr); - ROTE_ATTR_MOD_FG(rt->curattr, bg); - ROTE_ATTR_MOD_BG(rt->curattr, fg); + switch (param[i]) { +#define CASE(x, op) case x: op; break + CASE(0, rt->curattrs = A_NORMAL); + CASE(1, rt->curattrs |= A_BOLD); + CASE(4, rt->curattrs |= A_UNDERLINE); + CASE(5, rt->curattrs |= A_BLINK); + CASE(7, rt->curattrs |= A_REVERSE); + CASE(8, rt->curattrs |= A_INVIS); + CASE(22, rt->curattrs &= ~A_BOLD); + CASE(24, rt->curattrs &= ~A_UNDERLINE); + CASE(25, rt->curattrs &= ~A_BLINK); + CASE(27, rt->curattrs &= ~A_REVERSE); + CASE(28, rt->curattrs &= ~A_INVIS); + + case 30 ... 37: + rt->curattrs &= ~070; + rt->curattrs |= (param[i] - 30) << 3; + break; + + case 40 ... 47: + rt->curattrs &= ~007; + rt->curattrs |= (param[i] - 40); + break; + + case 39: + rt->curattrs &= ~070; + break; + + case 49: + rt->curattrs &= ~007; + break; + + default: + break; } - else if (param[i] == 8) rt->curattr = 0x0; /* invisible */ - else if (param[i] == 22 || param[i] == 24) /* bold off */ - ROTE_ATTR_MOD_BOLD(rt->curattr,0); - else if (param[i] == 25) /* blink off */ - ROTE_ATTR_MOD_BLINK(rt->curattr,0); - else if (param[i] == 28) /* invisible off */ - rt->curattr = 0x70; - else if (param[i] >= 30 && param[i] <= 37) /* set fg */ - ROTE_ATTR_MOD_FG(rt->curattr, param[i] - 30); - else if (param[i] >= 40 && param[i] <= 47) /* set bg */ - ROTE_ATTR_MOD_BG(rt->curattr, param[i] - 40); - else if (param[i] == 39) /* reset foreground to default */ - ROTE_ATTR_MOD_FG(rt->curattr, 7); - else if (param[i] == 49) /* reset background to default */ - ROTE_ATTR_MOD_BG(rt->curattr, 0); } } /* interprets an 'erase display' (ED) escape sequence */ -static void interpret_csi_ED(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_ED(madtty_t *rt, int param[], int pcount) { int r, c; int start_row, start_col, end_row, end_col; @@ -398,188 +293,173 @@ static void interpret_csi_ED(RoteTerm *rt, int param[], int pcount) if (pcount && param[0] == 1) { start_row = 0; start_col = 0; - end_row = rt->crow; - end_col = rt->ccol; + end_row = rt->curs_row; + end_col = rt->curs_col; } else { - start_row = rt->crow; - start_col = rt->ccol; + start_row = rt->curs_row; + start_col = rt->curs_col; end_row = rt->rows - 1; end_col = rt->cols - 1; } /* clean range */ for (r = start_row; r <= end_row; r++) { - rt->line_dirty[r] = true; - for (c = (r == start_row ? start_col : 0); c <= (r == end_row ? end_col : rt->cols - 1); c++) { - rt->cells[r][c].s[0] = 0x20; - rt->cells[r][c].len = 1; - rt->cells[r][c].attr = rt->curattr; + rt->cells[r][c].s[0] = 0x20; + rt->cells[r][c].len = 1; + rt->cells[r][c].attrs = rt->curattrs; } } } /* interprets a 'move cursor' (CUP) escape sequence */ -static void interpret_csi_CUP(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_CUP(madtty_t *rt, int param[], int pcount) { if (pcount == 0) { /* special case */ - rt->crow = rt->ccol = 0; + rt->curs_row = rt->curs_col = 0; return; } else if (pcount < 2) { return; /* malformed */ } - rt->crow = param[0] - 1; /* convert from 1-based to 0-based */ - rt->ccol = param[1] - 1; /* convert from 1-based to 0-based */ - - rt->curpos_dirty = true; + rt->curs_row = param[0] - 1; /* convert from 1-based to 0-based */ + rt->curs_col = param[1] - 1; /* convert from 1-based to 0-based */ clamp_cursor_to_bounds(rt); } /* Interpret the 'relative mode' sequences: CUU, CUD, CUF, CUB, CNL, * CPL, CHA, HPR, VPA, VPR, HPA */ -static void interpret_csi_C(RoteTerm *rt, char verb, int param[], int pcount) +static void interpret_csi_C(madtty_t *rt, char verb, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; switch (verb) { - case 'A': rt->crow -= n; break; - case 'B': case 'e': rt->crow += n; break; - case 'C': case 'a': rt->ccol += n; break; - case 'D': rt->ccol -= n; break; - case 'E': rt->crow += n; rt->ccol = 0; break; - case 'F': rt->crow -= n; rt->ccol = 0; break; - case 'G': case '`': rt->ccol = param[0] - 1; break; - case 'd': rt->crow = param[0] - 1; break; + case 'A': rt->curs_row -= n; break; + case 'B': case 'e': rt->curs_row += n; break; + case 'C': case 'a': rt->curs_col += n; break; + case 'D': rt->curs_col -= n; break; + case 'E': rt->curs_row += n; rt->curs_col = 0; break; + case 'F': rt->curs_row -= n; rt->curs_col = 0; break; + case 'G': case '`': rt->curs_col = param[0] - 1; break; + case 'd': rt->curs_row = param[0] - 1; break; } - rt->curpos_dirty = true; clamp_cursor_to_bounds(rt); } /* Interpret the 'erase line' escape sequence */ -static void interpret_csi_EL(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_EL(madtty_t *rt, int param[], int pcount) { int erase_start, erase_end, i; int cmd = pcount ? param[0] : 0; switch (cmd) { - case 1: erase_start = 0; erase_end = rt->ccol; break; + case 1: erase_start = 0; erase_end = rt->curs_col; break; case 2: erase_start = 0; erase_end = rt->cols - 1; break; - default: erase_start = rt->ccol; erase_end = rt->cols - 1; break; + default: erase_start = rt->curs_col; erase_end = rt->cols - 1; break; } for (i = erase_start; i <= erase_end; i++) { - rt->cells[rt->crow][i].s[0] = 0x20; - rt->cells[rt->crow][i].len = 1; - rt->cells[rt->crow][i].attr = rt->curattr; + rt->cells[rt->curs_row][i].s[0] = 0x20; + rt->cells[rt->curs_row][i].len = 1; + rt->cells[rt->curs_row][i].attrs = rt->curattrs; } - - rt->line_dirty[rt->crow] = true; } /* Interpret the 'insert blanks' sequence (ICH) */ -static void interpret_csi_ICH(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_ICH(madtty_t *rt, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; int i; - for (i = rt->cols - 1; i >= rt->ccol + n; i--) { - rt->cells[rt->crow][i] = rt->cells[rt->crow][i - n]; + for (i = rt->cols - 1; i >= rt->curs_col + n; i--) { + rt->cells[rt->curs_row][i] = rt->cells[rt->curs_row][i - n]; } - for (i = rt->ccol; i < rt->ccol + n; i++) { - rt->cells[rt->crow][i].s[0] = 0x20; - rt->cells[rt->crow][i].len = 1; - rt->cells[rt->crow][i].attr = rt->curattr; + for (i = rt->curs_col; i < rt->curs_col + n; i++) { + rt->cells[rt->curs_row][i].s[0] = 0x20; + rt->cells[rt->curs_row][i].len = 1; + rt->cells[rt->curs_row][i].attrs = rt->curattrs; } - - rt->line_dirty[rt->crow] = true; } /* Interpret the 'delete chars' sequence (DCH) */ -static void interpret_csi_DCH(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_DCH(madtty_t *rt, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; int i; - for (i = rt->ccol; i < rt->cols; i++) { + for (i = rt->curs_col; i < rt->cols; i++) { if (i + n < rt->cols) { - rt->cells[rt->crow][i] = rt->cells[rt->crow][i + n]; + rt->cells[rt->curs_row][i] = rt->cells[rt->curs_row][i + n]; } else { - rt->cells[rt->crow][i].s[0] = 0x20; - rt->cells[rt->crow][i].len = 1; - rt->cells[rt->crow][i].attr = rt->curattr; + rt->cells[rt->curs_row][i].s[0] = 0x20; + rt->cells[rt->curs_row][i].len = 1; + rt->cells[rt->curs_row][i].attrs = rt->curattrs; } } - - rt->line_dirty[rt->crow] = true; } /* Interpret an 'insert line' sequence (IL) */ -static void interpret_csi_IL(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_IL(madtty_t *rt, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; int i, j; - for (i = rt->pd->scrollbottom; i >= rt->crow + n; i--) { + for (i = rt->scrollbottom; i >= rt->curs_row + n; i--) { memcpy(rt->cells[i], rt->cells[i - n], sizeof(RoteCell) * rt->cols); } - for (i = rt->crow; i < rt->crow + n && i <= rt->pd->scrollbottom; i++) { - rt->line_dirty[i] = true; + for (i = rt->curs_row; i < rt->curs_row + n && i <= rt->scrollbottom; i++) { for (j = 0; j < rt->cols; j++) { - rt->cells[i][j].s[0] = 0x20; - rt->cells[i][j].len = 1; - rt->cells[i][j].attr = rt->curattr; + rt->cells[i][j].s[0] = 0x20; + rt->cells[i][j].len = 1; + rt->cells[i][j].attrs = rt->curattrs; } } } /* Interpret a 'delete line' sequence (DL) */ -static void interpret_csi_DL(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_DL(madtty_t *rt, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; int i, j; - for (i = rt->crow; i <= rt->pd->scrollbottom; i++) { - rt->line_dirty[i] = true; - if (i + n <= rt->pd->scrollbottom) { + for (i = rt->curs_row; i <= rt->scrollbottom; i++) { + if (i + n <= rt->scrollbottom) { memcpy(rt->cells[i], rt->cells[i+n], sizeof(RoteCell) * rt->cols); } else { for (j = 0; j < rt->cols; j++) { - rt->cells[i][j].s[0] = 0x20; - rt->cells[i][j].len = 1; - rt->cells[i][j].attr = rt->curattr; + rt->cells[i][j].s[0] = 0x20; + rt->cells[i][j].len = 1; + rt->cells[i][j].attrs = rt->curattrs; } } } } /* Interpret an 'erase characters' (ECH) sequence */ -static void interpret_csi_ECH(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_ECH(madtty_t *rt, int param[], int pcount) { int n = (pcount && param[0] > 0) ? param[0] : 1; int i; - for (i = rt->ccol; i < rt->ccol + n && i < rt->cols; i++) { - rt->cells[rt->crow][i].s[0] = 0x20; - rt->cells[rt->crow][i].len = 1; - rt->cells[rt->crow][i].attr = rt->curattr; + for (i = rt->curs_col; i < rt->curs_col + n && i < rt->cols; i++) { + rt->cells[rt->curs_row][i].s[0] = 0x20; + rt->cells[rt->curs_row][i].len = 1; + rt->cells[rt->curs_row][i].attrs = rt->curattrs; } - - rt->line_dirty[rt->crow] = true; } /* Interpret a 'set scrolling region' (DECSTBM) sequence */ -static void interpret_csi_DECSTBM(RoteTerm *rt, int param[], int pcount) +static void interpret_csi_DECSTBM(madtty_t *rt, int param[], int pcount) { int newtop, newbottom; @@ -607,37 +487,18 @@ static void interpret_csi_DECSTBM(RoteTerm *rt, int param[], int pcount) /* check for range validity */ if (newtop > newbottom) return; - rt->pd->scrolltop = newtop; - rt->pd->scrollbottom = newbottom; -} - -static void -interpret_csi_SAVECUR(RoteTerm *rt, - int param[] __attribute__((unused)), - int pcount __attribute__((unused))) -{ - rt->pd->saved_x = rt->ccol; - rt->pd->saved_y = rt->crow; -} - -static void -interpret_csi_RESTORECUR(RoteTerm *rt, - int param[] __attribute__((unused)), - int pcount __attribute__((unused))) -{ - rt->ccol = rt->pd->saved_x; - rt->crow = rt->pd->saved_y; - rt->curpos_dirty = true; + rt->scrolltop = newtop; + rt->scrollbottom = newbottom; } -static void rote_es_interpret_csi(RoteTerm *rt) +static void es_interpret_csi(madtty_t *rt) { static int csiparam[MAX_CSI_ES_PARAMS]; int param_count = 0; - const char *p = rt->pd->esbuf + 1; - char verb = rt->pd->esbuf[rt->pd->esbuf_len - 1]; + const char *p = rt->esbuf + 1; + char verb = rt->esbuf[rt->esbuf_len - 1]; - if (!strncmp(rt->pd->esbuf, "[?", 2)) { /* private-mode CSI, ignore */ + if (!strncmp(rt->esbuf, "[?", 2)) { /* private-mode CSI, ignore */ return; } @@ -690,23 +551,98 @@ static void rote_es_interpret_csi(RoteTerm *rt) case 'r': /* set scrolling region */ interpret_csi_DECSTBM(rt, csiparam, param_count); break; case 's': /* save cursor location */ - interpret_csi_SAVECUR(rt, csiparam, param_count); break; + rt->curs_srow = rt->curs_col; + rt->curs_scol = rt->curs_row; + break; case 'u': /* restore cursor location */ - interpret_csi_RESTORECUR(rt, csiparam, param_count); break; + rt->curs_col = rt->curs_srow; + rt->curs_row = rt->curs_scol; + break; default: break; } } -RoteTerm *rote_vt_create(int rows, int cols) +static void try_interpret_escape_seq(madtty_t *rt) { - RoteTerm *rt; + char firstchar = rt->esbuf[0]; + char lastchar = rt->esbuf[rt->esbuf_len-1]; + + if (!firstchar) + return; /* too early to do anything */ + + /* interpret ESC-M as reverse line-feed */ + if (firstchar == 'M') { + cursor_line_up(rt); + cancel_escape_sequence(rt); + return; + } + + if (firstchar != '[' && firstchar != ']') { + /* unrecognized escape sequence. Let's forget about it. */ + cancel_escape_sequence(rt); + return; + } + + if (firstchar == '[' && is_valid_csi_ender(lastchar)) { + es_interpret_csi(rt); + cancel_escape_sequence(rt); + } else if (firstchar == ']' && lastchar == '\a') { + /* we have an xterm escape sequence: interpret it */ + + /* es_interpret_xterm_es(rt); -- TODO!*/ + cancel_escape_sequence(rt); + } + + /* if the escape sequence took up all available space and could + * not yet be parsed, abort it */ + if (rt->esbuf_len + 1 >= ESEQ_BUF_SIZE) + cancel_escape_sequence(rt); +} + +int madtty_inject(madtty_t *rt, const char *data, int len) +{ + int pos; + + for (pos = 0; pos < len; pos++) { + if ((unsigned char)data[pos] <= 31) { + handle_control_char(rt, data[pos]); + continue; + } + + if (rt->escaped && rt->esbuf_len < ESEQ_BUF_SIZE) { + /* append character to ongoing escape sequence */ + rt->esbuf[rt->esbuf_len] = data[pos]; + rt->esbuf[++rt->esbuf_len] = 0; + + try_interpret_escape_seq(rt); + } else + if (rt->graphmode) { + put_graphmode_char(rt, data[pos]); + } else { + static int8_t const lens[5] = { 1, -1, 2, 3, 4 }; + int bsf = __builtin_clz(~((unsigned char)data[pos] << 24)); + + if (pos + lens[bsf] > len) + return pos; + + put_normal_char(rt, data + pos, lens[bsf]); + pos += lens[bsf] - 1; + } + } + + return len; +} + +madtty_t *madtty_create(int rows, int cols) +{ + madtty_t *rt; int i; if (rows <= 0 || cols <= 0) return NULL; - rt = (RoteTerm*)calloc(sizeof(RoteTerm), 1); + rt = (madtty_t*)calloc(sizeof(madtty_t), 1); if (!rt) return NULL; @@ -723,33 +659,25 @@ RoteTerm *rote_vt_create(int rows, int cols) rt->cells[i] = (RoteCell*)calloc(sizeof(RoteCell), rt->cols); } - /* allocate dirtiness array */ - rt->line_dirty = (bool*)calloc(sizeof(bool), rt->rows); - /* initialization of other public fields */ - rt->crow = rt->ccol = 0; - rt->curattr = 0x70; /* white text over black background */ - - /* allocate private data */ - rt->pd = (RoteTermPrivate*)calloc(sizeof(RoteTermPrivate), 1); + rt->curs_row = rt->curs_col = 0; + rt->curattrs = A_NORMAL; /* white text over black background */ rt->pty = -1; /* no pty for now */ /* initial scrolling area is the whole window */ - rt->pd->scrolltop = 0; - rt->pd->scrollbottom = rt->rows - 1; + rt->scrolltop = 0; + rt->scrollbottom = rt->rows - 1; return rt; } -void rote_vt_destroy(RoteTerm *rt) +void madtty_destroy(madtty_t *rt) { int i; if (!rt) return; - free(rt->pd); - free(rt->line_dirty); for (i = 0; i < rt->rows; i++) { free(rt->cells[i]); } @@ -757,30 +685,14 @@ void rote_vt_destroy(RoteTerm *rt) free(rt); } -static void cur_set_attr(WINDOW *win, uint8_t attr) -{ - unsigned cp = ROTE_ATTR_BG(attr) * 8 + 7 - ROTE_ATTR_FG(attr); - wattrset(win, cp ? COLOR_PAIR(cp) : A_NORMAL); - - if (ROTE_ATTR_BOLD(attr)) - wattron(win, A_BOLD); - if (ROTE_ATTR_BLINK(attr)) - wattron(win, A_BLINK); -} - -static unsigned ensure_printable(unsigned ch) -{ - return ch >= 32 ? ch : 32; -} - -void rote_vt_draw(RoteTerm *rt, WINDOW *win, int srow, int scol) +void madtty_draw(madtty_t *rt, WINDOW *win, int srow, int scol) { int i, j; for (i = 0; i < rt->rows; i++) { wmove(win, srow + i, scol); for (j = 0; j < rt->cols; j++) { - cur_set_attr(win, rt->cells[i][j].attr); + wattrset(win, (rt->cells[i][j].attrs & ~077) | COLOR_PAIR(rt->cells[i][j].attrs & 077)); if (rt->cells[i][j].len && rt->cells[i][j].s[0] >= ' ') { waddnstr(win, rt->cells[i][j].s, rt->cells[i][j].len); } else { @@ -789,12 +701,12 @@ void rote_vt_draw(RoteTerm *rt, WINDOW *win, int srow, int scol) } } - wmove(win, srow + rt->crow, scol + rt->ccol); + wmove(win, srow + rt->curs_row, scol + rt->curs_col); } /******************************************************/ -pid_t rote_vt_forkpty(RoteTerm *rt, const char *path, const char *argv[]) +pid_t madtty_forkpty(madtty_t *rt, const char *path, const char *argv[]) { struct winsize ws; pid_t pid; @@ -819,7 +731,7 @@ pid_t rote_vt_forkpty(RoteTerm *rt, const char *path, const char *argv[]) return rt->childpid = pid; } -int rote_vt_read(RoteTerm *rt, char *buf, int buflen) +int madtty_read(madtty_t *rt, char *buf, int buflen) { if (rt->pty < 0) { errno = EINVAL; @@ -829,7 +741,7 @@ int rote_vt_read(RoteTerm *rt, char *buf, int buflen) return read(rt->pty, buf, buflen); } -int rote_vt_write(RoteTerm *rt, const char *data, int len) +int madtty_write(madtty_t *rt, const char *data, int len) { int res; @@ -848,46 +760,38 @@ again: return res; } -static const char *keytable[KEY_MAX+1]; - -static void keytable_init() -{ - memset(keytable, 0, KEY_MAX+1 * sizeof(const char*)); - - keytable['\n'] = "\r"; - keytable[KEY_UP] = "\e[A"; - keytable[KEY_DOWN] = "\e[B"; - keytable[KEY_RIGHT] = "\e[C"; - keytable[KEY_LEFT] = "\e[D"; - keytable[KEY_BACKSPACE] = "\b"; - keytable[KEY_HOME] = "\e[1~"; - keytable[KEY_IC] = "\e[2~"; - keytable[KEY_DC] = "\e[3~"; - keytable[KEY_END] = "\e[4~"; - keytable[KEY_PPAGE] = "\e[5~"; - keytable[KEY_NPAGE] = "\e[6~"; - keytable[KEY_SUSPEND] = "\x1A"; /* Ctrl+Z gets mapped to this */ - keytable[KEY_F(1)] = "\e[[A"; - keytable[KEY_F(2)] = "\e[[B"; - keytable[KEY_F(3)] = "\e[[C"; - keytable[KEY_F(4)] = "\e[[D"; - keytable[KEY_F(5)] = "\e[[E"; - keytable[KEY_F(6)] = "\e[17~"; - keytable[KEY_F(7)] = "\e[18~"; - keytable[KEY_F(8)] = "\e[19~"; - keytable[KEY_F(9)] = "\e[20~"; - keytable[KEY_F(10)] = "\e[21~"; -} +static char const * const keytable[KEY_MAX+1] = { + ['\n'] = "\r", + [KEY_UP] = "\e[A", + [KEY_DOWN] = "\e[B", + [KEY_RIGHT] = "\e[C", + [KEY_LEFT] = "\e[D", + [KEY_BACKSPACE] = "\b", + [KEY_HOME] = "\e[1~", + [KEY_IC] = "\e[2~", + [KEY_DC] = "\e[3~", + [KEY_END] = "\e[4~", + [KEY_PPAGE] = "\e[5~", + [KEY_NPAGE] = "\e[6~", + [KEY_SUSPEND] = "\x1A", /* Ctrl+Z gets mapped to this */ + [KEY_F(1)] = "\e[[A", + [KEY_F(2)] = "\e[[B", + [KEY_F(3)] = "\e[[C", + [KEY_F(4)] = "\e[[D", + [KEY_F(5)] = "\e[[E", + [KEY_F(6)] = "\e[17~", + [KEY_F(7)] = "\e[18~", + [KEY_F(8)] = "\e[19~", + [KEY_F(9)] = "\e[20~", + [KEY_F(10)] = "\e[21~", +}; -void rote_vt_keypress(RoteTerm *rt, int keycode) +void madtty_keypress(madtty_t *rt, int keycode) { char c = (char)keycode; const char *buf; int len; - if (keytable['\n'] == NULL) - keytable_init(); - if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) { buf = keytable[keycode]; len = strlen(keytable[keycode]); @@ -897,7 +801,7 @@ void rote_vt_keypress(RoteTerm *rt, int keycode) } while (len > 0) { - int res = rote_vt_write(rt, buf, len); + int res = madtty_write(rt, buf, len); if (res < 0) return; @@ -906,3 +810,16 @@ void rote_vt_keypress(RoteTerm *rt, int keycode) } } +void madtty_initialize(void) +{ + setlocale(LC_ALL, ""); + initscr(); + noecho(); + start_color(); + raw(); + nodelay(stdscr, TRUE); + keypad(stdscr, TRUE); + + for (int i = 0; i < 8 * 8; i++) + init_pair(i, i >> 3, i & 7); +} diff --git a/madtty/madtty.h b/madtty/madtty.h index 7424429..25da678 100644 --- a/madtty/madtty.h +++ b/madtty/madtty.h @@ -29,163 +29,51 @@ #include #include -/* Color codes: 0 = black, 1 = red, 2 = green, 3 = yellow, 4 = blue, - * 5 = magenta, 6 = cyan, 7 = white. - * - * An 'attribute' as used in this library means an 8-bit value that conveys - * a foreground color code, a background color code, and the bold - * and blink bits. Each cell in the virtual terminal screen is associated - * with an attribute that specifies its appearance. The bits of an attribute, - * from most significant to least significant, are - * - * bit: 7 6 5 4 3 2 1 0 - * content: S F F F H B B B - * | `-,-' | `-,-' - * | | | | - * | | | `----- 3-bit background color code (0 - 7) - * | | `--------- blink bit (if on, text is blinking) - * | `------------- 3-bit foreground color code (0 - 7) - * `----------------- bold bit - * - * It is however recommended that you use the provided macros rather - * than dealing with this format directly. - * - * Sometimes we will call the 'SFFF' nibble above the 'extended - * foreground color code', and the 'HBBB' nibble the 'extended background - * color code'. So the extended color codes are just the regular - * color codes except that they have an additional bit (the most significant - * bit) indicating bold/blink. - */ - -/* retrieve attribute fields */ -#define ROTE_ATTR_BG(attr) ((attr) & 0x07) -#define ROTE_ATTR_FG(attr) (((attr) & 0x70) >> 4) - -/* retrieve 'extended' color codes (see above for info) */ -#define ROTE_ATTR_XBG(attr) ((attr) & 0x0F) -#define ROTE_ATTR_XFG(attr) (((attr) & 0xF0) >> 4) - -/* set attribute fields. This requires attr to be an lvalue, and it will - * be evaluated more than once. Use with care. */ -#define ROTE_ATTR_MOD_BG(attr, newbg) attr &= 0xF8, attr |= (newbg) -#define ROTE_ATTR_MOD_FG(attr, newfg) attr &= 0x8F, attr |= ((newfg) << 4) -#define ROTE_ATTR_MOD_XBG(attr, newxbg) attr &= 0xF0, attr |= (newxbg) -#define ROTE_ATTR_MOD_XFG(attr, newxfg) attr &= 0x0F, attr |= ((newxfg) << 4) -#define ROTE_ATTR_MOD_BOLD(attr, boldbit) \ - attr &= 0x7F, attr |= (boldbit)?0x80:0x00 -#define ROTE_ATTR_MOD_BLINK(attr, blinkbit) \ - attr &= 0xF7, attr |= (blinkbit)?0x08:0x00 - -/* these return non-zero for 'yes', zero for 'no'. Don't rely on them being - * any more specific than that (e.g. being exactly 1 for 'yes' or whatever). */ -#define ROTE_ATTR_BOLD(attr) ((attr) & 0x80) -#define ROTE_ATTR_BLINK(attr) ((attr) & 0x08) +#define MAX_CSI_ES_PARAMS 32 +#define ESEQ_BUF_SIZE 128 /* size of escape sequence buffer */ /* Represents each of the text cells in the terminal screen */ -typedef struct RoteCell_ { +typedef struct { char s[4]; char len; - uint8_t attr; /* a color attribute, as described previously */ + attr_t attrs; } RoteCell; -/* Declaration of opaque rote_Term_Private structure */ -typedef struct RoteTermPrivate_ RoteTermPrivate; - -/* Represents a virtual terminal. You may directly access the fields - * of this structure, but please pay close attention to the fields - * marked read-only or with special usage notes. */ -typedef struct RoteTerm_ { - int rows, cols; /* terminal dimensions, READ-ONLY. You - * can't resize the terminal by changing - * this (a segfault is about all you will - * accomplish). */ - - RoteCell **cells; /* matrix of cells. This - * matrix is indexed as cell[row][column] - * where 0 <= row < rows and - * 0 <= col < cols - * - * You may freely modify the contents of - * the cells. - */ - - int crow, ccol; /* cursor coordinates. READ-ONLY. */ - - uint8_t curattr; /* current attribute, that is the attribute - * that will be used for newly inserted - * characters */ - - int pty; /* pty of the process */ - pid_t childpid; /* pid of the child process running in the - * terminal; 0 for none. This is READ-ONLY. */ - - RoteTermPrivate *pd; /* private state data */ - - unsigned insert : 1; /* insert or replace mode */ - - /* --- dirtiness flags: the following flags will be raised when the - * corresponding items are modified. They can only be unset by YOU - * (when, for example, you redraw the term or something) --- */ - unsigned curpos_dirty : 1; /* whether cursor location has changed */ - bool *line_dirty; /* whether each row is dirty */ - /* --- end dirtiness flags */ -} RoteTerm; - -/* Creates a new virtual terminal with the given dimensions. You - * must destroy it with rote_vt_destroy after you are done with it. - * The terminal will be initially blank and the cursor will - * be at the top-left corner. - * - * Returns NULL on error. - */ -RoteTerm *rote_vt_create(int rows, int cols); - -/* Destroys a virtual terminal previously created with - * rote_vt_create. If rt == NULL, does nothing. */ -void rote_vt_destroy(RoteTerm *rt); - -/* Starts a forked process in the terminal. The parameter - * is a shell command to execute (it will be interpreted by '/bin/sh -c') - * Returns the pid of the forked process. - * - * Some useful reminders: If you want to be notified when child processes exit, - * you should handle the SIGCHLD signal. If, on the other hand, you want to - * ignore exitting child processes, you should set the SIGCHLD handler to - * SIG_IGN to prevent child processes from hanging around the system as 'zombie - * processes'. - * - * Continuing to write to a RoteTerm whose child process has died does not - * accomplish a lot, but is not an error and should not cause your program - * to crash or block indefinitely or anything of that sort :-) - * If, however, you want to be tidy and inform the RoteTerm that its - * child has died, call rote_vt_forsake_child when appropriate. - * - * If there is an error, returns -1. Notice that passing an invalid - * command will not cause an error at this level: the shell will try - * to execute the command and will exit with status 127. You can catch - * that by installing a SIGCHLD handler if you want. - */ -pid_t rote_vt_forkpty(RoteTerm *rt, const char *path, const char *argv[]); +typedef struct { + int pty; + pid_t childpid; -int rote_vt_read(RoteTerm *rt, char *buf, int buflen); -int rote_vt_write(RoteTerm *rt, const char *data, int length); + /* flags */ + unsigned insert : 1; + unsigned escaped : 1; + unsigned graphmode : 1; -/* Inject data into the terminal. needs NOT be 0-terminated: - * its length is solely determined by the parameter. Please - * notice that this writes directly to the terminal, that is, - * this function does NOT send the data to the forked process - * running in the terminal (if any). For that, you might want - * to use rote_vt_write. - */ -int rote_vt_inject(RoteTerm *rt, const char *data, int length); -void rote_vt_draw(RoteTerm *rt, WINDOW *win, int startrow, int startcol); - -/* Indicates to the terminal that the given key has been pressed. - * This will cause the terminal to rote_vt_write() the appropriate - * escape sequence for that key (that is, the escape sequence - * that the linux text-mode console would produce for it). The argument, - * keycode, must be a CURSES EXTENDED KEYCODE, the ones you get - * when you use keypad(somewin, TRUE) (see man page). */ -void rote_vt_keypress(RoteTerm *rt, int keycode); + /* geometry */ + int rows, cols; + int scrolltop, scrollbottom; + RoteCell **cells; + + /* cursor */ + attr_t curattrs; + int curs_row, curs_col; + int curs_srow, curs_scol; + + char esbuf[ESEQ_BUF_SIZE]; + int esbuf_len; +} madtty_t; + +void madtty_initialize(void); + +madtty_t *madtty_create(int rows, int cols); +void madtty_destroy(madtty_t *rt); +pid_t madtty_forkpty(madtty_t *rt, const char *path, const char *argv[]); + +int madtty_read(madtty_t *rt, char *buf, int buflen); +int madtty_write(madtty_t *rt, const char *data, int length); + +int madtty_inject(madtty_t *rt, const char *data, int length); +void madtty_draw(madtty_t *rt, WINDOW *win, int startrow, int startcol); + +void madtty_keypress(madtty_t *rt, int keycode); #endif /* MADTTY_MADTTY_H */ -- 2.20.1