Configuración propia del lanzador de aplicación dmenu

25 de junio de 2026 • fjbalon

dmenu es un lanzador de aplicaciones, extremadamente ligero y rápido para el sistemas de ventanas X, diseñado originalmente para el gestor de ventanas dwm, de suckless. Es una herramienta que, para mí, casa matrimonialmente con dwm; no pudiendo ser verdaderamente usable uno sin el otro. Al menos, dwm no sería tan útil sin él.

Puede encontrar mi propia versión del código en mi servidor de git: /git/dmenu/.

La versión inicial es muy sencilla, al ser llamado mediante el comando dmenu, que por defecto dwm trae con acceso directo mediante teclas (Mod1+p, aunque yo personalmente lo he modificado a Mod4+r, me gusta más), ejecuta en la barra superior el lanzador, que conforme escribes irá indicando una lista de coincidencias, con una siempre seleccionada.

Pero el código tiene algunas limitaciones para con mi forma de trabajar, por lo que he aplicado algunos cambios para que sea más ameno y útil su uso. Estos son los cambios que he realizado:

  1. Originalmente, como suele ser en el entorno de Linux, es case sensitive. Generalmente lo prefiero así, es más preciso. Sin embargo, siendo que se trata de un lanzador que quiero que sea rápido, o que me ayude a buscar con facilidad; prefiero que sea case insensitive. En suckless hay un parche oficial para ello (case-insensitive), que he aplicado:
    diff --git a/dmenu.c b/dmenu.c
    @@ -54,8 +54,9 @@ static Clr *scheme[SchemeLast];
     
     #include "config.h"
     
    -static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
    -static char *(*fstrstr)(const char *, const char *) = strstr;
    +static char * cistrstr(const char *s, const char *sub);
    +static int (*fstrncmp)(const char *, const char *, size_t) = strncasecmp;
    +static char *(*fstrstr)(const char *, const char *) = cistrstr;
     
     static unsigned int
     textw_clamp(const char *str, unsigned int n)
    @@ -733,9 +734,9 @@ 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], "-i")) { /* case-insensitive item matching */
    -            fstrncmp = strncasecmp;
    -            fstrstr = cistrstr;
    +        else if (!strcmp(argv[i], "-s")) { /* case-sensitive item matching */
    +            fstrncmp = strncmp;
    +            fstrstr = strstr;
             } else if (i + 1 == argc)
                 usage();
             /* these options take one argument */
  2. Del mismo modo, y con una pretensión de pérdida de cierta precisión con el objetivo de ganar eficiencia y velocidad de lanzamiento, implemento búsqueda difusa mediante la aplicación del parche fuzzymatch, que garantiza que el contenido resultado no tiene por qué coincidir matemáticamente con el patrón de búsqueda (por ejemplo, la búsqueda de rvt podrá dar como resultado urxvt), en combinación con el parche fuzzyhighlight, que resalta las coincidencias en la búsqueda.
    diff --git a/config.h b/config.h
    index 1edb647..64eab2a 100644
    --- a/config.h
    +++ b/config.h
    @@ -11,6 +11,8 @@ static const char *colors[SchemeLast][2] = {
         /*     fg         bg       */
         [SchemeNorm] = { "#bbbbbb", "#222222" },
         [SchemeSel] = { "#eeeeee", "#005577" },
    +    [SchemeSelHighlight] = { "#ffc978", "#005577" },
    +    [SchemeNormHighlight] = { "#ffc978", "#222222" },
         [SchemeOut] = { "#000000", "#00ffff" },
     };
     /* -l option; if nonzero, dmenu uses vertical list with given number of lines */
    diff --git a/dmenu.c b/dmenu.c
    index 40f93e0..662131a 100644
    --- a/dmenu.c
    +++ b/dmenu.c
    @@ -25,7 +25,9 @@
     #define TEXTW(X)              (drw_fontset_getwidth(drw, (X)) + lrpad)
     
     /* enums */
    -enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
    +enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight,
    +       SchemeOut, SchemeLast }; /* color schemes */
    +
     
     struct item {
         char *text;
    @@ -129,9 +131,47 @@ cistrstr(const char *h, const char *n)
         return NULL;
     }
     
    +static void
    +drawhighlights(struct item *item, int x, int y, int maxw)
    +{
    +    int i, indent;
    +    char c, *highlight;
    +
    +    if (!(strlen(item->text) && strlen(text)))
    +        return;
    +
    +    drw_setscheme(drw, scheme[item == sel
    +                       ? SchemeSelHighlight
    +                       : SchemeNormHighlight]);
    +    for (i = 0, highlight = item->text; *highlight && text[i];) {
    +        if (!fstrncmp(highlight, &text[i], 1)) {
    +            /* get indentation */
    +            c = *highlight;
    +            *highlight = '\0';
    +            indent = TEXTW(item->text);
    +            *highlight = c;
    +
    +            /* highlight character */
    +            c = highlight[1];
    +            highlight[1] = '\0';
    +            drw_text(
    +                drw,
    +                x + indent - (lrpad / 2.),
    +                y,
    +                MIN(maxw - indent, TEXTW(highlight) - lrpad),
    +                bh, 0, highlight, 0
    +            );
    +            highlight[1] = c;
    +            ++i;
    +        }
    +        ++highlight;
    +    }
    +}
    +
     static int
     drawitem(struct item *item, int x, int y, int w)
     {
    +    int r;
         if (item == sel)
             drw_setscheme(drw, scheme[SchemeSel]);
         else if (item->out)
    @@ -139,7 +179,9 @@ drawitem(struct item *item, int x, int y, int w)
         else
             drw_setscheme(drw, scheme[SchemeNorm]);
     
    -    return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
    +    r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
    +    drawhighlights(item, x, y, w);
    +    return r;
     }
     
     static void
    @@ -716,7 +758,8 @@ static void
     usage(void)
     {
         die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
    -        "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
    +        "             [-nb color] [-nf color] [-sb color] [-sf color]\n"
    +        "             [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]");
     }
     
     int
    @@ -756,6 +799,14 @@ main(int argc, char *argv[])
                 colors[SchemeSel][ColBg] = argv[++i];
             else if (!strcmp(argv[i], "-sf"))  /* selected foreground color */
                 colors[SchemeSel][ColFg] = argv[++i];
    +        else if (!strcmp(argv[i], "-nhb")) /* normal hi background color */
    +            colors[SchemeNormHighlight][ColBg] = argv[++i];
    +        else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */
    +            colors[SchemeNormHighlight][ColFg] = argv[++i];
    +        else if (!strcmp(argv[i], "-shb")) /* selected hi background color */
    +            colors[SchemeSelHighlight][ColBg] = argv[++i];
    +        else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */
    +            colors[SchemeSelHighlight][ColFg] = argv[++i];
             else if (!strcmp(argv[i], "-w"))   /* embedding window id */
                 embed = argv[++i];
             else
    diff --git a/config.h b/config.h
    index 1edb647..a4e6174 100644
    --- a/config.h
    +++ b/config.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 137f7c8..6a19175 100644
    --- a/config.mk
    +++ b/config.mk
    @@ -21,7 +21,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 40f93e0..03e1f45 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>
    @@ -31,6 +32,7 @@ struct item {
         char *text;
         struct item *left, *right;
         int out;
    +    double distance;
     };
     
     static char text[BUFSIZ] = "";
    @@ -226,9 +228,93 @@ 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;
     
    @@ -715,7 +801,7 @@ setup(void)
     static void
     usage(void)
     {
    -    die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
    +    die("usage: dmenu [-bFfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
             "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]");
     }
     
    @@ -732,6 +818,8 @@ main(int argc, char *argv[])
                 exit(0);
             } else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
                 topbar = 0;
    +        else if (!strcmp(argv[i], "-F"))   /* disables fuzzy matching */
    +            fuzzy = 0;
             else if (!strcmp(argv[i], "-f"))   /* grabs keyboard before reading stdin */
                 fast = 1;
             else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */