Compare commits

..

1 Commits
4.5 ... 4.4.1

Author SHA1 Message Date
91dc06d0b8 prepare 4.4.1 release 2011-09-19 10:41:30 +01:00
10 changed files with 168 additions and 432 deletions

View File

@ -1,7 +1,7 @@
MIT/X Consortium License
© 2010-2012 Connor Lane Smith <cls@lubutu.com>
© 2006-2012 Anselm R Garbe <anselm@garbe.us>
© 2010-2011 Connor Lane Smith <cls@lubutu.com>
© 2006-2011 Anselm R Garbe <anselm@garbe.us>
© 2009 Gottox <gottox@s01.de>
© 2009 Markus Schnalke <meillo@marmaro.de>
© 2009 Evan Gates <evan.gates@gmail.com>

View File

@ -3,10 +3,10 @@
include config.mk
SRC = dmenu.c draw.c stest.c
SRC = dmenu.c draw.c lsx.c
OBJ = ${SRC:.c=.o}
all: options dmenu stest
all: options dmenu lsx
options:
@echo dmenu build options:
@ -18,24 +18,24 @@ options:
@echo CC -c $<
@${CC} -c $< ${CFLAGS}
${OBJ}: config.mk draw.h
${OBJ}: config.mk
dmenu: dmenu.o draw.o
@echo CC -o $@
@${CC} -o $@ dmenu.o draw.o ${LDFLAGS}
stest: stest.o
lsx: lsx.o
@echo CC -o $@
@${CC} -o $@ stest.o ${LDFLAGS}
@${CC} -o $@ lsx.o ${LDFLAGS}
clean:
@echo cleaning
@rm -f dmenu stest ${OBJ} dmenu-${VERSION}.tar.gz
@rm -f dmenu lsx ${OBJ} dmenu-${VERSION}.tar.gz
dist: clean
@echo creating dist tarball
@mkdir -p dmenu-${VERSION}
@cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run stest.1 ${SRC} dmenu-${VERSION}
@cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run lsx.1 ${SRC} dmenu-${VERSION}
@tar -cf dmenu-${VERSION}.tar dmenu-${VERSION}
@gzip dmenu-${VERSION}.tar
@rm -rf dmenu-${VERSION}
@ -43,24 +43,24 @@ dist: clean
install: all
@echo installing executables to ${DESTDIR}${PREFIX}/bin
@mkdir -p ${DESTDIR}${PREFIX}/bin
@cp -f dmenu dmenu_run stest ${DESTDIR}${PREFIX}/bin
@cp -f dmenu dmenu_run lsx ${DESTDIR}${PREFIX}/bin
@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu
@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run
@chmod 755 ${DESTDIR}${PREFIX}/bin/stest
@chmod 755 ${DESTDIR}${PREFIX}/bin/lsx
@echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1
@mkdir -p ${DESTDIR}${MANPREFIX}/man1
@sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@sed "s/VERSION/${VERSION}/g" < stest.1 > ${DESTDIR}${MANPREFIX}/man1/stest.1
@sed "s/VERSION/${VERSION}/g" < lsx.1 > ${DESTDIR}${MANPREFIX}/man1/lsx.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/stest.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/lsx.1
uninstall:
@echo removing executables from ${DESTDIR}${PREFIX}/bin
@rm -f ${DESTDIR}${PREFIX}/bin/dmenu
@rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run
@rm -f ${DESTDIR}${PREFIX}/bin/stest
@rm -f ${DESTDIR}${PREFIX}/bin/lsx
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
@rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1
@rm -f ${DESTDIR}${MANPREFIX}/man1/lsx.1
.PHONY: all options clean dist install uninstall

View File

@ -1,5 +1,5 @@
# dmenu version
VERSION = 4.5
VERSION = 4.4.1
# paths
PREFIX = /usr/local
@ -17,9 +17,8 @@ INCS = -I${X11INC}
LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS}
# flags
CPPFLAGS = -D_BSD_SOURCE -D_POSIX_C_SOURCE=2 -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CFLAGS = -ansi -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
LDFLAGS = -s ${LIBS}
# compiler and linker

108
dmenu.1
View File

@ -25,23 +25,25 @@ dmenu \- dynamic menu
.BR dmenu_run " ..."
.SH DESCRIPTION
.B dmenu
is a dynamic menu for X, which reads a list of newline\-separated items from
stdin. When the user selects an item and presses Return, their choice is printed
to stdout and dmenu terminates. Entering text will narrow the items to those
matching the tokens in the input.
is a dynamic menu for X, originally designed for
.IR dwm (1).
It manages huge numbers of user\-defined menu items efficiently.
.P
dmenu reads a list of newline\-separated items from stdin and creates a menu.
When the user selects an item or enters any text and presses Return, their
choice is printed to stdout and dmenu terminates.
.P
.B dmenu_run
is a script used by
.IR dwm (1)
which lists programs in the user's $PATH and runs the result in their $SHELL.
is a dmenu script used by dwm which lists programs in the user's $PATH and
executes the selected item.
.SH OPTIONS
.TP
.B \-b
dmenu appears at the bottom of the screen.
.TP
.B \-f
dmenu grabs the keyboard before reading stdin. This is faster, but will lock up
X until stdin reaches end\-of\-file.
dmenu grabs the keyboard before reading stdin. This is faster, but may lock up
X if stdin is from a terminal.
.TP
.B \-i
dmenu matches menu items case insensitively.
@ -73,93 +75,25 @@ defines the selected foreground color.
.B \-v
prints version information to stdout, then exits.
.SH USAGE
dmenu is completely controlled by the keyboard. Items are selected using the
arrow keys, page up, page down, home, and end.
dmenu is completely controlled by the keyboard. Besides standard Unix line
editing and item selection (arrow keys, page up/down, home and end), the
following keys are recognized:
.TP
.B Tab
.B Tab (Ctrl\-i)
Copy the selected item to the input field.
.TP
.B Return
.B Return (Ctrl\-j)
Confirm selection. Prints the selected item to stdout and exits, returning
success.
.TP
.B Shift\-Return
.B Shift\-Return (Ctrl\-Shift\-j)
Confirm input. Prints the input text to stdout and exits, returning success.
.TP
.B Escape
.B Escape (Ctrl\-c)
Exit without selecting an item, returning failure.
.TP
C\-a
Home
.TP
C\-b
Left
.TP
C\-c
Escape
.TP
C\-d
Delete
.TP
C\-e
End
.TP
C\-f
Right
.TP
C\-h
Backspace
.TP
C\-i
Tab
.TP
C\-j
Return
.TP
C\-J
Shift-Return
.TP
C\-k
Delete line right
.TP
C\-m
Return
.TP
C\-n
Down
.TP
C\-p
Up
.TP
C\-u
Delete line left
.TP
C\-w
Delete word left
.TP
C\-y
Paste from primary X selection
.TP
C\-Y
Paste from X clipboard
.TP
M\-g
Home
.TP
M\-G
End
.TP
M\-h
Up
.TP
M\-j
Page down
.TP
M\-k
Page up
.TP
M\-l
Down
.B Ctrl\-y
Paste the current X selection into the input field.
.SH SEE ALSO
.IR dwm (1),
.IR stest (1)
.IR lsx (1)

219
dmenu.c
View File

