summaryrefslogtreecommitdiff
path: root/lib/draw/curses.c
diff options
context:
space:
mode:
authorJari Vetoniemi <mailroxas@gmail.com>2014-08-21 01:47:30 +0300
committerJari Vetoniemi <mailroxas@gmail.com>2014-08-21 01:47:30 +0300
commitf1bb87a808ff1383a2d27dc2c4a1f8812c470c31 (patch)
tree6669801544e51621c9269e5e89d3ae7a83c73ba8 /lib/draw/curses.c
parent5bd81e8d384e82926a7671527e62aebafed1183f (diff)
parent536eee6d0bf8eb99b6566f7f4fd646b3cd76f532 (diff)
downloadbemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.tar.gz
bemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.tar.bz2
bemenu-f1bb87a808ff1383a2d27dc2c4a1f8812c470c31.zip
Merge branch 'develop'
Diffstat (limited to 'lib/draw/curses.c')
-rw-r--r--lib/draw/curses.c497
1 files changed, 493 insertions, 4 deletions
diff --git a/lib/draw/curses.c b/lib/draw/curses.c
index c873496..a1f23ef 100644
--- a/lib/draw/curses.c
+++ b/lib/draw/curses.c
@@ -1,9 +1,498 @@
+#include "../internal.h"
+
+#if __APPLE__
+# define _C99_SOURCE
+# include <stdio.h> /* vsnprintf */
+# undef _C99_SOURCE
+#endif
+
+#define _XOPEN_SOURCE 500
+#include <signal.h> /* sigaction */
+#include <stdarg.h> /* vsnprintf */
+#undef _XOPEN_SOURCE
+
+#include <wchar.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <ncurses.h>
+#include <dlfcn.h>
+#include <assert.h>
+
+#if _WIN32
+static const char *TTY = "CON";
+#else
+static const char *TTY = "/dev/tty";
+#endif
+
+#if __APPLE__
+ const char *DL_PATH[] = {
+ "libncursesw.5.dylib",
+ "libncurses.5.dylib",
+ NULL
+ };
+#elif _WIN32
+# error FIXME: Compile with windows... or use relative path?
+#else
+ const char *DL_PATH[] = {
+ "libncursesw.so.5",
+ "libncurses.so.5",
+ NULL
+ };
+#endif
+
+/* these are implemented as macros in older curses */
+#ifndef NCURSES_OPAQUE
+static int wrap_getmaxx(WINDOW *win) { return getmaxx(win); }
+static int wrap_getmaxy(WINDOW *win) { return getmaxy(win); }
+#endif
+
+/* ncurses.h likes to define stuff for us.
+ * This unforunately mangles with our struct. */
+#undef erase
+#undef getch
+#undef get_wch
+#undef refresh
+#undef mvprintw
+#undef move
+#undef init_pair
+#undef attroff
+#undef attron
+#undef getmaxx
+#undef getmaxy
+
/**
- * @file curses.c
+ * Dynamically loaded curses API.
*/
+static struct curses {
+ struct sigaction abrtAction;
+ struct sigaction segvAction;
+ struct sigaction winchAction;
+ void *handle;
+ WINDOW *stdscr;
+ WINDOW* (*initscr)(void);
+ int (*endwin)(void);
+ int (*refresh)(void);
+ int (*erase)(void);
+ int (*getch)(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 (*use_default_colors)(void);
+ int (*getmaxx)(WINDOW *win);
+ int (*getmaxy)(WINDOW *win);
+ int (*keypad)(WINDOW *win, bool bf);
+ int (*curs_set)(int visibility);
+ int (*flushinp)(void);
+ int (*noecho)(void);
+ int (*raw)(void);
+ int *ESCDELAY;
+ int oldStdin;
+ int oldStdout;
+} curses;
-/*
- * code goes here
- */
+static int _bmDrawCursesResizeBuffer(char **buffer, size_t *osize, size_t nsize)
+{
+ assert(buffer);
+ assert(osize);
+
+ 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;
+ *osize = nsize;
+ return 1;
+}
+
+#if __GNUC__
+__attribute__((format(printf, 3, 4)))
+#endif
+static void _bmDrawCursesDrawLine(int pair, int y, const char *format, ...)
+{
+ static size_t blen = 0;
+ static char *buffer = NULL;
+
+ size_t ncols = curses.getmaxx(curses.stdscr);
+ if (ncols <= 0)
+ return;
+
+ va_list args;
+ va_start(args, format);
+ size_t nlen = vsnprintf(NULL, 0, format, args);
+ va_end(args);
+
+ if ((!buffer || nlen > blen) && !_bmDrawCursesResizeBuffer(&buffer, &blen, nlen + 1))
+ return;
+
+ va_start(args, format);
+ vsnprintf(buffer, blen - 1, format, args);
+ va_end(args);
+
+ size_t dw = 0, i = 0;
+ while (dw < ncols && i < nlen) {
+ if (buffer[i] == '\t') buffer[i] = ' ';
+ int next = _bmUtf8RuneNext(buffer, i);
+ dw += _bmUtf8RuneWidth(buffer + i, next);
+ i += (next ? next : 1);
+ }
+
+ if (dw < ncols) {
+ /* line is too short, widen it */
+ size_t offset = i + (ncols - dw);
+ if (blen <= offset && !_bmDrawCursesResizeBuffer(&buffer, &blen, offset + 1))
+ return;
+
+ memset(buffer + nlen, ' ', offset - nlen);
+ buffer[offset] = 0;
+ } else if (i < blen) {
+ /* line is too long, shorten it */
+ i -= _bmUtf8RunePrev(buffer, i - (dw - ncols)) - 1;
+ size_t cc = dw - (dw - ncols);
+
+ size_t offset = i - (dw - ncols) + (ncols - cc) + 1;
+ if (blen <= offset) {
+ int diff = offset - blen + 1;
+ if (!_bmDrawCursesResizeBuffer(&buffer, &blen, blen + diff))
+ return;
+ }
+
+ memset(buffer + i - (dw - ncols), ' ', (ncols - cc) + 1);
+ buffer[offset] = 0;
+ }
+
+ if (pair > 0)
+ curses.attron(COLOR_PAIR(pair));
+
+ curses.mvprintw(y, 0, "%s", 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.flushinp();
+ curses.keypad(curses.stdscr, true);
+ curses.curs_set(1);
+ curses.noecho();
+ curses.raw();
+
+ curses.start_color();
+ curses.use_default_colors();
+ curses.init_pair(1, COLOR_BLACK, COLOR_RED);
+ curses.init_pair(2, COLOR_RED, -1);
+ }
+
+ const unsigned int lines = curses.getmaxy(curses.stdscr);
+ curses.erase();
+
+ unsigned int ncols = curses.getmaxx(curses.stdscr);
+ unsigned int titleLen = (menu->title ? strlen(menu->title) + 1 : 0);
+
+ if (titleLen >= ncols)
+ titleLen = 0;
+
+ unsigned int ccols = ncols - titleLen - 1;
+ unsigned int dcols = 0, doffset = menu->cursor;
+
+ while (doffset > 0 && dcols < ccols) {
+ int prev = _bmUtf8RunePrev(menu->filter, doffset);
+ dcols += _bmUtf8RuneWidth(menu->filter + doffset - prev, prev);
+ doffset -= (prev ? prev : 1);
+ }
+
+ _bmDrawCursesDrawLine(0, 0, "%*s%s", titleLen, "", (menu->filter ? menu->filter + doffset : ""));
+
+ if (menu->title && titleLen > 0) {
+ 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 ? items[i]->text : ""));
+ }
+
+ curses.move(0, titleLen + (menu->cursesCursor < ccols ? menu->cursesCursor : ccols));
+ curses.refresh();
+}
+
+static unsigned int _bmDrawCursesDisplayedCount(const bmMenu *menu)
+{
+ (void)menu;
+ return (curses.stdscr ? curses.getmaxy(curses.stdscr) : 0);
+}
+
+static void _bmDrawCursesEndWin(void)
+{
+ if (!curses.stdscr)
+ return;
+
+ freopen(TTY, "w", stdout);
+
+ if (curses.refresh)
+ curses.refresh();
+
+ if (curses.endwin)
+ curses.endwin();
+
+ 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;
+
+ if (curses.get_wch)
+ curses.get_wch((wint_t*)unicode);
+ else if (curses.getch)
+ *unicode = curses.getch();
+
+ switch (*unicode) {
+#if KEY_RESIZE
+ case KEY_RESIZE:
+ return BM_KEY_NONE;
+#endif
+
+ 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 391: /* S-Home */
+ case KEY_HOME:
+ return BM_KEY_HOME;
+
+ case 5: /* C-e */
+ case 386: /* S-End */
+ 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 550: /* C-Page up */
+ case 398: /* S-Page up */
+ return BM_KEY_SHIFT_PAGE_UP;
+
+ case 545: /* C-Page down */
+ case 396: /* S-Page down */
+ return BM_KEY_SHIFT_PAGE_DOWN;
+
+ case 8: /* C-h */
+ case 127: /* Delete */
+ case KEY_BACKSPACE:
+ return BM_KEY_BACKSPACE;
+
+ case 4: /* C-d */
+ case KEY_DC:
+ return BM_KEY_DELETE;
+
+ case 383: /* S-Del */
+ 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_CONTROL_RETURN;
+
+ case 20: /* C-t */
+ case 331: /* Insert */
+ _bmDrawCursesEndWin();
+ 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.abrtAction, NULL);
+ sigaction(SIGSEGV, &curses.segvAction, NULL);
+ sigaction(SIGWINCH, &curses.winchAction, NULL);
+ memset(&curses, 0, sizeof(curses));
+}
+
+static void _bmDrawCursesCrashHandler(int sig)
+{
+ (void)sig;
+ _bmDrawCursesFree();
+}
+
+static void _bmDrawCursesResizeHandler(int sig)
+{
+ (void)sig;
+ if (!curses.stdscr)
+ return;
+
+ curses.endwin();
+ curses.refresh();
+}
+
+int _bmDrawCursesInit(struct _bmRenderApi *api)
+{
+ memset(&curses, 0, sizeof(curses));
+ const char *lib = NULL, *func = NULL;
+
+ unsigned int i;
+ for (i = 0; DL_PATH[i] && !curses.handle; ++i)
+ curses.handle = dlopen((lib = DL_PATH[i]), RTLD_LAZY);
+
+ if (!curses.handle)
+ return 0;
+
+#define bmLoadFunction(x) (curses.x = dlsym(curses.handle, (func = #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) && !bmLoadFunction(getch))
+ 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(use_default_colors))
+ goto function_pointer_exception;
+ if (!bmLoadFunction(keypad))
+ goto function_pointer_exception;
+ if (!bmLoadFunction(curs_set))
+ goto function_pointer_exception;
+ if (!bmLoadFunction(flushinp))
+ goto function_pointer_exception;
+ if (!bmLoadFunction(noecho))
+ goto function_pointer_exception;
+ if (!bmLoadFunction(raw))
+ goto function_pointer_exception;
+ if (!bmLoadFunction(ESCDELAY))
+ goto function_pointer_exception;
+
+#ifndef NCURSES_OPAQUE
+ curses.getmaxx = wrap_getmaxx;
+ curses.getmaxy = wrap_getmaxy;
+#else
+ if (!bmLoadFunction(getmaxx))
+ goto function_pointer_exception;
+ if (!bmLoadFunction(getmaxy))
+ goto function_pointer_exception;
+#endif
+
+#undef bmLoadFunction
+
+ api->displayedCount = _bmDrawCursesDisplayedCount;
+ 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.abrtAction);
+ sigaction(SIGSEGV, &action, &curses.segvAction);
+
+ action.sa_handler = _bmDrawCursesResizeHandler;
+ sigaction(SIGWINCH, &action, &curses.winchAction);
+ return 1;
+
+function_pointer_exception:
+ fprintf(stderr, "-!- Could not load function '%s' from '%s'\n", func, lib);
+ _bmDrawCursesFree();
+ return 0;
+}
/* vim: set ts=8 sw=4 tw=0 :*/