Compare commits

..

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

13 changed files with 482 additions and 643 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

4
arg.h
View File

@ -10,8 +10,8 @@ extern char *argv0;
/* use main(int argc, char *argv[]) */ /* use main(int argc, char *argv[]) */
#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ #define ARGBEGIN for (argv0 = *argv, argv++, argc--;\
argv[0] && argv[0][0] == '-'\ argv[0] && argv[0][1]\
&& argv[0][1];\ && argv[0][0] == '-';\
argc--, argv++) {\ argc--, argv++) {\
char argc_;\ char argc_;\
char **argv_;\ char **argv_;\

View File

@ -6,18 +6,12 @@ static int topbar = 1; /* -b option; if 0, dmenu appears a
static const char *fonts[] = { static const char *fonts[] = {
"monospace:size=10" "monospace:size=10"
}; };
static const char *prompt = NULL; /* -p option; prompt to the left of input field */ static const char *prompt = NULL; /* -p option; prompt to the elft of input field */
static const char *colors[SchemeLast][2] = { static const char *normbgcolor = "#222222"; /* -nb option; normal background */
/* fg bg */ static const char *normfgcolor = "#bbbbbb"; /* -nf option; normal foreground */
[SchemeNorm] = { "#bbbbbb", "#222222" }, static const char *selbgcolor = "#005577"; /* -sb option; selected background */
[SchemeSel] = { "#eeeeee", "#005577" }, static const char *selfgcolor = "#eeeeee"; /* -sf option; selected foreground */
[SchemeOut] = { "#000000", "#00ffff" }, static const char *outbgcolor = "#00ffff";
}; static const char *outfgcolor = "#000000";
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */ /* -l option; if nonzero, dmenu uses vertical list with given number of lines */
static unsigned int lines = 0; static unsigned int lines = 0;
/*
* Characters not considered part of a word while deleting words
* for example: " /?\"&[]"
*/
static const char worddelimiters[] = " ";

View File

@ -1,9 +1,9 @@
# dmenu version # dmenu version
VERSION = 5.3 VERSION = 4.6
# 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_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

80
dmenu.1
View File

@ -3,11 +3,13 @@
dmenu \- dynamic menu dmenu \- dynamic menu
.SH SYNOPSIS .SH SYNOPSIS
.B dmenu .B dmenu
.RB [ \-bfiv ] .RB [ \-b ]
.RB [ \-f ]
.RB [ \-i ]
.RB [ \-l .RB [ \-l
.IR lines ]
.RB [ \-m .RB [ \-m
.IR monitor ] .IR monitor ]
.IR lines ]
.RB [ \-p .RB [ \-p
.IR prompt ] .IR prompt ]
.RB [ \-fn .RB [ \-fn
@ -20,8 +22,7 @@ dmenu \- dynamic menu
.IR color ] .IR color ]
.RB [ \-sf .RB [ \-sf
.IR color ] .IR color ]
.RB [ \-w .RB [ \-v ]
.IR windowid ]
.P .P
.BR dmenu_run " ..." .BR dmenu_run " ..."
.SH DESCRIPTION .SH DESCRIPTION
@ -41,8 +42,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.
@ -77,9 +78,6 @@ defines the selected foreground color.
.TP .TP
.B \-v .B \-v
prints version information to stdout, then exits. prints version information to stdout, then exits.
.TP
.BI \-w " windowid"
embed into windowid.
.SH USAGE .SH USAGE
dmenu is completely controlled by the keyboard. Items are selected using the dmenu is completely controlled by the keyboard. Items are selected using the
arrow keys, page up, page down, home, and end. arrow keys, page up, page down, home, and end.
@ -100,94 +98,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),