@ -13,10 +13,9 @@
#endif
#include "draw.h"
#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \
* MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define INRECT(x,y,rx,ry,rw,rh) ((x) >= (rx) && (x) < (rx)+(rw) && (y) >= (ry) && (y) < (ry)+(rh))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
typedef struct Item Item;
struct Item {
@ -31,7 +30,7 @@ static void drawmenu(void);
static void grabkeyboard(void);
static void insert(const char *str, ssize_t n);
static void keypress(XKeyEvent *ev);
static void match(void);
static void match(Bool sub);
static size_t nextrune(int inc);
static void paste(void);
static void readstdin(void);
@ -42,24 +41,23 @@ static void usage(void);
static char text[BUFSIZ] = "";
static int bh, mw, mh;
static int inputw, promptw;
static int lines = 0;
static size_t cursor = 0;
static const char *font = NULL;
static const char *prompt = NULL;
static const char *normbgcolor = "#222222";
static const char *normfgcolor = "#bbbbbb";
static const char *selbgcolor = "#005577";
static const char *selfgcolor = "#eeeeee";
static unsigned int lines = 0;
static const char *normbgcolor = "#cccccc";
static const char *normfgcolor = "#000000";
static const char *selbgcolor = "#0066ff";
static const char *selfgcolor = "#ffffff";
static unsigned long normcol[ColLast];
static unsigned long selcol[ColLast];
static Atom clip, utf8;
static Atom utf8;
static Bool topbar = True;
static DC *dc;
static Item *items = NULL;
static Item *matches, *matchend;
static Item *prev, *curr, *next, *sel;
static Window win;
static XIC xic;
static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
static char *(*fstrstr)(const char *, const char *) = strstr;
@ -70,35 +68,35 @@ main(int argc, char *argv[]) {
int i;
for(i = 1; i < argc; i++)
/* these options take no arguments */
if(!strcmp(argv[i], "-v")) { /* prints version information */
puts("dmenu-"VERSION", © 2006-2012 dmenu engineers, see LICENSE for details");
/* single flags */
if(!strcmp(argv[i], "-v")) {
puts("dmenu-"VERSION", © 2006-2011 dmenu engineers, see LICENSE for details");
exit(EXIT_SUCCESS);
}
else if(!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
else if(!strcmp(argv[i], "-b"))
topbar = False;
else if(!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
else if(!strcmp(argv[i], "-f"))
fast = True;
else if(!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
else if(!strcmp(argv[i], "-i")) {
fstrncmp = strncasecmp;
fstrstr = cistrstr;
}
else if(i+1 == argc)
usage();
/* these options take one argument */
else if(!strcmp(argv[i], "-l")) /* number of lines in vertical list */
/* double flags */
else if(!strcmp(argv[i], "-l"))
lines = atoi(argv[++i]);
else if(!strcmp(argv[i], "-p")) /* adds prompt to left of input field */
else if(!strcmp(argv[i], "-p"))
prompt = argv[++i];
else if(!strcmp(argv[i], "-fn")) /* font or font set */
else if(!strcmp(argv[i], "-fn"))
font = argv[++i];
else if(!strcmp(argv[i], "-nb")) /* normal background color */
else if(!strcmp(argv[i], "-nb"))
normbgcolor = argv[++i];
else if(!strcmp(argv[i], "-nf")) /* normal foreground color */
else if(!strcmp(argv[i], "-nf"))
normfgcolor = argv[++i];
else if(!strcmp(argv[i], "-sb")) /* selected background color */
else if(!strcmp(argv[i], "-sb"))
selbgcolor = argv[++i];
else if(!strcmp(argv[i], "-sf")) /* selected foreground color */
else if(!strcmp(argv[i], "-sf"))
selfgcolor = argv[++i];
else
usage();
@ -117,15 +115,15 @@ main(int argc, char *argv[]) {
setup();
run();
return 1; /* unreachable */
return EXIT_FAILURE; /* unreachable */
}
void
appenditem(Item *item, Item **list, Item **last) {
if(*last)
(*last)->right = item;
else
if(!*last)
*list = item;
else
(*last)->right = item;
item->left = *last;
item->right = NULL;
@ -140,7 +138,7 @@ calcoffsets(void) {
n = lines * bh;
else
n = mw - (promptw + inputw + textw(dc, "<") + textw(dc, ">"));
/* 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(dc, next->text), n)) > n)
break;
@ -174,14 +172,12 @@ drawmenu(void) {
drawtext(dc, prompt, selcol);
dc->x = dc->w;
}
/* draw input field */
dc->w = (lines > 0 || !matches) ? mw - dc->x : inputw;
drawtext(dc, text, normcol);
if((curpos = textnw(dc, text, cursor) + dc->h/2 - 2) < dc->w)
drawrect(dc, curpos, 2, 1, dc->h - 4, True, FG(dc, normcol));
if(lines > 0) {
/* draw vertical list */
dc->w = mw - dc->x;
for(item = curr; item != next; item = item->right) {
dc->y += dc->h;
@ -189,7 +185,6 @@ drawmenu(void) {
}
}
else if(matches) {
/* draw horizontal list */
dc->x += inputw;
dc->w = textw(dc, "<");
if(curr->left)
@ -211,7 +206,6 @@ void
grabkeyboard(void) {
int i;
/* try to grab keyboard, we may have to wait for another process to ungrab */
for(i = 0; i < 1000; i++) {
if(XGrabKeyboard(dc->dpy, DefaultRootWindow(dc->dpy), True,
GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
@ -225,26 +219,24 @@ void
insert(const char *str, ssize_t n) {
if(strlen(text) + n > sizeof text - 1)
return;
/* move existing text out of the way, insert new text, and update cursor */
memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
if(n > 0)
memcpy(&text[cursor], str, n);
cursor += n;
match();
match(n > 0 && text[cursor] == '\0');
}
void
keypress(XKeyEvent *ev) {
char buf[32];
int len;
KeySym ksym = NoSymbol;
Status status;
KeySym ksym;
len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
if(status == XBufferOverflow)
return;
if(ev->state & ControlMask)
switch(ksym) {
XLookupString(ev, buf, sizeof buf, &ksym, NULL);
if(ev->state & ControlMask) {
KeySym lower, upper;
XConvertCase(ksym, &lower, &upper);
switch(lower) {
case XK_a: ksym = XK_Home; break;
case XK_b: ksym = XK_Left; break;
case XK_c: ksym = XK_Escape; break;
@ -255,12 +247,12 @@ keypress(XKeyEvent *ev) {
case XK_i: ksym = XK_Tab; break;
case XK_j: ksym = XK_Return; break;
case XK_m: ksym = XK_Return; break;
case XK_n: ksym = XK_Down; break;
case XK_p: ksym = XK_Up; break;
case XK_n: ksym = XK_Up; break;
case XK_p: ksym = XK_Down; break;
case XK_k: /* delete right */
text[cursor] = '\0';
match();
match(False);
break;
case XK_u: /* delete left */
insert(NULL, 0 - cursor);
@ -272,27 +264,16 @@ keypress(XKeyEvent *ev) {
insert(NULL, nextrune(-1) - cursor);
break;
case XK_y: /* paste selection */
XConvertSelection(dc->dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
utf8, utf8, win, CurrentTime);
XConvertSelection(dc->dpy, XA_PRIMARY, utf8, utf8, win, CurrentTime);
return;
default:
return;
}
else if(ev->state & Mod1Mask)
switch(ksym) {
case XK_g: ksym = XK_Home; break;
case XK_G: ksym = XK_End; break;
case XK_h: ksym = XK_Up; break;
case XK_j: ksym = XK_Next; break;
case XK_k: ksym = XK_Prior; break;
case XK_l: ksym = XK_Down; break;
default:
return;
}
}
switch(ksym) {
default:
if(!iscntrl(*buf))
insert(buf, len);
insert(buf, strlen(buf));
break;
case XK_Delete:
if(text[cursor] == '\0')
@ -310,7 +291,6 @@ keypress(XKeyEvent *ev) {
break;
}
if(next) {
/* jump to end of list and position items in reverse */
curr = matchend;
calcoffsets();
curr = prev;
@ -335,8 +315,6 @@ keypress(XKeyEvent *ev) {
cursor = nextrune(-1);
break;
}
if(lines > 0)
return;
/* fallthrough */
case XK_Up:
if(sel && sel->left && (sel = sel->left)->right == curr) {
@ -365,8 +343,6 @@ keypress(XKeyEvent *ev) {
cursor = nextrune(+1);
break;
}
if(lines > 0)
return;
/* fallthrough */
case XK_Down:
if(sel && sel->right && (sel = sel->right) == next) {
@ -379,46 +355,33 @@ keypress(XKeyEvent *ev) {
return;
strncpy(text, sel->text, sizeof text);
cursor = strlen(text);
match();
match(True);
break;
}
drawmenu();
}
void
match(void) {
static char **tokv = NULL;
static int tokn = 0;
match(Bool sub) {
size_t len = strlen(text);
Item *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
Item *item, *lnext;
char buf[sizeof text], *s;
int i, tokc = 0;
size_t len;
Item *item, *lprefix, *lsubstr, *prefixend, *substrend;
strcpy(buf, text);
/* separate input text into tokens to be matched individually */
for(s = strtok(buf, " "); s; tokv[tokc-1] = s, s = strtok(NULL, " "))
if(++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
eprintf("cannot realloc %u bytes\n", tokn * sizeof *tokv);
len = tokc ? strlen(tokv[0]) : 0;
matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
for(item = items; item && item->text; item++) {
for(i = 0; i < tokc; i++)
if(!fstrstr(item->text, tokv[i]))
break;
if(i != tokc) /* not all tokens match */
continue;
/* exact matches go first, then prefixes, then substrings */
if(!tokc || !fstrncmp(tokv[0], item->text, len+1))
appenditem(item, &matches, &matchend);
else if(!fstrncmp(tokv[0], item->text, len))
lexact = lprefix = lsubstr = exactend = prefixend = substrend = NULL;
for(item = sub ? matches : items; item && item->text; item = lnext) {
lnext = sub ? item->right : item + 1;
if(!fstrncmp(text, item->text, len + 1))
appenditem(item, &lexact, &exactend);
else if(!fstrncmp(text, item->text, len))
appenditem(item, &lprefix, &prefixend);
else
else if(fstrstr(item->text, text))
appenditem(item, &lsubstr, &substrend);
}
matches = lexact;
matchend = exactend;
if(lprefix) {
if(matches) {
if(matchend) {
matchend->right = lprefix;
lprefix->left = matchend;
}
@ -427,7 +390,7 @@ match(void) {
matchend = prefixend;
}
if(lsubstr) {
if(matches) {
if(matchend) {
matchend->right = lsubstr;
lsubstr->left = matchend;
}
@ -443,7 +406,6 @@ size_t
nextrune(int inc) {
ssize_t n;
/* return location of next utf8 rune in the given direction (+1 or -1) */
for(n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc);
return n;
}
@ -455,7 +417,6 @@ paste(void) {
unsigned long dl;
Atom da;
/* we have been given the current selection, now insert it into input */
XGetWindowProperty(dc->dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
utf8, &da, &di, &dl, &dl, (unsigned char **)&p);
insert(p, (q = strchr(p, '\n')) ? q-p : (ssize_t)strlen(p));
@ -468,7 +429,6 @@ readstdin(void) {
char buf[sizeof text], *p, *maxstr = NULL;
size_t i, max = 0, size = 0;
/* read each line from stdin and add it to the item list */
for(i = 0; fgets(buf, sizeof buf, stdin); i++) {
if(i+1 >= size / sizeof *items)
if(!(items = realloc(items, (size += BUFSIZ))))
@ -483,16 +443,13 @@ readstdin(void) {
if(items)
items[i].text = NULL;
inputw = maxstr ? textw(dc, maxstr) : 0;
lines = MIN(lines, i);
}
void
run(void) {
XEvent ev;
while(!XNextEvent(dc->dpy, &ev)) {
if(XFilterEvent(&ev, win))
continue;
while(!XNextEvent(dc->dpy, &ev))
switch(ev.type) {
case Expose:
if(ev.xexpose.count == 0)
@ -510,15 +467,13 @@ run(void) {
XRaiseWindow(dc->dpy, win);
break;
}
}
}
void
setup(void) {
int x, y, screen = DefaultScreen(dc->dpy);
Window root = RootWindow(dc->dpy, screen);
XSetWindowAttributes swa;
XIM xim;
XSetWindowAttributes wa;
#ifdef XINERAMA
int n;
XineramaScreenInfo *info;
@ -529,41 +484,22 @@ setup(void) {
selcol[ColBG] = getcolor(dc, selbgcolor);
selcol[ColFG] = getcolor(dc, selfgcolor);
clip = XInternAtom(dc->dpy, "CLIPBOARD", False);
utf8 = XInternAtom(dc->dpy, "UTF8_STRING", False);
/* calculate menu geometry */
/* menu geometry */
bh = dc->font.height + 2;
lines = MAX(lines, 0);
mh = (lines + 1) * bh;
#ifdef XINERAMA
if((info = XineramaQueryScreens(dc->dpy, &n))) {
int a, j, di, i = 0, area = 0;
int i, di;
unsigned int du;
Window w, pw, dw, *dws;
XWindowAttributes wa;
XGetInputFocus(dc->dpy, &w, &di);
if(w != root && w != PointerRoot && w != None) {
/* find top-level window containing current input focus */
do {
if(XQueryTree(dc->dpy, (pw = w), &dw, &w, &dws, &du) && dws)
XFree(dws);
} while(w != root && w != pw);
/* find xinerama screen with which the window intersects most */
if(XGetWindowAttributes(dc->dpy, pw, &wa))
for(j = 0; j < n; j++)
if((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
area = a;
i = j;
}
}
/* no focused window is on screen, so use pointer location instead */
if(!area && XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
for(i = 0; i < n; i++)
if(INTERSECT(x, y, 1, 1, info[i]))
break;
Window dw;
XQueryPointer(dc->dpy, root, &dw, &dw, &x, &y, &di, &di, &du);
for(i = 0; i < n-1; i++)
if(INRECT(x, y, info[i].x_org, info[i].y_org, info[i].width, info[i].height))
break;
x = info[i].x_org;
y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
mw = info[i].width;
@ -578,21 +514,16 @@ setup(void) {
}
promptw = prompt ? textw(dc, prompt) : 0;
inputw = MIN(inputw, mw/3);
match();
match(False);
/* create menu window */
swa.override_redirect = True;
swa.background_pixel = normcol[ColBG];
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
/* menu window */
wa.override_redirect = True;
wa.background_pixmap = ParentRelative;
wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
win = XCreateWindow(dc->dpy, root, x, y, mw, mh, 0,
DefaultDepth(dc->dpy, screen), CopyFromParent,
DefaultVisual(dc->dpy, screen),
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
/* open input methods */
xim = XOpenIM(dc->dpy, NULL, NULL, NULL);
xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win, XNFocusWindow, win, NULL);
CWOverrideRedirect | CWBackPixmap | CWEventMask, &wa);
XMapRaised(dc->dpy, win);
resizedc(dc, mw, mh);

View File

@ -1,15 +1,9 @@
#!/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
CACHE=${XDG_CACHE_HOME:-"$HOME/.cache"}/dmenu_run
(
IFS=:
if stest -dqr -n "$cache" $PATH; then
stest -flx $PATH | sort -u | tee "$cache" | dmenu "$@"
else
dmenu "$@" < "$cache"
if test "`ls -dt $PATH "$CACHE" 2> /dev/null | sed 1q`" != "$CACHE"; then
mkdir -p "`dirname "$CACHE"`" && lsx $PATH | sort -u > "$CACHE"
fi
) | ${SHELL:-"/bin/sh"} &
)
cmd=`dmenu "$@" < "$CACHE"` && exec sh -c "$cmd"

11
lsx.1 Normal file
View File

@ -0,0 +1,11 @@
.TH LSX 1 dmenu\-VERSION
.SH NAME
lsx \- list executables
.SH SYNOPSIS
.B lsx
.RI [ directory ...]
.SH DESCRIPTION
.B lsx
lists the executables in each
.IR directory .
If none are given the current working directory is used.

38
lsx.c Normal file
View File

@ -0,0 +1,38 @@
/* See LICENSE file for copyright and license details. */
#include <dirent.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
static void lsx(const char *dir);
int
main(int argc, char *argv[]) {
int i;
if(argc < 2)
lsx(".");
else for(i = 1; i < argc; i++)
lsx(argv[i]);
return EXIT_SUCCESS;
}
void
lsx(const char *dir) {
char buf[PATH_MAX];
struct dirent *d;
struct stat st;
DIR *dp;
if(!(dp = opendir(dir))) {
perror(dir);
return;
}
while((d = readdir(dp)))
if(snprintf(buf, sizeof buf, "%s/%s", dir, d->d_name) < (int)sizeof buf
&& !stat(buf, &st) && S_ISREG(st.st_mode) && access(buf, X_OK) == 0)
puts(d->d_name);
closedir(dp);
}

87
stest.1
View File

@ -1,87 +0,0 @@
.TH STEST 1 dmenu\-VERSION
.SH NAME
stest \- filter a list of files by properties
.SH SYNOPSIS
.B stest
.RB [ -abcdefghlpqrsuwx ]
.RB [ -n
.IR file ]
.RB [ -o
.IR file ]
.RI [ file ...]
.SH DESCRIPTION
.B stest
takes a list of files and filters by the files' properties, analogous to
.IR test (1).
Files which pass all tests are printed to stdout. If no files are given, stest
reads files from stdin.
.SH OPTIONS
.TP
.B \-a
Test hidden files.
.TP
.B \-b
Test that files are block specials.
.TP
.B \-c
Test that files are character specials.
.TP
.B \-d
Test that files are directories.
.TP
.B \-e
Test that files exist.
.TP
.B \-f
Test that files are regular files.
.TP
.B \-g
Test that files have their set-group-ID flag set.
.TP
.B \-h
Test that files are symbolic links.
.TP
.B \-l
Test the contents of a directory given as an argument.
.TP
.BI \-n " file"
Test that files are newer than
.IR file .
.TP
.BI \-o " file"
Test that files are older than
.IR file .
.TP
.B \-p
Test that files are named pipes.
.TP
.B \-q
No files are printed, only the exit status is returned.
.TP
.B \-r
Test that files are readable.
.TP
.B \-s
Test that files are not empty.
.TP
.B \-u
Test that files have their set-user-ID flag set.
.TP
.B \-w
Test that files are writable.
.TP
.B \-x
Test that files are executable.
.SH EXIT STATUS
.TP
.B 0
At least one file passed all tests.
.TP
.B 1
No files passed all tests.
.TP
.B 2
An error occurred.
.SH SEE ALSO
.IR dmenu (1),
.IR test (1)

84
stest.c
View File

@ -1,84 +0,0 @@
/* See LICENSE file for copyright and license details. */
#include <dirent.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#define FLAG(x) (flag[(x)-'a'])
static void test(const char *, const char *);
static bool match = false;
static bool flag[26];
static struct stat old, new;
int
main(int argc, char *argv[]) {
struct dirent *d;
char buf[BUFSIZ], *p;
DIR *dir;
int opt;
while((opt = getopt(argc, argv, "abcdefghln:o:pqrsuwx")) != -1)
switch(opt) {
case 'n': /* newer than file */
case 'o': /* older than file */
if(!(FLAG(opt) = !stat(optarg, (opt == 'n' ? &new : &old))))
perror(optarg);
break;
default: /* miscellaneous operators */
FLAG(opt) = true;
break;
case '?': /* error: unknown flag */
fprintf(stderr, "usage: %s [-abcdefghlpqrsuwx] [-n file] [-o file] [file...]\n", argv[0]);
exit(2);
}
if(optind == argc)
while(fgets(buf, sizeof buf, stdin)) {
if((p = strchr(buf, '\n')))
*p = '\0';
test(buf, buf);
}
for(; optind < argc; optind++)
if(FLAG('l') && (dir = opendir(argv[optind]))) {
/* test directory contents */
while((d = readdir(dir)))
if(snprintf(buf, sizeof buf, "%s/%s", argv[optind], d->d_name) < sizeof buf)
test(buf, d->d_name);
closedir(dir);
}
else
test(argv[optind], argv[optind]);
return match ? 0 : 1;
}
void
test(const char *path, const char *name) {
struct stat st, ln;
if(!stat(path, &st) && (FLAG('a') || name[0] != '.') /* hidden files */
&& (!FLAG('b') || S_ISBLK(st.st_mode)) /* block special */
&& (!FLAG('c') || S_ISCHR(st.st_mode)) /* character special */
&& (!FLAG('d') || S_ISDIR(st.st_mode)) /* directory */
&& (!FLAG('e') || access(path, F_OK) == 0) /* exists */
&& (!FLAG('f') || S_ISREG(st.st_mode)) /* regular file */
&& (!FLAG('g') || st.st_mode & S_ISGID) /* set-group-id flag */
&& (!FLAG('h') || (!lstat(path, &ln) && S_ISLNK(ln.st_mode))) /* symbolic link */
&& (!FLAG('n') || st.st_mtime > new.st_mtime) /* newer than file */
&& (!FLAG('o') || st.st_mtime < old.st_mtime) /* older than file */
&& (!FLAG('p') || S_ISFIFO(st.st_mode)) /* named pipe */
&& (!FLAG('r') || access(path, R_OK) == 0) /* readable */
&& (!FLAG('s') || st.st_size > 0) /* not empty */
&& (!FLAG('u') || st.st_mode & S_ISUID) /* set-user-id flag */
&& (!FLAG('w') || access(path, W_OK) == 0) /* writable */
&& (!FLAG('x') || access(path, X_OK) == 0)) { /* executable */
if(FLAG('q'))
exit(0);
match = true;
puts(name);
}
}