Compare commits

..

48 Commits
4.4 ... 4.5

Author SHA1 Message Date
89fc26e7f0 prepare 4.5 release 2012-01-08 13:14:24 +01:00
7f45b3f7aa update version 4.5-hg 2012-01-08 13:13:00 +01:00
0f91dd1c74 config.mk cleanup 2012-01-04 13:30:47 +01:00
3402a8d53a align M-[hjkl] closer to vi keys 2012-01-02 19:48:11 +01:00
d3e9bd152e happy new year! 2012-01-01 20:33:56 +01:00
b95fb20269 add Mod1Mask keybindings 2012-01-01 20:32:40 +01:00
6664e4233f merge stest -> default 2011-12-19 16:52:48 +01:00
26c78cd952 comment xdg fallback behaviour 2011-12-19 16:05:55 +01:00
6b1d658d12 stest: restore stream testing 2011-12-09 11:49:44 +01:00
56a0d1fa14 fork dmenu_run to disown child shell 2011-12-09 11:36:26 +01:00
32ef0f5662 stest: cleanup 2011-11-27 23:40:48 +01:00
dcd6e771a1 stest: remove unneeded lstat 2011-11-27 23:37:01 +01:00
8cc28cb426 simplify stest 2011-11-27 23:35:09 +01:00
942199ccd0 dmenu_run: use $SHELL 2011-11-23 15:17:32 +01:00
3f9b10c86a set window background_pixel 2011-11-23 14:40:21 +01:00
5162d1b37a fix dmenu_path exec sh 2011-11-19 21:31:18 +01:00
a02a1a6623 faster dmenu_run -f 2011-11-19 21:24:07 +01:00
bb4424df07 replace lsx with stest 2011-11-19 19:54:55 +01:00
8ac44eb75a update manpage 2011-11-16 11:26:15 +01:00
ebd94a6c00 limit direction keys in vline 2011-11-15 20:32:39 +01:00
2b31952731 fix C-n / C-p directions (thanks bastien) 2011-11-14 20:02:16 +01:00
c01f36a1f3 new default colour scheme 2011-11-14 00:46:56 +01:00
168221b2f8 makefile: *.o depend on draw.h 2011-10-26 14:16:12 +01:00
dc77337ef3 add paste from clipboard 2011-10-26 13:28:15 +01:00
7b1493a631 add lots of comments 2011-10-26 13:20:14 +01:00
dd29c5d480 input focus: calculate areas of intersection 2011-10-26 12:14:50 +01:00
70b3418e3e simplify dmenu_run 2011-10-17 10:22:23 +01:00
cf641ae496 merge xim -> default 2011-10-17 10:11:48 +01:00
5e0156c072 merge lsx -> default 2011-10-17 10:08:04 +01:00
ca7ef6d2c9 use ~/.dmenu_cache if no xdg cache 2011-10-17 02:12:33 +01:00
7bbd4c56eb lsx: detect read errors 2011-10-17 01:44:07 +01:00
24565608cb xim: check for corner cases 2011-10-17 01:18:57 +01:00
05026fb5ce fix extra warning 2011-10-16 18:26:11 +01:00
c71abdc65c lsx: return failure on error 2011-10-16 18:14:51 +01:00
4126b1e323 merge default -> lsx 2011-10-16 18:13:29 +01:00
c8f0a37638 add xim support 2011-10-16 17:21:33 +01:00
1cf3864464 limit lines to input 2011-10-13 20:43:59 +01:00
8e302717cb input focus: fix for reparenting wms 2011-10-04 21:39:56 +01:00
e30ad490b2 fix monitor select when no focus 2011-09-30 21:08:37 +01:00
5020734408 merge token -> default 2011-09-20 00:11:01 +01:00
4c8d43ab73 tweak match 2011-09-20 00:09:20 +01:00
0582480801 update manpage 2011-09-20 00:06:13 +01:00
f1848a3ef0 detect active monitor using input focus 2011-09-19 18:15:03 +01:00
b26d3f54ee add token matching 2011-09-19 10:40:56 +01:00
3af80cd7d8 change version to 'hg' 2011-09-19 10:40:07 +01:00
4692f84ff0 dmenu_run: sh -c 2011-07-24 20:04:58 +01:00
c805dcbeaf fix loadfont 2011-07-22 18:16:57 +01:00
a6cbc6c9a7 Added tag 4.4 for changeset 2b105eaae831 2011-07-19 21:30:14 +01:00
12 changed files with 446 additions and 180 deletions

