/* * emcurses.cpp: the part of the curses API that Umoria uses, for the fredygames.com browser build * (Emscripten). See ncurses.h. * * The screen is an 80x24 grid of characters in memory. refresh() hands it to the page (play.html), * which draws it on a canvas; getch() waits for a key typed on the page (with an optional timeout). * Waiting is done with an async JavaScript call, so the build needs -sASYNCIFY. * * Copyright (C) 2026 FredyGames. Distributed under the GNU General Public License, version 3 or (at * your option) any later version, like Umoria. */ #include #include #include #include "ncurses.h" namespace { const int SCREEN_ROWS = 24; const int SCREEN_COLS = 80; int read_timeout_ms = -1; // < 0: wait for a key } // namespace int LINES = SCREEN_ROWS; int COLS = SCREEN_COLS; WINDOW *stdscr = nullptr; WINDOW *curscr = nullptr; // Hands the screen to the page: the characters (rows * cols bytes) and the cursor position. EM_JS(void, emcurses_draw, (const unsigned char *cells, int rows, int cols, int cury, int curx), { if (Module.emcursesDraw) { Module.emcursesDraw(HEAPU8.subarray(cells, cells + rows * cols), rows, cols, cury, curx); } }); // Waits for a key typed on the page: returns its code, or -1 when `ms` >= 0 pass without one. EM_ASYNC_JS(int, emcurses_wait_key, (int ms), { if (!Module.emcursesWaitKey) { return -1; } return await Module.emcursesWaitKey(ms); }); static WINDOW *makeWindow(int rows, int cols) { WINDOW *win = (WINDOW *) calloc(1, sizeof(WINDOW)); win->rows = rows; win->cols = cols; win->cells = (unsigned char *) malloc((size_t) rows * (size_t) cols); memset(win->cells, ' ', (size_t) rows * (size_t) cols); return win; } WINDOW *initscr(void) { if (stdscr == nullptr) { stdscr = makeWindow(SCREEN_ROWS, SCREEN_COLS); curscr = stdscr; } return stdscr; } int endwin(void) { return OK; } int raw(void) { return OK; } int noecho(void) { return OK; } int nonl(void) { return OK; } int keypad(WINDOW *, bool) { return OK; } int curs_set(int) { return OK; } int set_escdelay(int) { return OK; } WINDOW *newwin(int rows, int cols, int, int) { return makeWindow(rows > 0 ? rows : SCREEN_ROWS, cols > 0 ? cols : SCREEN_COLS); } int overwrite(const WINDOW *src, WINDOW *dst) { int rows = src->rows < dst->rows ? src->rows : dst->rows; int cols = src->cols < dst->cols ? src->cols : dst->cols; for (int y = 0; y < rows; y++) { memcpy(dst->cells + y * dst->cols, src->cells + y * src->cols, (size_t) cols); } return OK; } int touchwin(WINDOW *) { return OK; } int wrefresh(WINDOW *win) { if (win == nullptr) { return ERR; } emcurses_draw(win->cells, win->rows, win->cols, win->cury, win->curx); return OK; } int refresh(void) { return wrefresh(stdscr); } int clear(void) { memset(stdscr->cells, ' ', (size_t) stdscr->rows * (size_t) stdscr->cols); stdscr->cury = 0; stdscr->curx = 0; return OK; } int clrtoeol(void) { for (int x = stdscr->curx; x < stdscr->cols; x++) { stdscr->cells[stdscr->cury * stdscr->cols + x] = ' '; } return OK; } int clrtobot(void) { clrtoeol(); for (int y = stdscr->cury + 1; y < stdscr->rows; y++) { memset(stdscr->cells + y * stdscr->cols, ' ', (size_t) stdscr->cols); } return OK; } int move(int y, int x) { if (y < 0 || y >= stdscr->rows || x < 0 || x >= stdscr->cols) { return ERR; } stdscr->cury = y; stdscr->curx = x; return OK; } int addch(chtype ch) { unsigned char c = (unsigned char) (ch & 0xff); if (c == '\n') { stdscr->curx = 0; if (stdscr->cury < stdscr->rows - 1) { stdscr->cury++; } return OK; } if (c == '\r') { stdscr->curx = 0; return OK; } stdscr->cells[stdscr->cury * stdscr->cols + stdscr->curx] = (c < 32 || c > 126) ? '?' : c; if (stdscr->curx < stdscr->cols - 1) { stdscr->curx++; } else if (stdscr->cury < stdscr->rows - 1) { stdscr->curx = 0; stdscr->cury++; } return OK; } int mvaddch(int y, int x, chtype ch) { if (move(y, x) == ERR) { return ERR; } return addch(ch); } int addstr(const char *str) { for (; *str != '\0'; str++) { addch((chtype) (unsigned char) *str); } return OK; } int mvaddstr(int y, int x, const char *str) { if (move(y, x) == ERR) { return ERR; } return addstr(str); } int timeout(int ms) { read_timeout_ms = ms; return OK; } int getch(void) { return emcurses_wait_key(read_timeout_ms); } int mvcur(int, int, int, int) { return OK; }