forked from RepoMirrors/bemenu
369 lines
9.0 KiB
C
369 lines
9.0 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>
|
|
#include <unistd.h>
|
|
|
|
#if _WIN32
|
|
static const char *TTY = "CON";
|
|
#else
|
|
static const char *TTY = "/dev/tty";
|
|
#endif
|
|
|
|
#if __APPLE__
|
|
const char *DL_PATH = "libncurses.5.dylib";
|
|
#elif _WIN32
|
|
# error FIXME: Compile with windows... or use relative path?
|
|
#else
|
|
const char *DL_PATH = "libncursesw.so.5";
|
|
#endif
|
|
|
|
/* ncurses.h likes to define stuff for us.
|
|
* This unforunately mangles with our struct. */
|
|
#undef erase
|
|
#undef get_wch
|
|
#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;
|
|
int oldStdin;
|
|
int oldStdout;
|
|
} curses;
|
|
|
|
static int _bmDrawCursesResizeBuffer(char **buffer, size_t osize, size_t nsize)
|
|
{
|
|
assert(buffer);
|
|
assert(nsize);
|
|
|
|
if (nsize == 0 || nsize < osize)
|
|
return 0;
|
|
|
|
void *tmp;
|
|
if (!*buffer || !(tmp = realloc(*buffer, nsize))) {
|
|
if (!(tmp = malloc(nsize)))
|
|
return 0;
|
|
|
|
if (*buffer) {
|
|
memcpy(tmp, *buffer, osize);
|
|
free(*buffer);
|
|
}
|
|
}
|
|
|
|
*buffer = tmp;
|
|
memset(*buffer + osize, ' ', (nsize - osize));
|
|
(*buffer)[nsize - 1] = 0;
|
|
return 1;
|
|
}
|
|
|
|
static void _bmDrawCursesDrawLine(int pair, int y, const char *format, ...)
|
|
{
|
|
static int blen = 0;
|
|
static char *buffer = NULL;
|
|
|
|
int ncols = curses.getmaxx(curses.stdscr);
|
|
if (ncols <= 0)
|
|
return;
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
int nlen = vsnprintf(NULL, 0, format, args) + 1;
|
|
if (nlen < ncols)
|
|
nlen = ncols;
|
|
va_end(args);
|
|
|
|
if ((!buffer || nlen > blen) && !_bmDrawCursesResizeBuffer(&buffer, blen, nlen))
|
|
return;
|
|
|
|
blen = nlen;
|
|
va_start(args, format);
|
|
int slen = vsnprintf(buffer, blen - 1, format, args);
|
|
memset(buffer + slen, ' ', (blen - slen));
|
|
buffer[blen - 1] = 0;
|
|
va_end(args);
|
|
|
|
int dw = 0, i = 0;
|
|
while (dw < ncols && i < blen) {
|
|
if (buffer[i] == '\t') buffer[i] = ' ';
|
|
int next = _bmUtf8RuneNext(buffer, i);
|
|
dw += _bmUtf8RuneWidth(buffer + i, next);
|
|
i += (next ? next : 1);
|
|
}
|
|
|
|
if (dw < ncols) {
|
|
if (!_bmDrawCursesResizeBuffer(&buffer, blen - 1, blen + (ncols - dw)))
|
|
return;
|
|
} else if (i < blen) {
|
|
int cc = dw - (dw - ncols);
|
|
memset(buffer + i - (dw - ncols), ' ', (ncols - cc) + 1);
|
|
buffer[i - (dw - ncols) + (ncols - cc) + 1] = 0;
|
|
}
|
|
|
|
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) {
|
|
curses.oldStdin = dup(STDIN_FILENO);
|
|
curses.oldStdout = dup(STDOUT_FILENO);
|
|
|
|
freopen(TTY, "w", stdout);
|
|
freopen(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 highlighted = (items[i] == bmMenuGetHighlightedItem(menu));
|
|
int color = (highlighted ? 2 : (_bmMenuItemIsSelected(menu, items[i]) ? 1 : 0));
|
|
_bmDrawCursesDrawLine(color, cl++, "%s%s", (highlighted ? ">> " : " "), items[i]->text);
|
|
}
|
|
|
|
unsigned int ncols = curses.getmaxx(curses.stdscr) - titleLen - 1;
|
|
curses.move(0, titleLen + (ncols < menu->cursesCursor ? ncols : menu->cursesCursor));
|
|
curses.refresh();
|
|
}
|
|
|
|
static void _bmDrawCursesEndWin(void)
|
|
{
|
|
freopen(TTY, "w", stdout);
|
|
|
|
if (curses.refresh)
|
|
curses.refresh();
|
|
|
|
if (curses.endwin)
|
|
curses.endwin();
|
|
|
|
if (curses.stdscr) {
|
|
dup2(curses.oldStdin, STDIN_FILENO);
|
|
dup2(curses.oldStdout, STDOUT_FILENO);
|
|
close(curses.oldStdin);
|
|
close(curses.oldStdout);
|
|
}
|
|
|
|
curses.stdscr = NULL;
|
|
}
|
|
|
|
static bmKey _bmDrawCursesGetKey(unsigned int *unicode)
|
|
{
|
|
assert(unicode);
|
|
*unicode = 0;
|
|
|
|
if (!curses.stdscr)
|
|
return BM_KEY_NONE;
|
|
|
|
curses.get_wch((wint_t*)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 18: /* C-r */
|
|
return BM_KEY_SHIFT_RETURN;
|
|
|
|
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));
|
|
curses.handle = dlopen(DL_PATH, 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 :*/
|