Compare commits

..

No commits in common. "master" and "4.7" have entirely different histories.
master ... 4.7

11 changed files with 248 additions and 360 deletions

12
LICENSE
View File

@ -1,15 +1,13 @@
MIT/X Consortium License MIT/X Consortium License
© 2006-2019 Anselm R Garbe <anselm@garbe.ca> © 2006-2014 Anselm R Garbe <anselm@garbe.us>
© 2006-2008 Sander van Dijk <a.h.vandijk@gmail.com> © 2010-2012 Connor Lane Smith <cls@lubutu.com>
© 2006-2007 Michał Janeczek <janeczek@gmail.com>
© 2007 Kris Maglione <jg@suckless.org>
© 2009 Gottox <gottox@s01.de> © 2009 Gottox <gottox@s01.de>
© 2009 Markus Schnalke <meillo@marmaro.de> © 2009 Markus Schnalke <meillo@marmaro.de>
© 2009 Evan Gates <evan.gates@gmail.com> © 2009 Evan Gates <evan.gates@gmail.com>
© 2010-2012 Connor Lane Smith <cls@lubutu.com> © 2006-2008 Sander van Dijk <a dot h dot vandijk at gmail dot com>
© 2014-2022 Hiltjo Posthuma <hiltjo@codemadness.org> © 2006-2007 Michał Janeczek <janeczek at gmail dot com>
© 2015-2019 Quentin Rameau <quinq@fifth.space> © 2014-2015 Hiltjo Posthuma <hiltjo@codemadness.org>
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),

View File

@ -4,55 +4,71 @@
include config.mk include config.mk
SRC = drw.c dmenu.c stest.c util.c SRC = drw.c dmenu.c stest.c util.c
OBJ = $(SRC:.c=.o) OBJ = ${SRC:.c=.o}
all: dmenu stest all: options dmenu stest
options:
@echo dmenu build options:
@echo "CFLAGS = ${CFLAGS}"
@echo "LDFLAGS = ${LDFLAGS}"
@echo "CC = ${CC}"
.c.o: .c.o:
$(CC) -c $(CFLAGS) $< @echo CC $<
@${CC} -c ${CFLAGS} $<
config.h: config.h:
cp config.def.h $@ @echo creating $@ from config.def.h
@cp config.def.h $@
$(OBJ): arg.h config.h config.mk drw.h ${OBJ}: arg.h config.h config.mk drw.h
dmenu: dmenu.o drw.o util.o dmenu: dmenu.o drw.o util.o
$(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS) @echo CC -o $@
@${CC} -o $@ dmenu.o drw.o util.o ${LDFLAGS}
stest: stest.o stest: stest.o
$(CC) -o $@ stest.o $(LDFLAGS) @echo CC -o $@
@${CC} -o $@ stest.o ${LDFLAGS}
clean: clean:
rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz @echo cleaning
@rm -f dmenu stest ${OBJ} dmenu-${VERSION}.tar.gz
dist: clean dist: clean
mkdir -p dmenu-$(VERSION) @echo creating dist tarball
cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\ @mkdir -p dmenu-${VERSION}
drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\ @cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1 \
dmenu-$(VERSION) drw.h util.h dmenu_path dmenu_run stest.1 ${SRC} \
tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION) dmenu-${VERSION}
gzip dmenu-$(VERSION).tar @tar -cf dmenu-${VERSION}.tar dmenu-${VERSION}
rm -rf dmenu-$(VERSION) @gzip dmenu-${VERSION}.tar
@rm -rf dmenu-${VERSION}
install: all install: all
mkdir -p $(DESTDIR)$(PREFIX)/bin @echo installing executables to ${DESTDIR}${PREFIX}/bin
cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin @mkdir -p ${DESTDIR}${PREFIX}/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu @cp -f dmenu dmenu_path dmenu_run stest ${DESTDIR}${PREFIX}/bin
chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu
chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_path
chmod 755 $(DESTDIR)$(PREFIX)/bin/stest @chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run
mkdir -p $(DESTDIR)$(MANPREFIX)/man1 @chmod 755 ${DESTDIR}${PREFIX}/bin/stest
sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 @echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1
sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1 @sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1
chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.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/stest.1
uninstall: uninstall:
rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\ @echo removing executables from ${DESTDIR}${PREFIX}/bin
$(DESTDIR)$(PREFIX)/bin/dmenu_path\ @rm -f ${DESTDIR}${PREFIX}/bin/dmenu
$(DESTDIR)$(PREFIX)/bin/dmenu_run\ @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_path
$(DESTDIR)$(PREFIX)/bin/stest\ @rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run
$(DESTDIR)$(MANPREFIX)/man1/dmenu.1\ @rm -f ${DESTDIR}${PREFIX}/bin/stest
$(DESTDIR)$(MANPREFIX)/man1/stest.1 @echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
@rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1
@rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1
.PHONY: all clean dist install uninstall .PHONY: all options clean dist install uninstall