View File

@ -46,3 +46,4 @@ e4c81a78ffbad6ba4d1ad119cc654da6eca63a4c 3.2
abb6579a324fffdf6a23c2fa4c32911277da594a 4.2.1
14c79f054bdf43ff3213af8e60a783192e92a018 4.3
34a2d77049a95b02f3332a0b88f9370965ebcfad 4.3.1
2b105eaae8315b076da93056da9ecd60de5a7ac9 4.4

View File

@ -1,7 +1,7 @@
MIT/X Consortium License
© 2010-2011 Connor Lane Smith <cls@lubutu.com>
© 2006-2011 Anselm R Garbe <anselm@garbe.us>
© 2010-2012 Connor Lane Smith <cls@lubutu.com>
© 2006-2012 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 lsx.c
SRC = dmenu.c draw.c stest.c
OBJ = ${SRC:.c=.o}
all: options dmenu lsx
all: options dmenu stest
options:
@echo dmenu build options:
@ -18,24 +18,24 @@ options:
@echo CC -c $<
@${CC} -c $< ${CFLAGS}
${OBJ}: config.mk
${OBJ}: config.mk draw.h
dmenu: dmenu.o draw.o
@echo CC -o $@
@${CC} -o $@ dmenu.o draw.o ${LDFLAGS}
lsx: lsx.o
stest: stest.o
@echo CC -o $@
@${CC} -o $@ lsx.o ${LDFLAGS}
@${CC} -o $@ stest.o ${LDFLAGS}
clean:
@echo cleaning
@rm -f dmenu lsx ${OBJ} dmenu-${VERSION}.tar.gz
@rm -f dmenu stest ${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 lsx.1 ${SRC} dmenu-${VERSION}
@cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run stest.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 lsx ${DESTDIR}${PREFIX}/bin
@cp -f dmenu dmenu_run stest ${DESTDIR}${PREFIX}/bin
@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu
@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run
@chmod 755 ${DESTDIR}${PREFIX}/bin/lsx
@chmod 755 ${DESTDIR}${PREFIX}/bin/stest
@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" < lsx.1 > ${DESTDIR}${MANPREFIX}/man1/lsx.1
@sed "s/VERSION/${VERSION}/g" < stest.1 > ${DESTDIR}${MANPREFIX}/man1/stest.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/lsx.1
@chmod 644 ${DESTDIR}${MANPREFIX}/man1/stest.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/lsx
@rm -f ${DESTDIR}${PREFIX}/bin/stest
@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
@rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@rm -f ${DESTDIR}${MANPREFIX}/man1/lsx.1
@rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1
.PHONY: all options clean dist install uninstall

View File

@ -1,5 +1,5 @@
# dmenu version
VERSION = 4.4
VERSION = 4.5
# paths
PREFIX = /usr/local
@ -17,8 +17,9 @@ INCS = -I${X11INC}
LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS}
# flags
CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CFLAGS = -ansi -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
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}
LDFLAGS = -s ${LIBS}
# compiler and linker

108
dmenu.1
View File

