Hi,

While browsing the wiki I came across a rewrite of dmenu_path in C
written by cls [1]

I've used this as the basis for the attached patch, which adds the
functionality of dmenu_run into dmenu.c
It adds just under 100 LOC, but means the shell scripts
dmenu_{run,path} are unneeded.

Comments, advice and flames welcome;

Jonny

=====
[1] http://tools.suckless.org/dmenu/patches/legacy/dmenu_path
diff --git a/Makefile b/Makefile
index 0f7dfbd..e07e87f 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@ clean:
 dist: clean
        @echo creating dist tarball
        @mkdir -p dmenu-${VERSION}
-       @cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_path 
dmenu_run stest.1 ${SRC} dmenu-${VERSION}
+       @cp LICENSE Makefile README config.mk dmenu.1 draw.h stest.1 ${SRC} 
dmenu-${VERSION}
        @tar -cf dmenu-${VERSION}.tar dmenu-${VERSION}
        @gzip dmenu-${VERSION}.tar
        @rm -rf dmenu-${VERSION}
@@ -47,10 +47,9 @@ dist: clean
 install: all
        @echo installing executables to ${DESTDIR}${PREFIX}/bin
        @mkdir -p ${DESTDIR}${PREFIX}/bin
-       @cp -f dmenu dmenu_path dmenu_run stest ${DESTDIR}${PREFIX}/bin
+       @cp -f dmenu stest ${DESTDIR}${PREFIX}/bin
        @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu
-       @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_path
-       @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run
+       @(cd ${DESTDIR}${PREFIX}/bin && ln -sf dmenu dmenu_run)
        @chmod 755 ${DESTDIR}${PREFIX}/bin/stest
        @echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1
        @mkdir -p ${DESTDIR}${MANPREFIX}/man1
@@ -62,7 +61,6 @@ install: all
 uninstall:
        @echo removing executables from ${DESTDIR}${PREFIX}/bin
        @rm -f ${DESTDIR}${PREFIX}/bin/dmenu
-       @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_path
        @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run
        @rm -f ${DESTDIR}${PREFIX}/bin/stest
        @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
diff --git a/config.def.h b/config.def.h
index c2a23fa..98a0524 100644
--- a/config.def.h
+++ b/config.def.h
@@ -3,6 +3,8 @@
  */
 /* Default settings; can be overrided by command line. */
 
+#define CACHE  ".cache/dmenu_run"
+
 static Bool topbar = True;                  /* -b  option; if False, dmenu 
appears at bottom */
 static const char *font = NULL;             /* -fn option; default X11 font or 
font set      */
 static const char *prompt = NULL;           /* -p  option; prompt to the elft 
of input field */
diff --git a/dmenu.c b/dmenu.c
index 94c70de..8a97eda 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -1,9 +1,11 @@
 /* See LICENSE file for copyright and license details. */
 #include <ctype.h>
+#include <dirent.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <strings.h>
+#include <sys/stat.h>
 #include <unistd.h>
 #include <X11/Xlib.h>
 #include <X11/Xatom.h>
@@ -35,9 +37,13 @@ static void keypress(XKeyEvent *ev);
 static void match(void);
 static size_t nextrune(int inc);
 static void paste(void);
+static int qstrcmp(const void *a, const void *b);
 static void readstdin(void);
 static void run(void);
+static void scan(void);
 static void setup(void);
+static void updatecache(void);
+static int uptodate(void);
 static void usage(void);
 
 static char text[BUFSIZ] = "";
@@ -52,6 +58,8 @@ static DC *dc;
 static Item *items = NULL;
 static Item *matches, *matchend;
 static Item *prev, *curr, *next, *sel;
+static Bool dmenurun = False;
+static const char *HOME, *PATH;
 static Window win;
 static XIC xic;
 static int mon = -1;