View File

@ -1,9 +1,9 @@
# dmenu version # dmenu version
VERSION = 5.3 VERSION = 4.7
# paths # paths
PREFIX = /usr/local PREFIX = /usr/local
MANPREFIX = $(PREFIX)/share/man MANPREFIX = ${PREFIX}/share/man
X11INC = /usr/X11R6/include X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib X11LIB = /usr/X11R6/lib
@ -16,17 +16,16 @@ XINERAMAFLAGS = -DXINERAMA
FREETYPELIBS = -lfontconfig -lXft FREETYPELIBS = -lfontconfig -lXft
FREETYPEINC = /usr/include/freetype2 FREETYPEINC = /usr/include/freetype2
# OpenBSD (uncomment) # OpenBSD (uncomment)
#FREETYPEINC = $(X11INC)/freetype2 #FREETYPEINC = ${X11INC}/freetype2
#MANPREFIX = ${PREFIX}/man
# includes and libs # includes and libs
INCS = -I$(X11INC) -I$(FREETYPEINC) INCS = -I${X11INC} -I${FREETYPEINC}
LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS}
# flags # flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS) CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CFLAGS = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS) CFLAGS = -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
LDFLAGS = $(LIBS) LDFLAGS = -s ${LIBS}
# compiler and linker # compiler and linker
CC = cc CC = cc

68
dmenu.1
View File

@ -41,8 +41,8 @@ which lists programs in the user's $PATH and runs the result in their $SHELL.
dmenu appears at the bottom of the screen. dmenu appears at the bottom of the screen.
.TP .TP
.B \-f .B \-f
dmenu grabs the keyboard before reading stdin if not reading from a tty. This dmenu grabs the keyboard before reading stdin. This is faster, but will lock up
is faster, but will lock up X until stdin reaches end\-of\-file. X until stdin reaches end\-of\-file.
.TP .TP
.B \-i .B \-i
dmenu matches menu items case insensitively. dmenu matches menu items case insensitively.
@ -100,94 +100,82 @@ Confirm input. Prints the input text to stdout and exits, returning success.
.B Escape .B Escape
Exit without selecting an item, returning failure. Exit without selecting an item, returning failure.
.TP .TP
.B Ctrl-Left C\-a
Move cursor to the start of the current word
.TP
.B Ctrl-Right
Move cursor to the end of the current word
.TP
.B C\-a
Home Home
.TP .TP
.B C\-b C\-b
Left Left
.TP .TP
.B C\-c C\-c
Escape Escape
.TP .TP
.B C\-d C\-d
Delete Delete
.TP .TP
.B C\-e C\-e
End End
.TP .TP
.B C\-f C\-f
Right Right
.TP .TP
.B C\-g C\-g
Escape Escape
.TP .TP
.B C\-h C\-h
Backspace Backspace
.TP .TP
.B C\-i C\-i
Tab Tab
.TP .TP
.B C\-j C\-j
Return Return
.TP .TP
.B C\-J C\-J
Shift-Return Shift-Return
.TP .TP
.B C\-k C\-k
Delete line right Delete line right
.TP .TP
.B C\-m C\-m
Return Return
.TP .TP
.B C\-M C\-M
Shift-Return Shift-Return
.TP .TP
.B C\-n C\-n
Down Down
.TP .TP
.B C\-p C\-p
Up Up
.TP .TP
.B C\-u C\-u
Delete line left Delete line left
.TP .TP
.B C\-w C\-w
Delete word left Delete word left
.TP .TP
.B C\-y C\-y
Paste from primary X selection Paste from primary X selection
.TP .TP
.B C\-Y C\-Y
Paste from X clipboard Paste from X clipboard
.TP .TP
.B M\-b M\-g
Move cursor to the start of the current word
.TP
.B M\-f
Move cursor to the end of the current word
.TP
.B M\-g
Home Home
.TP .TP
.B M\-G M\-G
End End
.TP .TP
.B M\-h M\-h
Up Up
.TP .TP
.B M\-j M\-j
Page down Page down
.TP .TP
.B M\-k M\-k
Page up Page up
.TP .TP
.B M\-l M\-l
Down Down
.SH SEE ALSO .SH SEE ALSO
.IR dwm (1), .IR dwm (1),

196
dmenu.c
View File