@ -25,25 +25,23 @@ dmenu \- dynamic menu
.BR dmenu_run " ..."
.SH DESCRIPTION
.B dmenu
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.
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.
.P
.B dmenu_run
is a dmenu script used by dwm which lists programs in the user's $PATH and
executes the selected item.
is a script used by
.IR dwm (1)
which lists programs in the user's $PATH and runs the result in their $SHELL.
.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 may lock up
X if stdin is from a terminal.
dmenu grabs the keyboard before reading stdin. This is faster, but will lock up
X until stdin reaches end\-of\-file.
.TP
.B \-i
dmenu matches menu items case insensitively.
@ -75,25 +73,93 @@ defines the selected foreground color.
.B \-v
prints version information to stdout, then exits.
.SH USAGE
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:
dmenu is completely controlled by the keyboard. Items are selected using the
arrow keys, page up, page down, home, and end.
.TP
.B Tab (Ctrl\-i)
.B Tab
Copy the selected item to the input field.
.TP
.B Return (Ctrl\-j)
.B Return
Confirm selection. Prints the selected item to stdout and exits, returning
success.
.TP
.B Shift\-Return (Ctrl\-Shift\-j)
.B Shift\-Return
Confirm input. Prints the input text to stdout and exits, returning success.
.TP
.B Escape (Ctrl\-c)
.B Escape
Exit without selecting an item, returning failure.
.TP
.B Ctrl\-y
Paste the current X selection into the input field.
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
.SH SEE ALSO
.IR dwm (1),
.IR lsx (1)
.IR stest (1)

219
dmenu.c
View File

@ -13,9 +13,10 @@
#endif
#include "draw.h"
#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))
#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))
typedef struct Item Item;
struct Item {
@ -30,7 +31,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(Bool sub);
static void match(void);
static size_t nextrune(int inc);
static void paste(void);
static void readstdin(void);
@ -41,23 +42,24 @@ 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 = "#cccccc";
static const char *normfgcolor = "#000000";
static const char *selbgcolor = "#0066ff";
static const char *selfgcolor = "#ffffff";
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 unsigned long normcol[ColLast];
static unsigned long selcol[ColLast];
static Atom utf8;
static Atom clip, 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;
@ -68,35 +70,35 @@ main(int argc, char *argv[]) {
int i;
for(i = 1; i < argc; i++)
/* single flags */
if(!strcmp(argv[i], "-v")) {
puts("dmenu-"VERSION", © 2006-2011 dmenu engineers, see LICENSE for details");
/* these options take no arguments */
if(!strcmp(argv[i], "-v")) { /* prints version information */
puts("dmenu-"VERSION", © 2006-2012 dmenu engineers, see LICENSE for details");
exit(EXIT_SUCCESS);
}
else if(!strcmp(argv[i], "-b"))
else if(!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
topbar = False;
else if(!strcmp(argv[i], "-f"))
else if(!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
fast = True;
else if(!strcmp(argv[i], "-i")) {
else if(!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
fstrncmp = strncasecmp;
fstrstr = cistrstr;
}
else if(i+1 == argc)
usage();
/* double flags */
else if(!strcmp(argv[i], "-l"))
/* these options take one argument */
else if(!strcmp(argv[i], "-l")) /* number of lines in vertical list */
lines = atoi(argv[++i]);
else if(!strcmp(argv[i], "-p"))
else if(!strcmp(argv[i], "-p")) /* adds prompt to left of input field */
prompt = argv[++i];
else if(!strcmp(argv[i], "-fn"))
else if(!strcmp(argv[i], "-fn")) /* font or font set */
font = argv[++i];
else if(!strcmp(argv[i], "-nb"))
else if(!strcmp(argv[i], "-nb")) /* normal background color */
normbgcolor = argv[++i];
else if(!strcmp(argv[i], "-nf"))
else if(!strcmp(argv[i], "-nf")) /* normal foreground color */
normfgcolor = argv[++i];
else if(!strcmp(argv[i], "-sb"))
else if(!strcmp(argv[i], "-sb")) /* selected background color */
selbgcolor = argv[++i];
else if(!strcmp(argv[i], "-sf"))
else if(!strcmp(argv[i], "-sf")) /* selected foreground color */
selfgcolor = argv[++i];
else
usage();
@ -115,15 +117,15 @@ main(int argc, char *argv[]) {
setup();
run();
return EXIT_FAILURE; /* unreachable */
return 1; /* unreachable */
}
void
appenditem(Item *item, Item **list, Item **last) {
if(!*last)
*list = item;
else
if(*last)
(*last)->right = item;
else
*list = item;
item->left = *last;
item->right = NULL;
@ -138,7 +140,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;
@ -172,12 +174,14 @@ 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;
@ -185,6 +189,7 @@ drawmenu(void) {
}
}
else if(matches) {
/* draw horizontal list */
dc->x += inputw;
dc->w = textw(dc, "<");
if(curr->left)
@ -206,6 +211,7 @@ 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)
@ -219,24 +225,26 @@ 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(n > 0 && text[cursor] == '\0');
match();
}
void
keypress(XKeyEvent *ev) {
char buf[32];
KeySym ksym;
int len;
KeySym ksym = NoSymbol;
Status status;
XLookupString(ev, buf, sizeof buf, &ksym, NULL);
if(ev->state & ControlMask) {
KeySym lower, upper;
XConvertCase(ksym, &lower, &upper);
switch(lower) {
len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
if(status == XBufferOverflow)
return;
if(ev->state & ControlMask)
switch(ksym) {
case XK_a: ksym = XK_Home; break;
case XK_b: ksym = XK_Left; break;
case XK_c: ksym = XK_Escape; break;
@ -247,12 +255,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_Up; break;
case XK_p: ksym = XK_Down; break;
case XK_n: ksym = XK_Down; break;
case XK_p: ksym = XK_Up; break;
case XK_k: /* delete right */
text[cursor] = '\0';
match(False);
match();
break;
case XK_u: /* delete left */
insert(NULL, 0 - cursor);
@ -264,16 +272,27 @@ keypress(XKeyEvent *ev) {
insert(NULL, nextrune(-1) - cursor);
break;
case XK_y: /* paste selection */
XConvertSelection(dc->dpy, XA_PRIMARY, utf8, utf8, win, CurrentTime);
XConvertSelection(dc->dpy, (ev->state & ShiftMask) ? clip : 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, strlen(buf));
insert(buf, len);
break;
case XK_Delete:
if(text[cursor] == '\0')
@ -291,6 +310,7 @@ keypress(XKeyEvent *ev) {
break;
}
if(next) {
/* jump to end of list and position items in reverse */
curr = matchend;
calcoffsets();
curr = prev;
@ -315,6 +335,8 @@ keypress(XKeyEvent *ev) {
cursor = nextrune(-1);
break;
}
if(lines > 0)
return;
/* fallthrough */
case XK_Up:
if(sel && sel->left && (sel = sel->left)->right == curr) {
@ -343,6 +365,8 @@ keypress(XKeyEvent *ev) {
cursor = nextrune(+1);
break;
}
if(lines > 0)
return;
/* fallthrough */
case XK_Down:
if(sel && sel->right && (sel = sel->right) == next) {
@ -355,33 +379,46 @@ keypress(XKeyEvent *ev) {
return;
strncpy(text, sel->text, sizeof text);
cursor = strlen(text);
match(True);
match();
break;
}
drawmenu();
}
void
match(Bool sub) {
size_t len = strlen(text);
Item *lexact, *lprefix, *lsubstr, *exactend, *prefixend, *substrend;
Item *item, *lnext;
match(void) {
static char **tokv = NULL;
static int tokn = 0;
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))
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))
appenditem(item, &lprefix, &prefixend);
else if(fstrstr(item->text, text))
else
appenditem(item, &lsubstr, &substrend);
}
matches = lexact;
matchend = exactend;
if(lprefix) {
if(matchend) {
if(matches) {
matchend->right = lprefix;
lprefix->left = matchend;
}
@ -390,7 +427,7 @@ match(Bool sub) {
matchend = prefixend;
}
if(lsubstr) {
if(matchend) {
if(matches) {
matchend->right = lsubstr;
lsubstr->left = matchend;
}
@ -406,6 +443,7 @@ 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;
}
@ -417,6 +455,7 @@ 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));
@ -429,6 +468,7 @@ 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))))
@ -443,13 +483,16 @@ 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))
while(!XNextEvent(dc->dpy, &ev)) {
if(XFilterEvent(&ev, win))
continue;
switch(ev.type) {
case Expose:
if(ev.xexpose.count == 0)
@ -467,13 +510,15 @@ run(void) {
XRaiseWindow(dc->dpy, win);
break;
}
}
}
void
setup(void) {
int x, y, screen = DefaultScreen(dc->dpy);
Window root = RootWindow(dc->dpy, screen);
XSetWindowAttributes wa;
XSetWindowAttributes swa;
XIM xim;
#ifdef XINERAMA
int n;
XineramaScreenInfo *info;
@ -484,22 +529,41 @@ 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);
/* menu geometry */
/* calculate menu geometry */
bh = dc->font.height + 2;
lines = MAX(lines, 0);
mh = (lines + 1) * bh;
#ifdef XINERAMA
if((info = XineramaQueryScreens(dc->dpy, &n))) {
int i, di;
int a, j, di, i = 0, area = 0;
unsigned int du;
Window dw;
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;
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;
@ -514,16 +578,21 @@ setup(void) {
}
promptw = prompt ? textw(dc, prompt) : 0;
inputw = MIN(inputw, mw/3);
match(False);
match();
/* menu window */
wa.override_redirect = True;
wa.background_pixmap = ParentRelative;
wa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
/* create menu window */
swa.override_redirect = True;
swa.background_pixel = normcol[ColBG];
swa.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 | CWBackPixmap | CWEventMask, &wa);
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);
XMapRaised(dc->dpy, win);
resizedc(dc, mw, mh);

View File

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

25
draw.c
View File

@ -121,26 +121,27 @@ initfont(DC *dc, const char *fontstr) {
Bool
loadfont(DC *dc, const char *fontstr) {
char *def, **missing, **names;
int i, n = 1;
int i, n;
XFontStruct **xfonts;
if(!*fontstr)
return False;
if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def)))
if((dc->font.set = XCreateFontSet(dc->dpy, fontstr, &missing, &n, &def))) {
n = XFontsOfFontSet(dc->font.set, &xfonts, &names);
else if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr)))
xfonts = &dc->font.xfont;
else
n = 0;
for(i = 0; i < n; i++) {
dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent);
dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent);
dc->font.width = MAX(dc->font.width, xfonts[i]->max_bounds.width);
for(i = 0; i < n; i++) {
dc->font.ascent = MAX(dc->font.ascent, xfonts[i]->ascent);
dc->font.descent = MAX(dc->font.descent, xfonts[i]->descent);
dc->font.width = MAX(dc->font.width, xfonts[i]->max_bounds.width);
}
}
else if((dc->font.xfont = XLoadQueryFont(dc->dpy, fontstr))) {
dc->font.ascent = dc->font.xfont->ascent;
dc->font.descent = dc->font.xfont->descent;
dc->font.width = dc->font.xfont->max_bounds.width;
}
if(missing)
XFreeStringList(missing);
return (dc->font.set || dc->font.xfont);
return dc->font.set || dc->font.xfont;
}
void

11
lsx.1
View File

@ -1,11 +0,0 @@
.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
View File

@ -1,38 +0,0 @@
/* 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 Normal file
View File

@ -0,0 +1,87 @@
.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 Normal file
View File

@ -0,0 +1,84 @@
/* 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);
}
}