@@ -66,6 +74,11 @@ main(int argc, char *argv[]) {
        Bool fast = False;
        int i;
 
+    if(strcmp(argv[0], "dmenu_run") == 0) {
+        dmenurun = True;
+        updatecache();
+    }
+
        for(i = 1; i < argc; i++)
                /* these options take no arguments */
                if(!strcmp(argv[i], "-v")) {      /* prints version information 
*/
@@ -367,6 +380,8 @@ keypress(XKeyEvent *ev) {
                break;
        case XK_Return:
        case XK_KP_Enter:
+        if(dmenurun)
+            execlp(sel->text, sel->text, NULL);     /* dmenu_run */
                puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
                if(!(ev->state & ControlMask))
                        exit(EXIT_SUCCESS);
@@ -477,13 +492,23 @@ paste(void) {
        drawmenu();
 }
 
+int
+qstrcmp(const void *a, const void *b) {
+       return strcmp(*(const char **)a, *(const char **)b);
+}
+
 void
 readstdin(void) {
        char buf[sizeof text], *p, *maxstr = NULL;
        size_t i, max = 0, size = 0;
+       FILE *cache;
+
+    if(dmenurun)
+        if(!(cache = fopen(CACHE, "r")))
+            eprintf("open failed");
 
-       /* read each line from stdin and add it to the item list */
-       for(i = 0; fgets(buf, sizeof buf, stdin); i++) {
+       /* read each line from stdin or cache and add it to the item list */
+       for(i = 0; fgets(buf, sizeof buf, (dmenurun) ? cache : stdin); i++) {
                if(i+1 >= size / sizeof *items)
                        if(!(items = realloc(items, (size += BUFSIZ))))
                                eprintf("cannot realloc %u bytes:", size);
@@ -499,6 +524,9 @@ readstdin(void) {
                items[i].text = NULL;
        inputw = maxstr ? textw(dc, maxstr) : 0;
        lines = MIN(lines, i);
+
+    if(dmenurun)
+        fclose(cache);
 }
 
 void
@@ -529,6 +557,43 @@ run(void) {
 }
 
 void
+scan(void) {
+       char buf[PATH_MAX];
+       char *dir, *path, **bins = NULL;
+       size_t i, count = 0;
+       struct dirent *ent;
+       DIR *dp;
+       FILE *cache;
+
+       if(!(path = strdup(PATH)))
+               eprintf("strdup failed");
+       for(dir = strtok(path, ":"); dir; dir = strtok(NULL, ":")) {
+               if(!(dp = opendir(dir)))
+                       continue;
+               while((ent = readdir(dp))) {
+                       snprintf(buf, sizeof buf, "%s/%s", dir, ent->d_name);
+                       if(ent->d_name[0] == '.' || access(buf, X_OK) < 0)
+                               continue;
+                       if(!(bins = realloc(bins, ++count * sizeof *bins)))
+                               eprintf("malloc failed");
+                       if(!(bins[count-1] = strdup(ent->d_name)))
+                               eprintf("strdup failed");
+               }
+               closedir(dp);
+       }
+       qsort(bins, count, sizeof *bins, qstrcmp);
+       if(!(cache = fopen(CACHE, "w")))
+               eprintf("open failed");
+       for(i = 0; i < count; i++) {
+               if(i > 0 && !strcmp(bins[i], bins[i-1]))
+                       continue;
+               fprintf(cache, "%s\n", bins[i]);
+       }
+       fclose(cache);
+       free(path);
+}
+
+void
 setup(void) {
        int x, y, screen = DefaultScreen(dc->dpy);
        Window root = RootWindow(dc->dpy, screen);
@@ -619,6 +684,36 @@ setup(void) {
 }
 
 void
+updatecache(void) {
+       if(!(HOME = getenv("HOME")))
+               eprintf("no $HOME");
+       if(!(PATH = getenv("PATH")))
+               eprintf("no $PATH");
+       if(chdir(HOME) < 0)
+               eprintf("chdir failed");
+       if(!(uptodate()))
+           scan();
+}
+
+int
+uptodate(void) {
+       char *dir, *path;
+       time_t mtime;
+       struct stat st;
+
+       if(stat(CACHE, &st) < 0)
+               return 0;
+       mtime = st.st_mtime;
+       if(!(path = strdup(PATH)))
+               eprintf("strdup failed");
+       for(dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
+               if(!stat(dir, &st) && st.st_mtime > mtime)
+                       return 0;
+       free(path);
+       return 1;
+}
+
+void
 usage(void) {
        fputs("usage: dmenu [-b] [-f] [-i] [-l lines] [-p prompt] [-fn font] 
[-m monitor]\n"
              "             [-nb color] [-nf color] [-sb color] [-sf color] 
[-v]\n", stderr);
diff --git a/dmenu_path b/dmenu_path
deleted file mode 100644
index 338bac4..0000000
--- a/dmenu_path
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"}
-if [ -d "$cachedir" ]; then
-       cache=$cachedir/dmenu_run
-else
-       cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~
-fi
-IFS=:
-if stest -dqr -n "$cache" $PATH; then
-       stest -flx $PATH | sort -u | tee "$cache"
-else
-       cat "$cache"
-fi
diff --git a/dmenu_run b/dmenu_run
deleted file mode 100755
index 834ede5..0000000
--- a/dmenu_run
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &

Reply via email to