@ -6,7 +6,6 @@
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <time.h> #include <time.h>
#include <unistd.h>
#include <X11/Xlib.h> #include <X11/Xlib.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
@ -22,6 +21,7 @@
/* macros */ /* macros */
#define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ #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))) * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
#define LENGTH(X) (sizeof X / sizeof X[0])
#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
/* enums */ /* enums */
@ -57,13 +57,6 @@ static Clr *scheme[SchemeLast];
static int (*fstrncmp)(const char *, const char *, size_t) = strncmp; static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
static char *(*fstrstr)(const char *, const char *) = strstr; static char *(*fstrstr)(const char *, const char *) = strstr;
static unsigned int
textw_clamp(const char *str, unsigned int n)
{
unsigned int w = drw_fontset_getwidth_clamp(drw, str, n) + lrpad;
return MIN(w, n);
}
static void static void
appenditem(struct item *item, struct item **list, struct item **last) appenditem(struct item *item, struct item **list, struct item **last)
{ {
@ -88,10 +81,10 @@ calcoffsets(void)
n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">")); n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
/* calculate which items will begin the next page and previous page */ /* calculate which items will begin the next page and previous page */
for (i = 0, next = curr; next; next = next->right) for (i = 0, next = curr; next; next = next->right)
if ((i += (lines > 0) ? bh : textw_clamp(next->text, n)) > n) if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
break; break;
for (i = 0, prev = curr; prev && prev->left; prev = prev->left) for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
if ((i += (lines > 0) ? bh : textw_clamp(prev->left->text, n)) > n) if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n)
break; break;
} }
@ -100,32 +93,22 @@ cleanup(void)
{ {
size_t i; size_t i;
XUngrabKeyboard(dpy, CurrentTime); XUngrabKey(dpy, AnyKey, AnyModifier, root);
for (i = 0; i < SchemeLast; i++) for (i = 0; i < SchemeLast; i++)
free(scheme[i]); free(scheme[i]);
for (i = 0; items && items[i].text; ++i)
free(items[i].text);
free(items);
drw_free(drw); drw_free(drw);
XSync(dpy, False); XSync(dpy, False);
XCloseDisplay(dpy); XCloseDisplay(dpy);
} }
static char * static char *
cistrstr(const char *h, const char *n) cistrstr(const char *s, const char *sub)
{ {
size_t i; size_t len;
if (!n[0]) for (len = strlen(sub); *s; s++)
return (char *)h; if (!strncasecmp(s, sub, len))
return (char *)s;
for (; *h; ++h) {
for (i = 0; n[i] && tolower((unsigned char)n[i]) ==
tolower((unsigned char)h[i]); ++i)
;
if (n[i] == '\0')
return (char *)h;
}
return NULL; return NULL;
} }
@ -161,7 +144,7 @@ drawmenu(void)
drw_setscheme(drw, scheme[SchemeNorm]); drw_setscheme(drw, scheme[SchemeNorm]);
drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0); drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);
curpos = TEXTW(text) - TEXTW(&text[cursor]); drw_font_getexts(drw->fonts, text, cursor, &curpos, NULL);
if ((curpos += lrpad / 2 - 1) < w) { if ((curpos += lrpad / 2 - 1) < w) {
drw_setscheme(drw, scheme[SchemeNorm]); drw_setscheme(drw, scheme[SchemeNorm]);
drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0); drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
@ -181,7 +164,7 @@ drawmenu(void)
} }
x += w; x += w;
for (item = curr; item != next; item = item->right) for (item = curr; item != next; item = item->right)
x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">")));
if (next) { if (next) {
w = TEXTW(">"); w = TEXTW(">");
drw_setscheme(drw, scheme[SchemeNorm]); drw_setscheme(drw, scheme[SchemeNorm]);
@ -241,7 +224,7 @@ match(void)
/* separate input text into tokens to be matched individually */ /* separate input text into tokens to be matched individually */
for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
die("cannot realloc %zu bytes:", tokn * sizeof *tokv); die("cannot realloc %u bytes:", tokn * sizeof *tokv);
len = tokc ? strlen(tokv[0]) : 0; len = tokc ? strlen(tokv[0]) : 0;
matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
@ -304,42 +287,18 @@ nextrune(int inc)
return n; return n;
} }
static void
movewordedge(int dir)
{
if (dir < 0) { /* move cursor to the start of the word*/
while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
cursor = nextrune(-1);
while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
cursor = nextrune(-1);
} else { /* move cursor to the end of the word */
while (text[cursor] && strchr(worddelimiters, text[cursor]))
cursor = nextrune(+1);
while (text[cursor] && !strchr(worddelimiters, text[cursor]))
cursor = nextrune(+1);
}
}
static void static void
keypress(XKeyEvent *ev) keypress(XKeyEvent *ev)
{ {
char buf[64]; char buf[32];
int len; int len;
KeySym ksym = NoSymbol; KeySym ksym = NoSymbol;
Status status; Status status;
len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status); len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
switch (status) { if (status == XBufferOverflow)
default: /* XLookupNone, XBufferOverflow */
return; return;
case XLookupChars: /* composed string from input method */ if (ev->state & ControlMask)
goto insert;
case XLookupKeySym:
case XLookupBoth: /* a KeySym and a string are returned: use keysym */
break;
}
if (ev->state & ControlMask) {
switch(ksym) { switch(ksym) {
case XK_a: ksym = XK_Home; break; case XK_a: ksym = XK_Home; break;
case XK_b: ksym = XK_Left; break; case XK_b: ksym = XK_Left; break;
@ -375,14 +334,6 @@ keypress(XKeyEvent *ev)
XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
utf8, utf8, win, CurrentTime); utf8, utf8, win, CurrentTime);
return; return;
case XK_Left:
case XK_KP_Left:
movewordedge(-1);
goto draw;
case XK_Right:
case XK_KP_Right:
movewordedge(+1);
goto draw;
case XK_Return: case XK_Return:
case XK_KP_Enter: case XK_KP_Enter:
break; break;
@ -392,14 +343,8 @@ keypress(XKeyEvent *ev)
default: default:
return; return;
} }
} else if (ev->state & Mod1Mask) { else if (ev->state & Mod1Mask)
switch(ksym) { switch(ksym) {
case XK_b:
movewordedge(-1);
goto draw;
case XK_f:
movewordedge(+1);
goto draw;
case XK_g: ksym = XK_Home; break; case XK_g: ksym = XK_Home; break;
case XK_G: ksym = XK_End; break; case XK_G: ksym = XK_End; break;
case XK_h: ksym = XK_Up; break; case XK_h: ksym = XK_Up; break;
@ -409,16 +354,12 @@ keypress(XKeyEvent *ev)
default: default:
return; return;
} }
}
switch(ksym) { switch(ksym) {
default: default:
insert: if (!iscntrl(*buf))
if (!iscntrl((unsigned char)*buf))
insert(buf, len); insert(buf, len);
break; break;
case XK_Delete: case XK_Delete:
case XK_KP_Delete:
if (text[cursor] == '\0') if (text[cursor] == '\0')
return; return;
cursor = nextrune(+1); cursor = nextrune(+1);
@ -429,7 +370,6 @@ insert:
insert(NULL, nextrune(-1) - cursor); insert(NULL, nextrune(-1) - cursor);
break; break;
case XK_End: case XK_End:
case XK_KP_End:
if (text[cursor] != '\0') { if (text[cursor] != '\0') {
cursor = strlen(text); cursor = strlen(text);
break; break;
@ -449,7 +389,6 @@ insert:
cleanup(); cleanup();
exit(1); exit(1);
case XK_Home: case XK_Home:
case XK_KP_Home:
if (sel == matches) { if (sel == matches) {
cursor = 0; cursor = 0;
break; break;
@ -458,7 +397,6 @@ insert:
calcoffsets(); calcoffsets();
break; break;
case XK_Left: case XK_Left:
case XK_KP_Left:
if (cursor > 0 && (!sel || !sel->left || lines > 0)) { if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
cursor = nextrune(-1); cursor = nextrune(-1);
break; break;
@ -467,21 +405,18 @@ insert:
return; return;
/* fallthrough */ /* fallthrough */
case XK_Up: case XK_Up:
case XK_KP_Up:
if (sel && sel->left && (sel = sel->left)->right == curr) { if (sel && sel->left && (sel = sel->left)->right == curr) {
curr = prev; curr = prev;
calcoffsets(); calcoffsets();
} }
break; break;
case XK_Next: case XK_Next:
case XK_KP_Next:
if (!next) if (!next)
return; return;
sel = curr = next; sel = curr = next;
calcoffsets(); calcoffsets();
break; break;
case XK_Prior: case XK_Prior:
case XK_KP_Prior:
if (!prev) if (!prev)
return; return;
sel = curr = prev; sel = curr = prev;
@ -498,7 +433,6 @@ insert:
sel->out = 1; sel->out = 1;
break; break;
case XK_Right: case XK_Right:
case XK_KP_Right:
if (text[cursor] != '\0') { if (text[cursor] != '\0') {
cursor = nextrune(+1); cursor = nextrune(+1);
break; break;
@ -507,7 +441,6 @@ insert:
return; return;
/* fallthrough */ /* fallthrough */
case XK_Down: case XK_Down:
case XK_KP_Down:
if (sel && sel->right && (sel = sel->right) == next) { if (sel && sel->right && (sel = sel->right) == next) {
curr = next; curr = next;
calcoffsets(); calcoffsets();
@ -516,14 +449,12 @@ insert:
case XK_Tab: case XK_Tab:
if (!sel) if (!sel)
return; return;
cursor = strnlen(sel->text, sizeof text - 1); strncpy(text, sel->text, sizeof text - 1);
memcpy(text, sel->text, cursor); text[sizeof text - 1] = '\0';
text[cursor] = '\0'; cursor = strlen(text);
match(); match();
break; break;
} }
draw:
drawmenu(); drawmenu();
} }
@ -536,39 +467,39 @@ paste(void)
Atom da; Atom da;
/* we have been given the current selection, now insert it into input */ /* we have been given the current selection, now insert it into input */
if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False, XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
utf8, &da, &di, &dl, &dl, (unsigned char **)&p) utf8, &da, &di, &dl, &dl, (unsigned char **)&p);
== Success && p) { insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); XFree(p);
XFree(p);
}
drawmenu(); drawmenu();
} }
static void static void
readstdin(void) readstdin(void)
{ {
char *line = NULL; char buf[sizeof text], *p;
size_t i, itemsiz = 0, linesiz = 0; size_t i, imax = 0, size = 0;
ssize_t len; unsigned int tmpmax = 0;
/* read each line from stdin and add it to the item list */ /* read each line from stdin and add it to the item list */
for (i = 0; (len = getline(&line, &linesiz, stdin)) != -1; i++) { for (i = 0; fgets(buf, sizeof buf, stdin); i++) {
if (i + 1 >= itemsiz) { if (i + 1 >= size / sizeof *items)
itemsiz += 256; if (!(items = realloc(items, (size += BUFSIZ))))
if (!(items = realloc(items, itemsiz * sizeof(*items)))) die("cannot realloc %u bytes:", size);
die("cannot realloc %zu bytes:", itemsiz * sizeof(*items)); if ((p = strchr(buf, '\n')))
} *p = '\0';
if (line[len - 1] == '\n') if (!(items[i].text = strdup(buf)))
line[len - 1] = '\0'; die("cannot strdup %u bytes:", strlen(buf) + 1);
if (!(items[i].text = strdup(line)))
die("strdup:");
items[i].out = 0; items[i].out = 0;
drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL);
if (tmpmax > inputw) {
inputw = tmpmax;
imax = i;
}
} }
free(line);
if (items) if (items)
items[i].text = NULL; items[i].text = NULL;
inputw = items ? TEXTW(items[imax].text) : 0;
lines = MIN(lines, i); lines = MIN(lines, i);
} }
@ -581,11 +512,6 @@ run(void)
if (XFilterEvent(&ev, win)) if (XFilterEvent(&ev, win))
continue; continue;
switch(ev.type) { switch(ev.type) {
case DestroyNotify:
if (ev.xdestroywindow.window != win)
break;
cleanup();
exit(1);
case Expose: case Expose:
if (ev.xexpose.count == 0) if (ev.xexpose.count == 0)
drw_map(drw, win, 0, 0, mw, mh); drw_map(drw, win, 0, 0, mw, mh);
@ -613,21 +539,22 @@ run(void)
static void static void
setup(void) setup(void)
{ {
int x, y, i, j; int x, y, i = 0;
unsigned int du; unsigned int du;
XSetWindowAttributes swa; XSetWindowAttributes swa;
XIM xim; XIM xim;
Window w, dw, *dws; Window w, dw, *dws;
XWindowAttributes wa; XWindowAttributes wa;
XClassHint ch = {"dmenu", "dmenu"};
#ifdef XINERAMA #ifdef XINERAMA
XineramaScreenInfo *info; XineramaScreenInfo *info;
Window pw; Window pw;
int a, di, n, area = 0; int a, j, di, n, area = 0;
#endif #endif
/* init appearance */ /* init appearance */
for (j = 0; j < SchemeLast; j++) scheme[SchemeNorm] = drw_scm_create(drw, colors[SchemeNorm], 2);
scheme[j] = drw_scm_create(drw, colors[j], 2); scheme[SchemeSel] = drw_scm_create(drw, colors[SchemeSel], 2);
scheme[SchemeOut] = drw_scm_create(drw, colors[SchemeOut], 2);
clip = XInternAtom(dpy, "CLIPBOARD", False); clip = XInternAtom(dpy, "CLIPBOARD", False);
utf8 = XInternAtom(dpy, "UTF8_STRING", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False);
@ -637,7 +564,6 @@ setup(void)
lines = MAX(lines, 0); lines = MAX(lines, 0);
mh = (lines + 1) * bh; mh = (lines + 1) * bh;
#ifdef XINERAMA #ifdef XINERAMA
i = 0;
if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
XGetInputFocus(dpy, &w, &di); XGetInputFocus(dpy, &w, &di);
if (mon >= 0 && mon < n) if (mon >= 0 && mon < n)
@ -659,7 +585,7 @@ setup(void)
/* no focused window is on screen, so use pointer location instead */ /* no focused window is on screen, so use pointer location instead */
if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
for (i = 0; i < n; i++) for (i = 0; i < n; i++)
if (INTERSECT(x, y, 1, 1, info[i]) != 0) if (INTERSECT(x, y, 1, 1, info[i]))
break; break;
x = info[i].x_org; x = info[i].x_org;
@ -677,29 +603,25 @@ setup(void)
mw = wa.width; mw = wa.width;
} }
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
inputw = mw / 3; /* input width: ~33% of monitor width */ inputw = MIN(inputw, mw/3);
match(); match();
/* create menu window */ /* create menu window */
swa.override_redirect = True; swa.override_redirect = True;
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel; swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
win = XCreateWindow(dpy, root, x, y, mw, mh, 0, win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
CopyFromParent, CopyFromParent, CopyFromParent, CopyFromParent, CopyFromParent, CopyFromParent,
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
XSetClassHint(dpy, win, &ch);
/* input methods */
if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
die("XOpenIM failed: could not open input device");
/* open input methods */
xim = XOpenIM(dpy, NULL, NULL, NULL);
xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win, XNFocusWindow, win, NULL); XNClientWindow, win, XNFocusWindow, win, NULL);
XMapRaised(dpy, win); XMapRaised(dpy, win);
if (embed) { if (embed) {
XReparentWindow(dpy, win, parentwin, x, y); XSelectInput(dpy, parentwin, FocusChangeMask);
XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
for (i = 0; i < du && dws[i] != win; ++i) for (i = 0; i < du && dws[i] != win; ++i)
XSelectInput(dpy, dws[i], FocusChangeMask); XSelectInput(dpy, dws[i], FocusChangeMask);
@ -714,8 +636,9 @@ setup(void)
static void static void
usage(void) usage(void)
{ {
die("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n" fputs("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] [-w windowid]\n", stderr);
exit(1);
} }
int int
@ -776,12 +699,7 @@ main(int argc, char *argv[])
die("no fonts could be loaded."); die("no fonts could be loaded.");
lrpad = drw->fonts->h; lrpad = drw->fonts->h;
#ifdef __OpenBSD__ if (fast) {
if (pledge("stdio rpath", NULL) == -1)
die("pledge");
#endif
if (fast && !isatty(0)) {
grabkeyboard(); grabkeyboard();
readstdin(); readstdin();
} else { } else {

12
dmenu_path Executable file → Normal file
View File

@ -1,10 +1,10 @@
#!/bin/sh #!/bin/sh
cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"}
cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}" if [ -d "$cachedir" ]; then
cache="$cachedir/dmenu_run" cache=$cachedir/dmenu_run
else
[ ! -e "$cachedir" ] && mkdir -p "$cachedir" cache=$HOME/.dmenu_cache # if no xdg dir, fall back to dotfile in ~
fi
IFS=: IFS=:
if stest -dqr -n "$cache" $PATH; then if stest -dqr -n "$cache" $PATH; then
stest -flx $PATH | sort -u | tee "$cache" stest -flx $PATH | sort -u | tee "$cache"

179
drw.c
View File

@ -9,40 +9,54 @@
#include "util.h" #include "util.h"
#define UTF_INVALID 0xFFFD #define UTF_INVALID 0xFFFD
#define UTF_SIZ 4
static int static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0};
utf8decode(const char *s_in, long *u, int *err) static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000};
static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
static long
utf8decodebyte(const char c, size_t *i)
{ {
static const unsigned char lens[] = { for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
/* 0XXXX */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
/* 10XXX */ 0, 0, 0, 0, 0, 0, 0, 0, /* invalid */ return (unsigned char)c & ~utfmask[*i];
/* 110XX */ 2, 2, 2, 2, return 0;
/* 1110X */ 3, 3, }
/* 11110 */ 4,
/* 11111 */ 0, /* invalid */ static size_t
}; utf8validate(long *u, size_t i)
static const unsigned char leading_mask[] = { 0x7F, 0x1F, 0x0F, 0x07 }; {
static const unsigned int overlong[] = { 0x0, 0x80, 0x0800, 0x10000 }; if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
*u = UTF_INVALID;
for (i = 1; *u > utfmax[i]; ++i)
;
return i;
}
static size_t
utf8decode(const char *c, long *u, size_t clen)
{
size_t i, j, len, type;
long udecoded;
const unsigned char *s = (const unsigned char *)s_in;
int len = lens[*s >> 3];
*u = UTF_INVALID; *u = UTF_INVALID;
*err = 1; if (!clen)
if (len == 0) return 0;
udecoded = utf8decodebyte(c[0], &len);
if (!BETWEEN(len, 1, UTF_SIZ))
return 1; return 1;
for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
long cp = s[0] & leading_mask[len - 1]; udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
for (int i = 1; i < len; ++i) { if (type)
if (s[i] == '\0' || (s[i] & 0xC0) != 0x80) return j;
return i;
cp = (cp << 6) | (s[i] & 0x3F);
} }
/* out of range, surrogate, overlong encoding */ if (j < len)
if (cp > 0x10FFFF || (cp >> 11) == 0x1B || cp < overlong[len - 1]) return 0;
return len; *u = udecoded;
utf8validate(u, len);
*err = 0;
*u = cp;
return len; return len;
} }
@ -81,7 +95,6 @@ drw_free(Drw *drw)
{ {
XFreePixmap(drw->dpy, drw->drawable); XFreePixmap(drw->dpy, drw->drawable);
XFreeGC(drw->dpy, drw->gc); XFreeGC(drw->dpy, drw->gc);
drw_fontset_free(drw->fonts);
free(drw); free(drw);
} }
@ -224,32 +237,29 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int
int int
drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert)
{ {
int ty, ellipsis_x = 0; char buf[1024];
unsigned int tmpw, ew, ellipsis_w = 0, ellipsis_len, hash, h0, h1; int ty;
unsigned int ew;
XftDraw *d = NULL; XftDraw *d = NULL;
Fnt *usedfont, *curfont, *nextfont; Fnt *usedfont, *curfont, *nextfont;
int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; size_t i, len;
int utf8strlen, utf8charlen, render = x || y || w || h;
long utf8codepoint = 0; long utf8codepoint = 0;
const char *utf8str; const char *utf8str;
FcCharSet *fccharset; FcCharSet *fccharset;
FcPattern *fcpattern; FcPattern *fcpattern;
FcPattern *match; FcPattern *match;
XftResult result; XftResult result;
int charexists = 0, overflow = 0; int charexists = 0;
/* keep track of a couple codepoints for which we have no match. */
static unsigned int nomatches[128], ellipsis_width, invalid_width;
static const char invalid[] = "<EFBFBD>";
if (!drw || (render && (!drw->scheme || !w)) || !text || !drw->fonts) if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
return 0; return 0;
if (!render) { if (!render) {
w = invert ? invert : ~invert; w = ~w;
} else { } else {
XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
if (w < lpad)
return x + w;
d = XftDrawCreate(drw->dpy, drw->drawable, d = XftDrawCreate(drw->dpy, drw->drawable,
DefaultVisual(drw->dpy, drw->screen), DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen)); DefaultColormap(drw->dpy, drw->screen));
@ -258,40 +268,18 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
} }
usedfont = drw->fonts; usedfont = drw->fonts;
if (!ellipsis_width && render)
ellipsis_width = drw_fontset_getwidth(drw, "...");
if (!invalid_width && render)
invalid_width = drw_fontset_getwidth(drw, invalid);
while (1) { while (1) {
ew = ellipsis_len = utf8err = utf8charlen = utf8strlen = 0; utf8strlen = 0;
utf8str = text; utf8str = text;
nextfont = NULL; nextfont = NULL;
while (*text) { while (*text) {
utf8charlen = utf8decode(text, &utf8codepoint, &utf8err); utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ);
for (curfont = drw->fonts; curfont; curfont = curfont->next) { for (curfont = drw->fonts; curfont; curfont = curfont->next) {
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint);
if (charexists) { if (charexists) {
drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); if (curfont == usedfont) {
if (ew + ellipsis_width <= w) { utf8strlen += utf8charlen;
/* keep track where the ellipsis still fits */
ellipsis_x = x + ew;
ellipsis_w = w - ew;
ellipsis_len = utf8strlen;
}
if (ew + tmpw > w) {
overflow = 1;
/* called from drw_fontset_getwidth_clamp():
* it wants the width AFTER the overflow
*/
if (!render)
x += tmpw;
else
utf8strlen = ellipsis_len;
} else if (curfont == usedfont) {
text += utf8charlen; text += utf8charlen;
utf8strlen += utf8err ? 0 : utf8charlen;
ew += utf8err ? 0 : tmpw;
} else { } else {
nextfont = curfont; nextfont = curfont;
} }
@ -299,31 +287,36 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
} }
} }
if (overflow || !charexists || nextfont || utf8err) if (!charexists || nextfont)
break; break;
else else
charexists = 0; charexists = 0;
} }
if (utf8strlen) { if (utf8strlen) {
if (render) { drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; /* shorten text if necessary */
XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--)
usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
}
x += ew;
w -= ew;
}
if (utf8err && (!render || invalid_width < w)) {
if (render)
drw_text(drw, x, y, w, h, 0, invalid, invert);
x += invalid_width;
w -= invalid_width;
}
if (render && overflow)
drw_text(drw, ellipsis_x, y, ellipsis_w, h, 0, "...", invert);
if (!*text || overflow) { if (len) {
memcpy(buf, utf8str, len);
buf[len] = '\0';
if (len < utf8strlen)
for (i = len; i && i > len - 3; buf[--i] = '.')
; /* NOP */
if (render) {
ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
usedfont->xfont, x, ty, (XftChar8 *)buf, len);
}
x += ew;
w -= ew;
}
}
if (!*text) {
break; break;
} else if (nextfont) { } else if (nextfont) {
charexists = 0; charexists = 0;
@ -333,15 +326,6 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
* character must be drawn. */ * character must be drawn. */
charexists = 1; charexists = 1;
hash = (unsigned int)utf8codepoint;
hash = ((hash >> 16) ^ hash) * 0x21F0AAAD;
hash = ((hash >> 15) ^ hash) * 0xD35A2D97;
h0 = ((hash >> 15) ^ hash) % LENGTH(nomatches);
h1 = (hash >> 17) % LENGTH(nomatches);
/* avoid expensive XftFontMatch call when we know we won't find a match */
if (nomatches[h0] == utf8codepoint || nomatches[h1] == utf8codepoint)
goto no_match;
fccharset = FcCharSetCreate(); fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, utf8codepoint); FcCharSetAddChar(fccharset, utf8codepoint);
@ -369,8 +353,6 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
curfont->next = usedfont; curfont->next = usedfont;
} else { } else {
xfont_free(usedfont); xfont_free(usedfont);
nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint;
no_match:
usedfont = drw->fonts; usedfont = drw->fonts;
} }
} }
@ -400,15 +382,6 @@ drw_fontset_getwidth(Drw *drw, const char *text)
return drw_text(drw, 0, 0, 0, 0, 0, text, 0); return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
} }
unsigned int
drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n)
{
unsigned int tmp = 0;
if (drw && drw->fonts && text && n)
tmp = drw_text(drw, 0, 0, 0, 0, 0, text, n);
return MIN(n, tmp);
}
void void
drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
{ {

1
drw.h
View File

@ -35,7 +35,6 @@ void drw_free(Drw *drw);
Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount);
void drw_fontset_free(Fnt* set); void drw_fontset_free(Fnt* set);
unsigned int drw_fontset_getwidth(Drw *drw, const char *text); unsigned int drw_fontset_getwidth(Drw *drw, const char *text);
unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n);
void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
/* Colorscheme abstraction */ /* Colorscheme abstraction */

