├── .gitignore ├── README.md ├── curses.h └── incurses.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *~ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a minimal implementation of an ncurses-like API. 2 | The aim is to allow the [Atto](https://github.com/hughbarney/atto) Emacs-like text editor to be run in embedded environments, 3 | in particular ARM's [mbed](https://www.mbed.com) operating system. 4 | 5 | The licence is MIT-style. 6 | 7 | Only those aspects of the API that are used by Atto are implemented in the first instance. 8 | -------------------------------------------------------------------------------- /curses.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Infinnovation Ltd 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /*======================================================================= 26 | * Minimal (n)curses implementation. 27 | * Only enough to be able to run Atto, a small Emacs-like editor. 28 | * Don't expect miracles. 29 | *=======================================================================*/ 30 | #ifndef INC_curses_h 31 | #define INC_curses_h 32 | 33 | #include 34 | #include 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | /* Types */ 41 | typedef struct _WINDOW WINDOW; 42 | typedef uint8_t chtype; 43 | typedef uint16_t attr_t; 44 | 45 | /* Constants */ 46 | enum { 47 | INCURSES_OK = 0, 48 | INCURSES_ERR = 1 49 | }; 50 | #ifndef OK 51 | #define OK INCURSES_OK 52 | #endif 53 | 54 | #ifndef ERR 55 | #define ERR INCURSES_ERR 56 | #endif 57 | 58 | /* Atto expects these */ 59 | #ifndef TRUE 60 | #define TRUE 1 61 | #endif 62 | 63 | #ifndef FALSE 64 | #define FALSE 0 65 | #endif 66 | 67 | /* Assume fixed screen size */ 68 | #define LINES 24 69 | #define COLS 80 70 | 71 | enum { 72 | A_NORMAL = ((attr_t)0x0000), 73 | A_UNDERLINE = ((attr_t)0x0001), 74 | A_REVERSE = ((attr_t)0x0002), 75 | A_STANDOUT = A_REVERSE 76 | }; 77 | 78 | #define INCURSES_ATTR_MASK 0x00ff 79 | #define INCURSES_FG_SHIFT 8 80 | #define INCURSES_FG_MASK 0x0f00 81 | #define INCURSES_BG_SHIFT 12 82 | #define INCURSES_BG_MASK 0xf000 83 | 84 | #define INCURSES_FG(c) (((c)+1) << INCURSES_FG_SHIFT) 85 | #define INCURSES_BG(c) (((c)+1) << INCURSES_BG_SHIFT) 86 | 87 | enum { 88 | COLOR_BLACK = 0, 89 | COLOR_RED = 1, 90 | COLOR_GREEN = 2, 91 | COLOR_YELLOW = 3, 92 | COLOR_BLUE = 4, 93 | COLOR_MAGENTA = 5, 94 | COLOR_CYAN = 6, 95 | COLOR_WHITE = 7 96 | }; 97 | 98 | #define COLOR_PAIRS 16 99 | 100 | /* Visible data */ 101 | extern WINDOW *stdscr; 102 | extern WINDOW *curscr; 103 | extern attr_t incurses_pairs[COLOR_PAIRS]; 104 | 105 | /* Macros */ 106 | #define COLOR_PAIR(p) ((unsigned)(p) >= COLOR_PAIRS ? (attr_t)0 : incurses_pairs[p]) 107 | #define addstr(s) addnstr(s, -1) 108 | #define attron(a) attr_on(a, 0) 109 | #define mvaddstr(y,x,s) (move(y,x), addstr(s)) 110 | #define standout() attron(A_STANDOUT) 111 | #define standend() attrset(A_NORMAL) 112 | 113 | /* API functions */ 114 | extern WINDOW *initscr(void); 115 | extern int endwin(void); 116 | extern int idlok(WINDOW *, bool); 117 | extern int start_color(void); 118 | extern int init_pair(unsigned, uint8_t, uint8_t); 119 | extern int getch(void); 120 | extern int flushinp(void); 121 | extern int noraw(void); 122 | extern int raw(void); 123 | extern int noecho(void); 124 | extern int keypad(WINDOW *, bool); 125 | extern int curs_set(bool); 126 | extern int addch(chtype); 127 | extern int addnstr(const char *, int n); 128 | extern int attr_on(attr_t, void *); 129 | extern int attrset(attr_t); 130 | extern int clear(void); 131 | extern int clrtoeol(void); 132 | extern int move(int, int); 133 | extern int refresh(void); 134 | extern char *unctrl(chtype); 135 | 136 | #ifdef __cplusplus 137 | } 138 | #endif 139 | #endif /* INC_curses_h */ 140 | -------------------------------------------------------------------------------- /incurses.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Infinnovation Ltd 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /*======================================================================= 26 | * Minimal (n)curses for Atto 27 | *=======================================================================*/ 28 | #include "curses.h" 29 | #include 30 | #include 31 | #include 32 | 33 | #define ESC "\x1b" 34 | 35 | /* Assume there's only ever one window, the whole thing */ 36 | WINDOW *stdscr = (WINDOW *)1; 37 | WINDOW *curscr = (WINDOW *)1; 38 | attr_t incurses_pairs[COLOR_PAIRS]; 39 | 40 | /* Private global data */ 41 | static struct { 42 | int started; /* incurses running? */ 43 | int echo; /* local echo of input */ 44 | int keypad; /* recognise e.g ESC[A as keys? */ 45 | attr_t attr; /* current colour and attributes */ 46 | uint8_t x, y; /* current column end row */ 47 | } incur_global = { 48 | .started = false, 49 | .echo = true, 50 | .keypad = false, 51 | .attr = 0x00, 52 | .x = 0, 53 | .y = LINES-1, 54 | }; 55 | 56 | /* Shortcut to private globals data */ 57 | #define G(v) (incur_global.v) 58 | 59 | /* Extract colours from attributes */ 60 | #define GET_FG(a) ((((a) & INCURSES_FG_MASK) >> INCURSES_FG_SHIFT) - 1) 61 | #define GET_BG(a) ((((a) & INCURSES_BG_MASK) >> INCURSES_BG_SHIFT) - 1) 62 | 63 | /* Forward declarations */ 64 | static void _move(unsigned y, unsigned x); 65 | 66 | #ifdef DEBUG 67 | /*----------------------------------------------------------------------- 68 | * Debug 69 | *-----------------------------------------------------------------------*/ 70 | #include 71 | static bool dbg_esc = false; 72 | static FILE *dbg = NULL; 73 | static void DBGOPEN(void) 74 | { 75 | if ((dbg = fopen("incur.log","w")) == NULL) { 76 | perror("open incur.log"); 77 | exit(2); 78 | } 79 | } 80 | 81 | static void DBG(const char *fmt, ...) 82 | { 83 | va_list ap; 84 | if (dbg == NULL) DBGOPEN(); 85 | va_start(ap, fmt); 86 | vfprintf(dbg, fmt, ap); 87 | fputc('\n', dbg); 88 | fflush(dbg); 89 | va_end(ap); 90 | } 91 | 92 | static void 93 | DBGC(uint8_t c) 94 | { 95 | if (dbg == NULL) DBGOPEN(); 96 | if (dbg_esc) { 97 | fputc(c, dbg); 98 | if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) { 99 | fputs("\"\n", dbg); 100 | dbg_esc = false; 101 | } 102 | } else if (c == 0x1b) { 103 | fputs("out \"\\e", dbg); 104 | dbg_esc = true; 105 | } 106 | } 107 | 108 | #else 109 | /* No debug */ 110 | #define DBG(...) 111 | #define DBGC(c) 112 | #endif 113 | 114 | #if defined(__MBED__) 115 | /*----------------------------------------------------------------------- 116 | * mbed serial driver 117 | * 118 | * Use the 'stdio_uart' global variable by which mbed connects each 119 | * target's serial implementation with the C library stdio. 120 | * See mbed-os/platform/mbed_retarget.cpp and e.g. 121 | * mbed-os/targets/TARGET_NXP/TARGET_LPC176X/serial_api.c 122 | * N.B. This is not an advertised part of the API so may break 123 | * in a future release of mbed. 124 | *-----------------------------------------------------------------------*/ 125 | #undef ERR /* Conflict with MK64F12 DMA register */ 126 | #include "serial_api.h" 127 | extern int stdio_uart_inited; 128 | extern serial_t stdio_uart; 129 | 130 | static int 131 | _mbedserial_getc(int timeout_ms) 132 | { 133 | /* FIXME honour timeout */ 134 | return serial_getc(&stdio_uart); 135 | } 136 | 137 | 138 | static void 139 | _mbedserial_putc(int c) 140 | { 141 | DBGC(c); 142 | serial_putc(&stdio_uart, c); 143 | } 144 | 145 | static void 146 | _mbedserial_puts(const char *str) 147 | { 148 | int c; 149 | while ((c = *str++) != 0) { 150 | _mbedserial_putc(c); 151 | } 152 | } 153 | 154 | #define DRV_RAW(bf) 155 | #define DRV_ECHO(bf) 156 | #define DRV_GETC _mbedserial_getc 157 | #define DRV_FLUSHIN() 158 | #define DRV_PUTC _mbedserial_putc 159 | #define DRV_PUTS _mbedserial_puts 160 | #define DRV_FLUSH() 161 | 162 | #else 163 | /*----------------------------------------------------------------------- 164 | * Unix terminal driver 165 | *-----------------------------------------------------------------------*/ 166 | #include 167 | 168 | static void 169 | unixterm_raw(bool flag) 170 | { 171 | struct termios t; 172 | tcgetattr(1, &t); 173 | if (flag) { 174 | t.c_lflag &= ~ (ICANON | ISIG); 175 | } else { 176 | t.c_lflag |= (ICANON | ISIG); 177 | } 178 | tcsetattr(1, TCSAFLUSH, &t); 179 | } 180 | 181 | static void 182 | unixterm_echo(bool flag) 183 | { 184 | struct termios t; 185 | tcgetattr(1, &t); 186 | if (flag) { 187 | t.c_lflag |= (ECHO); 188 | } else { 189 | t.c_lflag &= ~ (ECHO); 190 | } 191 | tcsetattr(1, TCSAFLUSH, &t); 192 | } 193 | 194 | static int 195 | unixterm_getc(int timeout_ms) 196 | { 197 | /* FIXME honour timeout */ 198 | return getchar(); 199 | } 200 | 201 | static void 202 | unixterm_putc(int c) 203 | { 204 | DBGC(c); 205 | fputc(c, stdout); 206 | } 207 | 208 | static void 209 | unixterm_puts(const char *str) 210 | { 211 | // DBG("puts \"%s\"", str); 212 | {const char *s=str; while (*s++) DBGC(s[-1]);} 213 | fputs(str, stdout); 214 | } 215 | 216 | #define DRV_RAW unixterm_raw 217 | #define DRV_ECHO unixterm_echo 218 | #define DRV_GETC unixterm_getc 219 | #define DRV_FLUSHIN() 220 | #define DRV_PUTC unixterm_putc 221 | #define DRV_PUTS unixterm_puts 222 | #define DRV_FLUSH() fflush(stdout) 223 | 224 | #endif 225 | 226 | /*----------------------------------------------------------------------- 227 | * initscr 228 | *-----------------------------------------------------------------------*/ 229 | WINDOW * 230 | initscr(void) 231 | { 232 | DRV_ECHO(false); 233 | attrset(A_NORMAL); 234 | clear(); 235 | move(0,0); 236 | G(started) = true; 237 | return stdscr; 238 | } 239 | 240 | int 241 | endwin(void) 242 | { 243 | move(LINES-1, 0); 244 | attrset(A_NORMAL); 245 | clrtoeol(); 246 | curs_set(true); 247 | DRV_PUTS(ESC"[4l"); /* set replace mode */ 248 | refresh(); 249 | DRV_ECHO(true); 250 | G(started) = false; 251 | return OK; 252 | } 253 | 254 | int 255 | idlok(WINDOW *win, bool bf) 256 | { 257 | return OK; 258 | } 259 | 260 | int 261 | start_color(void) 262 | { 263 | return OK; 264 | } 265 | 266 | int 267 | init_pair(unsigned pair, uint8_t fg, uint8_t bg) 268 | { 269 | if (pair == 0 || pair >= COLOR_PAIRS) return INCURSES_ERR; 270 | if (fg >= 8 || bg >= 8) return INCURSES_ERR; 271 | DBG("init_pair %u %u %u", pair, fg, bg); 272 | incurses_pairs[pair] = INCURSES_FG(fg) | INCURSES_BG(bg); 273 | return OK; 274 | } 275 | 276 | /*----------------------------------------------------------------------- 277 | * input 278 | *-----------------------------------------------------------------------*/ 279 | int 280 | getch(void) 281 | { 282 | int ch = DRV_GETC(-1); 283 | /* FIXME if G(keypad), recognise ESC sequences */ 284 | /* FIXME if G(echo), output character */ 285 | return ch; 286 | } 287 | 288 | int 289 | flushinp(void) 290 | { 291 | DRV_FLUSHIN(); 292 | return OK; 293 | } 294 | 295 | int 296 | noraw(void) 297 | { 298 | DRV_RAW(false); 299 | return OK; 300 | } 301 | 302 | int 303 | raw(void) 304 | { 305 | DRV_RAW(true); 306 | return OK; 307 | } 308 | 309 | int 310 | noecho(void) 311 | { 312 | G(echo) = 0; 313 | return OK; 314 | } 315 | 316 | int 317 | keypad(WINDOW *win, bool bf) 318 | { 319 | G(keypad) = bf; 320 | return OK; 321 | } 322 | 323 | /*----------------------------------------------------------------------- 324 | * output 325 | *-----------------------------------------------------------------------*/ 326 | int 327 | curs_set(bool visible) 328 | { 329 | if (visible) { 330 | DRV_PUTS(ESC"[?25h"); 331 | } else { 332 | DRV_PUTS(ESC"[?25l"); 333 | } 334 | return OK; 335 | } 336 | 337 | int 338 | addch(uint8_t ch) 339 | { 340 | switch (ch) { 341 | case 0x08: /* Left unless at first column */ 342 | if (G(x) > 0) { 343 | DRV_PUTC(ch); 344 | -- G(x); 345 | } 346 | break; 347 | case 0x09: /* Move to 8-sized tab stop */ 348 | /* FIXME */ 349 | break; 350 | case 0x0a: 351 | clrtoeol(); 352 | DRV_PUTC(ch); 353 | ++ G(y); 354 | G(x) = 0; 355 | break; 356 | case 0x0d: /* Move to first column */ 357 | DRV_PUTC(ch); 358 | G(x) = 0; 359 | break; 360 | default: 361 | if (ch < 0x20 || ch >= 0x7f) { 362 | /* FIXME */ 363 | DBG("addch %02x", ch); 364 | DRV_PUTC(ch); 365 | } else { 366 | DRV_PUTC(ch); 367 | ++ G(x); 368 | } 369 | } 370 | return OK; 371 | } 372 | 373 | int 374 | addnstr(const char *str, int n) 375 | { 376 | if (n < 0) n = strlen(str); 377 | DBG("addstr \"%.*s\"", n, str); 378 | while (n-- > 0) { 379 | chtype c = *str++; 380 | addch(c); 381 | } 382 | return OK; 383 | } 384 | 385 | int 386 | attr_on(attr_t on, void *opts) 387 | { 388 | attr_t attr = G(attr); 389 | attr |= on & INCURSES_ATTR_MASK; 390 | if (on & INCURSES_FG_MASK) { 391 | attr = (attr & ~ INCURSES_FG_MASK) | (on & INCURSES_FG_MASK); 392 | } 393 | if (on & INCURSES_BG_MASK) { 394 | attr = (attr & ~ INCURSES_BG_MASK) | (on & INCURSES_BG_MASK); 395 | } 396 | return attrset(attr); 397 | } 398 | 399 | int 400 | attrset(attr_t attr) 401 | { 402 | if (attr != G(attr)) { 403 | DBG("attrset %04x", attr); 404 | DRV_PUTS(ESC"[0"); 405 | if (attr & INCURSES_FG_MASK) { 406 | DRV_PUTS(";3"); 407 | DRV_PUTC('0' + GET_FG(attr)); 408 | } 409 | if (attr & INCURSES_BG_MASK) { 410 | DRV_PUTS(";4"); 411 | DRV_PUTC('0' + GET_BG(attr)); 412 | } 413 | if (attr & A_UNDERLINE) { 414 | DRV_PUTS(";4"); 415 | } 416 | if (attr & A_REVERSE) { 417 | DRV_PUTS(";7"); 418 | } 419 | DRV_PUTC('m'); 420 | 421 | G(attr) = attr; 422 | } 423 | return OK; 424 | } 425 | 426 | int 427 | clear(void) 428 | { 429 | DRV_PUTS(ESC"[2J"); /* clear screen */ 430 | return OK; 431 | } 432 | 433 | int 434 | clrtoeol(void) 435 | { 436 | DRV_PUTS(ESC"[K"); /* clear to end of line */ 437 | return OK; 438 | } 439 | 440 | int 441 | move(int y, int x) 442 | { 443 | if (! (x == G(x) && y == G(y))) { 444 | DBG("move %d %d", y, x); 445 | G(x) = x; 446 | G(y) = y; 447 | _move(y, x); 448 | } 449 | return OK; 450 | } 451 | 452 | int 453 | refresh(void) 454 | { 455 | DRV_FLUSH(); 456 | return OK; 457 | } 458 | 459 | char * 460 | unctrl(chtype c) 461 | { 462 | static char repr[4+1]; 463 | if (c >= 0x20 && c < 0x7f) { 464 | repr[0] = c; 465 | repr[1] = 0; 466 | } else if (c < 0x80) { 467 | repr[0] = '^'; 468 | repr[1] = c ^ 0x40; 469 | repr[2] = 0; 470 | } else if (c >= 0xa0 && c < 0xff) { 471 | repr[0] = 'M'; 472 | repr[1] = '-'; 473 | repr[2] = c ^ 0x80; 474 | repr[3] = 0; 475 | } else { 476 | repr[0] = '~'; 477 | repr[1] = c ^ 0xc0; 478 | repr[2] = 0; 479 | } 480 | return repr; 481 | } 482 | 483 | /*----------------------------------------------------------------------- 484 | * internals 485 | *-----------------------------------------------------------------------*/ 486 | static void 487 | _move(unsigned y, unsigned x) 488 | { 489 | char cmd[10]; 490 | sprintf(cmd, ESC"[%u;%uH", y+1, x+1); 491 | DRV_PUTS(cmd); 492 | } 493 | 494 | /* end */ 495 | --------------------------------------------------------------------------------