summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/CMakeLists.txt42
-rw-r--r--lib/bemenu.c9
-rw-r--r--lib/bemenu.h484
-rw-r--r--lib/draw/curses.c497
-rw-r--r--lib/filter.c188
-rw-r--r--lib/internal.h188
-rw-r--r--lib/item.c96
-rw-r--r--lib/library.c15
-rw-r--r--lib/list.c139
-rw-r--r--lib/menu.c727
-rw-r--r--lib/util.c314
-rw-r--r--lib/version.h.in3
12 files changed, 2680 insertions, 22 deletions
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 46c532b..a52bc7b 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -1,13 +1,16 @@
-SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY lib)
-SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY lib)
-
# Sources
SET(BEMENU_SOURCE
- bemenu.c
+ menu.c
+ item.c
+ list.c
+ util.c
+ filter.c
+ library.c
draw/curses.c
)
-SET(BEMENU_INCLUDE)
-SET(BEMENU_LIBRARIES)
+
+# Configure
+CONFIGURE_FILE(version.h.in version.h @ONLY)
# Warnings
IF (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
@@ -22,9 +25,30 @@ IF (UNIX AND CMAKE_COMPILER_IS_GNUCC)
ENDIF ()
ENDIF ()
+# Parse soversion version
+STRING(REGEX MATCHALL "[0-9]+" VERSION_COMPONENTS ${BEMENU_VERSION})
+LIST(GET VERSION_COMPONENTS 0 SOVERSION)
+
# Compile
-INCLUDE_DIRECTORIES(${BEMENU_INCLUDE})
-ADD_LIBRARY(bemenu ${BEMENU_SOURCE})
-TARGET_LINK_LIBRARIES(bemenu ${BEMENU_LIBRARIES})
+INCLUDE_DIRECTORIES(${BEMENU_INCLUDE} ${CMAKE_CURRENT_BINARY_DIR})
+ADD_LIBRARY(bemenu SHARED ${BEMENU_SOURCE})
+SET_TARGET_PROPERTIES(bemenu PROPERTIES
+ VERSION ${BEMENU_VERSION}
+ SOVERSION ${SOVERSION})
+TARGET_LINK_LIBRARIES(bemenu dl)
+
+# Install
+INSTALL(TARGETS bemenu DESTINATION lib)
+INSTALL(FILES bemenu.h DESTINATION include)
+
+# Unexport
+SET(BEMENU_INCLUDES)
+SET(BEMENU_INCLUDE_DIRS)
+SET(BEMENU_LIBRARIES)
+
+# Export
+SET(BEMENU_INCLUDES "bemenu.h" CACHE STRING "bemenu includes exported from CMake")
+SET(BEMENU_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} CACHE STRING "bemenu public header include path exported from CMake")
+SET(BEMENU_LIBRARIES "bemenu" "dl" CACHE STRING "bemenu libraries exported from CMake")
# vim: set ts=8 sw=4 tw=0 :
diff --git a/lib/bemenu.c b/lib/bemenu.c
deleted file mode 100644
index 2f92f27..0000000
--- a/lib/bemenu.c
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * @file bemenu.c
- */
-
-/*
- * code goes here
- */
-
-/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/bemenu.h b/lib/bemenu.h
new file mode 100644
index 0000000..5457cca
--- /dev/null
+++ b/lib/bemenu.h
@@ -0,0 +1,484 @@
+/**
+ * @file bemenu.h
+ *
+ * Public API header.
+ */
+
+typedef struct _bmMenu bmMenu;
+typedef struct _bmItem bmItem;
+
+/**
+ * @defgroup Library
+ * @brief Library functions.
+ *
+ * Query library version, etc...
+ */
+
+/**
+ * @defgroup Menu
+ * @brief Menu container.
+ *
+ * Holds all the items, runs logic and gets rendered.
+ */
+
+/**
+ * @defgroup Item
+ * @brief Item container.
+ *
+ * Contains properties for visual representation of item.
+ */
+
+/**
+ * @addtogroup Library
+ * @{ */
+
+/**
+ * @name Library Version
+ * @{ */
+
+/**
+ * Get version of the library in 'major.minor.patch' format.
+ *
+ * @see @link http://semver.org/ Semantic Versioning @endlink
+ *
+ * @return Null terminated C "string" to version string.
+ */
+const char* bmVersion(void);
+
+/** @} Library Version */
+
+/** @} Library */
+
+/**
+ * @addtogroup Menu
+ * @{ */
+
+/**
+ * Draw mode constants for bmMenu instance draw mode.
+ *
+ * @link ::bmDrawMode BM_DRAW_MODE_LAST @endlink is provided for enumerating draw modes.
+ * Instancing with it however provides exactly same functionality as BM_DRAW_MODE_NONE.
+ */
+typedef enum bmDrawMode {
+ BM_DRAW_MODE_NONE,
+ BM_DRAW_MODE_CURSES,
+ BM_DRAW_MODE_LAST
+} bmDrawMode;
+
+/**
+ * Filter mode constants for bmMenu instance filter mode.
+ *
+ * @link ::bmFilterMode BM_FILTER_MODE_LAST @endlink is provided for enumerating filter modes.
+ * Using it as filter mode however provides exactly same functionality as BM_FILTER_MODE_DMENU.
+ */
+typedef enum bmFilterMode {
+ BM_FILTER_MODE_DMENU,
+ BM_FILTER_MODE_DMENU_CASE_INSENSITIVE,
+ BM_FILTER_MODE_LAST
+} bmFilterMode;
+
+/**
+ * Result constants from bmMenuRunWithKey function.
+ *
+ * - @link ::bmRunResult BM_RUN_RESULT_RUNNING @endlink means that menu is running and thus should be still renderer && ran.
+ * - @link ::bmRunResult BM_RUN_RESULT_SELECTED @endlink means that menu was closed and items were selected.
+ * - @link ::bmRunResult BM_RUN_RESULT_CANCEL @endlink means that menu was closed and selection was canceled.
+ */
+typedef enum bmRunResult {
+ BM_RUN_RESULT_RUNNING,
+ BM_RUN_RESULT_SELECTED,
+ BM_RUN_RESULT_CANCEL,
+} bmRunResult;
+
+/**
+ * Key constants.
+ *
+ * @link ::bmKey BM_KEY_LAST @endlink is provided for enumerating keys.
+ */
+typedef enum bmKey {
+ BM_KEY_NONE,
+ BM_KEY_UP,
+ BM_KEY_DOWN,
+ BM_KEY_LEFT,
+ BM_KEY_RIGHT,
+ BM_KEY_HOME,
+ BM_KEY_END,
+ BM_KEY_PAGE_UP,
+ BM_KEY_PAGE_DOWN,
+ BM_KEY_SHIFT_PAGE_UP,
+ BM_KEY_SHIFT_PAGE_DOWN,
+ BM_KEY_BACKSPACE,
+ BM_KEY_DELETE,
+ BM_KEY_LINE_DELETE_LEFT,
+ BM_KEY_LINE_DELETE_RIGHT,
+ BM_KEY_WORD_DELETE,
+ BM_KEY_TAB,
+ BM_KEY_ESCAPE,
+ BM_KEY_RETURN,
+ BM_KEY_SHIFT_RETURN,
+ BM_KEY_CONTROL_RETURN,
+ BM_KEY_UNICODE,
+ BM_KEY_LAST
+} bmKey;
+
+/**
+ * @name Menu Memory
+ * @{ */
+
+/**
+ * Create new bmMenu instance.
+ *
+ * @param drawMode Render method to be used for this menu instance.
+ * @return bmMenu for new menu instance, **NULL** if creation failed.
+ */
+bmMenu* bmMenuNew(bmDrawMode drawMode);
+
+/**
+ * Release bmMenu instance.
+ *
+ * @param menu bmMenu instance to be freed from memory.
+ */
+void bmMenuFree(bmMenu *menu);
+
+/**
+ * Release items inside bmMenu instance.
+ *
+ * @param menu bmMenu instance which items will be freed from memory.
+ */
+void bmMenuFreeItems(bmMenu *menu);
+
+/** @} Menu Memory */
+
+/**
+ * @name Menu Properties
+ * @{ */
+
+/**
+ * Set userdata pointer to bmMenu instance.
+ * Userdata will be carried unmodified by the instance.
+ *
+ * @param menu bmMenu instance where to set userdata pointer.
+ * @param userdata Pointer to userdata.
+ */
+void bmMenuSetUserdata(bmMenu *menu, void *userdata);
+
+/**
+ * Get userdata pointer from bmMenu instance.
+ *
+ * @param menu bmMenu instance which userdata pointer to get.
+ * @return Pointer for unmodified userdata.
+ */
+void* bmMenuGetUserdata(bmMenu *menu);
+
+/**
+ * Set filter text to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set filter.
+ * @param filter Null terminated C "string" to act as filter.
+ */
+void bmMenuSetFilter(bmMenu *menu, const char *filter);
+
+/**
+ * Get filter text from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get filter.
+ * @return Const pointer to current filter text, may be **NULL** if empty.
+ */
+const char* bmMenuGetFilter(bmMenu *menu);
+
+/**
+ * Set active filter mode to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set filter mode.
+ * @param mode bmFilterMode constant.
+ */
+void bmMenuSetFilterMode(bmMenu *menu, bmFilterMode mode);
+
+/**
+ * Get active filter mode from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get filter mode.
+ * @return bmFilterMode constant.
+ */
+bmFilterMode bmMenuGetFilterMode(const bmMenu *menu);
+
+/**
+ * Set selection wrapping on/off.
+ *
+ * @param menu bmMenu instance where to toggle selection wrapping.
+ * @param int 1 == on, 0 == off.
+ */
+void bmMenuSetWrap(bmMenu *menu, int wrap);
+
+/**
+ * Get selection wrapping state.
+ *
+ * @param menu bmMenu instance where to get selection wrapping state.
+ * @return int for wrap state.
+ */
+int bmMenuGetWrap(const bmMenu *menu);
+
+/**
+ * Set title to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set title.
+ * @param title C "string" to set as title, can be **NULL** for empty title.
+ */
+int bmMenuSetTitle(bmMenu *menu, const char *title);
+
+/**
+ * Get title from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get title from.
+ * @return Pointer to null terminated C "string", can be **NULL** for empty title.
+ */
+const char* bmMenuGetTitle(const bmMenu *menu);
+
+/** @} Properties */
+
+/**
+ * @name Menu Items
+ * @{ */
+
+/**
+ * Add item to bmMenu instance at specific index.
+ *
+ * @param menu bmMenu instance where item will be added.
+ * @param item bmItem instance to add.
+ * @param index Index where item will be added.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuAddItemAt(bmMenu *menu, bmItem *item, unsigned int index);
+
+/**
+ * Add item to bmMenu instance.
+ *
+ * @param menu bmMenu instance where item will be added.
+ * @param item bmItem instance to add.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuAddItem(bmMenu *menu, bmItem *item);
+
+/**
+ * Remove item from bmMenu instance at specific index.
+ *
+ * @warning The item won't be freed, use bmItemFree to do that.
+ *
+ * @param menu bmMenu instance from where item will be removed.
+ * @param index Index of item to remove.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuRemoveItemAt(bmMenu *menu, unsigned int index);
+
+/**
+ * Remove item from bmMenu instance.
+ *
+ * @warning The item won't be freed, use bmItemFree to do that.
+ *
+ * @param menu bmMenu instance from where item will be removed.
+ * @param item bmItem instance to remove.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuRemoveItem(bmMenu *menu, bmItem *item);
+
+/**
+ * Highlight item in menu by index.
+ *
+ * @param menu bmMenu instance from where to highlight item.
+ * @param index Index of item to highlight.
+ * @return 1 on successful highlight, 0 on failure.
+ */
+int bmMenuSetHighlightedIndex(bmMenu *menu, unsigned int index);
+
+/**
+ * Highlight item in menu.
+ *
+ * @param menu bmMenu instance from where to highlight item.
+ * @param item bmItem instance to highlight.
+ * @return 1 on successful highlight, 0 on failure.
+ */
+int bmMenuSetHighlighted(bmMenu *menu, bmItem *item);
+
+/**
+ * Get highlighted item from bmMenu instance.
+ *
+ * @warning The pointer returned by this function may be invalid after items change.
+ *
+ * @param menu bmMenu instance from where to get highlighted item.
+ * @return Selected bmItem instance, **NULL** if none highlighted.
+ */
+bmItem* bmMenuGetHighlightedItem(const bmMenu *menu);
+
+/**
+ * Set selected items to bmMenu instance.
+ *
+ * @param menu bmMenu instance where items will be set.
+ * @param items Array of bmItem pointers to set.
+ * @param nmemb Total count of items in array.
+ * @return 1 on successful set, 0 on failure.
+ */
+int bmMenuSetSelectedItems(bmMenu *menu, bmItem **items, unsigned int nmemb);
+
+/**
+ * Get selected items from bmMenu instance.
+ *
+ * @warning The pointer returned by this function may be invalid after selection or items change.
+ *
+ * @param menu bmMenu instance from where to get selected items.
+ * @param outNmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetSelectedItems(const bmMenu *menu, unsigned int *outNmemb);
+
+/**
+ * Set items to bmMenu instance.
+ * Will replace all the old items on bmMenu instance.
+ *
+ * If items is **NULL**, or nmemb is zero, all items will be freed from the menu.
+ *
+ * @param menu bmMenu instance where items will be set.
+ * @param items Array of bmItem pointers to set.
+ * @param nmemb Total count of items in array.
+ * @return 1 on successful set, 0 on failure.
+ */
+int bmMenuSetItems(bmMenu *menu, const bmItem **items, unsigned int nmemb);
+
+/**
+ * Get items from bmMenu instance.
+ *
+ * @warning The pointer returned by this function may be invalid after removing or adding new items.
+ *
+ * @param menu bmMenu instance from where to get items.
+ * @param outNmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetItems(const bmMenu *menu, unsigned int *outNmemb);
+
+/**
+ * Get filtered (displayed) items from bmMenu instance.
+ *
+ * @warning The pointer returned by this function _will_ be invalid when menu internally filters its list again.
+ * Do not store this pointer.
+ *
+ * @param menu bmMenu instance from where to get filtered items.
+ * @param outNmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetFilteredItems(const bmMenu *menu, unsigned int *outNmemb);
+
+/** @} Menu Items */
+
+/**
+ * @name Menu Logic
+ * @{ */
+
+/**
+ * Render bmMenu instance using chosen draw method.
+ *
+ * @param menu bmMenu instance to be rendered.
+ */
+void bmMenuRender(const bmMenu *menu);
+
+/**
+ * Trigger filtering of menu manually.
+ * This is useful when adding new items and want to dynamically see them filtered.
+ *
+ * Do note that filtering might be heavy, so you should only call it after batch manipulation of items.
+ * Not after manipulation of each single item.
+ *
+ * @param menu bmMenu instance which to filter.
+ */
+void bmMenuFilter(bmMenu *menu);
+
+/**
+ * Poll key and unicode from underlying UI toolkit.
+ *
+ * This function will block on @link ::bmDrawMode BM_DRAW_MODE_CURSES @endlink draw mode.
+ *
+ * @param menu bmMenu instance from which to poll.
+ * @param outUnicode Reference to unsigned int.
+ * @return bmKey for polled key.
+ */
+bmKey bmMenuGetKey(bmMenu *menu, unsigned int *outUnicode);
+
+/**
+ * Advances menu logic with key and unicode as input.
+ *
+ * @param menu bmMenu instance to be advanced.
+ * @param key Key input that will advance menu logic.
+ * @param unicode Unicode input that will advance menu logic.
+ * @return bmRunResult for menu state.
+ */
+bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode);
+
+/** @} Menu Logic */
+
+/** @} Menu */
+
+/**
+ * @addtogroup Item
+ * @{ */
+
+/**
+ * @name Item Memory
+ * @{ */
+
+/**
+ * Allocate a new item.
+ *
+ * @param text Pointer to null terminated C "string", can be **NULL** for empty text.
+ * @return bmItem for new item instance, **NULL** if creation failed.
+ */
+bmItem* bmItemNew(const char *text);
+
+/**
+ * Release bmItem instance.
+ *
+ * @param item bmItem instance to be freed from memory.
+ */
+void bmItemFree(bmItem *item);
+
+/** @} Item Memory */
+
+/**
+ * @name Item Properties
+ * @{ */
+
+/**
+ * Set userdata pointer to bmItem instance.
+ * Userdata will be carried unmodified by the instance.
+ *
+ * @param item bmItem instance where to set userdata pointer.
+ * @param userdata Pointer to userdata.
+ */
+void bmItemSetUserdata(bmItem *item, void *userdata);
+
+/**
+ * Get userdata pointer from bmItem instance.
+ *
+ * @param item bmItem instance which userdata pointer to get.
+ * @return Pointer for unmodified userdata.
+ */
+void* bmItemGetUserdata(bmItem *item);
+
+/**
+ * Set text to bmItem instance.
+ *
+ * @param item bmItem instance where to set text.
+ * @param text C "string" to set as text, can be **NULL** for empty text.
+ */
+int bmItemSetText(bmItem *item, const char *text);
+
+/**
+ * Get text from bmItem instance.
+ *
+ * @param item bmItem instance where to get text from.
+ * @return Pointer to null terminated C "string", can be **NULL** for empty text.
+ */
+const char* bmItemGetText(const bmItem *item);
+
+/** @} Item Properties */
+
+/** @} Item */
+
+/* vim: set ts=8 sw=4 tw=0 :*/
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 :*/
diff --git a/lib/filter.c b/lib/filter.c
new file mode 100644
index 0000000..04733c5
--- /dev/null
+++ b/lib/filter.c
@@ -0,0 +1,188 @@
+#include "internal.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+/**
+ * Shrink bmItem** list pointer.
+ *
+ * Useful helper function for filter functions.
+ *
+ * @param list Pointer to pointer to list of bmItem pointers.
+ * @param osize Current size of the list.
+ * @param nsize New size the list will be shrinked to.
+ * @return Pointer to list of bmItem pointers.
+ */
+static bmItem** _bmFilterShrinkList(bmItem ***inOutList, size_t osize, size_t nsize)
+{
+ assert(inOutList);
+
+ if (nsize == 0) {
+ free(*inOutList);
+ return (*inOutList = NULL);
+ }
+
+ if (nsize >= osize)
+ return *inOutList;
+
+ void *tmp = malloc(sizeof(bmItem*) * nsize);
+ if (!tmp)
+ return *inOutList;
+
+ memcpy(tmp, *inOutList, sizeof(bmItem*) * nsize);
+ free(*inOutList);
+ return (*inOutList = tmp);
+}
+
+/**
+ * Text filter tokenizer helper.
+ *
+ * @param menu bmMenu instance which filter to tokenize.
+ * @param outTokv char pointer reference to list of tokens, this should be freed after use.
+ * @param outTokc unsigned int reference to number of tokens.
+ * @return Pointer to buffer that contains tokenized string, this should be freed after use.
+ */
+static char* _bmFilterTokenize(bmMenu *menu, char ***outTokv, unsigned int *outTokc)
+{
+ assert(menu);
+ assert(outTokv);
+ assert(outTokc);
+ *outTokv = NULL;
+ *outTokc = 0;
+
+ char **tokv = NULL, *buffer = NULL;
+ if (!(buffer = _bmStrdup(menu->filter)))
+ goto fail;
+
+ char *s;
+ for (s = buffer; *s && *s == ' '; ++s);
+
+ char **tmp = NULL;
+ size_t pos = 0, next;
+ unsigned int tokc = 0, tokn = 0;
+ for (; (pos = _bmStripToken(s, " ", &next)) > 0; tokv = tmp) {
+ if (++tokc > tokn && !(tmp = realloc(tokv, ++tokn * sizeof(char*))))
+ goto fail;
+
+ tmp[tokc - 1] = s;
+ s += next;
+ }
+
+ *outTokv = tmp;
+ *outTokc = tokc;
+ return buffer;
+
+fail:
+ if (buffer)
+ free(buffer);
+ if (tokv)
+ free(tokv);
+ return NULL;
+}
+
+/**
+ * Dmenu filterer that accepts substring function.
+ *
+ * @param menu bmMenu instance to filter.
+ * @param addition This will be 1, if filter is same as previous filter with something appended.
+ * @param fstrstr Substring function used to match items.
+ * @param outNmemb unsigned int reference to filtered items outNmemb.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** _bmFilterDmenuFun(bmMenu *menu, char addition, char* (*fstrstr)(const char *a, const char *b), int (*fstrncmp)(const char *a, const char *b, size_t len), unsigned int *outNmemb)
+{
+ assert(menu);
+ assert(fstrstr);
+ assert(fstrncmp);
+ assert(outNmemb);
+ *outNmemb = 0;
+
+ unsigned int itemsCount;
+ bmItem **items;
+
+ if (addition) {
+ items = bmMenuGetFilteredItems(menu, &itemsCount);
+ } else {
+ items = bmMenuGetItems(menu, &itemsCount);
+ }
+
+ char *buffer = NULL;
+ bmItem **filtered = calloc(itemsCount, sizeof(bmItem*));
+ if (!filtered)
+ goto fail;
+
+ char **tokv;
+ unsigned int tokc;
+ if (!(buffer = _bmFilterTokenize(menu, &tokv, &tokc)))
+ goto fail;
+
+ size_t len = (tokc ? strlen(tokv[0]) : 0);
+ unsigned int i, f, e;
+ for (e = f = i = 0; i < itemsCount; ++i) {
+ bmItem *item = items[i];
+ if (!item->text && tokc != 0)
+ continue;
+
+ if (tokc && item->text) {
+ unsigned int t;
+ for (t = 0; t < tokc && fstrstr(item->text, tokv[t]); ++t);
+ if (t < tokc)
+ continue;
+ }
+
+ if (tokc && item->text && !fstrncmp(tokv[0], item->text, len + 1)) { /* exact matches */
+ memmove(&filtered[1], filtered, f * sizeof(bmItem*));
+ filtered[0] = item;
+ e++; /* where do exact matches end */
+ } else if (tokc && item->text && !fstrncmp(tokv[0], item->text, len)) { /* prefixes */
+ memmove(&filtered[e + 1], &filtered[e], (f - e) * sizeof(bmItem*));
+ filtered[e] = item;
+ e++; /* where do exact matches end */
+ } else {
+ filtered[f] = item;
+ }
+ f++; /* where do all matches end */
+ }
+
+ if (buffer)
+ free(buffer);
+ if (tokv)
+ free(tokv);
+
+ return _bmFilterShrinkList(&filtered, menu->items.count, (*outNmemb = f));
+
+fail:
+ if (filtered)
+ free(filtered);
+ if (buffer)
+ free(buffer);
+ return NULL;
+}
+
+/**
+ * Filter that mimics the vanilla dmenu filtering.
+ *
+ * @param menu bmMenu instance to filter.
+ * @param addition This will be 1, if filter is same as previous filter with something appended.
+ * @param outNmemb unsigned int reference to filtered items outNmemb.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** _bmFilterDmenu(bmMenu *menu, char addition, unsigned int *outNmemb)
+{
+ return _bmFilterDmenuFun(menu, addition, strstr, strncmp, outNmemb);
+}
+
+/**
+ * Filter that mimics the vanilla case-insensitive dmenu filtering.
+ *
+ * @param menu bmMenu instance to filter.
+ * @param addition This will be 1, if filter is same as previous filter with something appended.
+ * @param outNmemb unsigned int reference to filtered items outNmemb.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, char addition, unsigned int *outNmemb)
+{
+ return _bmFilterDmenuFun(menu, addition, _bmStrupstr, _bmStrnupcmp, outNmemb);
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/internal.h b/lib/internal.h
new file mode 100644
index 0000000..2fc9393
--- /dev/null
+++ b/lib/internal.h
@@ -0,0 +1,188 @@
+#include "bemenu.h"
+
+#ifndef size_t
+# include <stddef.h> /* for size_t */
+#endif
+
+/**
+ * Internal bmItem struct that is not exposed to public.
+ * Represents a single item in menu.
+ */
+struct _bmItem {
+ /**
+ * Userdata pointer.
+ * This pointer will be passed around with the item untouched.
+ */
+ void *userdata;
+
+ /**
+ * Primary text shown on item as null terminated C "string".
+ * Matching will be done against this text as well.
+ */
+ char *text;
+};
+
+/**
+ * Internal bmRenderApi struct.
+ * Renderers should be able to fill this one as they see fit.
+ */
+struct _bmRenderApi {
+ /**
+ * Get count of displayed items by the underlying renderer.
+ */
+ unsigned int (*displayedCount)(const bmMenu *menu);
+
+ /**
+ * If the underlying renderer is a UI toolkit. (curses, etc...)
+ * There might be possibility to get user input, and this should be thus implemented.
+ */
+ bmKey (*getKey)(unsigned int *unicode);
+
+ /**
+ * Tells underlying renderer to draw the menu.
+ */
+ void (*render)(const bmMenu *menu);
+
+ /**
+ * Release underlying renderer.
+ */
+ void (*free)(void);
+};
+
+struct _bmItemList {
+ /**
+ * Items in the list.
+ */
+ struct _bmItem **list;
+
+ /**
+ * Number of items.
+ */
+ unsigned int count;
+
+ /**
+ * Number of allocated items.
+ */
+ unsigned int allocated;
+};
+
+/**
+ * Internal bmMenu struct that is not exposed to public.
+ */
+struct _bmMenu {
+ /**
+ * Userdata pointer.
+ * This pointer will be passed around with the menu untouched.
+ */
+ void *userdata;
+
+ /**
+ * Underlying renderer access.
+ */
+ struct _bmRenderApi renderApi;
+
+ /**
+ * Items contained in menu instance.
+ */
+ struct _bmItemList items;
+
+ /**
+ * Filtered/displayed items contained in menu instance.
+ */
+ struct _bmItemList filtered;
+
+ /**
+ * Selected items.
+ */
+ struct _bmItemList selection;
+
+ /**
+ * Menu instance title.
+ */
+ char *title;
+
+ /**
+ * Text used to filter matches.
+ */
+ char *filter;
+
+ /**
+ * Used as optimization.
+ */
+ char *oldFilter;
+
+ /**
+ * Size of filter buffer
+ */
+ size_t filterSize;
+
+ /**
+ * Current byte offset on filter text.
+ */
+ unsigned int cursor;
+
+ /**
+ * Current column/cursor position on filter text.
+ */
+ unsigned int cursesCursor;
+
+ /**
+ * Current filtered/highlighted item index in menu instance.
+ * This index is valid for the list returned by bmMenuGetFilteredItems.
+ */
+ unsigned int index;
+
+ /**
+ * Current filtering method in menu instance.
+ */
+ bmFilterMode filterMode;
+
+ /**
+ * Drawing mode used in menu instance.
+ */
+ bmDrawMode drawMode;
+
+ /**
+ * Should selection be wrapped?
+ */
+ char wrap;
+};
+
+/* draw/curses.c */
+int _bmDrawCursesInit(struct _bmRenderApi *api);
+
+/* menu.c */
+int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item);
+
+/* filter.c */
+bmItem** _bmFilterDmenu(bmMenu *menu, char addition, unsigned int *outNmemb);
+bmItem** _bmFilterDmenuCaseInsensitive(bmMenu *menu, char addition, unsigned int *outNmemb);
+
+/* list.c */
+void _bmItemListFreeList(struct _bmItemList *list);
+void _bmItemListFreeItems(struct _bmItemList *list);
+bmItem** _bmItemListGetItems(const struct _bmItemList *list, unsigned int *outNmemb);
+int _bmItemListSetItemsNoCopy(struct _bmItemList *list, bmItem **items, unsigned int nmemb);
+int _bmItemListSetItems(struct _bmItemList *list, const bmItem **items, unsigned int nmemb);
+int _bmItemListGrow(struct _bmItemList *list, unsigned int step);
+int _bmItemListAddItemAt(struct _bmItemList *list, bmItem *item, unsigned int index);
+int _bmItemListAddItem(struct _bmItemList *list, bmItem *item);
+int _bmItemListRemoveItemAt(struct _bmItemList *list, unsigned int index);
+int _bmItemListRemoveItem(struct _bmItemList *list, const bmItem *item);
+
+/* util.c */
+char* _bmStrdup(const char *s);
+size_t _bmStripToken(char *string, const char *token, size_t *outNext);
+int _bmStrupcmp(const char *hay, const char *needle);
+int _bmStrnupcmp(const char *hay, const char *needle, size_t len);
+char* _bmStrupstr(const char *hay, const char *needle);
+bmItem** _bmShrinkItemList(bmItem ***inOutList, size_t osize, size_t nsize);
+int _bmUtf8StringScreenWidth(const char *string);
+size_t _bmUtf8RuneNext(const char *string, size_t start);
+size_t _bmUtf8RunePrev(const char *string, size_t start);
+size_t _bmUtf8RuneWidth(const char *rune, unsigned int u8len);
+size_t _bmUtf8RuneRemove(char *string, size_t start, size_t *outRuneWidth);
+size_t _bmUtf8RuneInsert(char **string, size_t *bufSize, size_t start, const char *rune, unsigned int u8len, size_t *outRuneWidth);
+size_t _bmUnicodeInsert(char **string, size_t *bufSize, size_t start, unsigned int unicode, size_t *outRuneWidth);
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/item.c b/lib/item.c
new file mode 100644
index 0000000..f2a879a
--- /dev/null
+++ b/lib/item.c
@@ -0,0 +1,96 @@
+#include "internal.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+/**
+ * Allocate a new item.
+ *
+ * @param text Pointer to null terminated C "string", can be **NULL** for empty text.
+ * @return bmItem for new item instance, **NULL** if creation failed.
+ */
+bmItem* bmItemNew(const char *text)
+{
+ bmItem *item = calloc(1, sizeof(bmItem));
+
+ if (!item)
+ return NULL;
+
+ bmItemSetText(item, text);
+ return item;
+}
+
+/**
+ * Release bmItem instance.
+ *
+ * @param item bmItem instance to be freed from memory.
+ */
+void bmItemFree(bmItem *item)
+{
+ assert(item);
+
+ if (item->text)
+ free(item->text);
+
+ free(item);
+}
+
+/**
+ * Set userdata pointer to bmItem instance.
+ * Userdata will be carried unmodified by the instance.
+ *
+ * @param item bmItem instance where to set userdata pointer.
+ * @param userdata Pointer to userdata.
+ */
+void bmItemSetUserdata(bmItem *item, void *userdata)
+{
+ assert(item);
+ item->userdata = userdata;
+}
+
+/**
+ * Get userdata pointer from bmItem instance.
+ *
+ * @param item bmItem instance which userdata pointer to get.
+ * @return Pointer for unmodified userdata.
+ */
+void* bmItemGetUserdata(bmItem *item)
+{
+ assert(item);
+ return item->userdata;
+}
+
+/**
+ * Set text to bmItem instance.
+ *
+ * @param item bmItem instance where to set text.
+ * @param text C "string" to set as text, can be **NULL** for empty text.
+ */
+int bmItemSetText(bmItem *item, const char *text)
+{
+ assert(item);
+
+ char *copy = NULL;
+ if (text && !(copy = _bmStrdup(text)))
+ return 0;
+
+ if (item->text)
+ free(item->text);
+
+ item->text = copy;
+ return 1;
+}
+
+/**
+ * Get text from bmItem instance.
+ *
+ * @param item bmItem instance where to get text from.
+ * @return Pointer to null terminated C "string", can be **NULL** for empty text.
+ */
+const char* bmItemGetText(const bmItem *item)
+{
+ assert(item);
+ return item->text;
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/library.c b/lib/library.c
new file mode 100644
index 0000000..f432eda
--- /dev/null
+++ b/lib/library.c
@@ -0,0 +1,15 @@
+#include "version.h"
+
+/**
+ * Get version of the library in 'major.minor.patch' format.
+ *
+ * @see @link http://semver.org/ Semantic Versioning @endlink
+ *
+ * @return Null terminated C "string" to version string.
+ */
+const char *bmVersion(void)
+{
+ return BM_VERSION;
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/list.c b/lib/list.c
new file mode 100644
index 0000000..203adf2
--- /dev/null
+++ b/lib/list.c
@@ -0,0 +1,139 @@
+#include "internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+void _bmItemListFreeList(struct _bmItemList *list)
+{
+ assert(list);
+
+ if (list->list)
+ free(list->list);
+
+ list->allocated = list->count = 0;
+ list->list = NULL;
+}
+
+void _bmItemListFreeItems(struct _bmItemList *list)
+{
+ assert(list);
+
+ unsigned int i;
+ for (i = 0; i < list->count; ++i)
+ bmItemFree(list->list[i]);
+
+ _bmItemListFreeList(list);
+}
+
+bmItem** _bmItemListGetItems(const struct _bmItemList *list, unsigned int *outNmemb)
+{
+ assert(list);
+
+ if (outNmemb)
+ *outNmemb = list->count;
+
+ return list->list;
+}
+
+/** !!! Frees the old list, not items !!! */
+int _bmItemListSetItemsNoCopy(struct _bmItemList *list, bmItem **items, unsigned int nmemb)
+{
+ assert(list);
+
+ _bmItemListFreeList(list);
+
+ if (!items || nmemb == 0) {
+ items = NULL;
+ nmemb = 0;
+ }
+
+ list->list = items;
+ list->allocated = list->count = nmemb;
+ return 1;
+}
+
+/** !!! Frees the old items and list !!! */
+int _bmItemListSetItems(struct _bmItemList *list, const bmItem **items, unsigned int nmemb)
+{
+ assert(list);
+
+ if (!items || nmemb == 0) {
+ _bmItemListFreeItems(list);
+ return 1;
+ }
+
+ bmItem **newItems;
+ if (!(newItems = calloc(sizeof(bmItem*), nmemb)))
+ return 0;
+
+ memcpy(newItems, items, sizeof(bmItem*) * nmemb);
+ return _bmItemListSetItemsNoCopy(list, newItems, nmemb);
+}
+
+int _bmItemListGrow(struct _bmItemList *list, unsigned int step)
+{
+ assert(list);
+
+ void *tmp;
+ unsigned int nsize = sizeof(bmItem*) * (list->allocated + step);
+
+ if (!list->list || !(tmp = realloc(list->list, nsize))) {
+ if (!(tmp = malloc(nsize)))
+ return 0;
+
+ if (list->list) {
+ memcpy(tmp, list->list, sizeof(bmItem*) * list->allocated);
+ free(list->list);
+ }
+ }
+
+ list->list = tmp;
+ list->allocated += step;
+ memset(&list->list[list->count], 0, sizeof(bmItem*) * (list->allocated - list->count));
+ return 1;
+}
+
+int _bmItemListAddItemAt(struct _bmItemList *list, bmItem *item, unsigned int index)
+{
+ assert(list);
+ assert(item);
+
+ if ((!list->list || list->allocated <= list->count) && !_bmItemListGrow(list, 32))
+ return 0;
+
+ if (index + 1 != list->count) {
+ unsigned int i = index;
+ memmove(&list->list[i + 1], &list->list[i], sizeof(bmItem*) * (list->count - i));
+ }
+
+ list->list[index] = item;
+ list->count++;
+ return 1;
+}
+
+int _bmItemListAddItem(struct _bmItemList *list, bmItem *item)
+{
+ assert(list);
+ return _bmItemListAddItemAt(list, item, list->count);
+}
+
+int _bmItemListRemoveItemAt(struct _bmItemList *list, unsigned int index)
+{
+ assert(list);
+
+ unsigned int i = index;
+ if (!list->list || list->count <= i)
+ return 0;
+
+ memmove(&list->list[i], &list->list[i], sizeof(bmItem*) * (list->count - i));
+ return 1;
+}
+
+int _bmItemListRemoveItem(struct _bmItemList *list, const bmItem *item)
+{
+ unsigned int i;
+ for (i = 0; i < list->count && list->list[i] != item; ++i);
+ return _bmItemListRemoveItemAt(list, i);
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/menu.c b/lib/menu.c
new file mode 100644
index 0000000..b7ab021
--- /dev/null
+++ b/lib/menu.c
@@ -0,0 +1,727 @@
+#include "internal.h"
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+/**
+ * Filter function map.
+ */
+static bmItem** (*filterFunc[BM_FILTER_MODE_LAST])(bmMenu *menu, char addition, unsigned int *outNmemb) = {
+ _bmFilterDmenu, /* BM_FILTER_DMENU */
+ _bmFilterDmenuCaseInsensitive /* BM_FILTER_DMENU_CASE_INSENSITIVE */
+};
+
+int _bmMenuItemIsSelected(const bmMenu *menu, const bmItem *item)
+{
+ assert(menu);
+ assert(item);
+
+ unsigned int i, count;
+ bmItem **items = bmMenuGetSelectedItems(menu, &count);
+ for (i = 0; i < count && items[i] != item; ++i);
+ return (i < count);
+}
+
+/**
+ * Create new bmMenu instance.
+ *
+ * @param drawMode Render method to be used for this menu instance.
+ * @return bmMenu for new menu instance, **NULL** if creation failed.
+ */
+bmMenu* bmMenuNew(bmDrawMode drawMode)
+{
+ bmMenu *menu = calloc(1, sizeof(bmMenu));
+
+ menu->drawMode = drawMode;
+
+ if (!menu)
+ return NULL;
+
+ int status = 1;
+
+ switch (menu->drawMode) {
+ case BM_DRAW_MODE_CURSES:
+ status = _bmDrawCursesInit(&menu->renderApi);
+ break;
+
+ default: break;
+ }
+
+ if (status == 0) {
+ bmMenuFree(menu);
+ return NULL;
+ }
+
+ return menu;
+}
+
+/**
+ * Release bmMenu instance.
+ *
+ * @param menu bmMenu instance to be freed from memory.
+ */
+void bmMenuFree(bmMenu *menu)
+{
+ assert(menu);
+
+ if (menu->renderApi.free)
+ menu->renderApi.free();
+
+ if (menu->title)
+ free(menu->title);
+
+ if (menu->filter)
+ free(menu->filter);
+
+ if (menu->oldFilter)
+ free(menu->oldFilter);
+
+ bmMenuFreeItems(menu);
+ free(menu);
+}
+
+/**
+ * Release items inside bmMenu instance.
+ *
+ * @param menu bmMenu instance which items will be freed from memory.
+ */
+void bmMenuFreeItems(bmMenu *menu)
+{
+ assert(menu);
+ _bmItemListFreeList(&menu->selection);
+ _bmItemListFreeList(&menu->filtered);
+ _bmItemListFreeItems(&menu->items);
+}
+
+/**
+ * Set userdata pointer to bmMenu instance.
+ * Userdata will be carried unmodified by the instance.
+ *
+ * @param menu bmMenu instance where to set userdata pointer.
+ * @param userdata Pointer to userdata.
+ */
+void bmMenuSetUserdata(bmMenu *menu, void *userdata)
+{
+ assert(menu);
+ menu->userdata = userdata;
+}
+
+/**
+ * Get userdata pointer from bmMenu instance.
+ *
+ * @param menu bmMenu instance which userdata pointer to get.
+ * @return Pointer for unmodified userdata.
+ */
+void* bmMenuGetUserdata(bmMenu *menu)
+{
+ assert(menu);
+ return menu->userdata;
+}
+
+/**
+ * Set filter text to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set filter.
+ * @param filter Null terminated C "string" to act as filter.
+ */
+void bmMenuSetFilter(bmMenu *menu, const char *filter)
+{
+ assert(menu);
+
+ if (menu->filter)
+ free(menu->filter);
+
+ menu->filter = (filter ? _bmStrdup(filter) : NULL);
+ menu->filterSize = (filter ? strlen(filter) : 0);
+}
+
+/**
+ * Get filter text from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get filter.
+ * @return Const pointer to current filter text, may be **NULL** if empty.
+ */
+const char* bmMenuGetFilter(bmMenu *menu)
+{
+ assert(menu);
+ return menu->filter;
+}
+
+/**
+ * Set active filter mode to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set filter mode.
+ * @param mode bmFilterMode constant.
+ */
+void bmMenuSetFilterMode(bmMenu *menu, bmFilterMode mode)
+{
+ assert(menu);
+ menu->filterMode = (mode >= BM_FILTER_MODE_LAST ? BM_FILTER_MODE_DMENU : mode);
+}
+
+/**
+ * Get active filter mode from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get filter mode.
+ * @return bmFilterMode constant.
+ */
+bmFilterMode bmMenuGetFilterMode(const bmMenu *menu)
+{
+ assert(menu);
+ return menu->filterMode;
+}
+
+/**
+ * Set selection wrapping on/off.
+ *
+ * @param menu bmMenu instance where to toggle selection wrapping.
+ * @param int 1 == on, 0 == off.
+ */
+void bmMenuSetWrap(bmMenu *menu, int wrap)
+{
+ assert(menu);
+ menu->wrap = (wrap ? 1 : 0);
+}
+
+/**
+ * Get selection wrapping state.
+ *
+ * @param menu bmMenu instance where to get selection wrapping state.
+ * @return int for wrap state.
+ */
+int bmMenuGetWrap(const bmMenu *menu)
+{
+ assert(menu);
+ return menu->wrap;
+}
+
+/**
+ * Set title to bmMenu instance.
+ *
+ * @param menu bmMenu instance where to set title.
+ * @param title C "string" to set as title, can be **NULL** for empty title.
+ */
+int bmMenuSetTitle(bmMenu *menu, const char *title)
+{
+ assert(menu);
+
+ char *copy = NULL;
+ if (title && !(copy = _bmStrdup(title)))
+ return 0;
+
+ if (menu->title)
+ free(menu->title);
+
+ menu->title = copy;
+ return 1;
+}
+
+/**
+ * Get title from bmMenu instance.
+ *
+ * @param menu bmMenu instance where to get title from.
+ * @return Pointer to null terminated C "string", can be **NULL** for empty title.
+ */
+const char* bmMenuGetTitle(const bmMenu *menu)
+{
+ assert(menu);
+ return menu->title;
+}
+
+/**
+ * Add item to bmMenu instance at specific index.
+ *
+ * @param menu bmMenu instance where item will be added.
+ * @param item bmItem instance to add.
+ * @param index Index where item will be added.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuAddItemAt(bmMenu *menu, bmItem *item, unsigned int index)
+{
+ assert(menu);
+ return _bmItemListAddItemAt(&menu->items, item, index);
+}
+
+/**
+ * Add item to bmMenu instance.
+ *
+ * @param menu bmMenu instance where item will be added.
+ * @param item bmItem instance to add.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuAddItem(bmMenu *menu, bmItem *item)
+{
+ return _bmItemListAddItem(&menu->items, item);
+}
+
+/**
+ * Remove item from bmMenu instance at specific index.
+ *
+ * @warning The item won't be freed, use bmItemFree to do that.
+ *
+ * @param menu bmMenu instance from where item will be removed.
+ * @param index Index of item to remove.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuRemoveItemAt(bmMenu *menu, unsigned int index)
+{
+ assert(menu);
+
+ if (!menu->items.list || menu->items.count <= index)
+ return 0;
+
+ bmItem *item = menu->items.list[index];
+ int ret = _bmItemListRemoveItemAt(&menu->items, index);
+
+ if (ret) {
+ _bmItemListRemoveItem(&menu->selection, item);
+ _bmItemListRemoveItem(&menu->filtered, item);
+ }
+
+ return ret;
+}
+
+/**
+ * Remove item from bmMenu instance.
+ *
+ * @warning The item won't be freed, use bmItemFree to do that.
+ *
+ * @param menu bmMenu instance from where item will be removed.
+ * @param item bmItem instance to remove.
+ * @return 1 on successful add, 0 on failure.
+ */
+int bmMenuRemoveItem(bmMenu *menu, bmItem *item)
+{
+ assert(menu);
+
+ int ret = _bmItemListRemoveItem(&menu->items, item);
+
+ if (ret) {
+ _bmItemListRemoveItem(&menu->selection, item);
+ _bmItemListRemoveItem(&menu->filtered, item);
+ }
+
+ return ret;
+}
+
+/**
+ * Highlight item in menu by index.
+ *
+ * @param menu bmMenu instance from where to highlight item.
+ * @param index Index of item to highlight.
+ * @return 1 on successful highlight, 0 on failure.
+ */
+int bmMenuSetHighlightedIndex(bmMenu *menu, unsigned int index)
+{
+ assert(menu);
+
+ unsigned int itemsCount;
+ bmMenuGetFilteredItems(menu, &itemsCount);
+
+ if (itemsCount <= index)
+ return 0;
+
+ return (menu->index = index);
+}
+
+/**
+ * Highlight item in menu.
+ *
+ * @param menu bmMenu instance from where to highlight item.
+ * @param item bmItem instance to highlight.
+ * @return 1 on successful highlight, 0 on failure.
+ */
+int bmMenuSetHighlighted(bmMenu *menu, bmItem *item)
+{
+ assert(menu);
+
+ unsigned int i, itemsCount;
+ bmItem **items = bmMenuGetFilteredItems(menu, &itemsCount);
+ for (i = 0; i < itemsCount && items[i] != item; ++i);
+
+ if (itemsCount <= i)
+ return 0;
+
+ return (menu->index = i);
+}
+
+/**
+ * Get highlighted item from bmMenu instance.
+ *
+ * @warning The pointer returned by this function may be invalid after items change.
+ *
+ * @param menu bmMenu instance from where to get highlighted item.
+ * @return Selected bmItem instance, **NULL** if none highlighted.
+ */
+bmItem* bmMenuGetHighlightedItem(const bmMenu *menu)
+{
+ assert(menu);
+
+ unsigned int count;
+ bmItem **items = bmMenuGetFilteredItems(menu, &count);
+
+ if (!items || count <= menu->index)
+ return NULL;
+
+ return items[menu->index];
+}
+
+/**
+ * Set selected items to bmMenu instance.
+ *
+ * @param menu bmMenu instance where items will be set.
+ * @param items Array of bmItem pointers to set.
+ * @param nmemb Total count of items in array.
+ * @return 1 on successful set, 0 on failure.
+ */
+int bmMenuSetSelectedItems(bmMenu *menu, bmItem **items, unsigned int nmemb)
+{
+ assert(menu);
+
+ bmItem **newItems;
+ if (!(newItems = calloc(sizeof(bmItem*), nmemb)))
+ return 0;
+
+ memcpy(newItems, items, sizeof(bmItem*) * nmemb);
+ return _bmItemListSetItemsNoCopy(&menu->selection, newItems, nmemb);
+}
+
+/**
+ * Get selected items from bmMenu instance.
+ *
+ * @warning The pointer returned by this function may be invalid after selection or items change.
+ *
+ * @param menu bmMenu instance from where to get selected items.
+ * @param outNmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetSelectedItems(const bmMenu *menu, unsigned int *outNmemb)
+{
+ assert(menu);
+ return _bmItemListGetItems(&menu->selection, outNmemb);
+}
+
+/**
+ * Set items to bmMenu instance.
+ * Will replace all the old items on bmMenu instance.
+ *
+ * If items is **NULL**, or nmemb is zero, all items will be freed from the menu.
+ *
+ * @param menu bmMenu instance where items will be set.
+ * @param items Array of bmItem pointers to set.
+ * @param nmemb Total count of items in array.
+ * @return 1 on successful set, 0 on failure.
+ */
+int bmMenuSetItems(bmMenu *menu, const bmItem **items, unsigned int nmemb)
+{
+ assert(menu);
+
+ int ret = _bmItemListSetItems(&menu->items, items, nmemb);
+
+ if (ret) {
+ _bmItemListFreeList(&menu->selection);
+ _bmItemListFreeList(&menu->filtered);
+ }
+
+ return ret;
+}
+
+/**
+ * Get items from bmMenu instance.
+ *
+ * @warning The pointer returned by this function may be invalid after removing or adding new items.
+ *
+ * @param menu bmMenu instance from where to get items.
+ * @param outNmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetItems(const bmMenu *menu, unsigned int *outNmemb)
+{
+ assert(menu);
+ return _bmItemListGetItems(&menu->items, outNmemb);
+}
+
+/**
+ * Get filtered (displayed) items from bmMenu instance.
+ *
+ * @warning The pointer returned by this function _will_ be invalid when menu internally filters its list again.
+ * Do not store this pointer.
+ *
+ * @param menu bmMenu instance from where to get filtered items.
+ * @param outNmemb Reference to unsigned int where total count of returned items will be stored.
+ * @return Pointer to array of bmItem pointers.
+ */
+bmItem** bmMenuGetFilteredItems(const bmMenu *menu, unsigned int *outNmemb)
+{
+ assert(menu);
+
+ if (menu->filter && strlen(menu->filter))
+ return _bmItemListGetItems(&menu->filtered, outNmemb);
+
+ return _bmItemListGetItems(&menu->items, outNmemb);
+}
+
+/**
+ * Render bmMenu instance using chosen draw method.
+ *
+ * @param menu bmMenu instance to be rendered.
+ */
+void bmMenuRender(const bmMenu *menu)
+{
+ assert(menu);
+
+ if (menu->renderApi.render)
+ menu->renderApi.render(menu);
+}
+
+/**
+ * Trigger filtering of menu manually.
+ * This is useful when adding new items and want to dynamically see them filtered.
+ *
+ * Do note that filtering might be heavy, so you should only call it after batch manipulation of items.
+ * Not after manipulation of each single item.
+ *
+ * @param menu bmMenu instance which to filter.
+ */
+void bmMenuFilter(bmMenu *menu)
+{
+ assert(menu);
+
+ char addition = 0;
+ size_t len = (menu->filter ? strlen(menu->filter) : 0);
+
+ if (!len || !menu->items.list || menu->items.count <= 0) {
+ _bmItemListFreeList(&menu->filtered);
+
+ if (menu->oldFilter)
+ free(menu->oldFilter);
+
+ menu->oldFilter = NULL;
+ return;
+ }
+
+ if (menu->oldFilter) {
+ size_t oldLen = strlen(menu->oldFilter);
+ addition = (oldLen < len && !memcmp(menu->oldFilter, menu->filter, oldLen));
+ }
+
+ if (menu->oldFilter && addition && menu->filtered.count <= 0)
+ return;
+
+ if (menu->oldFilter && !strcmp(menu->filter, menu->oldFilter))
+ return;
+
+ unsigned int count;
+ bmItem **filtered = filterFunc[menu->filterMode](menu, addition, &count);
+
+ _bmItemListSetItemsNoCopy(&menu->filtered, filtered, count);
+ menu->index = 0;
+
+ if (menu->oldFilter)
+ free(menu->oldFilter);
+
+ menu->oldFilter = _bmStrdup(menu->filter);
+}
+
+/**
+ * Poll key and unicode from underlying UI toolkit.
+ *
+ * This function will block on @link ::bmDrawMode BM_DRAW_MODE_CURSES @endlink draw mode.
+ *
+ * @param menu bmMenu instance from which to poll.
+ * @param outUnicode Reference to unsigned int.
+ * @return bmKey for polled key.
+ */
+bmKey bmMenuGetKey(bmMenu *menu, unsigned int *outUnicode)
+{
+ assert(menu);
+ assert(outUnicode);
+
+ *outUnicode = 0;
+ bmKey key = BM_KEY_NONE;
+
+ if (menu->renderApi.getKey)
+ key = menu->renderApi.getKey(outUnicode);
+
+ return key;
+}
+
+/**
+ * Advances menu logic with key and unicode as input.
+ *
+ * @param menu bmMenu instance to be advanced.
+ * @param key Key input that will advance menu logic.
+ * @param unicode Unicode input that will advance menu logic.
+ * @return bmRunResult for menu state.
+ */
+bmRunResult bmMenuRunWithKey(bmMenu *menu, bmKey key, unsigned int unicode)
+{
+ assert(menu);
+
+ unsigned int itemsCount;
+ bmMenuGetFilteredItems(menu, &itemsCount);
+
+ unsigned int displayed = 0;
+ if (menu->renderApi.displayedCount)
+ displayed = menu->renderApi.displayedCount(menu);
+
+ if (!displayed)
+ displayed = itemsCount;
+
+ switch (key) {
+ case BM_KEY_LEFT:
+ if (menu->filter) {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor -= _bmUtf8RunePrev(menu->filter, menu->cursor);
+ menu->cursesCursor -= _bmUtf8RuneWidth(menu->filter + menu->cursor, oldCursor - menu->cursor);
+ }
+ break;
+
+ case BM_KEY_RIGHT:
+ if (menu->filter) {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor += _bmUtf8RuneNext(menu->filter, menu->cursor);
+ menu->cursesCursor += _bmUtf8RuneWidth(menu->filter + oldCursor, menu->cursor - oldCursor);
+ }
+ break;
+
+ case BM_KEY_HOME:
+ menu->cursesCursor = menu->cursor = 0;
+ break;
+
+ case BM_KEY_END:
+ menu->cursor = (menu->filter ? strlen(menu->filter) : 0);
+ menu->cursesCursor = (menu->filter ? _bmUtf8StringScreenWidth(menu->filter) : 0);
+ break;
+
+ case BM_KEY_UP:
+ if (menu->index > 0) {
+ menu->index--;
+ } else if (menu->wrap) {
+ menu->index = itemsCount - 1;
+ }
+ break;
+
+ case BM_KEY_DOWN:
+ if (menu->index < itemsCount - 1) {
+ menu->index++;
+ } else if (menu->wrap) {
+ menu->index = 0;
+ }
+ break;
+
+ case BM_KEY_PAGE_UP:
+ menu->index = (menu->index < displayed ? 0 : menu->index - (displayed - 1));
+ break;
+
+ case BM_KEY_PAGE_DOWN:
+ menu->index = (menu->index + displayed >= itemsCount ? itemsCount - 1 : menu->index + (displayed - 1));
+ break;
+
+ case BM_KEY_SHIFT_PAGE_UP:
+ menu->index = 0;
+ break;
+
+ case BM_KEY_SHIFT_PAGE_DOWN:
+ menu->index = itemsCount - 1;
+ break;
+
+ case BM_KEY_BACKSPACE:
+ if (menu->filter) {
+ size_t width;
+ menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width);
+ menu->cursesCursor -= width;
+ }
+ break;
+
+ case BM_KEY_DELETE:
+ if (menu->filter)
+ _bmUtf8RuneRemove(menu->filter, menu->cursor + 1, NULL);
+ break;
+
+ case BM_KEY_LINE_DELETE_LEFT:
+ if (menu->filter) {
+ while (menu->cursor > 0) {
+ size_t width;
+ menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width);
+ menu->cursesCursor -= width;
+ }
+ }
+ break;
+
+ case BM_KEY_LINE_DELETE_RIGHT:
+ if (menu->filter)
+ menu->filter[menu->cursor] = 0;
+ break;
+
+ case BM_KEY_WORD_DELETE:
+ if (menu->filter) {
+ while (menu->cursor < strlen(menu->filter) && !isspace(menu->filter[menu->cursor])) {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor += _bmUtf8RuneNext(menu->filter, menu->cursor);
+ menu->cursesCursor += _bmUtf8RuneWidth(menu->filter + oldCursor, menu->cursor - oldCursor);
+ }
+ while (menu->cursor > 0 && isspace(menu->filter[menu->cursor - 1])) {
+ unsigned int oldCursor = menu->cursor;
+ menu->cursor -= _bmUtf8RunePrev(menu->filter, menu->cursor);
+ menu->cursesCursor -= _bmUtf8RuneWidth(menu->filter + menu->cursor, oldCursor - menu->cursor);
+ }
+ while (menu->cursor > 0 && !isspace(menu->filter[menu->cursor - 1])) {
+ size_t width;
+ menu->cursor -= _bmUtf8RuneRemove(menu->filter, menu->cursor, &width);
+ menu->cursesCursor -= width;
+ }
+ }
+ break;
+
+ case BM_KEY_UNICODE:
+ {
+ size_t width;
+ menu->cursor += _bmUnicodeInsert(&menu->filter, &menu->filterSize, menu->cursor, unicode, &width);
+ menu->cursesCursor += width;
+ }
+ break;
+
+ case BM_KEY_TAB:
+ {
+ const char *text;
+ bmItem *highlighted = bmMenuGetHighlightedItem(menu);
+ if (highlighted && (text = bmItemGetText(highlighted))) {
+ bmMenuSetFilter(menu, text);
+ menu->cursor = (menu->filter ? strlen(menu->filter) : 0);
+ menu->cursesCursor = (menu->filter ? _bmUtf8StringScreenWidth(menu->filter) : 0);
+ }
+ }
+ break;
+
+ case BM_KEY_CONTROL_RETURN:
+ case BM_KEY_RETURN:
+ {
+ bmItem *highlighted = bmMenuGetHighlightedItem(menu);
+ if (highlighted && !_bmMenuItemIsSelected(menu, highlighted))
+ _bmItemListAddItem(&menu->selection, highlighted);
+ }
+ break;
+
+ case BM_KEY_SHIFT_RETURN:
+ case BM_KEY_ESCAPE:
+ _bmItemListFreeList(&menu->selection);
+ break;
+
+ default: break;
+ }
+
+ bmMenuFilter(menu);
+
+ switch (key) {
+ case BM_KEY_SHIFT_RETURN:
+ case BM_KEY_RETURN: return BM_RUN_RESULT_SELECTED;
+ case BM_KEY_ESCAPE: return BM_RUN_RESULT_CANCEL;
+ default: break;
+ }
+
+ return BM_RUN_RESULT_RUNNING;
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/util.c b/lib/util.c
new file mode 100644
index 0000000..a47ff8d
--- /dev/null
+++ b/lib/util.c
@@ -0,0 +1,314 @@
+#include "internal.h"
+
+#define _XOPEN_SOURCE
+#include <wchar.h> /* wcswidth */
+#undef _XOPEN_SOURCE
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+/**
+ * Portable strdup.
+ *
+ * @param string C "string" to copy.
+ * @return Copy of the given C "string".
+ */
+char* _bmStrdup(const char *string)
+{
+ assert(string);
+
+ size_t len = strlen(string);
+ if (len == 0)
+ return NULL;
+
+ void *copy = calloc(1, len + 1);
+ if (copy == NULL)
+ return NULL;
+
+ return (char *)memcpy(copy, string, len);
+}
+
+/**
+ * Replaces next token in string with '\0' and returns position for the replaced token.
+ *
+ * @param string C "string" where token will be replaced.
+ * @param outNext Reference to position of next delimiter, or 0 if none.
+ * @return Position of the replaced token.
+ */
+size_t _bmStripToken(char *string, const char *token, size_t *outNext)
+{
+ size_t len = strcspn(string, token);
+
+ if (outNext)
+ *outNext = len + (string[len] != 0);
+
+ string[len] = 0;
+ return len;
+}
+
+/**
+ * Portable case-insensitive strcmp.
+ *
+ * @param hay C "string" to match against.
+ * @param needle C "string" to match.
+ * @return Less than, equal to or greater than zero if hay is lexicographically less than, equal to or greater than needle.
+ */
+int _bmStrupcmp(const char *hay, const char *needle)
+{
+ return _bmStrnupcmp(hay, needle, strlen(hay));
+}
+
+/**
+ * Portable case-insensitive strncmp.
+ *
+ * @param hay C "string" to match against.
+ * @param needle C "string" to match.
+ * @return Less than, equal to or greater than zero if hay is lexicographically less than, equal to or greater than needle.
+ */
+int _bmStrnupcmp(const char *hay, const char *needle, size_t len)
+{
+ size_t i = 0;
+ unsigned char a = 0, b = 0;
+
+ const unsigned char *p1 = (const unsigned char*)hay;
+ const unsigned char *p2 = (const unsigned char*)needle;
+
+ for (i = 0; len > 0; --len, ++i)
+ if ((a = toupper(*p1++)) != (b = toupper(*p2++)))
+ return a - b;
+
+ return a - b;
+}
+
+/**
+ * Portable case-insensitive strstr.
+ *
+ * @param hay C "string" to substring against.
+ * @param needle C "string" to substring.
+ */
+char* _bmStrupstr(const char *hay, const char *needle)
+{
+ size_t i, r = 0, p = 0, len, len2;
+
+ if ((len = strlen(hay)) < (len2 = strlen(needle)))
+ return NULL;
+
+ if (!_bmStrnupcmp(hay, needle, len2))
+ return (char*)hay;
+
+ for (i = 0; i < len; ++i) {
+ if (p == len2)
+ return (char*)hay + r;
+
+ if (toupper(hay[i]) == toupper(needle[p++])) {
+ if (!r)
+ r = i;
+ } else {
+ if (r)
+ i = r;
+ r = p = 0;
+ }
+ }
+
+ return (p == len2 ? (char*)hay + r : NULL);
+}
+
+/**
+ * Determite columns needed to display UTF8 string.
+ *
+ * @param string C "string" to determite.
+ * @return Number of columns, or -1 on failure.
+ */
+int _bmUtf8StringScreenWidth(const char *string)
+{
+ assert(string);
+
+ char *mstr = _bmStrdup(string);
+ if (!mstr)
+ return strlen(string);
+
+ char *s;
+ for (s = mstr; *s; ++s) if (*s == '\t') *s = ' ';
+
+ int num_char = mbstowcs(NULL, mstr, 0) + 1;
+ wchar_t *wstring = malloc((num_char + 1) * sizeof (wstring[0]));
+
+ if (mbstowcs(wstring, mstr, num_char) == (size_t)(-1)) {
+ free(wstring);
+ int len = strlen(mstr);
+ free(mstr);
+ return len;
+ }
+
+ int length = wcswidth(wstring, num_char);
+ free(wstring);
+ free(mstr);
+ return length;
+}
+
+/**
+ * Figure out how many bytes to shift to next UTF8 rune.
+ *
+ * @param string C "string" which contains the runes.
+ * @param start Offset where to figure out next rune. (cursor)
+ * @return Number of bytes to next UTF8 rune.
+ */
+size_t _bmUtf8RuneNext(const char *string, size_t start)
+{
+ assert(string);
+
+ size_t len = strlen(string), i = start;
+ if (len == 0 || len <= i || !*string)
+ return 0;
+
+ while (++i < len && (string[i] & 0xc0) == 0x80);
+ return i - start;
+}
+
+/**
+ * Figure out how many bytes to shift to previous UTF8 rune.
+ *
+ * @param string C "string" which contains the runes.
+ * @param start Offset where to figure out previous rune. (cursor)
+ * @return Number of bytes to previous UTF8 rune.
+ */
+size_t _bmUtf8RunePrev(const char *string, size_t start)
+{
+ assert(string);
+
+ size_t len = strlen(string), i = start;
+ if (i == 0 || len < start || !*string)
+ return 0;
+
+ while (--i > 0 && (string[i] & 0xc0) == 0x80);
+ return start - i;
+}
+
+/**
+ * Figure out how many columns are needed to display UTF8 rune.
+ *
+ * @param rune Buffer which contains the rune.
+ * @param u8len Byte length of the rune.
+ * @return Number of columns, or -1 on failure.
+ */
+size_t _bmUtf8RuneWidth(const char *rune, unsigned int u8len)
+{
+ assert(rune);
+ char mb[5] = { 0, 0, 0, 0, 0 };
+ memcpy(mb, rune, (u8len > 4 ? 4 : u8len));
+ return _bmUtf8StringScreenWidth(mb);
+}
+
+/**
+ * Remove previous UTF8 rune from buffer.
+ *
+ * @param string Null terminated C "string".
+ * @param start Start offset where to delete from. (cursor)
+ * @param outRuneWidth Reference to size_t, return number of columns for removed rune, or -1 on failure.
+ * @return Number of bytes removed from buffer.
+ */
+size_t _bmUtf8RuneRemove(char *string, size_t start, size_t *outRuneWidth)
+{
+ assert(string);
+
+ if (outRuneWidth)
+ *outRuneWidth = 0;
+
+ size_t len = strlen(string), oldStart = start;
+ if (len == 0 || len < start || !*string)
+ return 0;
+
+ start -= _bmUtf8RunePrev(string, start);
+
+ if (outRuneWidth)
+ *outRuneWidth = _bmUtf8RuneWidth(string + start, oldStart - start);
+
+ memmove(string + start, string + oldStart, len - oldStart);
+ string[len - (oldStart - start)] = 0;
+ return (oldStart - start);
+}
+
+/**
+ * Insert UTF8 rune to buffer.
+ *
+ * @param inOutString Reference to buffer.
+ * @param inOutBufSize Reference to size of the buffer.
+ * @param start Start offset where to insert to. (cursor)
+ * @param rune Buffer to insert to string.
+ * @param u8len Byte length of the rune.
+ * @param outRuneWidth Reference to size_t, return number of columns for inserted rune, or -1 on failure.
+ * @return Number of bytes inserted to buffer.
+ */
+size_t _bmUtf8RuneInsert(char **inOutString, size_t *inOutBufSize, size_t start, const char *rune, unsigned int u8len, size_t *outRuneWidth)
+{
+ assert(inOutString);
+ assert(inOutBufSize);
+
+ if (outRuneWidth)
+ *outRuneWidth = 0;
+
+ if (u8len == 1 && !isprint(*rune))
+ return 0;
+
+ size_t len = (*inOutString ? strlen(*inOutString) : 0);
+ if (!*inOutString && !(*inOutString = calloc(1, (*inOutBufSize = u8len + 1))))
+ return 0;
+
+ if (len + u8len >= *inOutBufSize) {
+ void *tmp;
+ if (!(tmp = realloc(*inOutString, (*inOutBufSize * 2)))) {
+ if (!(tmp = malloc((*inOutBufSize * 2))))
+ return 0;
+
+ memcpy(tmp, *inOutString, *inOutBufSize);
+ free(*inOutString);
+ }
+
+ memset(tmp + *inOutBufSize, 0, *inOutBufSize);
+ *inOutString = tmp;
+ *inOutBufSize *= 2;
+ }
+
+ char *str = *inOutString + start;
+ memmove(str + u8len, str, len - start);
+ memcpy(str, rune, u8len);
+ (*inOutString)[len + u8len] = 0;
+
+ if (outRuneWidth)
+ *outRuneWidth = _bmUtf8RuneWidth(rune, u8len);
+ return u8len;
+}
+
+/**
+ * Insert unicode character to UTF8 buffer.
+ *
+ * @param inOutString Reference to buffer.
+ * @param inOutBufSize Reference to size of the buffer.
+ * @param start Start offset where to insert to. (cursor)
+ * @param unicode Unicode character to insert.
+ * @param outRuneWidth Reference to size_t, return number of columns for inserted rune, or -1 on failure.
+ * @return Number of bytes inserted to buffer.
+ */
+size_t _bmUnicodeInsert(char **inOutString, size_t *inOutBufSize, size_t start, unsigned int unicode, size_t *outRuneWidth)
+{
+ assert(inOutString);
+ assert(inOutBufSize);
+
+ char u8len = ((unicode < 0x80) ? 1 : ((unicode < 0x800) ? 2 : ((unicode < 0x10000) ? 3 : 4)));
+ char mb[5] = { 0, 0, 0, 0 };
+
+ if (u8len == 1) {
+ mb[0] = unicode;
+ } else {
+ size_t i, j;
+ for (i = j = u8len; j > 1; --j) mb[j - 1] = 0x80 | (0x3F & (unicode >> ((i - j) * 6)));
+ mb[0] = (~0) << (8 - i);
+ mb[0] |= (unicode >> (i * 6 - 6));
+ }
+
+ return _bmUtf8RuneInsert(inOutString, inOutBufSize, start, mb, u8len, outRuneWidth);
+}
+
+/* vim: set ts=8 sw=4 tw=0 :*/
diff --git a/lib/version.h.in b/lib/version.h.in
new file mode 100644
index 0000000..6be8b5f
--- /dev/null
+++ b/lib/version.h.in
@@ -0,0 +1,3 @@
+static const char *BM_VERSION = "@BEMENU_VERSION@";
+
+/* vim: set ts=8 sw=4 tw=0 :*/