View File

@ -84,7 +84,7 @@ main(int argc, char *argv[])
if (!argc) { if (!argc) {
/* read list from stdin */ /* read list from stdin */
while ((n = getline(&line, &linesiz, stdin)) > 0) { while ((n = getline(&line, &linesiz, stdin)) > 0) {
if (line[n - 1] == '\n') if (n && line[n - 1] == '\n')
line[n - 1] = '\0'; line[n - 1] = '\0';
test(line, line); test(line, line);
} }

38
util.c
View File

@ -1,5 +1,4 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE file for copyright and license details. */
#include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -7,25 +6,6 @@
#include "util.h" #include "util.h"
void
die(const char *fmt, ...)
{
va_list ap;
int saved_errno;
saved_errno = errno;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] && fmt[strlen(fmt)-1] == ':')
fprintf(stderr, " %s", strerror(saved_errno));
fputc('\n', stderr);
exit(1);
}
void * void *
ecalloc(size_t nmemb, size_t size) ecalloc(size_t nmemb, size_t size)
{ {
@ -35,3 +15,21 @@ ecalloc(size_t nmemb, size_t size)
die("calloc:"); die("calloc:");
return p; return p;
} }
void
die(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
exit(1);
}

1
util.h
View File

@ -3,7 +3,6 @@
#define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B))
#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
#define LENGTH(X) (sizeof (X) / sizeof (X)[0])
void die(const char *fmt, ...); void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size); void *ecalloc(size_t nmemb, size_t size);