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"} &