diff options
Diffstat (limited to 'lib/draw')
| -rw-r--r-- | lib/draw/curses.c | 258 | 
1 files changed, 255 insertions, 3 deletions
| diff --git a/lib/draw/curses.c b/lib/draw/curses.c index c873496..48a146a 100644 --- a/lib/draw/curses.c +++ b/lib/draw/curses.c @@ -2,8 +2,260 @@   * @file curses.c   */ -/* - * code goes here - */ +#include "../internal.h" +#include <wchar.h> +#include <string.h> +#include <stdlib.h> +#include <locale.h> +#include <ncurses.h> +#include <dlfcn.h> +#include <assert.h> + +/* ncurses.h likes to define stuff for us. + * This unforunately mangles with our struct. */ +#undef erase +#undef refresh +#undef mvprintw +#undef move +#undef init_pair +#undef attroff +#undef attron +#undef getmaxx +#undef getmaxy +#undef timeout + +static struct curses { +    void *handle; +    WINDOW *stdscr; +    WINDOW* (*initscr)(void); +    int (*endwin)(void); +    int (*refresh)(void); +    int (*erase)(void); +    int (*get_wch)(wint_t *wch); +    int (*mvprintw)(int x, int y, const char *fmt, ...); +    int (*move)(int x, int y); +    int (*init_pair)(short color, short f, short b); +    int (*attroff)(int attrs); +    int (*attron)(int attrs); +    int (*start_color)(void); +    int (*getmaxx)(WINDOW *win); +    int (*getmaxy)(WINDOW *win); +    int (*keypad)(WINDOW *win, bool bf); +    int *ESCDELAY; +} curses; + +static void _bmDrawCursesDrawLine(int pair, int y, const char *format, ...) +{ +    static int ncols = 0; +    static char *buffer = NULL; +    int new_ncols = curses.getmaxx(curses.stdscr); + +    if (new_ncols <= 0) +        return; + +    if (!buffer || new_ncols > ncols) { +        if (buffer) +            free(buffer); + +        ncols = new_ncols; + +        if (!(buffer = calloc(1, ncols + 1))) +            return; +    } + +    va_list args; +    va_start(args, format); +    int tlen = vsnprintf(NULL, 0, format, args) + 1; +    if (tlen > ncols) +        tlen = ncols; +    va_end(args); + +    va_start(args, format); +    vsnprintf(buffer, tlen, format, args); +    va_end(args); + +    memset(buffer + tlen - 1, ' ', ncols - tlen + 1); + +    if (pair > 0) +        curses.attron(COLOR_PAIR(pair)); + +    curses.mvprintw(y, 0, buffer); + +    if (pair > 0) +        curses.attroff(COLOR_PAIR(pair)); +} + +static void _bmDrawCursesRender(const bmMenu *menu) +{ +    if (!curses.stdscr) { +        freopen("/dev/tty", "rw", stdin); +        setlocale(LC_CTYPE, ""); +        if ((curses.stdscr = curses.initscr()) == NULL) +            return; + +        *curses.ESCDELAY = 25; +        curses.keypad(curses.stdscr, true); + +        curses.start_color(); +        curses.init_pair(1, COLOR_BLACK, COLOR_RED); +        curses.init_pair(2, COLOR_RED, COLOR_BLACK); +    } + +    const unsigned int lines = curses.getmaxy(curses.stdscr); +    curses.erase(); + +    size_t titleLen = (menu->title ? strlen(menu->title) + 1 : 0); + +    _bmDrawCursesDrawLine(0, 0, "%*s%s", titleLen, "", menu->filter); + +    if (menu->title) { +        curses.attron(COLOR_PAIR(1)); +        curses.mvprintw(0, 0, menu->title); +        curses.attroff(COLOR_PAIR(1)); +    } + +    unsigned int i, cl = 1; +    unsigned int itemsCount; +    bmItem **items = bmMenuGetFilteredItems(menu, &itemsCount); +    for (i = (menu->index / (lines - 1)) * (lines - 1); i < itemsCount && cl < lines; ++i) { +        int selected = (items[i] == bmMenuGetSelectedItem(menu)); +        _bmDrawCursesDrawLine((selected ? 2 : 0), cl++, "%s%s", (selected ? ">> " : "   "), items[i]->text); +    } + +    curses.move(0, titleLen + menu->cursesCursor); +    curses.refresh(); +} + +static void _bmDrawCursesEndWin(void) +{ +    if (curses.endwin) +        curses.endwin(); + +    curses.stdscr = NULL; +} + +static bmKey _bmDrawCursesGetKey(unsigned int *unicode) +{ +    assert(unicode != NULL); +    *unicode = 0; + +    if (!curses.stdscr) +        return BM_KEY_NONE; + +    curses.get_wch(unicode); +    switch (*unicode) { +        case 16: /* C-p */ +        case KEY_UP: return BM_KEY_UP; + +        case 14: /* C-n */ +        case KEY_DOWN: return BM_KEY_DOWN; + +        case 2: /* C-b */ +        case KEY_LEFT: return BM_KEY_LEFT; + +        case 6: /* C-f */ +        case KEY_RIGHT: return BM_KEY_RIGHT; + +        case 1: /* C-a */ +        case KEY_HOME: return BM_KEY_HOME; + +        case 5: /* C-e */ +        case KEY_END: return BM_KEY_END; + +        case KEY_PPAGE: return BM_KEY_PAGE_UP; +        case KEY_NPAGE: return BM_KEY_PAGE_DOWN; + +        case 8: /* C-h */ +        case KEY_BACKSPACE: return BM_KEY_BACKSPACE; + +        case 4: /* C-d */ +        case KEY_DC: return BM_KEY_DELETE; + +        case 21: return BM_KEY_LINE_DELETE_LEFT; /* C-u */ +        case 11: return BM_KEY_LINE_DELETE_RIGHT; /* C-k */ +        case 23: return BM_KEY_WORD_DELETE; /* C-w */ + +        case 9: return BM_KEY_TAB; /* Tab */ + +        case 10: /* Return */ +            _bmDrawCursesEndWin(); +            return BM_KEY_RETURN; + +        case 7: /* C-g */ +        case 27: /* Escape */ +            _bmDrawCursesEndWin(); +            return BM_KEY_ESCAPE; + +        default: break; +    } + +    return BM_KEY_UNICODE; +} + +static void _bmDrawCursesFree(void) +{ +    _bmDrawCursesEndWin(); + +    if (curses.handle) +        dlclose(curses.handle); + +    memset(&curses, 0, sizeof(curses)); +} + +int _bmDrawCursesInit(struct _bmRenderApi *api) +{ +    memset(&curses, 0, sizeof(curses)); + +    /* FIXME: hardcoded and not cross-platform */ +    curses.handle = dlopen("/usr/lib/libncursesw.so.5", RTLD_LAZY); + +    if (!curses.handle) +        return 0; + +#define bmLoadFunction(x) (curses.x = dlsym(curses.handle, #x)) + +    if (!bmLoadFunction(initscr)) +        goto function_pointer_exception; +    if (!bmLoadFunction(endwin)) +        goto function_pointer_exception; +    if (!bmLoadFunction(refresh)) +        goto function_pointer_exception; +    if (!bmLoadFunction(get_wch)) +        goto function_pointer_exception; +    if (!bmLoadFunction(erase)) +        goto function_pointer_exception; +    if (!bmLoadFunction(mvprintw)) +        goto function_pointer_exception; +    if (!bmLoadFunction(move)) +        goto function_pointer_exception; +    if (!bmLoadFunction(init_pair)) +        goto function_pointer_exception; +    if (!bmLoadFunction(attroff)) +        goto function_pointer_exception; +    if (!bmLoadFunction(attron)) +        goto function_pointer_exception; +    if (!bmLoadFunction(start_color)) +        goto function_pointer_exception; +    if (!bmLoadFunction(getmaxx)) +        goto function_pointer_exception; +    if (!bmLoadFunction(getmaxy)) +        goto function_pointer_exception; +    if (!bmLoadFunction(keypad)) +        goto function_pointer_exception; +    if (!bmLoadFunction(ESCDELAY)) +        goto function_pointer_exception; + +#undef bmLoadFunction + +    api->getKey = _bmDrawCursesGetKey; +    api->render = _bmDrawCursesRender; +    api->free = _bmDrawCursesFree; + +    return 1; + +function_pointer_exception: +    _bmDrawCursesFree(); +    return 0; +}  /* vim: set ts=8 sw=4 tw=0 :*/ | 
