/* * curses.c: a small curses for the fredygames.com browser builds (Emscripten). See curses.h and README.md. * * Window contents live in memory as cells (code point, attributes, colour pair). The "physical screen" * (curscr) is what the page shows: wrefresh() copies the touched lines of a window into it and, when * something changed, hands it to the page (curses-web.js), which draws it on a canvas. Keys come from the * page through Module.cwPollKey / Module.cwWaitKey (an async call, so programs need -sASYNCIFY). * * Copyright (C) 2026 FredyGames. Distributed under the MIT License (see LICENSE). */ #include #include #include #include #include #include #include "curses.h" /* Programs that define functions with the names of curses calls (Hack has timeout() and unctrl()) win at link time. */ #define CW_WEAK __attribute__((weak)) /* ------------------------------------------------------------------------------------------------ */ /* Bridge to the page */ /* ------------------------------------------------------------------------------------------------ */ EM_JS(int, cw_js_rows, (void), { return (Module.cwConfig && Module.cwConfig.rows) || 24; }); EM_JS(int, cw_js_cols, (void), { return (Module.cwConfig && Module.cwConfig.cols) || 80; }); EM_JS(void, cw_js_draw, (const void *cells, int rows, int cols, int cury, int curx, int vis, const void *pal, const void *pfg, const void *pbg), { if (Module.cwDraw) { const c = cells >> 2, p = pal >> 2, f = pfg >> 1, b = pbg >> 1; Module.cwDraw(HEAPU32.slice(c, c + rows * cols * 2), rows, cols, cury, curx, vis, HEAPU32.slice(p, p + 256), HEAP16.slice(f, f + 256), HEAP16.slice(b, b + 256)); } }); /* Returns a key code or -2 when none is waiting. */ EM_JS(int, cw_js_poll, (void), { return Module.cwPollKey ? Module.cwPollKey() : -2; }); /* Waits for a key: its code, or -1 when `ms` >= 0 pass without one. */ EM_ASYNC_JS(int, cw_js_wait, (int ms), { if (!Module.cwWaitKey) return -1; return await Module.cwWaitKey(ms); }); EM_ASYNC_JS(void, cw_js_sleep, (int ms), { await new Promise((resolve) => setTimeout(resolve, ms)); }); EM_JS(void, cw_js_bell, (int visual), { if (Module.cwBell) Module.cwBell(visual); }); EM_JS(void, cw_js_flushkeys, (void), { if (Module.cwFlushKeys) Module.cwFlushKeys(); }); EM_JS(void, cw_js_end, (int ended), { if (Module.cwEnd) Module.cwEnd(ended); }); EM_JS(void, cw_js_mode, (int nl, int raw, int echo), { if (Module.cwMode) Module.cwMode(nl, raw, echo); }); /* ------------------------------------------------------------------------------------------------ */ /* State */ /* ------------------------------------------------------------------------------------------------ */ struct cw_screen { int unused; }; static SCREEN the_screen; WINDOW *stdscr = NULL; WINDOW *curscr = NULL; WINDOW *newscr = NULL; int LINES = 24; int COLS = 80; int COLORS = 256; int COLOR_PAIRS = 256; int TABSIZE = 8; int ESCDELAY = 0; chtype acs_map[128]; static WINDOW the_curscr; static bool inited = false; static bool ended = false; static bool ansi_stdout_mode = false; /* stdout is drawn on the screen (termcap programs) */ static bool g_echo = true, g_nl = true, g_raw = false; static int g_half_ms = 0; static int g_curs = 1; static bool phys_dirty = true; static int phys_cury = 0, phys_curx = 0; static short pair_fg[256], pair_bg[256]; static uint32_t palette[256]; static int unget_buf[64]; static int unget_n = 0; static char nokeypad[9] = "kjhlybun"; /* up down left right home end pgup pgdn */ static const uint32_t base_palette[16] = { 0x000000, 0xcc0000, 0x4e9a06, 0xc4a000, 0x3465a4, 0x75507b, 0x06989a, 0xd3d7cf, 0x555753, 0xef2929, 0x8ae234, 0xfce94f, 0x729fcf, 0xad7fa8, 0x34e2e2, 0xeeeeec, }; static void init_palette(void) { for (int i = 0; i < 16; i++) palette[i] = base_palette[i]; static const int lv[6] = {0, 95, 135, 175, 215, 255}; for (int i = 0; i < 216; i++) { palette[16 + i] = (uint32_t)(lv[i / 36] << 16 | lv[(i / 6) % 6] << 8 | lv[i % 6]); } for (int i = 0; i < 24; i++) { int v = 8 + i * 10; palette[232 + i] = (uint32_t)(v << 16 | v << 8 | v); } for (int i = 0; i < 256; i++) { pair_fg[i] = -1; pair_bg[i] = -1; } } #define ATTRBITS (A_ATTRIBUTES & ~A_COLOR) /* ACS characters: the table gives the Unicode character for the VT100 line-drawing code. */ static uint32_t acs_unicode(unsigned c) { switch (c) { case 'l': return 0x250c; case 'm': return 0x2514; case 'k': return 0x2510; case 'j': return 0x2518; case 't': return 0x251c; case 'u': return 0x2524; case 'v': return 0x2534; case 'w': return 0x252c; case 'q': return 0x2500; case 'x': return 0x2502; case 'n': return 0x253c; case 'o': return 0x23ba; case 's': return 0x23bd; case '`': return 0x25c6; case 'a': return 0x2592; case 'f': return 0x00b0; case 'g': return 0x00b1; case '~': return 0x00b7; case ',': return 0x2190; case '+': return 0x2192; case '.': return 0x2193; case '-': return 0x2191; case 'h': return 0x2591; case 'i': return 0x2603; case '0': return 0x2588; case 'y': return 0x2264; case 'z': return 0x2265; case '{': return 0x03c0; case '|': return 0x2260; case '}': return 0x00a3; default: return c; } } /* ------------------------------------------------------------------------------------------------ */ /* Windows */ /* ------------------------------------------------------------------------------------------------ */ static cw_cell blank_cell(const WINDOW *w) { cw_cell c; c.ch = w->_bkgd & A_CHARTEXT; if (c.ch == 0) c.ch = ' '; c.attr = (uint16_t)((w->_bkgd & ATTRBITS) >> 16); c.pair = (uint8_t)PAIR_NUMBER(w->_bkgd); c.pad = 0; return c; } static inline cw_cell *cell_at(const WINDOW *w, int y, int x) { return &w->_cells[y * w->_stride + x]; } static void touch_row(WINDOW *w, int y) { int yy = y; for (WINDOW *c = w; c != NULL; c = c->_parent) { if (yy >= 0 && yy <= c->_maxy) c->_touch[yy] = 1; if (c->_parent == NULL) break; yy += c->_begy - c->_parent->_begy; } } static void init_window_fields(WINDOW *w, int rows, int cols, int by, int bx) { memset(w, 0, sizeof(*w)); w->_maxy = rows - 1; w->_maxx = cols - 1; w->_begy = by; w->_begx = bx; w->_bkgd = ' '; w->_delay = -1; w->_regtop = 0; w->_regbottom = rows - 1; } static WINDOW *make_window(int rows, int cols, int by, int bx) { if (rows < 1 || cols < 1) return NULL; WINDOW *w = (WINDOW *)calloc(1, sizeof(WINDOW)); init_window_fields(w, rows, cols, by, bx); w->_cells = (cw_cell *)malloc(sizeof(cw_cell) * (size_t)rows * (size_t)cols); w->_touch = (char *)malloc((size_t)rows); w->_stride = cols; w->_owns_cells = true; cw_cell b = blank_cell(w); for (int i = 0; i < rows * cols; i++) w->_cells[i] = b; memset(w->_touch, 1, (size_t)rows); return w; } static void ensure_init(void) { if (inited) return; inited = true; init_palette(); LINES = cw_js_rows(); COLS = cw_js_cols(); memset(acs_map, 0, sizeof(acs_map)); for (int i = 0; i < 128; i++) acs_map[i] = A_ALTCHARSET | (chtype)i; stdscr = make_window(LINES, COLS, 0, 0); init_window_fields(&the_curscr, LINES, COLS, 0, 0); the_curscr._cells = (cw_cell *)malloc(sizeof(cw_cell) * (size_t)LINES * (size_t)COLS); the_curscr._touch = (char *)calloc((size_t)LINES, 1); the_curscr._stride = COLS; the_curscr._owns_cells = true; cw_cell b = blank_cell(&the_curscr); for (int i = 0; i < LINES * COLS; i++) the_curscr._cells[i] = b; curscr = &the_curscr; newscr = curscr; stdscr->_clear = true; phys_dirty = true; cw_js_mode(g_nl, g_raw, g_echo); } CW_WEAK WINDOW *initscr(void) { ensure_init(); ended = false; return stdscr; } CW_WEAK SCREEN *newterm(const char *type, FILE *outfd, FILE *infd) { (void)type; (void)outfd; (void)infd; ensure_init(); ended = false; return &the_screen; } CW_WEAK SCREEN *set_term(SCREEN *screen) { return screen; } static void ansi_init(void); static struct { int state; /* 0 text, 1 ESC, 2 CSI, 3 OSC, 4 charset */ int params[8]; int nparams; bool priv; int y, x; /* cursor */ bool wrap_pending; int sy, sx; /* saved cursor */ int top, bottom; /* scroll region */ unsigned attrs; /* A_* bits */ int fg, bg; /* -1 default */ bool fresh; int pair_n; /* pairs handed out so far (from 255 downwards) */ short pfg[256], pbg[256]; } an; /* After endwin() the program is back in "shell" mode: what it prints (stdout) is drawn below the last * screen, like a terminal would, and scrolls it. */ CW_WEAK int endwin(void) { if (!inited) return ERR; ended = true; ansi_init(); an.x = 0; an.y = LINES - 1; an.state = 0; an.wrap_pending = false; cw_js_end(1); return OK; } CW_WEAK bool isendwin(void) { return ended; } CW_WEAK WINDOW *newwin(int nlines, int ncols, int begin_y, int begin_x) { ensure_init(); if (nlines == 0) nlines = LINES - begin_y; if (ncols == 0) ncols = COLS - begin_x; return make_window(nlines, ncols, begin_y, begin_x); } CW_WEAK WINDOW *derwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x) { if (orig == NULL) return NULL; if (nlines == 0) nlines = orig->_maxy + 1 - begin_y; if (ncols == 0) ncols = orig->_maxx + 1 - begin_x; if (nlines < 1 || ncols < 1 || begin_y < 0 || begin_x < 0 || begin_y + nlines > orig->_maxy + 1 || begin_x + ncols > orig->_maxx + 1) { return NULL; } WINDOW *w = (WINDOW *)calloc(1, sizeof(WINDOW)); init_window_fields(w, nlines, ncols, orig->_begy + begin_y, orig->_begx + begin_x); w->_parent = orig; w->_cells = cell_at(orig, begin_y, begin_x); w->_stride = orig->_stride; w->_touch = (char *)malloc((size_t)nlines); memset(w->_touch, 1, (size_t)nlines); w->_bkgd = orig->_bkgd; return w; } CW_WEAK WINDOW *subwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x) { if (orig == NULL) return NULL; return derwin(orig, nlines, ncols, begin_y - orig->_begy, begin_x - orig->_begx); } CW_WEAK WINDOW *dupwin(WINDOW *win) { if (win == NULL) return NULL; WINDOW *w = make_window(win->_maxy + 1, win->_maxx + 1, win->_begy, win->_begx); for (int y = 0; y <= win->_maxy; y++) memcpy(cell_at(w, y, 0), cell_at(win, y, 0), sizeof(cw_cell) * (size_t)(win->_maxx + 1)); w->_attrs = win->_attrs; w->_bkgd = win->_bkgd; w->_cury = win->_cury; w->_curx = win->_curx; return w; } CW_WEAK int delwin(WINDOW *win) { if (win == NULL || win == stdscr || win == curscr) return ERR; if (win->_owns_cells) free(win->_cells); free(win->_touch); free(win); return OK; } CW_WEAK int mvwin(WINDOW *win, int y, int x) { if (win == NULL || win->_parent != NULL) return ERR; if (y < 0 || x < 0 || y + win->_maxy >= LINES || x + win->_maxx >= COLS) return ERR; win->_begy = y; win->_begx = x; return touchwin(win); } CW_WEAK int mvderwin(WINDOW *win, int par_y, int par_x) { if (win == NULL || win->_parent == NULL) return ERR; WINDOW *p = win->_parent; if (par_y < 0 || par_x < 0 || par_y + win->_maxy >= p->_maxy + 1 || par_x + win->_maxx >= p->_maxx + 1) return ERR; win->_cells = cell_at(p, par_y, par_x); win->_begy = p->_begy + par_y; win->_begx = p->_begx + par_x; return touchwin(win); } CW_WEAK int wresize(WINDOW *win, int lines, int columns) { if (win == NULL || lines < 1 || columns < 1 || win->_parent != NULL || !win->_owns_cells) return ERR; cw_cell *cells = (cw_cell *)malloc(sizeof(cw_cell) * (size_t)lines * (size_t)columns); cw_cell b = blank_cell(win); for (int i = 0; i < lines * columns; i++) cells[i] = b; int ry = lines < win->_maxy + 1 ? lines : win->_maxy + 1; int rx = columns < win->_maxx + 1 ? columns : win->_maxx + 1; for (int y = 0; y < ry; y++) memcpy(&cells[y * columns], cell_at(win, y, 0), sizeof(cw_cell) * (size_t)rx); free(win->_cells); free(win->_touch); win->_cells = cells; win->_stride = columns; win->_maxy = lines - 1; win->_maxx = columns - 1; win->_touch = (char *)malloc((size_t)lines); memset(win->_touch, 1, (size_t)lines); win->_regtop = 0; win->_regbottom = lines - 1; if (win->_cury > win->_maxy) win->_cury = win->_maxy; if (win->_curx > win->_maxx) win->_curx = win->_maxx; return OK; } CW_WEAK int resizeterm(int lines, int columns) { (void)lines; (void)columns; return ERR; } CW_WEAK int resize_term(int lines, int columns) { return resizeterm(lines, columns); } CW_WEAK bool is_term_resized(int lines, int columns) { return lines != LINES || columns != COLS; } CW_WEAK int touchwin(WINDOW *win) { if (win == NULL) return ERR; for (int y = 0; y <= win->_maxy; y++) touch_row(win, y); return OK; } CW_WEAK int untouchwin(WINDOW *win) { if (win == NULL) return ERR; memset(win->_touch, 0, (size_t)(win->_maxy + 1)); return OK; } CW_WEAK int touchline(WINDOW *win, int start, int count) { return wtouchln(win, start, count, 1); } CW_WEAK int wtouchln(WINDOW *win, int y, int n, int changed) { if (win == NULL || y < 0 || y > win->_maxy) return ERR; for (int i = y; i < y + n && i <= win->_maxy; i++) { if (changed) touch_row(win, i); else win->_touch[i] = 0; } return OK; } CW_WEAK bool is_wintouched(WINDOW *win) { if (win == NULL) return false; for (int y = 0; y <= win->_maxy; y++) { if (win->_touch[y]) return true; } return false; } CW_WEAK bool is_linetouched(WINDOW *win, int line) { if (win == NULL || line < 0 || line > win->_maxy) return false; return win->_touch[line] != 0; } CW_WEAK int redrawwin(WINDOW *win) { return touchwin(win); } CW_WEAK int wredrawln(WINDOW *win, int beg_line, int num_lines) { return wtouchln(win, beg_line, num_lines, 1); } CW_WEAK int clearok(WINDOW *win, bool bf) { if (win == NULL) return ERR; win->_clear = bf; return OK; } CW_WEAK int leaveok(WINDOW *win, bool bf) { if (win == NULL) return ERR; win->_leaveok = bf; return OK; } CW_WEAK int scrollok(WINDOW *win, bool bf) { if (win == NULL) return ERR; win->_scrollok = bf; return OK; } CW_WEAK int idlok(WINDOW *win, bool bf) { if (win == NULL) return ERR; win->_idlok = bf; return OK; } CW_WEAK void idcok(WINDOW *win, bool bf) { (void)win; (void)bf; } CW_WEAK void immedok(WINDOW *win, bool bf) { if (win != NULL) win->_immed = bf; } CW_WEAK int syncok(WINDOW *win, bool bf) { if (win == NULL) return ERR; win->_sync = bf; return OK; } CW_WEAK void wsyncup(WINDOW *win) { (void)win; } CW_WEAK void wsyncdown(WINDOW *win) { (void)win; } CW_WEAK void wcursyncup(WINDOW *win) { (void)win; } CW_WEAK int wsetscrreg(WINDOW *win, int top, int bot) { if (win == NULL || top < 0 || bot > win->_maxy || top > bot) return ERR; win->_regtop = top; win->_regbottom = bot; return OK; } CW_WEAK int setscrreg(int top, int bot) { return wsetscrreg(stdscr, top, bot); } /* ------------------------------------------------------------------------------------------------ */ /* Output */ /* ------------------------------------------------------------------------------------------------ */ CW_WEAK int wmove(WINDOW *win, int y, int x) { if (win == NULL || y < 0 || y > win->_maxy || x < 0 || x > win->_maxx) return ERR; win->_cury = y; win->_curx = x; return OK; } static void set_cell(WINDOW *w, int y, int x, uint32_t code, chtype attrs) { cw_cell *c = cell_at(w, y, x); chtype at = attrs & A_ATTRIBUTES; chtype merged = (at | w->_attrs | w->_bkgd) & ATTRBITS; int pair = PAIR_NUMBER(at); if (pair == 0) pair = PAIR_NUMBER(w->_attrs); if (pair == 0) pair = PAIR_NUMBER(w->_bkgd); c->ch = code; c->attr = (uint16_t)(merged >> 16); c->pair = (uint8_t)pair; touch_row(w, y); } static void blank_range(WINDOW *w, int y, int x0, int x1) { cw_cell b = blank_cell(w); for (int x = x0; x <= x1; x++) *cell_at(w, y, x) = b; touch_row(w, y); } static void do_scroll(WINDOW *w, int n) { int top = w->_regtop, bot = w->_regbottom; int cols = w->_maxx + 1; if (n > 0) { for (int k = 0; k < n; k++) { for (int y = top; y < bot; y++) memmove(cell_at(w, y, 0), cell_at(w, y + 1, 0), sizeof(cw_cell) * (size_t)cols); blank_range(w, bot, 0, w->_maxx); } } else { for (int k = 0; k < -n; k++) { for (int y = bot; y > top; y--) memmove(cell_at(w, y, 0), cell_at(w, y - 1, 0), sizeof(cw_cell) * (size_t)cols); blank_range(w, top, 0, w->_maxx); } } for (int y = top; y <= bot; y++) touch_row(w, y); } CW_WEAK int wscrl(WINDOW *win, int n) { if (win == NULL || !win->_scrollok) return ERR; do_scroll(win, n); return OK; } static int newline(WINDOW *w) { w->_curx = 0; if (w->_cury == w->_regbottom) { if (w->_scrollok) do_scroll(w, 1); return OK; } if (w->_cury < w->_maxy) w->_cury++; return OK; } CW_WEAK int wclrtoeol(WINDOW *win) { if (win == NULL) return ERR; blank_range(win, win->_cury, win->_curx, win->_maxx); return OK; } CW_WEAK int wclrtobot(WINDOW *win) { if (win == NULL) return ERR; blank_range(win, win->_cury, win->_curx, win->_maxx); for (int y = win->_cury + 1; y <= win->_maxy; y++) blank_range(win, y, 0, win->_maxx); return OK; } CW_WEAK int werase(WINDOW *win) { if (win == NULL) return ERR; for (int y = 0; y <= win->_maxy; y++) blank_range(win, y, 0, win->_maxx); win->_cury = 0; win->_curx = 0; return OK; } CW_WEAK int wclear(WINDOW *win) { if (win == NULL) return ERR; werase(win); win->_clear = true; return OK; } static int add_cp(WINDOW *w, uint32_t code, chtype attrs) { switch (code) { case '\n': wclrtoeol(w); return newline(w); case '\r': w->_curx = 0; return OK; case '\b': if (w->_curx > 0) w->_curx--; return OK; case '\t': { int next = (w->_curx + TABSIZE) / TABSIZE * TABSIZE; while (w->_curx < next) { if (add_cp(w, ' ', attrs) == ERR) return ERR; if (w->_curx == 0) break; /* wrapped */ } return OK; } case 7: cw_js_bell(0); return OK; default: break; } if (code < 32 || code == 127) { if (add_cp(w, '^', attrs) == ERR) return ERR; return add_cp(w, code == 127 ? '?' : code + 64, attrs); } set_cell(w, w->_cury, w->_curx, code, attrs); if (w->_curx < w->_maxx) { w->_curx++; return OK; } /* last column */ if (w->_cury == w->_regbottom) { if (!w->_scrollok) return ERR; do_scroll(w, 1); w->_curx = 0; return OK; } if (w->_cury < w->_maxy) { w->_cury++; w->_curx = 0; return OK; } return ERR; } CW_WEAK int waddch(WINDOW *win, const chtype ch) { if (win == NULL) return ERR; uint32_t code = ch & A_CHARTEXT; if (ch & A_ALTCHARSET) code = acs_unicode(code); int r = add_cp(win, code, ch & A_ATTRIBUTES); if (win->_immed) wrefresh(win); return r; } CW_WEAK int wechochar(WINDOW *win, const chtype ch) { int r = waddch(win, ch); wrefresh(win); return r; } CW_WEAK int waddchnstr(WINDOW *win, const chtype *chstr, int n) { if (win == NULL || chstr == NULL) return ERR; int x = win->_curx; for (int i = 0; (n < 0 ? chstr[i] != 0 : i < n) && x <= win->_maxx; i++, x++) { uint32_t code = chstr[i] & A_CHARTEXT; if (chstr[i] & A_ALTCHARSET) code = acs_unicode(code); set_cell(win, win->_cury, x, code, chstr[i] & A_ATTRIBUTES); } return OK; } /* Decodes one UTF-8 sequence; invalid bytes are taken as Latin-1. */ static uint32_t utf8_next(const unsigned char **pp, const unsigned char *end) { const unsigned char *p = *pp; uint32_t c = *p++; int extra = 0; uint32_t cp = c; if (c >= 0xf0 && c < 0xf8) { extra = 3; cp = c & 7; } else if (c >= 0xe0) { extra = c < 0xf0 ? 2 : 0; cp = c & 15; } else if (c >= 0xc2) { extra = c < 0xe0 ? 1 : 0; cp = c & 31; } if (extra > 0 && c >= 0xc2 && c < 0xf8 && p + extra <= end) { uint32_t v = cp; int ok = 1; for (int i = 0; i < extra; i++) { if ((p[i] & 0xc0) != 0x80) { ok = 0; break; } v = (v << 6) | (p[i] & 0x3f); } if (ok) { *pp = p + extra; return v > 0xffff ? '?' : v; } } *pp = p; return c; } CW_WEAK int waddnstr(WINDOW *win, const char *str, int n) { if (win == NULL || str == NULL) return ERR; const unsigned char *p = (const unsigned char *)str; const unsigned char *end = n < 0 ? p + strlen(str) : p + n; if (n >= 0) { const unsigned char *z = memchr(p, 0, (size_t)n); if (z != NULL) end = z; } int r = OK; while (p < end) { uint32_t cp = utf8_next(&p, end); if (add_cp(win, cp, 0) == ERR) r = ERR; } if (win->_immed) wrefresh(win); return r; } CW_WEAK int vw_printw(WINDOW *win, const char *fmt, va_list ap) { char buf[1024]; va_list ap2; va_copy(ap2, ap); int n = vsnprintf(buf, sizeof(buf), fmt, ap); int r; if (n < 0) { r = ERR; } else if ((size_t)n < sizeof(buf)) { r = waddnstr(win, buf, n); } else { char *big = (char *)malloc((size_t)n + 1); vsnprintf(big, (size_t)n + 1, fmt, ap2); r = waddnstr(win, big, n); free(big); } va_end(ap2); return r; } CW_WEAK int vwprintw(WINDOW *win, const char *fmt, va_list ap) { return vw_printw(win, fmt, ap); } CW_WEAK int wprintw(WINDOW *win, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int r = vw_printw(win, fmt, ap); va_end(ap); return r; } CW_WEAK int winsch(WINDOW *win, chtype ch) { if (win == NULL) return ERR; int y = win->_cury; for (int x = win->_maxx; x > win->_curx; x--) *cell_at(win, y, x) = *cell_at(win, y, x - 1); uint32_t code = ch & A_CHARTEXT; if (ch & A_ALTCHARSET) code = acs_unicode(code); set_cell(win, y, win->_curx, code, ch & A_ATTRIBUTES); return OK; } CW_WEAK int winsnstr(WINDOW *win, const char *str, int n) { if (win == NULL || str == NULL) return ERR; int len = n < 0 ? (int)strlen(str) : n; int y = win->_cury, x = win->_curx; for (int i = len - 1; i >= 0; i--) { win->_curx = x; winsch(win, (unsigned char)str[i]); } win->_cury = y; win->_curx = x; return OK; } CW_WEAK int wdelch(WINDOW *win) { if (win == NULL) return ERR; int y = win->_cury; for (int x = win->_curx; x < win->_maxx; x++) *cell_at(win, y, x) = *cell_at(win, y, x + 1); *cell_at(win, y, win->_maxx) = blank_cell(win); touch_row(win, y); return OK; } CW_WEAK int winsdelln(WINDOW *win, int n) { if (win == NULL) return ERR; if (win->_cury < win->_regtop || win->_cury > win->_regbottom) return ERR; int top = win->_regtop; win->_regtop = win->_cury; do_scroll(win, -n); /* n > 0 inserts lines (scrolls down), n < 0 deletes them */ win->_regtop = top; return OK; } CW_WEAK int winsertln(WINDOW *win) { return winsdelln(win, 1); } CW_WEAK int wdeleteln(WINDOW *win) { return winsdelln(win, -1); } static void set_hline_cell(WINDOW *w, int y, int x, chtype ch) { uint32_t code = ch & A_CHARTEXT; if (code == 0) code = ' '; if (ch & A_ALTCHARSET) code = acs_unicode(code); set_cell(w, y, x, code, ch & A_ATTRIBUTES); } CW_WEAK int whline(WINDOW *win, chtype ch, int n) { if (win == NULL) return ERR; if ((ch & A_CHARTEXT) == 0) ch |= ACS_HLINE; for (int i = 0; i < n && win->_curx + i <= win->_maxx; i++) set_hline_cell(win, win->_cury, win->_curx + i, ch); return OK; } CW_WEAK int wvline(WINDOW *win, chtype ch, int n) { if (win == NULL) return ERR; if ((ch & A_CHARTEXT) == 0) ch |= ACS_VLINE; for (int i = 0; i < n && win->_cury + i <= win->_maxy; i++) set_hline_cell(win, win->_cury + i, win->_curx, ch); return OK; } CW_WEAK int wborder(WINDOW *win, chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br) { if (win == NULL) return ERR; if (!(ls & A_CHARTEXT)) ls |= ACS_VLINE; if (!(rs & A_CHARTEXT)) rs |= ACS_VLINE; if (!(ts & A_CHARTEXT)) ts |= ACS_HLINE; if (!(bs & A_CHARTEXT)) bs |= ACS_HLINE; if (!(tl & A_CHARTEXT)) tl |= ACS_ULCORNER; if (!(tr & A_CHARTEXT)) tr |= ACS_URCORNER; if (!(bl & A_CHARTEXT)) bl |= ACS_LLCORNER; if (!(br & A_CHARTEXT)) br |= ACS_LRCORNER; int my = win->_maxy, mx = win->_maxx; for (int y = 1; y < my; y++) { set_hline_cell(win, y, 0, ls); set_hline_cell(win, y, mx, rs); } for (int x = 1; x < mx; x++) { set_hline_cell(win, 0, x, ts); set_hline_cell(win, my, x, bs); } set_hline_cell(win, 0, 0, tl); set_hline_cell(win, 0, mx, tr); set_hline_cell(win, my, 0, bl); set_hline_cell(win, my, mx, br); return OK; } CW_WEAK int box(WINDOW *win, chtype verch, chtype horch) { return wborder(win, verch, verch, horch, horch, 0, 0, 0, 0); } /* attributes and colours */ CW_WEAK int wattr_on(WINDOW *win, attr_t attrs, void *opts) { (void)opts; if (win == NULL) return ERR; if (attrs & A_COLOR) win->_attrs &= ~A_COLOR; win->_attrs |= attrs & A_ATTRIBUTES; return OK; } CW_WEAK int wattr_off(WINDOW *win, attr_t attrs, void *opts) { (void)opts; if (win == NULL) return ERR; win->_attrs &= ~(attrs & A_ATTRIBUTES); return OK; } CW_WEAK int wattr_set(WINDOW *win, attr_t attrs, short pair, void *opts) { (void)opts; if (win == NULL) return ERR; win->_attrs = (attrs & ATTRBITS) | COLOR_PAIR(pair); return OK; } CW_WEAK int wattr_get(WINDOW *win, attr_t *attrs, short *pair, void *opts) { (void)opts; if (win == NULL) return ERR; if (attrs) *attrs = win->_attrs & ATTRBITS; if (pair) *pair = (short)PAIR_NUMBER(win->_attrs); return OK; } CW_WEAK int wattron(WINDOW *win, int attrs) { return wattr_on(win, (attr_t)attrs, NULL); } CW_WEAK int wattroff(WINDOW *win, int attrs) { return wattr_off(win, (attr_t)attrs, NULL); } CW_WEAK int wattrset(WINDOW *win, int attrs) { if (win == NULL) return ERR; win->_attrs = (chtype)attrs & A_ATTRIBUTES; return OK; } CW_WEAK int wstandout(WINDOW *win) { return wattr_on(win, A_STANDOUT, NULL); } CW_WEAK int wstandend(WINDOW *win) { if (win == NULL) return ERR; win->_attrs = 0; return OK; } CW_WEAK int wcolor_set(WINDOW *win, short pair, void *opts) { (void)opts; if (win == NULL || pair < 0 || pair > 255) return ERR; win->_attrs = (win->_attrs & ~A_COLOR) | COLOR_PAIR(pair); return OK; } CW_WEAK int color_set(short pair, void *opts) { return wcolor_set(stdscr, pair, opts); } CW_WEAK void wbkgdset(WINDOW *win, chtype ch) { if (win == NULL) return; win->_bkgd = ch; if ((win->_bkgd & A_CHARTEXT) == 0) win->_bkgd |= ' '; } CW_WEAK int wbkgd(WINDOW *win, chtype ch) { if (win == NULL) return ERR; chtype old = win->_bkgd; wbkgdset(win, ch); /* The new background replaces the old one in every cell. */ cw_cell nb = blank_cell(win); uint16_t oldattr = (uint16_t)((old & ATTRBITS) >> 16); uint8_t oldpair = (uint8_t)PAIR_NUMBER(old); for (int y = 0; y <= win->_maxy; y++) { for (int x = 0; x <= win->_maxx; x++) { cw_cell *c = cell_at(win, y, x); if (c->ch == (old & A_CHARTEXT)) c->ch = nb.ch; c->attr = (uint16_t)((c->attr & ~oldattr) | nb.attr); if (c->pair == oldpair) c->pair = nb.pair; } touch_row(win, y); } return OK; } CW_WEAK chtype getbkgd(WINDOW *win) { return win ? win->_bkgd : 0; } CW_WEAK bool has_colors(void) { return true; } CW_WEAK bool can_change_color(void) { return true; } CW_WEAK int start_color(void) { ensure_init(); return OK; } CW_WEAK int use_default_colors(void) { return OK; } CW_WEAK int assume_default_colors(int fg, int bg) { (void)fg; (void)bg; return OK; } CW_WEAK int init_pair(short pair, short fg, short bg) { if (pair < 0 || pair > 255 || fg < -1 || fg > 255 || bg < -1 || bg > 255) return ERR; ensure_init(); pair_fg[pair] = fg; pair_bg[pair] = bg; phys_dirty = true; return OK; } CW_WEAK int pair_content(short pair, short *fg, short *bg) { if (pair < 0 || pair > 255) return ERR; if (fg) *fg = pair_fg[pair] < 0 ? 7 : pair_fg[pair]; if (bg) *bg = pair_bg[pair] < 0 ? 0 : pair_bg[pair]; return OK; } CW_WEAK int init_color(short color, short r, short g, short b) { if (color < 0 || color > 255) return ERR; ensure_init(); int rr = r * 255 / 1000, gg = g * 255 / 1000, bb = b * 255 / 1000; palette[color] = (uint32_t)(rr << 16 | gg << 8 | bb); phys_dirty = true; return OK; } CW_WEAK int color_content(short color, short *r, short *g, short *b) { if (color < 0 || color > 255) return ERR; ensure_init(); uint32_t v = palette[color]; if (r) *r = (short)(((v >> 16) & 255) * 1000 / 255); if (g) *g = (short)(((v >> 8) & 255) * 1000 / 255); if (b) *b = (short)((v & 255) * 1000 / 255); return OK; } /* copying between windows */ CW_WEAK int copywin(const WINDOW *src, WINDOW *dst, int sminrow, int smincol, int dminrow, int dmincol, int dmaxrow, int dmaxcol, int overlay) { if (src == NULL || dst == NULL) return ERR; if (sminrow < 0 || smincol < 0 || dminrow < 0 || dmincol < 0) return ERR; for (int dy = dminrow; dy <= dmaxrow && dy <= dst->_maxy; dy++) { int sy = sminrow + (dy - dminrow); if (sy > src->_maxy) break; for (int dx = dmincol; dx <= dmaxcol && dx <= dst->_maxx; dx++) { int sx = smincol + (dx - dmincol); if (sx > src->_maxx) break; cw_cell *s = cell_at(src, sy, sx); if (overlay && s->ch == ' ') continue; *cell_at(dst, dy, dx) = *s; } touch_row(dst, dy); } return OK; } static int copy_overlap(const WINDOW *src, WINDOW *dst, int overlay) { if (src == NULL || dst == NULL) return ERR; int sy0 = src->_begy, sx0 = src->_begx, sy1 = src->_begy + src->_maxy, sx1 = src->_begx + src->_maxx; int dy0 = dst->_begy, dx0 = dst->_begx, dy1 = dst->_begy + dst->_maxy, dx1 = dst->_begx + dst->_maxx; int y0 = sy0 > dy0 ? sy0 : dy0, x0 = sx0 > dx0 ? sx0 : dx0; int y1 = sy1 < dy1 ? sy1 : dy1, x1 = sx1 < dx1 ? sx1 : dx1; if (y0 > y1 || x0 > x1) return ERR; return copywin(src, dst, y0 - sy0, x0 - sx0, y0 - dy0, x0 - dx0, y1 - dy0, x1 - dx0, overlay); } CW_WEAK int overlay(const WINDOW *src, WINDOW *dst) { return copy_overlap(src, dst, 1); } CW_WEAK int overwrite(const WINDOW *src, WINDOW *dst) { return copy_overlap(src, dst, 0); } /* reading the screen back */ CW_WEAK chtype winch(WINDOW *win) { if (win == NULL) return (chtype)ERR; cw_cell *c = cell_at(win, win->_cury, win->_curx); chtype ch = c->ch < 256 ? c->ch : '?'; return ch | ((chtype)c->attr << 16) | COLOR_PAIR(c->pair); } CW_WEAK int winchnstr(WINDOW *win, chtype *chstr, int n) { if (win == NULL || chstr == NULL) return ERR; int i = 0; for (int x = win->_curx; x <= win->_maxx && (n < 0 || i < n); x++, i++) { cw_cell *c = cell_at(win, win->_cury, x); chstr[i] = (c->ch < 256 ? c->ch : '?') | ((chtype)c->attr << 16) | COLOR_PAIR(c->pair); } chstr[i] = 0; return i; } CW_WEAK int winnstr(WINDOW *win, char *str, int n) { if (win == NULL || str == NULL) return ERR; int i = 0; for (int x = win->_curx; x <= win->_maxx && (n < 0 || i < n); x++, i++) { cw_cell *c = cell_at(win, win->_cury, x); str[i] = c->ch < 256 ? (char)c->ch : '?'; } str[i] = 0; return i; } /* ------------------------------------------------------------------------------------------------ */ /* Refresh */ /* ------------------------------------------------------------------------------------------------ */ static void flush_screen(void) { if (!phys_dirty) return; phys_dirty = false; cw_js_draw(curscr->_cells, LINES, COLS, phys_cury, phys_curx, g_curs, palette, pair_fg, pair_bg); } static void clear_phys(void) { cw_cell b = blank_cell(curscr); for (int i = 0; i < LINES * COLS; i++) curscr->_cells[i] = b; phys_dirty = true; } CW_WEAK int wnoutrefresh(WINDOW *win) { if (win == NULL) return ERR; ensure_init(); ended = false; if (win == curscr) return OK; if (curscr->_clear) { curscr->_clear = false; clear_phys(); touchwin(win); } bool all = false; if (win->_clear) { win->_clear = false; clear_phys(); all = true; } if (win->_parent != NULL) all = true; for (int y = 0; y <= win->_maxy; y++) { if (!all && !win->_touch[y]) continue; win->_touch[y] = 0; int sy = win->_begy + y; if (sy < 0 || sy >= LINES) continue; int w = win->_maxx + 1; if (win->_begx + w > COLS) w = COLS - win->_begx; if (w <= 0) continue; cw_cell *d = &curscr->_cells[sy * COLS + win->_begx]; cw_cell *s = cell_at(win, y, 0); if (memcmp(d, s, sizeof(cw_cell) * (size_t)w) != 0) { memcpy(d, s, sizeof(cw_cell) * (size_t)w); phys_dirty = true; } } if (!win->_leaveok) { int cy = win->_begy + win->_cury, cx = win->_begx + win->_curx; if (cy != phys_cury || cx != phys_curx) { phys_cury = cy; phys_curx = cx; phys_dirty = true; } } return OK; } CW_WEAK int doupdate(void) { ensure_init(); flush_screen(); return OK; } CW_WEAK int wrefresh(WINDOW *win) { if (win == NULL) return ERR; if (wnoutrefresh(win) == ERR) return ERR; flush_screen(); return OK; } /* ------------------------------------------------------------------------------------------------ */ /* Modes */ /* ------------------------------------------------------------------------------------------------ */ CW_WEAK int cbreak(void) { g_half_ms = 0; return OK; } CW_WEAK int nocbreak(void) { g_half_ms = 0; return OK; } CW_WEAK int raw(void) { g_raw = true; g_half_ms = 0; cw_js_mode(g_nl, g_raw, g_echo); return OK; } CW_WEAK int noraw(void) { g_raw = false; g_half_ms = 0; cw_js_mode(g_nl, g_raw, g_echo); return OK; } CW_WEAK int echo(void) { g_echo = true; cw_js_mode(g_nl, g_raw, g_echo); return OK; } CW_WEAK int noecho(void) { g_echo = false; cw_js_mode(g_nl, g_raw, g_echo); return OK; } CW_WEAK int nl(void) { g_nl = true; cw_js_mode(g_nl, g_raw, g_echo); return OK; } CW_WEAK int nonl(void) { g_nl = false; cw_js_mode(g_nl, g_raw, g_echo); return OK; } CW_WEAK int halfdelay(int tenths) { if (tenths < 1 || tenths > 255) return ERR; g_half_ms = tenths * 100; return OK; } CW_WEAK int keypad(WINDOW *win, bool enable) { if (win == NULL) return ERR; win->_use_keypad = enable; return OK; } CW_WEAK int meta(WINDOW *win, bool enable) { (void)win; (void)enable; return OK; } CW_WEAK int intrflush(WINDOW *win, bool enable) { (void)win; (void)enable; return OK; } CW_WEAK int nodelay(WINDOW *win, bool enable) { if (win == NULL) return ERR; win->_delay = enable ? 0 : -1; return OK; } CW_WEAK int notimeout(WINDOW *win, bool enable) { if (win == NULL) return ERR; win->_notimeout = enable; return OK; } CW_WEAK void wtimeout(WINDOW *win, int ms) { if (win != NULL) win->_delay = ms < 0 ? -1 : ms; } CW_WEAK void timeout(int ms) { ensure_init(); wtimeout(stdscr, ms); } CW_WEAK int typeahead(int fd) { (void)fd; return OK; } CW_WEAK int qiflush(void) { return OK; } CW_WEAK void noqiflush(void) { } CW_WEAK int curs_set(int visibility) { if (visibility < 0 || visibility > 2) return ERR; int old = g_curs; if (visibility != g_curs) { g_curs = visibility; phys_dirty = true; } return old; } CW_WEAK int set_escdelay(int ms) { ESCDELAY = ms; return OK; } CW_WEAK int def_prog_mode(void) { return OK; } CW_WEAK int def_shell_mode(void) { return OK; } CW_WEAK int reset_prog_mode(void) { ended = false; return OK; } CW_WEAK int reset_shell_mode(void) { return OK; } CW_WEAK int use_env(bool b) { (void)b; return OK; } CW_WEAK void filter(void) { } CW_WEAK char erasechar(void) { return 8; } CW_WEAK char killchar(void) { return 21; } CW_WEAK int baudrate(void) { return 38400; } CW_WEAK char *longname(void) { return (char *)"xterm-256color"; } CW_WEAK char *termname(void) { return (char *)"xterm-256color"; } CW_WEAK bool has_ic(void) { return true; } CW_WEAK bool has_il(void) { return true; } CW_WEAK int napms(int ms) { if (inited) flush_screen(); if (ms > 0) cw_js_sleep(ms); return OK; } /* sleep() and usleep() of the C library busy-wait in the browser; these wait without blocking the page. */ CW_WEAK unsigned int sleep(unsigned int seconds) { napms((int)(seconds * 1000)); return 0; } CW_WEAK int usleep(useconds_t usec) { napms((int)(usec / 1000)); return 0; } CW_WEAK int beep(void) { cw_js_bell(0); return OK; } CW_WEAK int flash(void) { cw_js_bell(1); return OK; } CW_WEAK int ripoffline(int line, int (*init)(WINDOW *, int)) { (void)line; (void)init; return OK; } CW_WEAK mmask_t mousemask(mmask_t newmask, mmask_t *oldmask) { (void)newmask; if (oldmask) *oldmask = 0; return 0; } CW_WEAK int getmouse(MEVENT *event) { (void)event; return ERR; } CW_WEAK int ungetmouse(MEVENT *event) { (void)event; return ERR; } CW_WEAK int mouseinterval(int erval) { (void)erval; return 0; } CW_WEAK bool has_mouse(void) { return false; } /* ------------------------------------------------------------------------------------------------ */ /* Input */ /* ------------------------------------------------------------------------------------------------ */ CW_WEAK void cw_set_nokeypad_keys(const char *keys) { memcpy(nokeypad, keys != NULL && strlen(keys) >= 8 ? keys : "kjhlybun", 8); } /* A key without keypad(): arrows and friends become the configured plain keys; the rest is dropped. */ static int filter_key(bool keypad_on, int k) { if (k < KEY_MIN || keypad_on) return k; switch (k) { case KEY_UP: return nokeypad[0]; case KEY_DOWN: return nokeypad[1]; case KEY_LEFT: return nokeypad[2]; case KEY_RIGHT: return nokeypad[3]; case KEY_HOME: return nokeypad[4]; case KEY_END: return nokeypad[5]; case KEY_PPAGE: return nokeypad[6]; case KEY_NPAGE: return nokeypad[7]; case KEY_DC: return 127; case KEY_BACKSPACE: return 8; case KEY_ENTER: return 10; default: return -1; } } static int read_key(int delay, bool keypad_on) { for (;;) { int k; if (unget_n > 0) { k = unget_buf[--unget_n]; } else { k = cw_js_poll(); if (k == -2) { if (delay == 0) return ERR; k = cw_js_wait(delay); if (k < 0) return ERR; } } k = filter_key(keypad_on, k); if (k < 0) continue; if (k == 13 && g_nl && !g_raw) k = 10; return k; } } CW_WEAK int wgetch(WINDOW *win) { if (win == NULL) return ERR; ensure_init(); if (win == curscr) flush_screen(); else wrefresh(win); int delay = win->_delay; if (delay < 0 && g_half_ms > 0) delay = g_half_ms; int k = read_key(delay, win->_use_keypad); if (k != ERR && g_echo && k >= 32 && k < 256 && k != 127) { waddch(win, (chtype)k); wrefresh(win); } return k; } CW_WEAK int cw_getkey(int ms) { if (inited && (ended || ansi_stdout_mode) && an.fresh) cw_ansi_flush(); else if (inited) flush_screen(); return read_key(ms, false); } /* Waits up to `ms` milliseconds (-1: forever) until a key is waiting; the key stays queued for getch(). */ int cw_wait_input(int ms) { if (inited && (ended || ansi_stdout_mode) && an.fresh) cw_ansi_flush(); else if (inited) flush_screen(); if (unget_n > 0) return 1; int k = cw_js_poll(); if (k == -2) { if (ms == 0) return 0; k = cw_js_wait(ms); if (k < 0) return 0; } if (unget_n >= 64) return 1; unget_buf[unget_n++] = k; return 1; } CW_WEAK int ungetch(int ch) { if (unget_n >= 64) return ERR; unget_buf[unget_n++] = ch; return OK; } CW_WEAK int has_key(int ch) { return ch >= KEY_MIN && ch < KEY_MAX; } CW_WEAK int flushinp(void) { unget_n = 0; cw_js_flushkeys(); return OK; } CW_WEAK int wgetnstr(WINDOW *win, char *str, int n) { if (win == NULL || str == NULL || n < 0) return ERR; bool echo_on = g_echo; g_echo = false; int len = 0; int result = OK; for (;;) { int k = wgetch(win); if (k == ERR) { result = ERR; break; } if (k == '\n' || k == '\r' || k == KEY_ENTER) { if (echo_on) { win->_curx = 0; if (win->_cury < win->_maxy) win->_cury++; wrefresh(win); } break; } if (k == 8 || k == 127 || k == KEY_BACKSPACE || k == KEY_LEFT) { if (len > 0) { len--; if (echo_on && win->_curx > 0) { win->_curx--; set_cell(win, win->_cury, win->_curx, ' ', 0); } } continue; } if (k == 21) { /* kill */ while (len > 0) { len--; if (echo_on && win->_curx > 0) { win->_curx--; set_cell(win, win->_cury, win->_curx, ' ', 0); } } continue; } if (k >= 32 && k < 256 && k != 127 && len < n) { str[len++] = (char)k; if (echo_on) waddch(win, (chtype)k); } } g_echo = echo_on; str[len] = 0; return result; } CW_WEAK const char *unctrl(chtype c) { static char table[256][4]; static bool ready = false; if (!ready) { for (int i = 0; i < 256; i++) { if (i < 32) snprintf(table[i], 4, "^%c", i + 64); else if (i == 127) snprintf(table[i], 4, "^?"); else if (i < 127) snprintf(table[i], 4, "%c", i); else snprintf(table[i], 4, "~%c", (i - 128 < 32 || i == 255) ? '?' : (i - 128)); } ready = true; } return table[c & 0xff]; } CW_WEAK const char *keyname(int c) { static char buf[32]; if (c < 0) return NULL; if (c < 256) { if (c >= 128) { snprintf(buf, sizeof(buf), "M-%s", unctrl((chtype)(c - 128))); return buf; } return unctrl((chtype)c); } switch (c) { case KEY_DOWN: return "KEY_DOWN"; case KEY_UP: return "KEY_UP"; case KEY_LEFT: return "KEY_LEFT"; case KEY_RIGHT: return "KEY_RIGHT"; case KEY_HOME: return "KEY_HOME"; case KEY_END: return "KEY_END"; case KEY_BACKSPACE: return "KEY_BACKSPACE"; case KEY_DC: return "KEY_DC"; case KEY_IC: return "KEY_IC"; case KEY_NPAGE: return "KEY_NPAGE"; case KEY_PPAGE: return "KEY_PPAGE"; case KEY_ENTER: return "KEY_ENTER"; case KEY_BTAB: return "KEY_BTAB"; default: if (c >= KEY_F0 && c < KEY_F0 + 64) { snprintf(buf, sizeof(buf), "KEY_F(%d)", c - KEY_F0); return buf; } return "UNKNOWN KEY"; } } /* ------------------------------------------------------------------------------------------------ */ /* ANSI output for programs that write to a terminal directly (termcap): see cw_ansi_write. */ /* ------------------------------------------------------------------------------------------------ */ static void ansi_init(void) { ensure_init(); if (an.fresh) return; an.fresh = true; an.top = 0; an.bottom = LINES - 1; an.fg = an.bg = -1; an.pair_n = 0; stdscr->_scrollok = true; } CW_WEAK void cw_ansi_stdout(int on) { ansi_init(); ansi_stdout_mode = on != 0; } CW_WEAK void cw_ansi_reset(void) { ansi_init(); an.state = 0; an.x = an.y = 0; an.attrs = 0; an.fg = an.bg = -1; an.top = 0; an.bottom = LINES - 1; an.wrap_pending = false; } static int ansi_pair(void) { if (an.fg < 0 && an.bg < 0) return 0; for (int i = 0; i < an.pair_n; i++) { if (an.pfg[i] == an.fg && an.pbg[i] == an.bg) return 255 - i; } if (an.pair_n >= 200) return 255; an.pfg[an.pair_n] = (short)an.fg; an.pbg[an.pair_n] = (short)an.bg; pair_fg[255 - an.pair_n] = (short)an.fg; pair_bg[255 - an.pair_n] = (short)an.bg; return 255 - an.pair_n++; } static void ansi_set(int y, int x, uint32_t code) { cw_cell *c = cell_at(stdscr, y, x); c->ch = code; c->attr = (uint16_t)(an.attrs >> 16); c->pair = (uint8_t)ansi_pair(); touch_row(stdscr, y); } static void ansi_blank(int y, int x0, int x1) { for (int x = x0; x <= x1; x++) { cw_cell *c = cell_at(stdscr, y, x); c->ch = ' '; c->attr = 0; c->pair = (uint8_t)ansi_pair(); } touch_row(stdscr, y); } static void ansi_scroll(int n) { int savedtop = stdscr->_regtop, savedbot = stdscr->_regbottom; stdscr->_regtop = an.top; stdscr->_regbottom = an.bottom; do_scroll(stdscr, n); stdscr->_regtop = savedtop; stdscr->_regbottom = savedbot; /* do_scroll blanked with the default background; the colour of the erased lines is not kept */ } static void ansi_linefeed(void) { if (an.y == an.bottom) ansi_scroll(1); else if (an.y < LINES - 1) an.y++; } static int param(int i, int def) { return (i < an.nparams && an.params[i] > 0) ? an.params[i] : def; } static void ansi_sgr(void) { if (an.nparams == 0) { an.attrs = 0; an.fg = an.bg = -1; return; } for (int i = 0; i < an.nparams; i++) { int p = an.params[i]; if (p == 0) { an.attrs = 0; an.fg = an.bg = -1; } else if (p == 1) an.attrs |= A_BOLD; else if (p == 2) an.attrs |= A_DIM; else if (p == 3) an.attrs |= A_ITALIC; else if (p == 4) an.attrs |= A_UNDERLINE; else if (p == 5) an.attrs |= A_BLINK; else if (p == 7) an.attrs |= A_REVERSE; else if (p == 22) an.attrs &= ~(A_BOLD | A_DIM); else if (p == 23) an.attrs &= ~A_ITALIC; else if (p == 24) an.attrs &= ~A_UNDERLINE; else if (p == 25) an.attrs &= ~A_BLINK; else if (p == 27) an.attrs &= ~A_REVERSE; else if (p >= 30 && p <= 37) an.fg = p - 30; else if (p == 39) an.fg = -1; else if (p >= 40 && p <= 47) an.bg = p - 40; else if (p == 49) an.bg = -1; else if (p >= 90 && p <= 97) an.fg = p - 90 + 8; else if (p >= 100 && p <= 107) an.bg = p - 100 + 8; else if ((p == 38 || p == 48) && i + 2 < an.nparams && an.params[i + 1] == 5) { if (p == 38) an.fg = an.params[i + 2] & 255; else an.bg = an.params[i + 2] & 255; i += 2; } } } static void ansi_csi(char final) { int n; switch (final) { case 'A': an.y -= param(0, 1); if (an.y < an.top && an.y < 0) an.y = 0; if (an.y < 0) an.y = 0; break; case 'B': an.y += param(0, 1); if (an.y >= LINES) an.y = LINES - 1; break; case 'C': an.x += param(0, 1); if (an.x >= COLS) an.x = COLS - 1; break; case 'D': an.x -= param(0, 1); if (an.x < 0) an.x = 0; break; case 'E': an.y += param(0, 1); if (an.y >= LINES) an.y = LINES - 1; an.x = 0; break; case 'F': an.y -= param(0, 1); if (an.y < 0) an.y = 0; an.x = 0; break; case 'G': an.x = param(0, 1) - 1; if (an.x >= COLS) an.x = COLS - 1; break; case 'd': an.y = param(0, 1) - 1; if (an.y >= LINES) an.y = LINES - 1; break; case 'H': case 'f': an.y = param(0, 1) - 1; an.x = param(1, 1) - 1; if (an.y >= LINES) an.y = LINES - 1; if (an.x >= COLS) an.x = COLS - 1; break; case 'J': n = an.nparams > 0 ? an.params[0] : 0; if (n == 0) { ansi_blank(an.y, an.x, COLS - 1); for (int y = an.y + 1; y < LINES; y++) ansi_blank(y, 0, COLS - 1); } else if (n == 1) { for (int y = 0; y < an.y; y++) ansi_blank(y, 0, COLS - 1); ansi_blank(an.y, 0, an.x); } else { for (int y = 0; y < LINES; y++) ansi_blank(y, 0, COLS - 1); } break; case 'K': n = an.nparams > 0 ? an.params[0] : 0; if (n == 0) ansi_blank(an.y, an.x, COLS - 1); else if (n == 1) ansi_blank(an.y, 0, an.x); else ansi_blank(an.y, 0, COLS - 1); break; case 'L': case 'M': if (an.y >= an.top && an.y <= an.bottom) { int savedtop = an.top; an.top = an.y; ansi_scroll(final == 'L' ? -param(0, 1) : param(0, 1)); an.top = savedtop; } break; case '@': n = param(0, 1); for (int x = COLS - 1; x >= an.x + n; x--) *cell_at(stdscr, an.y, x) = *cell_at(stdscr, an.y, x - n); ansi_blank(an.y, an.x, an.x + n - 1 < COLS ? an.x + n - 1 : COLS - 1); break; case 'P': n = param(0, 1); for (int x = an.x; x < COLS; x++) { if (x + n < COLS) *cell_at(stdscr, an.y, x) = *cell_at(stdscr, an.y, x + n); else ansi_blank(an.y, x, x); } touch_row(stdscr, an.y); break; case 'X': n = param(0, 1); ansi_blank(an.y, an.x, an.x + n - 1 < COLS ? an.x + n - 1 : COLS - 1); break; case 'S': ansi_scroll(param(0, 1)); break; case 'T': ansi_scroll(-param(0, 1)); break; case 'm': ansi_sgr(); break; case 'r': an.top = param(0, 1) - 1; an.bottom = an.nparams > 1 && an.params[1] > 0 ? an.params[1] - 1 : LINES - 1; if (an.bottom >= LINES) an.bottom = LINES - 1; if (an.top >= an.bottom) { an.top = 0; an.bottom = LINES - 1; } an.y = an.x = 0; break; case 's': an.sy = an.y; an.sx = an.x; break; case 'u': an.y = an.sy; an.x = an.sx; break; case 'h': case 'l': if (an.priv && an.nparams > 0 && an.params[0] == 25) { g_curs = final == 'h' ? 1 : 0; phys_dirty = true; } break; default: break; } an.wrap_pending = false; } static void ansi_putc(unsigned char c) { switch (an.state) { case 1: /* after ESC */ an.state = 0; switch (c) { case '[': an.state = 2; an.nparams = 0; an.priv = false; memset(an.params, 0, sizeof(an.params)); return; case ']': an.state = 3; return; case '(': case ')': case '*': case '+': an.state = 4; return; case '7': an.sy = an.y; an.sx = an.x; return; case '8': an.y = an.sy; an.x = an.sx; return; case 'D': ansi_linefeed(); return; case 'E': an.x = 0; ansi_linefeed(); return; case 'M': if (an.y == an.top) ansi_scroll(-1); else if (an.y > 0) an.y--; return; case 'c': cw_ansi_reset(); for (int y = 0; y < LINES; y++) ansi_blank(y, 0, COLS - 1); return; default: return; } case 2: /* CSI */ if (c >= '0' && c <= '9') { if (an.nparams == 0) an.nparams = 1; int *p = &an.params[an.nparams - 1]; *p = *p * 10 + (c - '0'); return; } if (c == ';') { if (an.nparams == 0) an.nparams = 1; if (an.nparams < 8) an.nparams++; return; } if (c == '?' || c == '>' || c == '=') { an.priv = true; return; } if (c >= 0x20 && c <= 0x2f) return; /* intermediate */ an.state = 0; ansi_csi((char)c); return; case 3: /* OSC: skip until BEL or ESC \ */ if (c == 7 || c == 27) an.state = (c == 27) ? 4 : 0; return; case 4: an.state = 0; return; default: break; } switch (c) { case 27: an.state = 1; return; case '\r': an.x = 0; an.wrap_pending = false; return; case '\n': an.wrap_pending = false; an.x = 0; ansi_linefeed(); return; /* ONLCR */ case '\b': if (an.x > 0) an.x--; an.wrap_pending = false; return; case '\t': an.x = (an.x + 8) / 8 * 8; if (an.x >= COLS) an.x = COLS - 1; return; case 7: cw_js_bell(0); return; case 0: return; default: break; } if (c < 32) return; if (an.wrap_pending) { an.x = 0; ansi_linefeed(); an.wrap_pending = false; } ansi_set(an.y, an.x, c); if (an.x < COLS - 1) an.x++; else an.wrap_pending = true; } CW_WEAK void cw_ansi_write(const char *s, int len) { ansi_init(); if (len < 0) len = (int)strlen(s); for (int i = 0; i < len; i++) ansi_putc((unsigned char)s[i]); } /* Called by the page for every byte the program writes to stdout (Module.stdout). Ignored while the * curses screen is active (it would garble it, as it would on a terminal); drawn after endwin(). */ EMSCRIPTEN_KEEPALIVE void cw_stdout_byte(int c) { if (!inited || !(ended || ansi_stdout_mode)) return; ansi_putc((unsigned char)c); if (c == '\n') cw_ansi_flush(); } CW_WEAK void cw_ansi_flush(void) { ansi_init(); stdscr->_cury = an.y; stdscr->_curx = an.x; stdscr->_leaveok = false; wrefresh(stdscr); }