bemenu/lib/draw/curses.c
2014-04-10 20:01:34 +03:00

298 lines
7.2 KiB
C

#include "../internal.h"
#define _XOPEN_SOURCE 700
#include <wchar.h>
#include <string.h>
#include <stdlib.h>
#include <locale.h>
#include <ncurses.h>
#include <dlfcn.h>
#include <signal.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
/**
* Dynamically loaded curses API.
*/
static struct curses {
struct sigaction action;
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 newNcols = curses.getmaxx(curses.stdscr);
if (newNcols <= 0)
return;
if (!buffer || newNcols > ncols) {
if (buffer)
free(buffer);
ncols = newNcols;
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", "r", 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.refresh)
curses.refresh();
if (curses.endwin)
curses.endwin();
curses.stdscr = NULL;
freopen("/dev/tty", "w", stdout);
}
static bmKey _bmDrawCursesGetKey(unsigned int *unicode)
{
assert(unicode);
*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: /* PAGE UP */
return BM_KEY_PAGE_UP;
case KEY_NPAGE: /* PAGE DOWN */
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: /* C-u */
return BM_KEY_LINE_DELETE_LEFT;
case 11: /* C-k */
return BM_KEY_LINE_DELETE_RIGHT;
case 23: /* C-w */
return BM_KEY_WORD_DELETE;
case 9: /* Tab */
return BM_KEY_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);
sigaction(SIGABRT, &curses.action, NULL);
sigaction(SIGSEGV, &curses.action, NULL);
memset(&curses, 0, sizeof(curses));
}
static void _bmDrawCursesCrashHandler(int sig)
{
(void)sig;
_bmDrawCursesFree();
}
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;
struct sigaction action;
memset(&action, 0, sizeof(struct sigaction));
action.sa_handler = _bmDrawCursesCrashHandler;
sigaction(SIGABRT, &action, &curses.action);
sigaction(SIGSEGV, &action, &curses.action);
return 1;
function_pointer_exception:
_bmDrawCursesFree();
return 0;
}
/* vim: set ts=8 sw=4 tw=0 :*/