commit 5f3067878286e39d683cb8ecb04cd0870176130b
parent 928b2b749512c2746191d67116afe26fc0617f0d
Author: klewer-martin <martin.cachari@gmail.com>
Date: Tue, 15 Mar 2022 16:39:35 -0300
update before modifing
Diffstat:
7 files changed, 832 insertions(+), 4 deletions(-)
diff --git a/config.def.h b/config.def.h
@@ -2,8 +2,9 @@
/* Default settings; can be overriden by command line. */
static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
+static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
static int centered = 0; /* -c option; centers dmenu on screen */
-static int min_width = -200; /* minimum width when centered */
+/* static int min_width = -200; /1* minimum width when centered *1/ */
/* -fn option overrides fonts[0]; default X11 font or font set */
static const char *fonts[] = {
"DejaVuSansMono Nerd Font:style=Regular:size=8"
diff --git a/config.mk b/config.mk
@@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2
# includes and libs
INCS = -I$(X11INC) -I$(FREETYPEINC)
-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)
+LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm
# flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
diff --git a/dmenu.c b/dmenu.c
@@ -8,6 +8,7 @@
#include <strings.h>
#include <time.h>
#include <unistd.h>
+#include <math.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
@@ -33,6 +34,7 @@ struct item {
char *text;
struct item *left, *right;
int out;
+ double distance;
};
static char text[BUFSIZ] = "";
@@ -84,11 +86,13 @@ calcoffsets(void)
if (lines > 0)
n = lines * bh;
else
- n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
+ n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
+
/* calculate which items will begin the next page and previous page */
for (i = 0, next = curr; next; next = next->right)
if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
break;
+
for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n)
break;
@@ -224,9 +228,94 @@ grabkeyboard(void)
die("cannot grab keyboard");
}
+int
+compare_distance(const void *a, const void *b)
+{
+ struct item *da = *(struct item **) a;
+ struct item *db = *(struct item **) b;
+
+ if (!db)
+ return 1;
+ if (!da)
+ return -1;
+
+ return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
+}
+
+void
+fuzzymatch(void)
+{
+ /* bang - we have so much memory */
+ struct item *it;
+ struct item **fuzzymatches = NULL;
+ char c;
+ int number_of_matches = 0, i, pidx, sidx, eidx;
+ int text_len = strlen(text), itext_len;
+
+ matches = matchend = NULL;
+
+ /* walk through all items */
+ for (it = items; it && it->text; it++) {
+ if (text_len) {
+ itext_len = strlen(it->text);
+ pidx = 0; /* pointer */
+ sidx = eidx = -1; /* start of match, end of match */
+ /* walk through item text */
+ for (i = 0; i < itext_len && (c = it->text[i]); i++) {
+ /* fuzzy match pattern */
+ if (!fstrncmp(&text[pidx], &c, 1)) {
+ if(sidx == -1)
+ sidx = i;
+ pidx++;
+ if (pidx == text_len) {
+ eidx = i;
+ break;
+ }
+ }
+ }
+ /* build list of matches */
+ if (eidx != -1) {
+ /* compute distance */
+ /* add penalty if match starts late (log(sidx+2))
+ * add penalty for long a match without many matching characters */
+ it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
+ /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
+ appenditem(it, &matches, &matchend);
+ number_of_matches++;
+ }
+ } else {
+ appenditem(it, &matches, &matchend);
+ }
+ }
+
+ if (number_of_matches) {
+ /* initialize array with matches */
+ if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
+ die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
+ for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
+ fuzzymatches[i] = it;
+ }
+ /* sort matches according to distance */
+ qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
+ /* rebuild list of matches */
+ matches = matchend = NULL;
+ for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \
+ it->text; i++, it = fuzzymatches[i]) {
+ appenditem(it, &matches, &matchend);
+ }
+ free(fuzzymatches);
+ }
+ curr = sel = matches;
+ calcoffsets();
+}
+
static void
match(void)
{
+ if (fuzzy) {
+ fuzzymatch();
+ return;
+ }
static char **tokv = NULL;
static int tokn = 0;
@@ -520,6 +609,156 @@ draw:
}
static void
+buttonpress(XEvent *e)
+{
+ struct item *item;
+ XButtonPressedEvent *ev = &e->xbutton;
+ int x = 0, y = 0, h = bh, w;
+
+ if (ev->window != win)
+ return;
+
+ /* right-click: exit */
+ if (ev->button == Button3)
+ exit(1);
+
+ if (prompt && *prompt)
+ x += promptw;
+
+ /* input field */
+ w = (lines > 0 || !matches) ? mw - x : inputw;
+
+ /* left-click on input: clear input,
+ * NOTE: if there is no left-arrow the space for < is reserved so
+ * add that to the input width */
+ if (ev->button == Button1 &&
+ ((lines <= 0 && ev->x >= 0 && ev->x <= x + w +
+ ((!prev || !curr->left) ? TEXTW("<") : 0)) ||
+ (lines > 0 && ev->y >= y && ev->y <= y + h))) {
+ insert(NULL, -cursor);
+ drawmenu();
+ return;
+ }
+ /* middle-mouse click: paste selection */
+ if (ev->button == Button2) {
+ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
+ utf8, utf8, win, CurrentTime);
+ drawmenu();
+ return;
+ }
+ /* scroll up */
+ if (ev->button == Button4 && prev) {
+ sel = curr = prev;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ /* scroll down */
+ if (ev->button == Button5 && next) {
+ sel = curr = next;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ if (ev->button != Button1)
+ return;
+ /* disabled below, needs to be fixed */
+ /*
+ if (ev->state & ~ControlMask)
+ return;
+ */
+ if (lines > 0) {
+ /* vertical list: (ctrl)left-click on item */
+ w = mw - x;
+ for (item = curr; item != next; item = item->right) {
+ y += h;
+ if (ev->y >= y && ev->y <= (y + h)) {
+ puts(item->text);
+ if (!(ev->state & ControlMask))
+ exit(0);
+ sel = item;
+ if (sel) {
+ sel->out = 1;
+ drawmenu();
+ }
+ return;
+ }
+ }
+ } else if (matches) {
+ /* left-click on left arrow */
+ x += inputw;
+ w = TEXTW("<");
+ if (prev && curr->left) {
+ if (ev->x >= x && ev->x <= x + w) {
+ sel = curr = prev;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ }
+ /* horizontal list: (ctrl)left-click on item */
+ for (item = curr; item != next; item = item->right) {
+ x += w;
+ w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
+ if (ev->x >= x && ev->x <= x + w) {
+ puts(item->text);
+ if (!(ev->state & ControlMask))
+ exit(0);
+ sel = item;
+ if (sel) {
+ sel->out = 1;
+ drawmenu();
+ }
+ return;
+ }
+ }
+ /* left-click on right arrow */
+ w = TEXTW(">");
+ x = mw - w;
+ if (next && ev->x >= x && ev->x <= x + w) {
+ sel = curr = next;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ }
+}
+
+static void
+mousemove(XEvent *e)
+{
+ struct item *item;
+ XPointerMovedEvent *ev = &e->xmotion;
+ int x = 0, y = 0, h = bh, w;
+
+ if (lines > 0) {
+ w = mw - x;
+ for (item = curr; item != next; item = item->right) {
+ y += h;
+ if (ev->y >= y && ev->y <= (y + h)) {
+ sel = item;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ }
+ } else if (matches) {
+ x += inputw + promptw;
+ w = TEXTW("<");
+ for (item = curr; item != next; item = item->right) {
+ x += w;
+ w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
+ if (ev->x >= x && ev->x <= x + w) {
+ sel = item;
+ calcoffsets();
+ drawmenu();
+ return;
+ }
+ }
+ }
+}
+
+static void
paste(void)
{
char *p, *q;
@@ -580,6 +819,12 @@ run(void)
break;
cleanup();
exit(1);
+ case ButtonPress:
+ buttonpress(&ev);
+ break;
+ case MotionNotify:
+ mousemove(&ev);
+ break;
case Expose:
if (ev.xexpose.count == 0)
drw_map(drw, win, 0, 0, mw, mh);
@@ -699,7 +944,8 @@ setup(void)
/* create menu window */
swa.override_redirect = True;
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
- swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
+ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask |
+ ButtonPressMask | PointerMotionMask;
win = XCreateWindow(dpy, parentwin, x, y - (topbar ? 0 : border_width * 2), mw - border_width * 2, mh, border_width,
CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
@@ -753,6 +999,8 @@ main(int argc, char *argv[])
topbar = 0;
else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
fast = 1;
+ else if (!strcmp(argv[i], "-F")) /* grabs keyboard before reading stdin */
+ fuzzy = 0;
else if (!strcmp(argv[i], "-S")) /* do not sort matches */
sortmatches = false;
else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */
diff --git a/drw.c b/drw.c
@@ -379,6 +379,175 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
return x + (render ? w : 0);
}
+int
+utf8nextchar(const char *str, int len, int i, int inc)
+{
+ int n;
+
+ for (n = i + inc; n + inc >= 0 && n + inc <= len
+ && (str[n] & 0xc0) == 0x80; n += inc)
+ ;
+ return n;
+}
+
+int
+drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align)
+{
+ int ty;
+ unsigned int ew;
+ XftDraw *d = NULL;
+ Fnt *usedfont, *curfont, *nextfont;
+ size_t len;
+ int utf8strlen, utf8charlen, render = x || y || w || h;
+ long utf8codepoint = 0;
+ const char *utf8str;
+ FcCharSet *fccharset;
+ FcPattern *fcpattern;
+ FcPattern *match;
+ XftResult result;
+ int charexists = 0;
+ int i, n;
+
+ if (!drw || (render && !drw->scheme) || !text || !drw->fonts || textlen <= 0
+ || (align != AlignL && align != AlignR))
+ return 0;
+
+ if (!render) {
+ w = ~w;
+ } else {
+ XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel);
+ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
+ d = XftDrawCreate(drw->dpy, drw->drawable,
+ DefaultVisual(drw->dpy, drw->screen),
+ DefaultColormap(drw->dpy, drw->screen));
+ }
+
+ usedfont = drw->fonts;
+ i = align == AlignL ? 0 : textlen;
+ x = align == AlignL ? x : x + w;
+ while (1) {
+ utf8strlen = 0;
+ nextfont = NULL;
+ /* if (align == AlignL) */
+ utf8str = text + i;
+
+ while ((align == AlignL && i < textlen) || (align == AlignR && i > 0)) {
+ if (align == AlignL) {
+ utf8charlen = utf8decode(text + i, &utf8codepoint, MIN(textlen - i, UTF_SIZ));
+ if (!utf8charlen) {
+ textlen = i;
+ break;
+ }
+ } else {
+ n = utf8nextchar(text, textlen, i, -1);
+ utf8charlen = utf8decode(text + n, &utf8codepoint, MIN(textlen - n, UTF_SIZ));
+ if (!utf8charlen) {
+ textlen -= i;
+ text += i;
+ i = 0;
+ break;
+ }
+ }
+ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
+ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
+ if (charexists) {
+ if (curfont == usedfont) {
+ utf8strlen += utf8charlen;
+ i += align == AlignL ? utf8charlen : -utf8charlen;
+ } else {
+ nextfont = curfont;
+ }
+ break;
+ }
+ }
+
+ if (!charexists || nextfont)
+ break;
+ else
+ charexists = 0;
+ }
+
+ if (align == AlignR)
+ utf8str = text + i;
+
+ if (utf8strlen) {
+ drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
+ /* shorten text if necessary */
+ if (align == AlignL) {
+ for (len = utf8strlen; len && ew > w; ) {
+ len = utf8nextchar(utf8str, len, len, -1);
+ drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
+ }
+ } else {
+ for (len = utf8strlen; len && ew > w; ) {
+ n = utf8nextchar(utf8str, len, 0, +1);
+ utf8str += n;
+ len -= n;
+ drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
+ }
+ }
+
+ if (len) {
+ if (render) {
+ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
+ XftDrawStringUtf8(d, &drw->scheme[ColFg],
+ usedfont->xfont, align == AlignL ? x : x - ew, ty, (XftChar8 *)utf8str, len);
+ }
+ x += align == AlignL ? ew : -ew;
+ w -= ew;
+ }
+ if (len < utf8strlen)
+ break;
+ }
+
+ if ((align == AlignR && i <= 0) || (align == AlignL && i >= textlen)) {
+ break;
+ } else if (nextfont) {
+ charexists = 0;
+ usedfont = nextfont;
+ } else {
+ /* Regardless of whether or not a fallback font is found, the
+ * character must be drawn. */
+ charexists = 1;
+
+ fccharset = FcCharSetCreate();
+ FcCharSetAddChar(fccharset, utf8codepoint);
+
+ if (!drw->fonts->pattern) {
+ /* Refer to the comment in xfont_create for more information. */
+ die("the first font in the cache must be loaded from a font string.");
+ }
+
+ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
+ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
+ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
+
+ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
+ FcDefaultSubstitute(fcpattern);
+ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
+
+ FcCharSetDestroy(fccharset);
+ FcPatternDestroy(fcpattern);
+
+ if (match) {
+ usedfont = xfont_create(drw, NULL, match);
+ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
+ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
+ ; /* NOP */
+ curfont->next = usedfont;
+ } else {
+ xfont_free(usedfont);
+ usedfont = drw->fonts;
+ }
+ }
+ }
+ }
+ if (d)
+ XftDrawDestroy(d);
+
+ return x;
+}
+
void
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
{
diff --git a/drw.h b/drw.h
@@ -13,6 +13,7 @@ typedef struct Fnt {
} Fnt;
enum { ColFg, ColBg }; /* Clr scheme index */
+enum { AlignL, AlignR };
typedef XftColor Clr;
typedef struct {
@@ -52,6 +53,7 @@ void drw_setscheme(Drw *drw, Clr *scm);
/* Drawing functions */
void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
+int drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align);
/* Map functions */
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);
diff --git a/patches/dmenu-fuzzymatch-4.9.diff b/patches/dmenu-fuzzymatch-4.9.diff
@@ -0,0 +1,163 @@
+From 94353eb52055927d9079f3d9e33da1c954abf386 Mon Sep 17 00:00:00 2001
+From: aleks <aleks.stier@icloud.com>
+Date: Wed, 26 Jun 2019 13:25:10 +0200
+Subject: [PATCH] Add support for fuzzy-matching
+
+---
+ config.def.h | 1 +
+ config.mk | 2 +-
+ dmenu.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 91 insertions(+), 1 deletion(-)
+
+diff --git a/config.def.h b/config.def.h
+index 1edb647..51612b9 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -2,6 +2,7 @@
+ /* Default settings; can be overriden by command line. */
+
+ static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
++static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
+ /* -fn option overrides fonts[0]; default X11 font or font set */
+ static const char *fonts[] = {
+ "monospace:size=10"
+diff --git a/config.mk b/config.mk
+index 0929b4a..d14309a 100644
+--- a/config.mk
++++ b/config.mk
+@@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2
+
+ # includes and libs
+ INCS = -I$(X11INC) -I$(FREETYPEINC)
+-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)
++LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm
+
+ # flags
+ CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
+diff --git a/dmenu.c b/dmenu.c
+index 6b8f51b..96ddc98 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -1,6 +1,7 @@
+ /* See LICENSE file for copyright and license details. */
+ #include <ctype.h>
+ #include <locale.h>
++#include <math.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+@@ -32,6 +33,7 @@ struct item {
+ char *text;
+ struct item *left, *right;
+ int out;
++ double distance;
+ };
+
+ static char text[BUFSIZ] = "";
+@@ -210,9 +212,94 @@ grabkeyboard(void)
+ die("cannot grab keyboard");
+ }
+
++int
++compare_distance(const void *a, const void *b)
++{
++ struct item *da = *(struct item **) a;
++ struct item *db = *(struct item **) b;
++
++ if (!db)
++ return 1;
++ if (!da)
++ return -1;
++
++ return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
++}
++
++void
++fuzzymatch(void)
++{
++ /* bang - we have so much memory */
++ struct item *it;
++ struct item **fuzzymatches = NULL;
++ char c;
++ int number_of_matches = 0, i, pidx, sidx, eidx;
++ int text_len = strlen(text), itext_len;
++
++ matches = matchend = NULL;
++
++ /* walk through all items */
++ for (it = items; it && it->text; it++) {
++ if (text_len) {
++ itext_len = strlen(it->text);
++ pidx = 0; /* pointer */
++ sidx = eidx = -1; /* start of match, end of match */
++ /* walk through item text */
++ for (i = 0; i < itext_len && (c = it->text[i]); i++) {
++ /* fuzzy match pattern */
++ if (!fstrncmp(&text[pidx], &c, 1)) {
++ if(sidx == -1)
++ sidx = i;
++ pidx++;
++ if (pidx == text_len) {
++ eidx = i;
++ break;
++ }
++ }
++ }
++ /* build list of matches */
++ if (eidx != -1) {
++ /* compute distance */
++ /* add penalty if match starts late (log(sidx+2))
++ * add penalty for long a match without many matching characters */
++ it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
++ /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
++ appenditem(it, &matches, &matchend);
++ number_of_matches++;
++ }
++ } else {
++ appenditem(it, &matches, &matchend);
++ }
++ }
++
++ if (number_of_matches) {
++ /* initialize array with matches */
++ if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
++ die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
++ for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
++ fuzzymatches[i] = it;
++ }
++ /* sort matches according to distance */
++ qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
++ /* rebuild list of matches */
++ matches = matchend = NULL;
++ for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \
++ it->text; i++, it = fuzzymatches[i]) {
++ appenditem(it, &matches, &matchend);
++ }
++ free(fuzzymatches);
++ }
++ curr = sel = matches;
++ calcoffsets();
++}
++
+ static void
+ match(void)
+ {
++ if (fuzzy) {
++ fuzzymatch();
++ return;
++ }
+ static char **tokv = NULL;
+ static int tokn = 0;
+
+@@ -702,6 +789,8 @@ main(int argc, char *argv[])
+ topbar = 0;
+ else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
+ fast = 1;
++ else if (!strcmp(argv[i], "-F")) /* grabs keyboard before reading stdin */
++ fuzzy = 0;
+ else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
+ fstrncmp = strncasecmp;
+ fstrstr = cistrstr;
+--
+2.22.0
+
diff --git a/patches/dmenu-scroll-20180607-a314412.diff b/patches/dmenu-scroll-20180607-a314412.diff
@@ -0,0 +1,245 @@
+diff --git a/dmenu.c b/dmenu.c
+index 5c835dd..71efe52 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -131,9 +131,10 @@ drawitem(struct item *item, int x, int y, int w)
+ static void
+ drawmenu(void)
+ {
+- unsigned int curpos;
++ static int curpos, oldcurlen;
+ struct item *item;
+ int x = 0, y = 0, w;
++ int curlen, rcurlen;
+
+ drw_setscheme(drw, scheme[SchemeNorm]);
+ drw_rect(drw, 0, 0, mw, mh, 1, 1);
+@@ -144,14 +145,21 @@ drawmenu(void)
+ }
+ /* draw input field */
+ w = (lines > 0 || !matches) ? mw - x : inputw;
+- drw_setscheme(drw, scheme[SchemeNorm]);
+- drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
++ w -= lrpad / 2;
++ x += lrpad / 2;
+
+- curpos = TEXTW(text) - TEXTW(&text[cursor]);
+- if ((curpos += lrpad / 2 - 1) < w) {
+- drw_setscheme(drw, scheme[SchemeNorm]);
+- drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
+- }
++ rcurlen = drw_fontset_getwidth(drw, text + cursor);
++ curlen = drw_fontset_getwidth(drw, text) - rcurlen;
++ curpos += curlen - oldcurlen;
++ curpos = MIN(w, MAX(0, curpos));
++ curpos = MAX(curpos, w - rcurlen);
++ curpos = MIN(curpos, curlen);
++ oldcurlen = curlen;
++
++ drw_setscheme(drw, scheme[SchemeNorm]);
++ drw_text_align(drw, x, 0, curpos, bh, text, cursor, AlignR);
++ drw_text_align(drw, x + curpos, 0, w - curpos, bh, text + cursor, strlen(text) - cursor, AlignL);
++ drw_rect(drw, x + curpos - 1, 2, 2, bh - 4, 1, 0);
+
+ if (lines > 0) {
+ /* draw vertical list */
+diff --git a/drw.c b/drw.c
+index c638323..bfffbc1 100644
+--- a/drw.c
++++ b/drw.c
+@@ -364,6 +364,175 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
+ return x + (render ? w : 0);
+ }
+
++int
++utf8nextchar(const char *str, int len, int i, int inc)
++{
++ int n;
++
++ for (n = i + inc; n + inc >= 0 && n + inc <= len
++ && (str[n] & 0xc0) == 0x80; n += inc)
++ ;
++ return n;
++}
++
++int
++drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align)
++{
++ int ty;
++ unsigned int ew;
++ XftDraw *d = NULL;
++ Fnt *usedfont, *curfont, *nextfont;
++ size_t len;
++ int utf8strlen, utf8charlen, render = x || y || w || h;
++ long utf8codepoint = 0;
++ const char *utf8str;
++ FcCharSet *fccharset;
++ FcPattern *fcpattern;
++ FcPattern *match;
++ XftResult result;
++ int charexists = 0;
++ int i, n;
++
++ if (!drw || (render && !drw->scheme) || !text || !drw->fonts || textlen <= 0
++ || (align != AlignL && align != AlignR))
++ return 0;
++
++ if (!render) {
++ w = ~w;
++ } else {
++ XSetForeground(drw->dpy, drw->gc, drw->scheme[ColBg].pixel);
++ XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
++ d = XftDrawCreate(drw->dpy, drw->drawable,
++ DefaultVisual(drw->dpy, drw->screen),
++ DefaultColormap(drw->dpy, drw->screen));
++ }
++
++ usedfont = drw->fonts;
++ i = align == AlignL ? 0 : textlen;
++ x = align == AlignL ? x : x + w;
++ while (1) {
++ utf8strlen = 0;
++ nextfont = NULL;
++ /* if (align == AlignL) */
++ utf8str = text + i;
++
++ while ((align == AlignL && i < textlen) || (align == AlignR && i > 0)) {
++ if (align == AlignL) {
++ utf8charlen = utf8decode(text + i, &utf8codepoint, MIN(textlen - i, UTF_SIZ));
++ if (!utf8charlen) {
++ textlen = i;
++ break;
++ }
++ } else {
++ n = utf8nextchar(text, textlen, i, -1);
++ utf8charlen = utf8decode(text + n, &utf8codepoint, MIN(textlen - n, UTF_SIZ));
++ if (!utf8charlen) {
++ textlen -= i;
++ text += i;
++ i = 0;
++ break;
++ }
++ }
++ for (curfont = drw->fonts; curfont; curfont = curfont->next) {
++ charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
++ if (charexists) {
++ if (curfont == usedfont) {
++ utf8strlen += utf8charlen;
++ i += align == AlignL ? utf8charlen : -utf8charlen;
++ } else {
++ nextfont = curfont;
++ }
++ break;
++ }
++ }
++
++ if (!charexists || nextfont)
++ break;
++ else
++ charexists = 0;
++ }
++
++ if (align == AlignR)
++ utf8str = text + i;
++
++ if (utf8strlen) {
++ drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
++ /* shorten text if necessary */
++ if (align == AlignL) {
++ for (len = utf8strlen; len && ew > w; ) {
++ len = utf8nextchar(utf8str, len, len, -1);
++ drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
++ }
++ } else {
++ for (len = utf8strlen; len && ew > w; ) {
++ n = utf8nextchar(utf8str, len, 0, +1);
++ utf8str += n;
++ len -= n;
++ drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
++ }
++ }
++
++ if (len) {
++ if (render) {
++ ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
++ XftDrawStringUtf8(d, &drw->scheme[ColFg],
++ usedfont->xfont, align == AlignL ? x : x - ew, ty, (XftChar8 *)utf8str, len);
++ }
++ x += align == AlignL ? ew : -ew;
++ w -= ew;
++ }
++ if (len < utf8strlen)
++ break;
++ }
++
++ if ((align == AlignR && i <= 0) || (align == AlignL && i >= textlen)) {
++ break;
++ } else if (nextfont) {
++ charexists = 0;
++ usedfont = nextfont;
++ } else {
++ /* Regardless of whether or not a fallback font is found, the
++ * character must be drawn. */
++ charexists = 1;
++
++ fccharset = FcCharSetCreate();
++ FcCharSetAddChar(fccharset, utf8codepoint);
++
++ if (!drw->fonts->pattern) {
++ /* Refer to the comment in xfont_create for more information. */
++ die("the first font in the cache must be loaded from a font string.");
++ }
++
++ fcpattern = FcPatternDuplicate(drw->fonts->pattern);
++ FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
++ FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
++
++ FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
++ FcDefaultSubstitute(fcpattern);
++ match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result);
++
++ FcCharSetDestroy(fccharset);
++ FcPatternDestroy(fcpattern);
++
++ if (match) {
++ usedfont = xfont_create(drw, NULL, match);
++ if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
++ for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
++ ; /* NOP */
++ curfont->next = usedfont;
++ } else {
++ xfont_free(usedfont);
++ usedfont = drw->fonts;
++ }
++ }
++ }
++ }
++ if (d)
++ XftDrawDestroy(d);
++
++ return x;
++}
++
+ void
+ drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
+ {
+diff --git a/drw.h b/drw.h
+index 4c67419..b66a83e 100644
+--- a/drw.h
++++ b/drw.h
+@@ -13,6 +13,7 @@ typedef struct Fnt {
+ } Fnt;
+
+ enum { ColFg, ColBg }; /* Clr scheme index */
++enum { AlignL, AlignR };
+ typedef XftColor Clr;
+
+ typedef struct {
+@@ -52,6 +53,7 @@ void drw_setscheme(Drw *drw, Clr *scm);
+ /* Drawing functions */
+ void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert);
+ int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert);
++int drw_text_align(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int textlen, int align);
+
+ /* Map functions */
+ void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);