385
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,7 +21,9 @@
/* 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 TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) #define LENGTH(X) (sizeof X / sizeof X[0])
#define TEXTNW(X,N) (drw_font_getexts_width(drw->fonts[0], (X), (N)))
#define TEXTW(X) (drw_text(drw, 0, 0, 0, 0, (X), 0) + drw->fonts[0]->h)
/* enums */ /* enums */
enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */ enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
@ -34,10 +35,9 @@ struct item {
}; };
static char text[BUFSIZ] = ""; static char text[BUFSIZ] = "";
static char *embed;
static int bh, mw, mh; static int bh, mw, mh;
static int inputw = 0, promptw; static int sw, sh; /* X display screen geometry width, height */
static int lrpad; /* sum of left and right padding */ static int inputw, promptw;
static size_t cursor; static size_t cursor;
static struct item *items = NULL; static struct item *items = NULL;
static struct item *matches, *matchend; static struct item *matches, *matchend;
@ -46,24 +46,17 @@ static int mon = -1, screen;
static Atom clip, utf8; static Atom clip, utf8;
static Display *dpy; static Display *dpy;
static Window root, parentwin, win; static Window root, win;
static XIC xic; static XIC xic;
static ClrScheme scheme[SchemeLast];
static Drw *drw; static Drw *drw;
static Clr *scheme[SchemeLast];
#include "config.h" #include "config.h"
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,130 +93,110 @@ 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]); drw_clr_free(scheme[i].bg);
for (i = 0; items && items[i].text; ++i) drw_clr_free(scheme[i].fg);
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;
} }
static int
drawitem(struct item *item, int x, int y, int w)
{
if (item == sel)
drw_setscheme(drw, scheme[SchemeSel]);
else if (item->out)
drw_setscheme(drw, scheme[SchemeOut]);
else
drw_setscheme(drw, scheme[SchemeNorm]);
return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
}
static void static void
drawmenu(void) drawmenu(void)
{ {
unsigned int curpos; int curpos;
struct item *item; struct item *item;
int x = 0, y = 0, w; int x = 0, y = 0, h = bh, w;
drw_setscheme(drw, scheme[SchemeNorm]); drw_setscheme(drw, &scheme[SchemeNorm]);
drw_rect(drw, 0, 0, mw, mh, 1, 1); drw_rect(drw, 0, 0, mw, mh, 1, 1, 1);
if (prompt && *prompt) { if (prompt && *prompt) {
drw_setscheme(drw, scheme[SchemeSel]); drw_setscheme(drw, &scheme[SchemeSel]);
x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); drw_text(drw, x, 0, promptw, bh, prompt, 0);
x += promptw;
} }
/* draw input field */ /* draw input field */
w = (lines > 0 || !matches) ? mw - x : inputw; w = (lines > 0 || !matches) ? mw - x : inputw;
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, text, 0);
curpos = TEXTW(text) - TEXTW(&text[cursor]); if ((curpos = TEXTNW(text, cursor) + bh / 2 - 2) < w) {
if ((curpos += lrpad / 2 - 1) < w) { drw_setscheme(drw, &scheme[SchemeNorm]);
drw_setscheme(drw, scheme[SchemeNorm]); drw_rect(drw, x + curpos + 2, 2, 1, bh - 4, 1, 1, 0);
drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
} }
if (lines > 0) { if (lines > 0) {
/* draw vertical list */ /* draw vertical list */
for (item = curr; item != next; item = item->right) w = mw - x;
drawitem(item, x, y += bh, mw - x); for (item = curr; item != next; item = item->right) {
y += h;
if (item == sel)
drw_setscheme(drw, &scheme[SchemeSel]);
else if (item->out)
drw_setscheme(drw, &scheme[SchemeOut]);
else
drw_setscheme(drw, &scheme[SchemeNorm]);
drw_text(drw, x, y, w, bh, item->text, 0);
}
} else if (matches) { } else if (matches) {
/* draw horizontal list */ /* draw horizontal list */
x += inputw; x += inputw;
w = TEXTW("<"); w = TEXTW("<");
if (curr->left) { if (curr->left) {
drw_setscheme(drw, scheme[SchemeNorm]); drw_setscheme(drw, &scheme[SchemeNorm]);
drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0); drw_text(drw, x, 0, w, bh, "<", 0);
} }
x += w; for (item = curr; item != next; item = item->right) {
for (item = curr; item != next; item = item->right) x += w;
x = drawitem(item, x, 0, textw_clamp(item->text, mw - x - TEXTW(">"))); w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
if (item == sel)
drw_setscheme(drw, &scheme[SchemeSel]);
else if (item->out)
drw_setscheme(drw, &scheme[SchemeOut]);
else
drw_setscheme(drw, &scheme[SchemeNorm]);
drw_text(drw, x, 0, w, bh, item->text, 0);
}
w = TEXTW(">");
x = mw - w;
if (next) { if (next) {
w = TEXTW(">"); drw_setscheme(drw, &scheme[SchemeNorm]);
drw_setscheme(drw, scheme[SchemeNorm]); drw_text(drw, x, 0, w, bh, ">", 0);
drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
} }
} }
drw_map(drw, win, 0, 0, mw, mh); drw_map(drw, win, 0, 0, mw, mh);
} }
static void
grabfocus(void)
{
struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 };
Window focuswin;
int i, revertwin;
for (i = 0; i < 100; ++i) {
XGetInputFocus(dpy, &focuswin, &revertwin);
if (focuswin == win)
return;
XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
nanosleep(&ts, NULL);
}
die("cannot grab focus");
}
static void static void
grabkeyboard(void) grabkeyboard(void)
{ {
struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 };
int i; int i;
if (embed)
return;
/* try to grab keyboard, we may have to wait for another process to ungrab */ /* try to grab keyboard, we may have to wait for another process to ungrab */
for (i = 0; i < 1000; i++) { for (i = 0; i < 1000; i++) {
if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True,
GrabModeAsync, CurrentTime) == GrabSuccess) GrabModeAsync, GrabModeAsync, CurrentTime) == GrabSuccess)
return; return;
nanosleep(&ts, NULL); nanosleep(&ts, NULL);
} }
die("cannot grab keyboard"); die("cannot grab keyboard\n");
} }
static void static void
@ -241,11 +214,11 @@ 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\n", 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;
textsize = strlen(text) + 1; textsize = strlen(text);
for (item = items; item && item->text; item++) { for (item = items; item && item->text; item++) {
for (i = 0; i < tokc; i++) for (i = 0; i < tokc; i++)
if (!fstrstr(item->text, tokv[i])) if (!fstrstr(item->text, tokv[i]))
@ -304,42 +277,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;
@ -365,9 +314,9 @@ keypress(XKeyEvent *ev)
insert(NULL, 0 - cursor); insert(NULL, 0 - cursor);
break; break;
case XK_w: /* delete word */ case XK_w: /* delete word */
while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)])) while (cursor > 0 && text[nextrune(-1)] == ' ')
insert(NULL, nextrune(-1) - cursor); insert(NULL, nextrune(-1) - cursor);
while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)])) while (cursor > 0 && text[nextrune(-1)] != ' ')
insert(NULL, nextrune(-1) - cursor); insert(NULL, nextrune(-1) - cursor);
break; break;
case XK_y: /* paste selection */ case XK_y: /* paste selection */
@ -375,14 +324,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 +333,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 +344,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 +360,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 +379,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 +387,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 +395,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 +423,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 +431,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 +439,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 +457,35 @@ 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, *maxstr = NULL;
size_t i, itemsiz = 0, linesiz = 0; size_t i, max = 0, size = 0;
ssize_t len;
/* 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;
if (strlen(items[i].text) > max)
max = strlen(maxstr = items[i].text);
} }
free(line);
if (items) if (items)
items[i].text = NULL; items[i].text = NULL;
inputw = maxstr ? TEXTW(maxstr) : 0;
lines = MIN(lines, i); lines = MIN(lines, i);
} }
@ -581,20 +498,10 @@ 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);
break; break;
case FocusIn:
/* regrab focus from parent window */
if (ev.xfocus.window != win)
grabfocus();
break;
case KeyPress: case KeyPress:
keypress(&ev.xkey); keypress(&ev.xkey);
break; break;
@ -613,34 +520,36 @@ run(void)
static void static void
setup(void) setup(void)
{ {
int x, y, i, j; int x, y;
unsigned int du;
XSetWindowAttributes swa; XSetWindowAttributes swa;
XIM xim; XIM xim;
Window w, dw, *dws;
XWindowAttributes wa;
XClassHint ch = {"dmenu", "dmenu"};
#ifdef XINERAMA #ifdef XINERAMA
XineramaScreenInfo *info; XineramaScreenInfo *info;
Window pw; Window w, pw, dw, *dws;
int a, di, n, area = 0; XWindowAttributes wa;
int a, j, di, n, i = 0, area = 0;
unsigned int du;
#endif #endif
/* init appearance */ /* init appearance */
for (j = 0; j < SchemeLast; j++) scheme[SchemeNorm].bg = drw_clr_create(drw, normbgcolor);
scheme[j] = drw_scm_create(drw, colors[j], 2); scheme[SchemeNorm].fg = drw_clr_create(drw, normfgcolor);
scheme[SchemeSel].bg = drw_clr_create(drw, selbgcolor);
scheme[SchemeSel].fg = drw_clr_create(drw, selfgcolor);
scheme[SchemeOut].bg = drw_clr_create(drw, outbgcolor);
scheme[SchemeOut].fg = drw_clr_create(drw, outfgcolor);
clip = XInternAtom(dpy, "CLIPBOARD", False); clip = XInternAtom(dpy, "CLIPBOARD", False);
utf8 = XInternAtom(dpy, "UTF8_STRING", False); utf8 = XInternAtom(dpy, "UTF8_STRING", False);
/* calculate menu geometry */ /* calculate menu geometry */
bh = drw->fonts->h + 2; bh = drw->fonts[0]->h + 2;
lines = MAX(lines, 0); lines = MAX(lines, 0);
mh = (lines + 1) * bh; mh = (lines + 1) * bh;
#ifdef XINERAMA #ifdef XINERAMA
i = 0; if ((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 != -1 && mon < n)
i = mon; i = mon;
else if (w != root && w != PointerRoot && w != None) { else if (w != root && w != PointerRoot && w != None) {
/* find top-level window containing current input focus */ /* find top-level window containing current input focus */
@ -657,9 +566,9 @@ 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 == -1 && !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;
@ -669,44 +578,29 @@ setup(void)
} else } else
#endif #endif
{ {
if (!XGetWindowAttributes(dpy, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
parentwin);
x = 0; x = 0;
y = topbar ? 0 : wa.height - mh; y = topbar ? 0 : sh - mh;
mw = wa.width; mw = sw;
} }
promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0; promptw = (prompt && *prompt) ? TEXTW(prompt) : 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].bg->pix;
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
win = XCreateWindow(dpy, root, x, y, mw, mh, 0, win = XCreateWindow(dpy, root, x, y, mw, mh, 0,
CopyFromParent, CopyFromParent, CopyFromParent, DefaultDepth(dpy, screen), CopyFromParent,
DefaultVisual(dpy, screen),
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) {
XReparentWindow(dpy, win, parentwin, x, y);
XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
for (i = 0; i < du && dws[i] != win; ++i)
XSelectInput(dpy, dws[i], FocusChangeMask);
XFree(dws);
}
grabfocus();
}
drw_resize(drw, mw, mh); drw_resize(drw, mw, mh);
drawmenu(); drawmenu();
} }
@ -714,14 +608,14 @@ 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 [-b] [-f] [-i] [-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] [-v]\n", stderr);
exit(1);
} }
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
XWindowAttributes wa;
int i, fast = 0; int i, fast = 0;
for (i = 1; i < argc; i++) for (i = 1; i < argc; i++)
@ -748,40 +642,31 @@ main(int argc, char *argv[])
else if (!strcmp(argv[i], "-fn")) /* font or font set */ else if (!strcmp(argv[i], "-fn")) /* font or font set */
fonts[0] = argv[++i]; fonts[0] = argv[++i];
else if (!strcmp(argv[i], "-nb")) /* normal background color */ else if (!strcmp(argv[i], "-nb")) /* normal background color */
colors[SchemeNorm][ColBg] = argv[++i]; normbgcolor = argv[++i];
else if (!strcmp(argv[i], "-nf")) /* normal foreground color */ else if (!strcmp(argv[i], "-nf")) /* normal foreground color */
colors[SchemeNorm][ColFg] = argv[++i]; normfgcolor = argv[++i];
else if (!strcmp(argv[i], "-sb")) /* selected background color */ else if (!strcmp(argv[i], "-sb")) /* selected background color */
colors[SchemeSel][ColBg] = argv[++i]; selbgcolor = argv[++i];
else if (!strcmp(argv[i], "-sf")) /* selected foreground color */ else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
colors[SchemeSel][ColFg] = argv[++i]; selfgcolor = argv[++i];
else if (!strcmp(argv[i], "-w")) /* embedding window id */
embed = argv[++i];
else else
usage(); usage();
if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
fputs("warning: no locale support\n", stderr); fputs("warning: no locale support\n", stderr);
if (!(dpy = XOpenDisplay(NULL))) if (!(dpy = XOpenDisplay(NULL)))
die("cannot open display"); die("cannot open display\n");
screen = DefaultScreen(dpy); screen = DefaultScreen(dpy);
root = RootWindow(dpy, screen); root = RootWindow(dpy, screen);
if (!embed || !(parentwin = strtol(embed, NULL, 0))) sw = DisplayWidth(dpy, screen);
parentwin = root; sh = DisplayHeight(dpy, screen);
if (!XGetWindowAttributes(dpy, parentwin, &wa)) drw = drw_create(dpy, screen, root, sw, sh);
die("could not get embedding window attributes: 0x%lx", drw_load_fonts(drw, fonts, LENGTH(fonts));
parentwin); if (!drw->fontcount)
drw = drw_create(dpy, screen, root, wa.width, wa.height); die("no fonts could be loaded.\n");
if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) drw_setscheme(drw, &scheme[SchemeNorm]);
die("no fonts could be loaded.");
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"

404
drw.c
View File

@ -9,48 +9,63 @@
#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;
} }
Drw * Drw *
drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
{ {
Drw *drw = ecalloc(1, sizeof(Drw)); Drw *drw;
drw = ecalloc(1, sizeof(Drw));
drw->dpy = dpy; drw->dpy = dpy;
drw->screen = screen; drw->screen = screen;
drw->root = root; drw->root = root;
@ -58,6 +73,7 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h
drw->h = h; drw->h = h;
drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
drw->gc = XCreateGC(dpy, root, 0, NULL); drw->gc = XCreateGC(dpy, root, 0, NULL);
drw->fontcount = 0;
XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
return drw; return drw;
@ -66,9 +82,6 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h
void void
drw_resize(Drw *drw, unsigned int w, unsigned int h) drw_resize(Drw *drw, unsigned int w, unsigned int h)
{ {
if (!drw)
return;
drw->w = w; drw->w = w;
drw->h = h; drw->h = h;
if (drw->drawable) if (drw->drawable)
@ -79,57 +92,84 @@ drw_resize(Drw *drw, unsigned int w, unsigned int h)
void void
drw_free(Drw *drw) drw_free(Drw *drw)
{ {
size_t i;
for (i = 0; i < drw->fontcount; i++)
drw_font_free(drw->fonts[i]);
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);
} }
/* This function is an implementation detail. Library users should use /* This function is an implementation detail. Library users should use
* drw_fontset_create instead. * drw_font_create instead.
*/ */
static Fnt * static Fnt *
xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) drw_font_xcreate(Drw *drw, const char *fontname, FcPattern *fontpattern)
{ {
Fnt *font; Fnt *font;
XftFont *xfont = NULL; XftFont *xfont = NULL;
FcPattern *pattern = NULL; FcPattern *pattern = NULL;
if (fontname) { if (fontname) {
/* Using the pattern found at font->xfont->pattern does not yield the /* Using the pattern found at font->xfont->pattern does not yield same
* same substitution results as using the pattern returned by * the same substitution results as using the pattern returned by
* FcNameParse; using the latter results in the desired fallback * FcNameParse; using the latter results in the desired fallback
* behaviour whereas the former just results in missing-character * behaviour whereas the former just results in
* rectangles being drawn, at least with some fonts. */ * missing-character-rectangles being drawn, at least with some fonts.
*/
if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); fprintf(stderr, "error, cannot load font: '%s'\n", fontname);
return NULL; return NULL;
} }
if (!(pattern = FcNameParse((FcChar8 *) fontname))) { if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); fprintf(stderr, "error, cannot load font: '%s'\n", fontname);
XftFontClose(drw->dpy, xfont); XftFontClose(drw->dpy, xfont);
return NULL; return NULL;
} }
} else if (fontpattern) { } else if (fontpattern) {
if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
fprintf(stderr, "error, cannot load font from pattern.\n"); fprintf(stderr, "error, cannot load font pattern.\n");
return NULL; return NULL;
} }
} else { } else {
die("no font specified."); die("no font specified.\n");
} }
font = ecalloc(1, sizeof(Fnt)); font = ecalloc(1, sizeof(Fnt));
font->xfont = xfont; font->xfont = xfont;
font->pattern = pattern; font->pattern = pattern;
font->h = xfont->ascent + xfont->descent; font->ascent = xfont->ascent;
font->descent = xfont->descent;
font->h = font->ascent + font->descent;
font->dpy = drw->dpy; font->dpy = drw->dpy;
return font; return font;
} }
static void Fnt*
xfont_free(Fnt *font) drw_font_create(Drw *drw, const char *fontname)
{
return drw_font_xcreate(drw, fontname, NULL);
}
void
drw_load_fonts(Drw* drw, const char *fonts[], size_t fontcount)
{
size_t i;
Fnt *font;
for (i = 0; i < fontcount; i++) {
if (drw->fontcount >= DRW_FONT_CACHE_SIZE) {
die("Font cache exhausted.\n");
} else if ((font = drw_font_xcreate(drw, fonts[i], NULL))) {
drw->fonts[drw->fontcount++] = font;
}
}
}
void
drw_font_free(Fnt *font)
{ {
if (!font) if (!font)
return; return;
@ -139,218 +179,150 @@ xfont_free(Fnt *font)
free(font); free(font);
} }
Fnt* Clr *
drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) drw_clr_create(Drw *drw, const char *clrname)
{ {
Fnt *cur, *ret = NULL; Clr *clr;
size_t i;
if (!drw || !fonts)
return NULL;
for (i = 1; i <= fontcount; i++) {
if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
cur->next = ret;
ret = cur;
}
}
return (drw->fonts = ret);
}
void
drw_fontset_free(Fnt *font)
{
if (font) {
drw_fontset_free(font->next);
xfont_free(font);
}
}
void
drw_clr_create(Drw *drw, Clr *dest, const char *clrname)
{
if (!drw || !dest || !clrname)
return;
clr = ecalloc(1, sizeof(Clr));
if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen),
DefaultColormap(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen),
clrname, dest)) clrname, &clr->rgb))
die("error, cannot allocate color '%s'", clrname); die("error, cannot allocate color '%s'\n", clrname);
} clr->pix = clr->rgb.pixel;
/* Wrapper to create color schemes. The caller has to call free(3) on the return clr;
* returned color scheme when done using it. */
Clr *
drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount)
{
size_t i;
Clr *ret;
/* need at least two colors for a scheme */
if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor))))
return NULL;
for (i = 0; i < clrcount; i++)
drw_clr_create(drw, &ret[i], clrnames[i]);
return ret;
} }
void void
drw_setfontset(Drw *drw, Fnt *set) drw_clr_free(Clr *clr)
{ {
if (drw) free(clr);
drw->fonts = set;
} }
void void
drw_setscheme(Drw *drw, Clr *scm) drw_setscheme(Drw *drw, ClrScheme *scheme)
{ {
if (drw) drw->scheme = scheme;
drw->scheme = scm;
} }
void void
drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int empty, int invert)
{ {
if (!drw || !drw->scheme) if (!drw->scheme)
return; return;
XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->bg->pix : drw->scheme->fg->pix);
if (filled) if (filled)
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w + 1, h + 1);
else else if (empty)
XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
} }
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, 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 tx, ty, th;
Extnts tex;
XftDraw *d = NULL; XftDraw *d = NULL;
Fnt *usedfont, *curfont, *nextfont; Fnt *curfont, *nextfont;
int utf8strlen, utf8charlen, utf8err, render = x || y || w || h; size_t i, len;
int utf8strlen, utf8charlen, render;
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->scheme || !drw->fontcount)
return 0; return 0;
if (!render) { if (!(render = x || y || w || h)) {
w = invert ? invert : ~invert; w = ~w;
} else { } else {
XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); XSetForeground(drw->dpy, drw->gc, invert ?
drw->scheme->fg->pix : drw->scheme->bg->pix);
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));
x += lpad;
w -= lpad;
} }
usedfont = drw->fonts; curfont = drw->fonts[0];
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 (i = 0; i < drw->fontcount; i++) {
charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); charexists = charexists || XftCharExists(drw->dpy, drw->fonts[i]->xfont, utf8codepoint);
if (charexists) { if (charexists) {
drw_font_getexts(curfont, text, utf8charlen, &tmpw, NULL); if (drw->fonts[i] == curfont) {
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 = drw->fonts[i];
} }
break; break;
} }
} }
if (overflow || !charexists || nextfont || utf8err) if (!charexists || (nextfont && nextfont != curfont))
break; break;
else else
charexists = 0; charexists = 0;
} }
if (utf8strlen) { if (utf8strlen) {
if (render) { drw_font_getexts(curfont, utf8str, utf8strlen, &tex);
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 && (tex.w > w - drw->fonts[0]->h || w < drw->fonts[0]->h); len--)
usedfont->xfont, x, ty, (XftChar8 *)utf8str, utf8strlen); drw_font_getexts(curfont, utf8str, len, &tex);
}
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] = '.');
if (render) {
th = curfont->ascent + curfont->descent;
ty = y + (h / 2) - (th / 2) + curfont->ascent;
tx = x + (h / 2);
XftDrawStringUtf8(d, invert ? &drw->scheme->bg->rgb : &drw->scheme->fg->rgb, curfont->xfont, tx, ty, (XftChar8 *)buf, len);
}
x += tex.w;
w -= tex.w;
}
}
if (!*text) {
break; break;
} else if (nextfont) { } else if (nextfont) {
charexists = 0; charexists = 0;
usedfont = nextfont; curfont = nextfont;
} else { } else {
/* Regardless of whether or not a fallback font is found, the /* Regardless of whether or not a fallback font is found, the
* character must be drawn. */ * character must be drawn.
*/
charexists = 1; charexists = 1;
hash = (unsigned int)utf8codepoint; if (drw->fontcount >= DRW_FONT_CACHE_SIZE)
hash = ((hash >> 16) ^ hash) * 0x21F0AAAD; continue;
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);
if (!drw->fonts->pattern) { if (!drw->fonts[0]->pattern) {
/* Refer to the comment in xfont_create for more information. */ /* Refer to the comment in drw_font_xcreate for more
die("the first font in the cache must be loaded from a font string."); * information. */
die("the first font in the cache must be loaded from a font string.\n");
} }
fcpattern = FcPatternDuplicate(drw->fonts->pattern); fcpattern = FcPatternDuplicate(drw->fonts[0]->pattern);
FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue);
@ -362,16 +334,12 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lp
FcPatternDestroy(fcpattern); FcPatternDestroy(fcpattern);
if (match) { if (match) {
usedfont = xfont_create(drw, NULL, match); curfont = drw_font_xcreate(drw, NULL, match);
if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { if (curfont && XftCharExists(drw->dpy, curfont->xfont, utf8codepoint)) {
for (curfont = drw->fonts; curfont->next; curfont = curfont->next) drw->fonts[drw->fontcount++] = curfont;
; /* NOP */
curfont->next = usedfont;
} else { } else {
xfont_free(usedfont); drw_font_free(curfont);
nomatches[nomatches[h0] ? h1 : h0] = utf8codepoint; curfont = drw->fonts[0];
no_match:
usedfont = drw->fonts;
} }
} }
} }
@ -379,49 +347,34 @@ no_match:
if (d) if (d)
XftDrawDestroy(d); XftDrawDestroy(d);
return x + (render ? w : 0); return x;
} }
void void
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
{ {
if (!drw)
return;
XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
XSync(drw->dpy, False); XSync(drw->dpy, False);
} }
unsigned int
drw_fontset_getwidth(Drw *drw, const char *text)
{
if (!drw || !drw->fonts || !text)
return 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, Extnts *tex)
{ {
XGlyphInfo ext; XGlyphInfo ext;
if (!font || !text)
return;
XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
if (w) tex->h = font->h;
*w = ext.xOff; tex->w = ext.xOff;
if (h) }
*h = font->h;
unsigned int
drw_font_getexts_width(Fnt *font, const char *text, unsigned int len)
{
Extnts tex;
drw_font_getexts(font, text, len, &tex);
return tex.w;
} }
Cur * Cur *
@ -429,9 +382,7 @@ drw_cur_create(Drw *drw, int shape)
{ {
Cur *cur; Cur *cur;
if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) cur = ecalloc(1, sizeof(Cur));
return NULL;
cur->cursor = XCreateFontCursor(drw->dpy, shape); cur->cursor = XCreateFontCursor(drw->dpy, shape);
return cur; return cur;
@ -442,7 +393,6 @@ drw_cur_free(Drw *drw, Cur *cursor)
{ {
if (!cursor) if (!cursor)
return; return;
XFreeCursor(drw->dpy, cursor->cursor); XFreeCursor(drw->dpy, cursor->cursor);
free(cursor); free(cursor);
} }

64
drw.h
View File

@ -1,19 +1,29 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE file for copyright and license details. */
#define DRW_FONT_CACHE_SIZE 32
typedef struct {
unsigned long pix;
XftColor rgb;
} Clr;
typedef struct { typedef struct {
Cursor cursor; Cursor cursor;
} Cur; } Cur;
typedef struct Fnt { typedef struct {
Display *dpy; Display *dpy;
int ascent;
int descent;
unsigned int h; unsigned int h;
XftFont *xfont; XftFont *xfont;
FcPattern *pattern; FcPattern *pattern;
struct Fnt *next;
} Fnt; } Fnt;
enum { ColFg, ColBg }; /* Clr scheme index */ typedef struct {
typedef XftColor Clr; Clr *fg;
Clr *bg;
Clr *border;
} ClrScheme;
typedef struct { typedef struct {
unsigned int w, h; unsigned int w, h;
@ -22,37 +32,43 @@ typedef struct {
Window root; Window root;
Drawable drawable; Drawable drawable;
GC gc; GC gc;
Clr *scheme; ClrScheme *scheme;
Fnt *fonts; size_t fontcount;
Fnt *fonts[DRW_FONT_CACHE_SIZE];
} Drw; } Drw;
typedef struct {
unsigned int w;
unsigned int h;
} Extnts;
/* Drawable abstraction */ /* Drawable abstraction */
Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); Drw *drw_create(Display *, int, Window, unsigned int, unsigned int);
void drw_resize(Drw *drw, unsigned int w, unsigned int h); void drw_resize(Drw *, unsigned int, unsigned int);
void drw_free(Drw *drw); void drw_free(Drw *);
/* Fnt abstraction */ /* Fnt abstraction */
Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); Fnt *drw_font_create(Drw *, const char *);
void drw_fontset_free(Fnt* set); void drw_load_fonts(Drw *, const char *[], size_t);
unsigned int drw_fontset_getwidth(Drw *drw, const char *text); void drw_font_free(Fnt *);
unsigned int drw_fontset_getwidth_clamp(Drw *drw, const char *text, unsigned int n); void drw_font_getexts(Fnt *, const char *, unsigned int, Extnts *);
void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); unsigned int drw_font_getexts_width(Fnt *, const char *, unsigned int);
/* Colorscheme abstraction */ /* Colour abstraction */
void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); Clr *drw_clr_create(Drw *, const char *);
Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); void drw_clr_free(Clr *);
/* Cursor abstraction */ /* Cursor abstraction */
Cur *drw_cur_create(Drw *drw, int shape); Cur *drw_cur_create(Drw *, int);
void drw_cur_free(Drw *drw, Cur *cursor); void drw_cur_free(Drw *, Cur *);
/* Drawing context manipulation */ /* Drawing context manipulation */
void drw_setfontset(Drw *drw, Fnt *set); void drw_setfont(Drw *, Fnt *);
void drw_setscheme(Drw *drw, Clr *scm); void drw_setscheme(Drw *, ClrScheme *);
/* Drawing functions */ /* Drawing functions */
void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); void drw_rect(Drw *, int, int, unsigned int, unsigned int, int, 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); int drw_text(Drw *, int, int, unsigned int, unsigned int, const char *, int);
/* Map functions */ /* Map functions */
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); void drw_map(Drw *, Window, int, int, unsigned int, unsigned int);

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,31 +6,28 @@
#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)
{ {
void *p; void *p;
if (!(p = calloc(nmemb, size))) if (!(p = calloc(nmemb, size)))
die("calloc:"); perror(NULL);
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);
}
exit(1);
}

5
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 *errstr, ...);
void *ecalloc(size_t nmemb, size_t size); void *ecalloc(size_t, size_t);